first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_actionlogs</name>
<author>Joomla! Project</author>
<creationDate>2018-05</creationDate>
<copyright>(C) 2018 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>3.9.0</version>
<description>COM_ACTIONLOGS_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Component\Actionlogs</namespace>
<administration>
<menu>COM_ACTIONLOGS</menu>
<files folder="admin">
<file>actionlogs.xml</file>
<file>config.xml</file>
<folder>forms</folder>
<folder>layouts</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_actionlogs.ini</language>
<language tag="en-GB">language/en-GB/com_actionlogs.sys.ini</language>
</languages>
</administration>
</extension>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<config addfieldprefix="Joomla\Component\Actionlogs\Administrator\Field">
<help key="User_Actions_Log:_Options"/>
<inlinehelp button="show"/>
<fieldset name="actionlogs" label="COM_ACTIONLOGS_OPTIONS">
<field
name="ip_logging"
type="radio"
label="COM_ACTIONLOGS_IP_LOGGING_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
filter="integer"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="csv_delimiter"
type="list"
label="COM_ACTIONLOGS_CSV_DELIMITER_LABEL"
default=","
validate="options"
>
<option value=",">COM_ACTIONLOGS_COMMA</option>
<option value=";">COM_ACTIONLOGS_SEMICOLON</option>
</field>
<field
name="loggable_extensions"
type="logtype"
label="COM_ACTIONLOGS_LOG_EXTENSIONS_LABEL"
multiple="true"
layout="joomla.form.field.list-fancy-select"
default="com_banners,com_cache,com_categories,com_checkin,com_config,com_contact,com_content,com_fields,com_installer,com_media,com_menus,com_messages,com_modules,com_newsfeeds,com_plugins,com_redirect,com_scheduler,com_tags,com_templates,com_users"
/>
<field
name="loggable_api"
type="radio"
label="COM_ACTIONLOGS_API_LOGGING_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
filter="integer"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="loggable_verbs"
type="list"
label="COM_ACTIONLOGS_LOG_VERBS_LABEL"
multiple="true"
showon="loggable_api:1"
default="GET"
validate="options"
layout="joomla.form.field.list-fancy-select"
>
<option value="GET">COM_ACTIONLOGS_FIELD_VALUE_GET</option>
<option value="POST">COM_ACTIONLOGS_FIELD_VALUE_POST</option>
<option value="DELETE">COM_ACTIONLOGS_FIELD_VALUE_DELETE</option>
<option value="PUT">COM_ACTIONLOGS_FIELD_VALUE_PUT</option>
<option value="PATCH">COM_ACTIONLOGS_FIELD_VALUE_PATCH</option>
</field>
<field
name="date_relative"
type="radio"
label="COM_ACTIONLOGS_DATE_RELATIVE_LABEL"
layout="joomla.form.field.radio.switcher"
default="1"
filter="integer"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
</fieldset>
</config>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<form addfieldprefix="Joomla\Component\Actionlogs\Administrator\Field">
<fields name="filter">
<field
name="search"
type="text"
inputmode="search"
label="COM_ACTIONLOGS_FILTER_SEARCH_LABEL"
description="COM_ACTIONLOGS_FILTER_SEARCH_DESC"
hint="JSEARCH_FILTER"
/>
<field
name="extension"
type="extension"
label="COM_ACTIONLOGS_EXTENSION"
class="js-select-submit-on-change"
>
<option value="">COM_ACTIONLOGS_SELECT_EXTENSION</option>
</field>
<field
name="dateRange"
type="logsdaterange"
label="COM_ACTIONLOGS_DATE"
class="js-select-submit-on-change"
>
<option value="">COM_ACTIONLOGS_OPTION_FILTER_DATE</option>
</field>
<field
name="user"
type="logcreator"
label="COM_ACTIONLOGS_NAME"
class="js-select-submit-on-change"
>
<option value="">COM_ACTIONLOGS_SELECT_USER</option>
</field>
</fields>
<fields name="list">
<field
name="fullordering"
type="list"
label="JGLOBAL_SORT_BY"
class="js-select-submit-on-change"
default="a.id DESC"
validate="options"
>
<option value="">JGLOBAL_SORT_BY</option>
<option value="a.message ASC">COM_ACTIONLOGS_ACTION_ASC</option>
<option value="a.message DESC">COM_ACTIONLOGS_ACTION_DESC</option>
<option value="a.extension ASC">COM_ACTIONLOGS_EXTENSION_ASC</option>
<option value="a.extension DESC">COM_ACTIONLOGS_EXTENSION_DESC</option>
<option value="a.log_date ASC">JDATE_ASC</option>
<option value="a.log_date DESC">JDATE_DESC</option>
<option value="a.user_id ASC">JGLOBAL_NAME_ASC</option>
<option value="a.user_id DESC">JGLOBAL_NAME_DESC</option>
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
</field>
</fields>
<fields name="list">
<field
name="limit"
type="limitbox"
label="JGLOBAL_LIST_LIMIT"
class="input-mini js-select-submit-on-change"
default="25"
/>
</fields>
</form>

View File

@ -0,0 +1,53 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
/**
* The actionlogs service provider.
*
* @since 4.0.0
*/
return new class () implements ServiceProviderInterface {
/**
* 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));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
return $component;
}
);
}
};

View File

@ -0,0 +1,164 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Event\ActionLog\AfterLogExportEvent;
use Joomla\CMS\Input\Input;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\Router\Route;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
use Joomla\Component\Actionlogs\Administrator\Model\ActionlogsModel;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Actionlogs list controller class.
*
* @since 3.9.0
*/
class ActionlogsController extends AdminController
{
/**
* 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 = [];
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->getDispatcher()->dispatch('onAfterLogExport', new AfterLogExportEvent('onAfterLogExport'));
$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);
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Controller;
use Joomla\CMS\MVC\Controller\BaseController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Plugins display controller.
*
* @since 4.0.0
*/
class DisplayController extends BaseController
{
/**
* The default view.
*
* @var string
* @since 1.6
*/
protected $default_view = 'actionlogs';
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Dispatcher;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Dispatcher\ComponentDispatcher;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* ComponentDispatcher class for com_actionlogs
*
* @since 4.2.7
*/
class Dispatcher extends ComponentDispatcher
{
/**
* Method to check component access permission
*
* @return void
*
* @since 4.2.7
*/
protected function checkAccess()
{
$user = $this->app->getIdentity();
// Access check
if (!$user->authorise('core.admin')) {
throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
}
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Field;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Field to load a list of all extensions that have logged actions
*
* @since 3.9.0
*/
class ExtensionField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 3.9.0
*/
protected $type = '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();
$options = [];
if (\count($context) > 0) {
foreach ($context as $item) {
$extensions[] = strtok($item, '.');
}
$extensions = array_unique($extensions);
foreach ($extensions as $extension) {
ActionlogsHelper::loadTranslationFiles($extension);
$options[] = HTMLHelper::_('select.option', $extension, Text::_($extension));
}
}
return array_merge(parent::getOptions(), $options);
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Field;
use Joomla\CMS\Form\Field\ListField;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Field to load a list of all users that have logged actions
*
* @since 3.9.0
*/
class LogcreatorField extends ListField
{
/**
* Cached array of the category items.
*
* @var array
* @since 3.9.0
*/
protected static $options = [];
/**
* The form field type.
*
* @var string
* @since 3.9.0
*/
protected $type = 'LogCreator';
/**
* 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();
$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'));
// Setup the query
$db->setQuery($query);
// Return the result
if ($options = $db->loadObjectList()) {
static::$options[$hash] = array_merge(static::$options[$hash], $options);
}
}
return static::$options[$hash];
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\PredefinedlistField;
use Joomla\CMS\Form\Form;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Field to show a list of range dates to sort with
*
* @since 3.9.0
*/
class LogsdaterangeField extends PredefinedlistField
{
/**
* The form field type.
*
* @var string
* @since 3.9.0
*/
protected $type = 'logsdaterange';
/**
* Available options
*
* @var array
* @since 3.9.0
*/
protected $predefinedOptions = [
'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);
// Load the required language
$lang = Factory::getLanguage();
$lang->load('com_actionlogs', JPATH_ADMINISTRATOR);
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Field;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Field to load a list of all extensions that have logged actions
*
* @since 3.9.0
*/
class LogtypeField extends ListField
{
/**
* 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));
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Field;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Information field.
*
* @since 3.9.2
*/
class PlugininfoField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 3.9.2
*/
protected $type = 'PluginInfo';
/**
* 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();
$link = HTMLHelper::_(
'link',
Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . $result),
Text::_('PLG_SYSTEM_ACTIONLOGS_JOOMLA_ACTIONLOG_DISABLED'),
['class' => 'alert-link']
);
return '<div class="alert alert-info">'
. '<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden">'
. Text::_('INFO')
. '</span>'
. Text::sprintf('PLG_SYSTEM_ACTIONLOGS_JOOMLA_ACTIONLOG_DISABLED_REDIRECT', $link)
. '</div>';
}
}

View File

@ -0,0 +1,352 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Helper;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Router\Route;
use Joomla\Filesystem\Path;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Actionlogs component helper.
*
* @since 3.9.0
*/
class ActionlogsHelper
{
/**
* Array of characters starting a formula
*
* @var array
*
* @since 3.9.7
*/
private static $characters = ['=', '+', '-', '@'];
/**
* 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__,
\is_object($data) ? \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 [
'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 = [];
$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 4.3 will be removed in 6.0
* Use the action log config model instead
* Example: Factory::getApplication()->bootComponent('actionlogs')->getMVCFactory()
* ->createModel('ActionlogConfig', 'Administrator')->getLogContentTypeParams($context);
*/
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 = [];
$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 = $value !== null ? 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([$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(
[
'folder',
'element',
'params',
'extension_id',
],
[
'type',
'name',
'params',
'id',
]
)
)
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('actionlog'))
->whereIn($db->quoteName('state'), [0, 1])
->order($db->quoteName('ordering'));
$db->setQuery($query);
try {
$rows = $db->loadObjectList();
} catch (\RuntimeException $e) {
$rows = [];
}
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;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Model;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Model to interact with the action log configuration.
*
* @since 4.2.0
*/
class ActionlogConfigModel extends BaseDatabaseModel
{
/**
* 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);
return $db->loadObject();
}
}

View File

@ -0,0 +1,177 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Mail\Exception\MailDisabledException;
use Joomla\CMS\Mail\MailTemplate;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
use Joomla\Utilities\IpHelper;
use PHPMailer\PHPMailer\Exception as phpMailerException;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of Actionlog records.
*
* @since 3.9.0
*/
class ActionlogModel extends BaseDatabaseModel implements UserFactoryAwareInterface
{
use UserFactoryAwareTrait;
/**
* 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 = 0)
{
if (!is_numeric($userId)) {
@trigger_error(sprintf('User ID must be an integer in %s.', __METHOD__), E_USER_DEPRECATED);
}
$user = $userId ? $this->getUserFactory()->loadUserById($userId) : $this->getCurrentUser();
$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 = [];
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(['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 = [];
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();
}
}

View File

@ -0,0 +1,399 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Event\ActionLog\AfterLogPurgeEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\DatabaseIterator;
use Joomla\Database\DatabaseQuery;
use Joomla\Database\ParameterType;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of article records.
*
* @since 3.9.0
*/
class ActionlogsModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
*
* @since 3.9.0
*
* @throws \Exception
*/
public function __construct($config = [])
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'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 .= '%';
$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 ['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;
}
$this->getDispatcher()->dispatch('onAfterLogPurge', new AfterLogPurgeEvent('onAfterLogPurge'));
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;
}
$this->getDispatcher()->dispatch('onAfterLogPurge', new AfterLogPurgeEvent('onAfterLogPurge'));
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 = [], $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'), ['value' => 'a.ip_address ASC']);
$field->addOption(Text::_('COM_ACTIONLOGS_IP_ADDRESS_DESC'), ['value' => 'a.ip_address DESC']);
}
return $form;
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\Plugin;
use Joomla\CMS\Plugin\CMSPlugin;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Abstract Action Log Plugin
*
* @since 3.9.0
*/
abstract class ActionLogPlugin extends CMSPlugin
{
/**
* Application object.
*
* @var \Joomla\CMS\Application\CMSApplication
* @since 3.9.0
*/
protected $app;
/**
* Database object.
*
* @var \Joomla\Database\DatabaseDriver
* @since 3.9.0
*/
protected $db;
/**
* Load plugin language file automatically so that it can be used inside component
*
* @var boolean
* @since 3.9.0
*/
protected $autoloadLanguage = true;
/**
* Proxy for ActionlogsModelUserlog addLog method
*
* 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 int $userId ID of user perform the action, usually ID of current logged in user
*
* @return void
*
* @since 3.9.0
*/
protected function addLog($messages, $messageLanguageKey, $context, $userId = null)
{
$user = $this->app->getIdentity();
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('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('app', $message)) {
$message['app'] = strtoupper($message['app']);
}
$messages[$index] = $message;
}
/** @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);
}
}

View File

@ -0,0 +1,157 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Actionlogs\Administrator\View\Actionlogs;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
use Joomla\Component\Actionlogs\Administrator\Model\ActionlogsModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of logs.
*
* @since 3.9.0
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items.
*
* @var array
* @since 3.9.0
*/
protected $items;
/**
* The model state
*
* @var array
* @since 3.9.0
*/
protected $state;
/**
* The pagination object
*
* @var Pagination
* @since 3.9.0
*/
protected $pagination;
/**
* Form object for search filters
*
* @var Form
* @since 3.9.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
* @since 3.9.0
*/
public $activeFilters;
/**
* Setting if the IP column should be shown
*
* @var boolean
* @since 3.9.0
*/
protected $showIpColumn = false;
/**
* Setting if the date should be displayed relative to the current date.
*
* @var boolean
* @since 4.1.0
*/
protected $dateRelative = false;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 3.9.0
*
* @throws \Exception
*/
public function display($tpl = null)
{
/** @var ActionlogsModel $model */
$model = $this->getModel();
$this->items = $model->getItems();
$this->state = $model->getState();
$this->pagination = $model->getPagination();
$this->filterForm = $model->getFilterForm();
$this->activeFilters = $model->getActiveFilters();
$params = ComponentHelper::getParams('com_actionlogs');
$this->showIpColumn = (bool) $params->get('ip_logging', 0);
$this->dateRelative = (bool) $params->get('date_relative', 1);
if (\count($errors = $model->getErrors())) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->addToolbar();
// Load all actionlog plugins language files
ActionlogsHelper::loadActionLogPluginsLanguage();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 3.9.0
*/
protected function addToolbar()
{
ToolbarHelper::title(Text::_('COM_ACTIONLOGS_MANAGER_USERLOGS'), 'icon-list-2');
$toolbar = Toolbar::getInstance();
$toolbar->standardButton('download', 'COM_ACTIONLOGS_EXPORT_CSV', 'actionlogs.exportSelectedLogs')
->icon('icon-download')
->listCheck(true);
$toolbar->standardButton('download', 'COM_ACTIONLOGS_EXPORT_ALL_CSV', 'actionlogs.exportLogs')
->icon('icon-download')
->listCheck(false);
$toolbar->delete('actionlogs.delete')
->message('JGLOBAL_CONFIRM_DELETE');
$toolbar->confirmButton('delete', 'COM_ACTIONLOGS_TOOLBAR_PURGE', 'actionlogs.purge')
->message('COM_ACTIONLOGS_PURGE_CONFIRM')
->listCheck(false);
$toolbar->preferences('com_actionlogs');
$toolbar->help('User_Actions_Log');
}
}

View File

@ -0,0 +1,130 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_actionlogs
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
use Joomla\Component\Actionlogs\Administrator\View\Actionlogs\HtmlView;
/** @var HtmlView $this */
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive')
->useScript('table.columns')
->useScript('multiselect')
->useScript('com_actionlogs.admin-actionlogs');
?>
<form action="<?php echo Route::_('index.php?option=com_actionlogs&view=actionlogs'); ?>" method="post" name="adminForm" id="adminForm">
<div id="j-main-container" class="j-main-container">
<?php // Search tools bar ?>
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this]); ?>
<?php if (empty($this->items)) : ?>
<div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
</div>
<?php else : ?>
<table class="table" id="logsList">
<caption class="visually-hidden">
<?php echo Text::_('COM_ACTIONLOGS_TABLE_CAPTION'); ?>,
<span id="orderedBy"><?php echo Text::_('JGLOBAL_SORTED_BY'); ?> </span>,
<span id="filteredBy"><?php echo Text::_('JGLOBAL_FILTERED_BY'); ?></span>
</caption>
<thead>
<tr>
<td class="w-1 text-center">
<?php echo HTMLHelper::_('grid.checkall'); ?>
</td>
<th scope="col" class="d-md-table-cell">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_ACTIONLOGS_ACTION', 'a.message', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-15 d-none d-md-table-cell">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_ACTIONLOGS_EXTENSION', 'a.extension', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-15 d-none d-md-table-cell">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_ACTIONLOGS_DATE', 'a.log_date', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-10 d-md-table-cell">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_ACTIONLOGS_NAME', 'a.user_id', $listDirn, $listOrder); ?>
</th>
<?php if ($this->showIpColumn) : ?>
<th scope="col" class="w-10 d-none d-md-table-cell">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_ACTIONLOGS_IP_ADDRESS', 'a.ip_address', $listDirn, $listOrder); ?>
</th>
<?php endif; ?>
<th scope="col" class="w-1 d-none d-md-table-cell">
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->items as $i => $item) :
$extension = strtok($item->extension, '.');
ActionlogsHelper::loadTranslationFiles($extension); ?>
<tr class="row<?php echo $i % 2; ?>">
<td class="text-center">
<?php echo HTMLHelper::_('grid.id', $i, $item->id); ?>
</td>
<th scope="row" class="d-md-table-cell">
<?php echo ActionlogsHelper::getHumanReadableLogMessage($item); ?>
</th>
<td class="d-none d-md-table-cell">
<?php echo $this->escape(Text::_($extension)); ?>
</td>
<td class="d-none d-md-table-cell">
<?php if ($this->dateRelative) : ?>
<?php echo HTMLHelper::_('date.relative', $item->log_date); ?>
<div class="small">
<?php endif; ?>
<?php echo HTMLHelper::_('date', $item->log_date, Text::_('DATE_FORMAT_LC6')); ?>
<?php if ($this->dateRelative) : ?>
</div>
<?php endif; ?>
</td>
<td class="d-md-table-cell">
<?php echo $this->escape($item->name); ?>
</td>
<?php if ($this->showIpColumn) : ?>
<td class="d-none d-md-table-cell">
<?php echo Text::_($this->escape($item->ip_address)); ?>
</td>
<?php endif;?>
<td class="d-none d-md-table-cell">
<?php echo (int) $item->id; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php // Load the pagination. ?>
<?php echo $this->pagination->getListFooter(); ?>
<?php endif;?>
<input type="hidden" name="task" value="" />
<input type="hidden" name="boxchecked" value="0" />
<?php echo HTMLHelper::_('form.token'); ?>
</div>
</form>
<form action="<?php echo Route::_('index.php?option=com_actionlogs&view=actionlogs'); ?>" method="post" name="exportForm" id="exportForm">
<input type="hidden" name="task" value="" />
<input type="hidden" name="cids" value="" />
<?php echo HTMLHelper::_('form.token'); ?>
</form>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="COM_ACTIONLOGS_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_ACTIONLOGS_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_admin</name>
<author>Joomla! Project</author>
<creationDate>2006-04</creationDate>
<copyright>(C) 2006 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>4.0.0</version>
<description>COM_ADMIN_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Component\Admin</namespace>
<media />
<administration>
<files folder="admin">
<filename>admin.xml</filename>
<filename>script.php</filename>
<folder>forms</folder>
<folder>postinstall</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_admin.ini</language>
<language tag="en-GB">language/en-GB/com_admin.sys.ini</language>
</languages>
</administration>
</extension>

View File

@ -0,0 +1,30 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* This file contains post-installation message handling for notifying users of a change
* in the default .htaccess and web.config files.
*/
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Notifies users of the add the nosniff headers by applying the changes from the default .htaccess or web.config file
*
* This check returns true regardless of condition.
*
* @return boolean
*
* @since 3.4
*/
function admin_postinstall_addnosniff_condition()
{
return true;
}

View File

@ -0,0 +1,86 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Path;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Notifies users of the new Behind Load Balancer option in Global Config, if we detect they might be behind a proxy
*
* @return boolean
*
* @since 3.9.26
*/
function admin_postinstall_behindproxy_condition()
{
$app = Factory::getApplication();
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_CLIENT_IP', $_SERVER) && !empty($_SERVER['HTTP_CLIENT_IP'])) {
return true;
}
return false;
}
/**
* Enables the Behind Load Balancer setting in Global Configuration
*
* @return void
*
* @since 3.9.26
*/
function behindproxy_postinstall_action()
{
$prev = ArrayHelper::fromObject(new JConfig());
$data = array_merge($prev, ['behind_loadbalancer' => '1']);
$config = new Registry($data);
// 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');
return;
}
// Attempt to write the configuration file as a PHP class named JConfig.
$configuration = $config->toString('PHP', ['class' => 'JConfig', 'closingtag' => false]);
if (!File::write($file, $configuration)) {
Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_WRITE_FAILED'), 'error');
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');
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* This file contains post-installation message handling for notifying users of a change
* in the default .htaccess file regarding setting the Content-Encoding header.
*/
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Notifies users of a change in the default .htaccess file regarding setting the Content-Encoding header
*
* This check returns true regardless of condition.
*
* @return boolean
*
* @since 4.2.9
*/
function admin_postinstall_htaccesssetce_condition()
{
return true;
}

View File

@ -0,0 +1,30 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* This file contains post-installation message handling for notifying users of a change
* in the default .htaccess file regarding hardening against XSS in SVG's
*/
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Notifies users of a change in the default .htaccess file regarding hardening against XSS in SVG's
*
* This check returns true regardless of condition.
*
* @return boolean
*
* @since 3.9.21
*/
function admin_postinstall_htaccesssvg_condition()
{
return true;
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* This file contains post-installation message handling for the checks if the installation is
* affected by the issue with content languages access in 3.4.0
*/
use Joomla\CMS\Factory;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Checks if the installation is affected by the issue with content languages access in 3.4.0
*
* @link https://github.com/joomla/joomla-cms/pull/6172
* @link https://github.com/joomla/joomla-cms/pull/6194
*
* @return boolean
*
* @since 3.4.1
*/
function admin_postinstall_languageaccess340_condition()
{
$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;
}
// All good the query return nothing.
return false;
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* This file contains post-installation message handling for the checking minimum PHP version support
*/
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Alerts the user we are collecting anonymous data as of Joomla 3.5.0.
*
* @return boolean
*
* @since 3.5
*/
function admin_postinstall_statscollection_condition()
{
return true;
}

View File

@ -0,0 +1,30 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* This file contains post-installation message handling for notifying users of a change
* in the default textfilter settings
*/
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Notifies users the changes from the default textfilter.
*
* This check returns true regardless of condition.
*
* @return boolean
*
* @since 3.9.19
*/
function admin_postinstall_textfilter3919_condition()
{
return true;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\HTML\Registry;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\Component\Admin\Administrator\Extension\AdminComponent;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
/**
* The admin service provider.
*
* @since 4.0.0
*/
return new class () implements ServiceProviderInterface {
/**
* 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\\Admin'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Admin'));
$container->set(
ComponentInterface::class,
function (Container $container) {
$component = new AdminComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
$component->setRegistry($container->get(Registry::class));
return $component;
}
);
}
};

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS `#__utf8_conversion`;

View File

@ -0,0 +1 @@
DELETE FROM `#__scheduler_tasks` WHERE `type` = 'demoTask_r1.sleep';

View File

@ -0,0 +1,2 @@
ALTER TABLE `#__menu_types` ADD COLUMN `ordering` int NOT NULL DEFAULT 0 AFTER `client_id` /** CAN FAIL **/;
UPDATE `#__menu_types` SET `ordering` = `id` WHERE `client_id` = 0;

View File

@ -0,0 +1,23 @@
--
-- Table structure for table `#__schemaorg`
--
CREATE TABLE IF NOT EXISTS `#__schemaorg` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`itemId` int unsigned,
`context` varchar(100),
`schemaType` varchar(100),
`schema` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
-- Add plugins to `#__extensions`
INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `custom_data`, `ordering`, `state`) VALUES
(0, 'plg_schemaorg_blogposting', 'plugin', 'blogposting', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 1, 0),
(0, 'plg_schemaorg_book', 'plugin', 'book', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 2, 0),
(0, 'plg_schemaorg_event', 'plugin', 'event', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 3, 0),
(0, 'plg_schemaorg_organization', 'plugin', 'organization', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 4, 0),
(0, 'plg_schemaorg_person', 'plugin', 'person', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 5, 0),
(0, 'plg_schemaorg_recipe', 'plugin', 'recipe', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 6, 0),
(0, 'plg_schemaorg_jobposting', 'plugin', 'jobposting', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 7, 0),
(0, 'plg_system_schemaorg', 'plugin', 'schemaorg', 'system', 0, 1, 1, 0, 0, '', '{}', '', 0, 0);

View File

@ -0,0 +1,3 @@
INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `custom_data`, `ordering`, `state`)
SELECT 0, 'plg_behaviour_compat', 'plugin', 'compat', 'behaviour', 0, 1, 1, 0, 1, '', '{"classes_aliases":"1","es5_assets":"1"}', '', -1, 0
WHERE NOT EXISTS (SELECT * FROM `#__extensions` e WHERE e.`type` = 'plugin' AND e.`element` = 'compat' AND e.`folder` = 'behaviour' AND e.`client_id` = 0);

View File

@ -0,0 +1,17 @@
--
-- Attention: In the below SQL statements, the value of the filter is unescaped, i.e. uses "\\", while
-- in base.sql the same value is using "\\\\". This is expected because of how JSON_REPLACE works.
--
UPDATE `#__extensions`
SET `params` = JSON_REPLACE(`params`, '$.filter' , '\\Joomla\\CMS\\Component\\ComponentHelper::filterText')
WHERE `type` = 'plugin'
AND `folder` = 'fields'
AND `element` IN ('editor', 'text', 'textarea')
AND `params` <> ''
AND JSON_EXTRACT(`params`, '$.filter') = 'JComponentHelper::filterText';
UPDATE `#__fields`
SET `fieldparams` = JSON_REPLACE(`fieldparams`, '$.filter' , '\\Joomla\\CMS\\Component\\ComponentHelper::filterText')
WHERE `type` IN ('editor', 'text', 'textarea')
AND `fieldparams` <> ''
AND JSON_EXTRACT(`fieldparams`, '$.filter') = 'JComponentHelper::filterText';

View File

@ -0,0 +1,3 @@
INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `custom_data`, `ordering`, `state`) VALUES
(0, 'plg_task_globalcheckin', 'plugin', 'globalcheckin', 'task', 0, 1, 1, 0, 0, '', '{}', '', 5, 0);

View File

@ -0,0 +1,13 @@
UPDATE `#__guidedtours` SET `extensions` = '["com_content","com_categories"]' WHERE `url` LIKE '%option=com_content%';
UPDATE `#__guidedtours` SET `extensions` = '["com_content","com_categories"]' WHERE `url` LIKE '%option=com_categories%';
UPDATE `#__guidedtours` SET `extensions` = '["com_menus"]' WHERE `url` LIKE '%com_menus%';
UPDATE `#__guidedtours` SET `extensions` = '["com_tags"]' WHERE `url` LIKE '%com_tags%';
UPDATE `#__guidedtours` SET `extensions` = '["com_banners"]' WHERE `url` LIKE '%com_banners%';
UPDATE `#__guidedtours` SET `extensions` = '["com_contact"]' WHERE `url` LIKE '%com_contact%';
UPDATE `#__guidedtours` SET `extensions` = '["com_newsfeeds"]' WHERE `url` LIKE '%com_newsfeeds%';
UPDATE `#__guidedtours` SET `extensions` = '["com_finder"]' WHERE `url` LIKE '%com_finder%';
UPDATE `#__guidedtours` SET `extensions` = '["com_users"]' WHERE `url` LIKE '%com_users%';
UPDATE `#__update_sites`
SET `location` = 'https://update.joomla.org/language/translationlist_5.xml'
WHERE `location` = 'https://update.joomla.org/language/translationlist_4.xml';

View File

@ -0,0 +1,14 @@
ALTER TABLE `#__guidedtours` ADD COLUMN `uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL AFTER `title`/** CAN FAIL **/;
ALTER TABLE `#__guidedtours` ADD INDEX `idx_uid` (`uid`(191)) /** CAN FAIL **/;
UPDATE `#__guidedtours` SET `uid` = 'joomla-guidedtours' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_GUIDEDTOURS_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-guidedtoursteps' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_GUIDEDTOURSTEPS_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-articles' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_ARTICLES_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-categories' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_CATEGORIES_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-menus' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_MENUS_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-tags' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_TAGS_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-banners' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_BANNERS_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-contacts' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_CONTACTS_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-newsfeeds' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_NEWSFEEDS_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-smartsearch' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_SMARTSEARCH_TITLE';
UPDATE `#__guidedtours` SET `uid` = 'joomla-users' WHERE `title` = 'COM_GUIDEDTOURS_TOUR_USERS_TITLE';

View File

@ -0,0 +1,3 @@
UPDATE `#__extensions`
SET `locked` = 0
WHERE `type` = 'plugin' AND `element` = 'recaptcha_invisible' AND `folder` = 'captcha';

View File

@ -0,0 +1,14 @@
INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `custom_data`, `checked_out`, `checked_out_time`, `ordering`, `state`) VALUES
('plg_task_deleteactionlogs', 'plugin', 'deleteactionlogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_privacyconsent', 'plugin', 'privacyconsent', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_sessiongc', 'plugin', 'sessiongc', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0);
INSERT INTO `#__mail_templates` (`template_id`, `extension`, `language`, `subject`, `body`, `htmlbody`, `attachments`, `params`) VALUES
('plg_task_privacyconsent.request.reminder', 'plg_task_privacyconsent', '', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
('plg_task_updatenotification.mail', 'plg_task_updatenotification', '', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_SUBJECT', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_BODY', '', '', '{"tags":["newversion","curversion","sitename","url","link","releasenews"]}');
DELETE FROM `#__mail_templates` WHERE `template_id` IN ('plg_system_privacyconsent.request.reminder', 'plg_system_updatenotification.mail');
DELETE FROM `#__postinstall_messages` WHERE `condition_file` = 'site://plugins/system/updatenotification/postinstall/updatecachetime.php';

View File

@ -0,0 +1,5 @@
UPDATE `#__scheduler_tasks` SET `title` = 'Delete Action Logs' WHERE `type` = 'delete.actionlogs' AND `title` = 'DeleteActionLogs';
UPDATE `#__scheduler_tasks` SET `title` = 'Privacy Consent' WHERE `type` = 'privacy.consent' AND `title` = 'PrivacyConsent';
UPDATE `#__scheduler_tasks` SET `title` = 'Rotate Logs' WHERE `type` = 'rotation.logs' AND `title` = 'RotateLogs';
UPDATE `#__scheduler_tasks` SET `title` = 'Session GC' WHERE `type` = 'session.gc' AND `title` = 'SessionGC';
UPDATE `#__scheduler_tasks` SET `title` = 'Update Notification' WHERE `type` = 'update.notification' AND `title` = 'UpdateNotification';

View File

@ -0,0 +1,5 @@
-- Add com_fields to action logs
INSERT INTO `#__action_logs_extensions` (`extension`) VALUES ('com_fields');
INSERT INTO `#__action_log_config` (`type_title`, `type_alias`, `id_holder`, `title_holder`, `table_name`, `text_prefix`) VALUES
('field', 'com_fields.field', 'id', 'title', '#__fields', 'PLG_ACTIONLOG_JOOMLA');

View File

@ -0,0 +1 @@
UPDATE `#__extensions` SET `enabled` = 1 WHERE `type` = 'plugin' AND `element` = 'schedulerunner' AND `folder` = 'system';

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS "#__utf8_conversion";

View File

@ -0,0 +1 @@
DELETE FROM "#__scheduler_tasks" WHERE "type" = 'demoTask_r1.sleep';

View File

@ -0,0 +1,2 @@
ALTER TABLE "#__menu_types" ADD COLUMN "ordering" int NOT NULL DEFAULT 0 /** CAN FAIL **/;
UPDATE "#__menu_types" SET "ordering" = "id" WHERE "client_id" = 0;

View File

@ -0,0 +1,23 @@
--
-- Table structure for table "#__schemaorg"
--
CREATE TABLE IF NOT EXISTS "#__schemaorg" (
"id" serial NOT NULL,
"itemId" bigint,
"context" varchar(100),
"schemaType" varchar(100),
"schema" text,
PRIMARY KEY ("id")
);
-- Add plugins to "#__extensions"
INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "custom_data", "ordering", "state") VALUES
(0, 'plg_schemaorg_blogposting', 'plugin', 'blogposting', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 1, 0),
(0, 'plg_schemaorg_book', 'plugin', 'book', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 2, 0),
(0, 'plg_schemaorg_event', 'plugin', 'event', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 3, 0),
(0, 'plg_schemaorg_organization', 'plugin', 'organization', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 4, 0),
(0, 'plg_schemaorg_person', 'plugin', 'person', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 5, 0),
(0, 'plg_schemaorg_recipe', 'plugin', 'recipe', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 6, 0),
(0, 'plg_schemaorg_jobposting', 'plugin', 'jobposting', 'schemaorg', 0, 1, 1, 0, 0, '', '{}', '', 7, 0),
(0, 'plg_system_schemaorg', 'plugin', 'schemaorg', 'system', 0, 1, 1, 0, 0, '', '{}', '', 0, 0);

View File

@ -0,0 +1,3 @@
INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "custom_data", "ordering", "state")
SELECT 0, 'plg_behaviour_compat', 'plugin', 'compat', 'behaviour', 0, 1, 1, 0, 1, '', '{"classes_aliases":"1","es5_assets":"1"}', '', -1, 0
WHERE NOT EXISTS (SELECT * FROM "#__extensions" e WHERE e."type" = 'plugin' AND e."element" = 'compat' AND e."folder" = 'behaviour' AND e."client_id" = 0);

View File

@ -0,0 +1,13 @@
UPDATE "#__extensions"
SET "params" = jsonb_set("params"::jsonb, '{filter}' , '"\\\\Joomla\\\\CMS\\\\Component\\\\ComponentHelper::filterText"')
WHERE "type" = 'plugin'
AND "folder" = 'fields'
AND "element" IN ('editor', 'text', 'textarea')
AND "params" <> ''
AND "params"::jsonb->>'filter' = 'JComponentHelper::filterText';
UPDATE "#__fields"
SET "fieldparams" = jsonb_set("fieldparams"::jsonb, '{filter}' , '"\\\\Joomla\\\\CMS\\\\Component\\\\ComponentHelper::filterText"')
WHERE "type" IN ('editor', 'text', 'textarea')
AND "fieldparams" <> ''
AND "fieldparams"::jsonb->>'filter' = 'JComponentHelper::filterText';

View File

@ -0,0 +1,3 @@
INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "custom_data", "ordering", "state") VALUES
(0, 'plg_task_globalcheckin', 'plugin', 'globalcheckin', 'task', 0, 1, 1, 0, 0, '', '{}', '', 5, 0);

View File

@ -0,0 +1,14 @@
UPDATE "#__guidedtours" SET "extensions" = '["com_content","com_categories"]' WHERE "url" LIKE '%option=com_content%';
UPDATE "#__guidedtours" SET "extensions" = '["com_content","com_categories"]' WHERE "url" LIKE '%option=com_categories%';
UPDATE "#__guidedtours" SET "extensions" = '["com_menus"]' WHERE "url" LIKE '%com_menus%';
UPDATE "#__guidedtours" SET "extensions" = '["com_tags"]' WHERE "url" LIKE '%com_tags%';
UPDATE "#__guidedtours" SET "extensions" = '["com_banners"]' WHERE "url" LIKE '%com_banners%';
UPDATE "#__guidedtours" SET "extensions" = '["com_contact"]' WHERE "url" LIKE '%com_contact%';
UPDATE "#__guidedtours" SET "extensions" = '["com_newsfeeds"]' WHERE "url" LIKE '%com_newsfeeds%';
UPDATE "#__guidedtours" SET "extensions" = '["com_finder"]' WHERE "url" LIKE '%com_finder%';
UPDATE "#__guidedtours" SET "extensions" = '["com_users"]' WHERE "url" LIKE '%com_users%';
UPDATE "#__update_sites"
SET "location" = 'https://update.joomla.org/language/translationlist_5.xml'
WHERE "location" = 'https://update.joomla.org/language/translationlist_4.xml';

View File

@ -0,0 +1,14 @@
ALTER TABLE "#__guidedtours" ADD COLUMN "uid" varchar(255) DEFAULT '' NOT NULL /** CAN FAIL **/;
CREATE INDEX "#__guidedtours_idx_uid" ON "#__guidedtours" ("uid") /** CAN FAIL **/;
UPDATE "#__guidedtours" SET "uid" = 'joomla-guidedtours' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_GUIDEDTOURS_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-guidedtoursteps' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_GUIDEDTOURSTEPS_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-articles' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_ARTICLES_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-categories' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_CATEGORIES_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-menus' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_MENUS_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-tags' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_TAGS_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-banners' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_BANNERS_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-contacts' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_CONTACTS_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-newsfeeds' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_NEWSFEEDS_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-smartsearch' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_SMARTSEARCH_TITLE';
UPDATE "#__guidedtours" SET "uid" = 'joomla-users' WHERE "title" = 'COM_GUIDEDTOURS_TOUR_USERS_TITLE';

View File

@ -0,0 +1,3 @@
UPDATE "#__extensions"
SET "locked" = 0
WHERE "type" = 'plugin' AND "element" = 'recaptcha_invisible' AND "folder" = 'captcha';

View File

@ -0,0 +1,14 @@
INSERT INTO "#__extensions" ("name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "custom_data", "checked_out", "checked_out_time", "ordering", "state") VALUES
('plg_task_deleteactionlogs', 'plugin', 'deleteactionlogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_privacyconsent', 'plugin', 'privacyconsent', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_sessiongc', 'plugin', 'sessiongc', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
('plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0);
INSERT INTO "#__mail_templates" ("template_id", "extension", "language", "subject", "body", "htmlbody", "attachments", "params") VALUES
('plg_task_privacyconsent.request.reminder', 'plg_task_privacyconsent', '', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
('plg_task_updatenotification.mail', 'plg_task_updatenotification', '', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_SUBJECT', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_BODY', '', '', '{"tags":["newversion","curversion","sitename","url","link","releasenews"]}');
DELETE FROM "#__mail_templates" WHERE "template_id" IN ('plg_system_privacyconsent.request.reminder', 'plg_system_updatenotification.mail');
DELETE FROM "#__postinstall_messages" WHERE "condition_file" = 'site://plugins/system/updatenotification/postinstall/updatecachetime.php';

View File

@ -0,0 +1,5 @@
UPDATE "#__scheduler_tasks" SET "title" = 'Delete Action Logs' WHERE "type" = 'delete.actionlogs' AND "title" = 'DeleteActionLogs';
UPDATE "#__scheduler_tasks" SET "title" = 'Privacy Consent' WHERE "type" = 'privacy.consent' AND "title" = 'PrivacyConsent';
UPDATE "#__scheduler_tasks" SET "title" = 'Rotate Logs' WHERE "type" = 'rotation.logs' AND "title" = 'RotateLogs';
UPDATE "#__scheduler_tasks" SET "title" = 'Session GC' WHERE "type" = 'session.gc' AND "title" = 'SessionGC';
UPDATE "#__scheduler_tasks" SET "title" = 'Update Notification' WHERE "type" = 'update.notification' AND "title" = 'UpdateNotification';

View File

@ -0,0 +1,5 @@
-- Add com_fields to action logs extensions
INSERT INTO "#__action_logs_extensions" ("extension") VALUES ('com_fields');
INSERT INTO "#__action_log_config" ("type_title", "type_alias", "id_holder", "title_holder", "table_name", "text_prefix") VALUES
('field', 'com_fields.field', 'id', 'title', '#__fields', 'PLG_ACTIONLOG_JOOMLA');

View File

@ -0,0 +1 @@
UPDATE "#__extensions" SET "enabled" = 1 WHERE "type" = 'plugin' AND "element" = 'schedulerunner' AND "folder" = 'system';

View File

@ -0,0 +1,50 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Controller;
use Joomla\CMS\MVC\Controller\BaseController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Admin Controller
*
* @since 1.6
*/
class DisplayController extends BaseController
{
/**
* View method
*
* @param boolean $cachable If true, the view output will be cached
* @param array $urlparams An array of safe URL parameters and their variable types.
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
*
* @return static Supports chaining.
*
* @since 3.9
*/
public function display($cachable = false, $urlparams = [])
{
$viewName = $this->input->get('view', $this->default_view);
$format = $this->input->get('format', 'html');
// Check CSRF token for sysinfo export views
if ($viewName === 'sysinfo' && ($format === 'text' || $format === 'json')) {
// Check for request forgeries.
$this->checkToken('GET');
}
return parent::display($cachable, $urlparams);
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Dispatcher;
use Joomla\CMS\Dispatcher\ComponentDispatcher;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* ComponentDispatcher class for com_admin
*
* @since 4.0.0
*/
class Dispatcher extends ComponentDispatcher
{
/**
* com_admin does not require check permission, so we override checkAccess method and have it empty
*
* @return void
*/
protected function checkAccess()
{
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Extension;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use Joomla\Component\Admin\Administrator\Service\HTML\Configuration;
use Joomla\Component\Admin\Administrator\Service\HTML\Directory;
use Joomla\Component\Admin\Administrator\Service\HTML\PhpSetting;
use Joomla\Component\Admin\Administrator\Service\HTML\System;
use Psr\Container\ContainerInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Component class for com_admin
*
* @since 4.0.0
*/
class AdminComponent extends MVCComponent implements BootableExtensionInterface
{
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)
{
$this->getRegistry()->register('system', new System());
$this->getRegistry()->register('phpsetting', new PhpSetting());
$this->getRegistry()->register('directory', new Directory());
$this->getRegistry()->register('configuration', new Configuration());
}
}

View File

@ -0,0 +1,189 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Model;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Help\Help;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Admin Component Help Model
*
* @since 1.6
*/
class HelpModel extends BaseDatabaseModel
{
/**
* The search string
*
* @var string
* @since 1.6
*/
protected $help_search = null;
/**
* The page to be viewed
*
* @var string
* @since 1.6
*/
protected $page = null;
/**
* The ISO language tag
*
* @var string
* @since 1.6
*/
protected $lang_tag = null;
/**
* Table of contents
*
* @var array
* @since 1.6
*/
protected $toc = [];
/**
* URL for the latest version check
*
* @var string
* @since 1.6
*/
protected $latest_version_check = null;
/**
* Method to get the help search string
*
* @return string Help search string
*
* @since 1.6
*/
public function &getHelpSearch()
{
if (\is_null($this->help_search)) {
$this->help_search = Factory::getApplication()->getInput()->getString('helpsearch');
}
return $this->help_search;
}
/**
* Method to get the page
*
* @return string The page
*
* @since 1.6
*/
public function &getPage()
{
if (\is_null($this->page)) {
$this->page = Help::createUrl(Factory::getApplication()->getInput()->get('page', 'Start_Here'));
}
return $this->page;
}
/**
* Method to get the lang tag
*
* @return string lang iso tag
*
* @since 1.6
*/
public function getLangTag()
{
if (\is_null($this->lang_tag)) {
$this->lang_tag = Factory::getLanguage()->getTag();
if (!is_dir(JPATH_BASE . '/help/' . $this->lang_tag)) {
// Use English as fallback
$this->lang_tag = 'en-GB';
}
}
return $this->lang_tag;
}
/**
* Method to get the table of contents
*
* @return array Table of contents
*/
public function &getToc()
{
if (\count($this->toc)) {
return $this->toc;
}
// Get vars
$lang_tag = $this->getLangTag();
$help_search = $this->getHelpSearch();
// New style - Check for a TOC \JSON file
if (file_exists(JPATH_BASE . '/help/' . $lang_tag . '/toc.json')) {
$data = json_decode(file_get_contents(JPATH_BASE . '/help/' . $lang_tag . '/toc.json'));
// Loop through the data array
foreach ($data as $key => $value) {
$this->toc[$key] = Text::_('COM_ADMIN_HELP_' . $value);
}
// Sort the Table of Contents
asort($this->toc);
return $this->toc;
}
// Get Help files
$files = Folder::files(JPATH_BASE . '/help/' . $lang_tag, '\.xml$|\.html$');
foreach ($files as $file) {
$buffer = file_get_contents(JPATH_BASE . '/help/' . $lang_tag . '/' . $file);
if (!preg_match('#<title>(.*?)</title>#', $buffer, $m)) {
continue;
}
$title = trim($m[1]);
if (!$title) {
continue;
}
// Translate the page title
$title = Text::_($title);
// Strip the extension
$file = preg_replace('#\.xml$|\.html$#', '', $file);
if ($help_search && StringHelper::strpos(StringHelper::strtolower(strip_tags($buffer)), StringHelper::strtolower($help_search)) === false) {
continue;
}
// Add an item in the Table of Contents
$this->toc[$file] = $title;
}
// Sort the Table of Contents
asort($this->toc);
return $this->toc;
}
}

View File

@ -0,0 +1,732 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Extension\ExtensionHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Model for the display of system information.
*
* @since 1.6
*/
class SysinfoModel extends BaseDatabaseModel
{
/**
* 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(),
'compatpluginenabled' => PluginHelper::isEnabled('behaviour', 'compat'),
'compatpluginparameters' => $this->getCompatPluginParameters(),
'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
];
return $this->info;
}
private function getCompatPluginParameters()
{
$record = ExtensionHelper::getExtensionRecord('compat', 'plugin', 0, 'behaviour');
if ($record) {
$params = new Registry($record->params);
return ArrayHelper::toString($params->toArray(), ':', ', ');
}
return '';
}
/**
* Check if the phpinfo function is enabled
*
* @return boolean True if enabled
*
* @since 3.4.1
*/
public function phpinfoEnabled(): bool
{
// remove any spaces from the ini value before exploding it
$disabledFunctions = str_replace(' ', '', ini_get('disable_functions'));
return !\in_array('phpinfo', explode(',', $disabledFunctions));
}
/**
* 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_clean();
preg_match_all('#<body[^>]*>(.*)</body>#siU', $phpInfo, $output);
$output = preg_replace('#<table[^>]*>#', '<table class="table">', $output[1][0]);
$output = preg_replace('#(\w),(\w)#', '\1, \2', $output);
$output = str_replace('<hr />', '', $output);
$output = str_replace('<div class="text-center">', '', $output);
$output = preg_replace('#<tr class="h">(.*)</tr>#', '<thead><tr class="h">$1</tr></thead><tbody>', $output);
$output = str_replace('</table>', '</tbody></table>', $output);
$output = str_replace('</div>', '', $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 = $this->getDatabase();
$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, '<h2><th><td>');
$html = preg_replace('/<th[^>]*>([^<]+)<\/th>/', '<info>\1</info>', $html);
$html = preg_replace('/<td[^>]*>([^<]+)<\/td>/', '<info>\1</info>', $html);
$t = preg_split('/(<h2[^>]*>[^<]+<\/h2>)/', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
$r = [];
$count = \count($t);
$p1 = '<info>([^<]+)<\/info>';
$p2 = '/' . $p1 . '\s*' . $p1 . '\s*' . $p1 . '/';
$p3 = '/' . $p1 . '\s*' . $p1 . '/';
for ($i = 1; $i < $count; $i++) {
if (preg_match('/<h2[^>]*>([^<]+)<\/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;
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Service\HTML;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Class for rendering configuration values
*
* @since 4.0.0
*/
class Configuration
{
/**
* Method to generate a string for a value
*
* @param mixed $value The configuration value
*
* @return string Formatted and escaped string
*
* @since 4.0.0
*/
public function value($value): string
{
if (\is_bool($value)) {
return $value ? 'true' : 'false';
}
if (\is_array($value)) {
$value = implode(', ', $value);
}
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Service\HTML;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Utility class working with directory
*
* @since 1.6
*/
class Directory
{
/**
* 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 '<span class="badge bg-success">' . Text::_('COM_ADMIN_WRITABLE') . '</span>';
}
return '<span class="badge bg-danger">' . Text::_('COM_ADMIN_UNWRITABLE') . '</span>';
}
/**
* 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 . ' <strong>' . Text::_($message) . '</strong>';
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Service\HTML;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Utility class working with phpsetting
*
* @since 1.6
*/
class PhpSetting
{
/**
* Method to generate a boolean message for a value
*
* @param boolean $val is the value set?
*
* @return string html code
*/
public function boolean($val)
{
return Text::_($val ? 'JON' : 'JOFF');
}
/**
* Method to generate a boolean message for a value
*
* @param boolean $val is the value set?
*
* @return string html code
*/
public function set($val)
{
return Text::_($val ? 'JYES' : 'JNO');
}
/**
* Method to generate a string message for a value
*
* @param string $val a php ini value
*
* @return string html code
*/
public function string($val)
{
return !empty($val) ? $val : Text::_('JNONE');
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\Service\HTML;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Utility class working with system
*
* @since 1.6
*/
class System
{
/**
* Method to generate a string message for a value
*
* @param string $val a php ini value
*
* @return string html code
*/
public function server($val)
{
return !empty($val) ? $val : Text::_('COM_ADMIN_NA');
}
}

View File

@ -0,0 +1,97 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\View\Help;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Admin\Administrator\Model\HelpModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* HTML View class for the Admin component
*
* @since 1.6
*/
class HtmlView extends BaseHtmlView
{
/**
* The search string
*
* @var string
* @since 1.6
*/
protected $helpSearch = null;
/**
* The page to be viewed
*
* @var string
* @since 1.6
*/
protected $page = null;
/**
* The iso language tag
*
* @var string
* @since 1.6
*/
protected $languageTag = null;
/**
* Table of contents
*
* @var array
* @since 1.6
*/
protected $toc = [];
/**
* 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();
parent::display($tpl);
}
/**
* Setup the Toolbar
*
* @return void
*
* @since 1.6
*/
protected function addToolbar(): void
{
ToolbarHelper::title(Text::_('COM_ADMIN_HELP'), 'support help_header');
}
}

View File

@ -0,0 +1,123 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\View\Sysinfo;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Admin\Administrator\Model\SysinfoModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Sysinfo View class for the Admin component
*
* @since 1.6
*/
class HtmlView extends BaseHtmlView
{
/**
* Some PHP settings
*
* @var array
* @since 1.6
*/
protected $phpSettings = [];
/**
* 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 $phpInfo = null;
/**
* Information about writable state of directories
*
* @var array
* @since 1.6
*/
protected $directory = [];
/**
* 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();
$this->addToolbar();
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');
$toolbar = Toolbar::getInstance();
$toolbar->linkButton('download', 'COM_ADMIN_DOWNLOAD_SYSTEM_INFORMATION_TEXT')
->url(Route::_('index.php?option=com_admin&view=sysinfo&format=text&' . Session::getFormToken() . '=1'));
$toolbar->linkButton('download', 'COM_ADMIN_DOWNLOAD_SYSTEM_INFORMATION_JSON')
->url(Route::_('index.php?option=com_admin&view=sysinfo&format=json&' . Session::getFormToken() . '=1'));
$toolbar->help('Site_System_Information');
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\View\Sysinfo;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\AbstractView;
use Joomla\CMS\User\CurrentUserInterface;
use Joomla\CMS\User\CurrentUserTrait;
use Joomla\Component\Admin\Administrator\Model\SysinfoModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Sysinfo View class for the Admin component
*
* @since 3.5
*/
class JsonView extends AbstractView implements CurrentUserInterface
{
use CurrentUserTrait;
/**
* 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 (!$this->getCurrentUser()->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');
$data = $this->getLayoutData();
echo json_encode($data, JSON_PRETTY_PRINT);
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' => $model->getSafeData('info'),
'phpSettings' => $model->getSafeData('phpSettings'),
'config' => $model->getSafeData('config'),
'directories' => $model->getSafeData('directory', true),
'phpInfo' => $model->getSafeData('phpInfoArray'),
'extensions' => $model->getSafeData('extensions'),
];
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Admin\Administrator\View\Sysinfo;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\AbstractView;
use Joomla\CMS\User\CurrentUserInterface;
use Joomla\CMS\User\CurrentUserTrait;
use Joomla\Component\Admin\Administrator\Model\SysinfoModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Sysinfo View class for the Admin component
*
* @since 3.5
*/
class TextView extends AbstractView implements CurrentUserInterface
{
use CurrentUserTrait;
/**
* 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 (!$this->getCurrentUser()->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)) {
// The term "Master" is used because it is the term used in phpinfo() and this is a text representation of that.
$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);
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Help\Help;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
/** @var \Joomla\Component\Admin\Administrator\View\Help\HtmlView $this */
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('com_admin.admin-help');
?>
<form action="<?php echo Route::_('index.php?option=com_admin&amp;view=help'); ?>" method="post" name="adminForm" id="adminForm" class="main-card">
<div class="row mt-sm-3">
<div id="sidebar" class="col-md-3">
<button class="btn btn-sm btn-secondary my-2 options-menu d-md-none" type="button" data-bs-toggle="collapse" data-bs-target=".sidebar-nav" aria-controls="help-index" aria-expanded="false">
<span class="icon-align-justify" aria-hidden="true"></span>
<?php echo Text::_('JTOGGLE_SIDEBAR_MENU'); ?>
</button>
<div class="sidebar-nav" id="help-index">
<ul class="nav flex-column">
<li class="item"><?php echo HTMLHelper::_('link', Help::createUrl('Start_Here'), Text::_('COM_ADMIN_START_HERE'), ['target' => 'helpFrame']); ?></li>
<li class="item"><?php echo HTMLHelper::_('link', 'https://help.joomla.org/proxy?keyref=Help4.x:License', Text::_('COM_ADMIN_LICENSE'), ['target' => 'helpFrame']); ?></li>
<li class="item"><?php echo HTMLHelper::_('link', Help::createUrl('Glossary'), Text::_('COM_ADMIN_GLOSSARY'), ['target' => 'helpFrame']); ?></li>
<li class="divider"></li>
<li class="nav-header"><?php echo Text::_('COM_ADMIN_ALPHABETICAL_INDEX'); ?></li>
<?php foreach ($this->toc as $k => $v) : ?>
<li class="item">
<?php $url = Help::createUrl($k); ?>
<?php echo HTMLHelper::_('link', $url, $v, ['target' => 'helpFrame']); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
<div class="col-md-9">
<iframe onLoad="var x = getElementById('help-index'); x.classList.remove('show');" name="helpFrame" title="helpFrame" height="2100px" src="<?php echo $this->page; ?>" class="helpFrame table table-bordered"></iframe>
</div>
</div>
<input class="textarea" type="hidden" name="option" value="com_admin">
</form>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="COM_ADMIN_HELP_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_ADMIN_HELP_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>

View File

@ -0,0 +1,26 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
$this->getLanguage()->load('mod_menu', JPATH_ADMINISTRATOR);
$forumId = (int) Text::_('MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM_VALUE');
if (empty($forumId)) {
$forumId = 511;
}
$forumUrl = 'https://forum.joomla.org/viewforum.php?f=' . $forumId;
Factory::getApplication()->redirect($forumUrl);

View File

@ -0,0 +1,44 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/** @var \Joomla\Component\Admin\Administrator\View\Sysinfo\HtmlView $this */
?>
<div class="main-card">
<?php echo HTMLHelper::_('uitab.startTabSet', 'myTab', ['active' => 'site', 'recall' => true, 'breakpoint' => 768]); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'site', Text::_('COM_ADMIN_SYSTEM_INFORMATION')); ?>
<?php echo $this->loadTemplate('system'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'phpsettings', Text::_('COM_ADMIN_PHP_SETTINGS')); ?>
<?php echo $this->loadTemplate('phpsettings'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'config', Text::_('COM_ADMIN_CONFIGURATION_FILE')); ?>
<?php echo $this->loadTemplate('config'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'directory', Text::_('COM_ADMIN_DIRECTORY_PERMISSIONS')); ?>
<?php echo $this->loadTemplate('directory'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'phpinfo', Text::_('COM_ADMIN_PHP_INFORMATION')); ?>
<?php echo $this->loadTemplate('phpinfo'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.endTabSet'); ?>
</div>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="COM_ADMIN_SYSINFO_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_ADMIN_SYSINFO_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>

View File

@ -0,0 +1,46 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/** @var \Joomla\Component\Admin\Administrator\View\Sysinfo\HtmlView $this */
?>
<div class="sysinfo">
<table class="table">
<caption class="visually-hidden">
<?php echo Text::_('COM_ADMIN_CONFIGURATION_FILE'); ?>
</caption>
<thead>
<tr>
<th scope="col" class="w-30">
<?php echo Text::_('COM_ADMIN_SETTING'); ?>
</th>
<th scope="col">
<?php echo Text::_('COM_ADMIN_VALUE'); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->config as $key => $value) : ?>
<tr>
<th scope="row">
<?php echo $key; ?>
</th>
<td>
<?php echo HTMLHelper::_('configuration.value', $value); ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>

View File

@ -0,0 +1,47 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/** @var \Joomla\Component\Admin\Administrator\View\Sysinfo\HtmlView $this */
?>
<div class="sysinfo">
<table class="table">
<caption class="visually-hidden">
<?php echo Text::_('COM_ADMIN_DIRECTORY_PERMISSIONS'); ?>
</caption>
<thead>
<tr>
<th scope="col" class="w-60">
<?php echo Text::_('COM_ADMIN_DIRECTORY'); ?>
</th>
<th scope="col">
<?php echo Text::_('COM_ADMIN_STATUS'); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->directory as $dir => $info) : ?>
<tr>
<th scope="row">
<?php echo '&#x200E;' . HTMLHelper::_('directory.message', $dir, $info['message']); ?>
</th>
<td>
<?php echo HTMLHelper::_('directory.writable', $info['writable']); ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>

View File

@ -0,0 +1,18 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var \Joomla\Component\Admin\Administrator\View\Sysinfo\HtmlView $this */
?>
<div class="sysinfo">
<?php echo $this->phpInfo; ?>
</div>

View File

@ -0,0 +1,197 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/** @var \Joomla\Component\Admin\Administrator\View\Sysinfo\HtmlView $this */
?>
<div class="sysinfo">
<table class="table">
<caption class="visually-hidden">
<?php echo Text::_('COM_ADMIN_PHP_SETTINGS'); ?>
</caption>
<thead>
<tr>
<th scope="col" class="w-30">
<?php echo Text::_('COM_ADMIN_SETTING'); ?>
</th>
<th scope="col">
<?php echo Text::_('COM_ADMIN_VALUE'); ?>
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_UPLOAD_MAX_FILESIZE'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.string', $this->phpSettings['upload_max_filesize']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_POST_MAX_SIZE'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.string', $this->phpSettings['post_max_size']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_MEMORY_LIMIT'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.string', $this->phpSettings['memory_limit']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_OPEN_BASEDIR'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.string', $this->phpSettings['open_basedir']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DISPLAY_ERRORS'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.boolean', $this->phpSettings['display_errors']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_SHORT_OPEN_TAGS'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.boolean', $this->phpSettings['short_open_tag']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_FILE_UPLOADS'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.boolean', $this->phpSettings['file_uploads']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_OUTPUT_BUFFERING'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.boolean', $this->phpSettings['output_buffering']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_SESSION_SAVE_PATH'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.string', $this->phpSettings['session.save_path']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_SESSION_AUTO_START'); ?>
</th>
<td>
<?php echo (int) $this->phpSettings['session.auto_start']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_XML_ENABLED'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['xml']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_ZLIB_ENABLED'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['zlib']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_ZIP_ENABLED'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['zip']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DISABLED_FUNCTIONS'); ?>
</th>
<td class="break-word">
<?php echo HTMLHelper::_('phpsetting.string', $this->phpSettings['disable_functions']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::sprintf('COM_ADMIN_EXTENSION_AVAILABLE', 'Fileinfo'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['fileinfo']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_MBSTRING_ENABLED'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['mbstring']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::sprintf('COM_ADMIN_EXTENSION_AVAILABLE', 'GD'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['gd']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::sprintf('COM_ADMIN_EXTENSION_AVAILABLE', 'iconv'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['iconv']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::sprintf('COM_ADMIN_EXTENSION_AVAILABLE', 'intl'); ?>
</th>
<td>
<?php echo HTMLHelper::_('phpsetting.set', $this->phpSettings['intl']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_MAX_INPUT_VARS'); ?>
</th>
<td>
<?php echo (int) $this->phpSettings['max_input_vars']; ?>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,141 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_admin
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/** @var \Joomla\Component\Admin\Administrator\View\Sysinfo\HtmlView $this */
?>
<div class="sysinfo">
<table class="table">
<caption class="visually-hidden">
<?php echo Text::_('COM_ADMIN_SYSTEM_INFORMATION'); ?>
</caption>
<thead>
<tr>
<th scope="col" class="w-30">
<?php echo Text::_('COM_ADMIN_SETTING'); ?>
</th>
<th scope="col">
<?php echo Text::_('COM_ADMIN_VALUE'); ?>
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_PHP_BUILT_ON'); ?>
</th>
<td>
<?php echo $this->info['php']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DATABASE_TYPE'); ?>
</th>
<td>
<?php echo $this->info['dbserver']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DATABASE_VERSION'); ?>
</th>
<td>
<?php echo $this->info['dbversion']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DATABASE_COLLATION'); ?>
</th>
<td>
<?php echo $this->info['dbcollation']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DATABASE_CONNECTION_COLLATION'); ?>
</th>
<td>
<?php echo $this->info['dbconnectioncollation']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DATABASE_CONNECTION_ENCRYPTION'); ?>
</th>
<td>
<?php echo $this->info['dbconnectionencryption'] ?: Text::_('JNONE'); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_DATABASE_CONNECTION_ENCRYPTION_SUPPORTED'); ?>
</th>
<td>
<?php echo $this->info['dbconnencryptsupported'] ? Text::_('JYES') : Text::_('JNO'); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_PHP_VERSION'); ?>
</th>
<td>
<?php echo $this->info['phpversion']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_WEB_SERVER'); ?>
</th>
<td>
<?php echo HTMLHelper::_('system.server', $this->info['server']); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_WEBSERVER_TO_PHP_INTERFACE'); ?>
</th>
<td>
<?php echo $this->info['sapi_name']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_JOOMLA_VERSION'); ?>
</th>
<td>
<?php echo $this->info['version']; ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_JOOMLA_COMPAT_PLUGIN'); ?>
</th>
<td>
<?php echo $this->info['compatpluginenabled'] ? Text::_('JENABLED') . ' (' . $this->info['compatpluginparameters'] . ')' : Text::_('JDISABLED'); ?>
</td>
</tr>
<tr>
<th scope="row">
<?php echo Text::_('COM_ADMIN_USER_AGENT'); ?>
</th>
<td>
<?php echo htmlspecialchars($this->info['useragent'], ENT_COMPAT, 'UTF-8'); ?>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,13 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_ajax
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
require_once JPATH_SITE . '/components/com_ajax/ajax.php';

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_ajax</name>
<author>Joomla! Project</author>
<creationDate>2013-08</creationDate>
<copyright>(C) 2013 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>4.0.0</version>
<description>COM_AJAX_XML_DESCRIPTION</description>
<files folder="site">
<filename>ajax.php</filename>
</files>
<languages folder="site">
<language tag="en-GB">language/en-GB/com_ajax.ini</language>
</languages>
<administration>
<files folder="admin">
<filename>ajax.php</filename>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_ajax.ini</language>
<language tag="en-GB">language/en-GB/com_ajax.sys.ini</language>
</languages>
</administration>
</extension>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<access component="com_associations">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN" />
<action name="core.options" title="JACTION_OPTIONS" />
<action name="core.manage" title="JACTION_MANAGE" />
</section>
</access>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_associations</name>
<author>Joomla! Project</author>
<creationDate>2017-01</creationDate>
<copyright>(C) 2017 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>4.0.0</version>
<description>COM_ASSOCIATIONS_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Component\Associations</namespace>
<media destination="com_associations" folder="media">
<folder>css</folder>
<folder>js</folder>
</media>
<administration>
<menu img="class:language">COM_ASSOCIATIONS</menu>
<files folder="admin">
<filename>access.xml</filename>
<filename>associations.xml</filename>
<filename>config.xml</filename>
<folder>forms</folder>
<folder>layouts</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_associations.ini</language>
<language tag="en-GB">language/en-GB/com_associations.sys.ini</language>
</languages>
</administration>
</extension>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<config>
<help key="Multilingual_Associations:_Options"/>
<fieldset
name="permissions"
label="JCONFIG_PERMISSIONS_LABEL"
>
<field
name="rules"
type="rules"
label="JCONFIG_PERMISSIONS_LABEL"
filter="rules"
validate="rules"
component="com_associations"
section="component"
/>
</fieldset>
</config>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<form addfieldprefix="Joomla\Component\Associations\Administrator\Field">
<fieldset>
<field
name="itemlanguage"
type="itemlanguage"
label="COM_ASSOCIATIONS_SELECT_TARGET_LANGUAGE_LABEL"
>
<option value="">COM_ASSOCIATIONS_SELECT_TARGET_LANGUAGE</option>
</field>
<field
name="modalassociation"
type="modal_association"
/>
</fieldset>
<fields name="params">
</fields>
</form>

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<form addfieldprefix="Joomla\Component\Associations\Administrator\Field">
<field
name="itemtype"
type="itemtype"
label="COM_ASSOCIATIONS_FILTER_SELECT_ITEM"
filtermode="selector"
class="js-select-reset-on-change"
>
<option value="">COM_ASSOCIATIONS_FILTER_SELECT_ITEM_TYPE</option>
</field>
<field
name="language"
type="contentlanguage"
label="COM_ASSOCIATIONS_FILTER_SELECT_LANGUAGE"
filtermode="selector"
class="js-select-submit-on-change"
>
<option value="">JOPTION_SELECT_LANGUAGE</option>
</field>
<fields name="filter">
<field
name="search"
type="text"
inputmode="search"
label="COM_ASSOCIATIONS_FILTER_SEARCH_LABEL"
description="COM_ASSOCIATIONS_FILTER_SEARCH_DESC"
hint="JSEARCH_FILTER"
/>
<field
name="state"
type="status"
label="JSTATUS"
class="js-select-submit-on-change"
>
<option value="">JOPTION_SELECT_PUBLISHED</option>
</field>
<field
name="category_id"
type="category"
label="JCATEGORY"
published="0,1,2"
extension="dynamic"
class="js-select-submit-on-change"
>
<option value="">JOPTION_SELECT_CATEGORY</option>
</field>
<field
name="menutype"
type="menu"
label="COM_ASSOCIATIONS_HEADING_MENUTYPE"
clientid="0"
class="js-select-submit-on-change"
>
<option value="">COM_ASSOCIATIONS_SELECT_MENU</option>
</field>
<field
name="access"
type="accesslevel"
label="JGRID_HEADING_ACCESS"
class="js-select-submit-on-change"
>
<option value="">JOPTION_SELECT_ACCESS</option>
</field>
<field
name="level"
type="integer"
label="JGLOBAL_MAXLEVEL_LABEL"
first="1"
last="10"
step="1"
class="js-select-submit-on-change"
>
<option value="">JOPTION_SELECT_MAX_LEVELS</option>
</field>
</fields>
<fields name="list">
<field
name="fullordering"
type="list"
label="JGLOBAL_SORT_BY"
default="id ASC"
class="js-select-submit-on-change"
validate="options"
>
<option value="">JGLOBAL_SORT_BY</option>
<option value="state ASC">JSTATUS_ASC</option>
<option value="state DESC">JSTATUS_DESC</option>
<option value="title ASC">JGLOBAL_TITLE_ASC</option>
<option value="title DESC">JGLOBAL_TITLE_DESC</option>
<option value="access_level ASC">JGRID_HEADING_ACCESS_ASC</option>
<option value="access_level DESC">JGRID_HEADING_ACCESS_DESC</option>
<option value="id ASC">JGRID_HEADING_ID_ASC</option>
<option value="id DESC">JGRID_HEADING_ID_DESC</option>
</field>
<field
name="limit"
type="limitbox"
label="JGLOBAL_LIST_LIMIT"
default="25"
class="js-select-submit-on-change"
/>
</fields>
</form>

View File

@ -0,0 +1,124 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
$data = $displayData;
// Receive overridable options
$data['options'] = !empty($data['options']) ? $data['options'] : [];
$noResultsText = '';
$hideActiveFilters = false;
$showFilterButton = false;
$showSelector = false;
$selectorFieldName = $data['options']['selectorFieldName'] ?? 'client_id';
// If a filter form exists.
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 = [
'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.
$data['options'] = array_merge($customOptions, $data['options']);
// Add class to hide the active filters if needed.
$filtersActiveClass = $hideActiveFilters ? '' : ' js-stools-container-filters-visible';
// Load search tools
HTMLHelper::_('searchtools.form', $data['options']['formSelector'], $data['options']);
?>
<div class="js-stools" role="search">
<?php // Add the itemtype and language selectors before the form filters. Do not display in modal. ?>
<?php $app = Factory::getApplication(); ?>
<?php if ($app->getInput()->get('forcedItemType', '', 'string') == '') : ?>
<?php $itemTypeField = $data['view']->filterForm->getField('itemtype'); ?>
<div class="js-stools-container-selector">
<div class="js-stools-field-selector js-stools-itemtype">
<div class="visually-hidden"><?php echo $itemTypeField->label; ?></div>
<?php echo $itemTypeField->input; ?>
</div>
</div>
<?php endif; ?>
<?php if ($app->getInput()->get('forcedLanguage', '', 'cmd') == '') : ?>
<?php $languageField = $data['view']->filterForm->getField('language'); ?>
<div class="js-stools-container-selector">
<div class="js-stools-field-selector js-stools-language">
<div class="visually-hidden"><?php echo $languageField->label; ?></div>
<?php echo $languageField->input; ?>
</div>
</div>
<?php endif; ?>
<?php if ($data['options']['showSelector']) : ?>
<div class="js-stools-container-selector">
<?php echo LayoutHelper::render('joomla.searchtools.default.selector', $data); ?>
</div>
<?php endif; ?>
<div class="js-stools-container-bar ms-auto">
<div class="btn-toolbar">
<?php echo $this->sublayout('bar', $data); ?>
<?php echo $this->sublayout('list', $data); ?>
</div>
</div>
<!-- Filters div -->
<div class="js-stools-container-filters clearfix<?php echo $filtersActiveClass; ?>">
<?php if ($data['options']['filterButton']) : ?>
<?php echo $this->sublayout('filters', $data); ?>
<?php endif; ?>
</div>
</div>
<?php if ($data['options']['showNoResults']) : ?>
<?php echo $this->sublayout('noitems', $data); ?>
<?php endif; ?>

View File

@ -0,0 +1,53 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
/**
* The associations service provider.
*
* @since 4.0.0
*/
return new class () implements ServiceProviderInterface {
/**
* 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));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
return $component;
}
);
}
};

View File

@ -0,0 +1,90 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Router\Route;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Association edit controller class.
*
* @since 3.7.0
*/
class AssociationController extends FormController
{
/**
* 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');
// 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 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();
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 = [];
$targetId = $this->input->get('target-id', '', 'string');
if ($targetId !== '') {
$ids = array_unique(explode(',', $targetId));
}
$ids[] = $this->input->get('id', 0, 'int');
foreach ($ids as $key => $id) {
AssociationsHelper::getItem($extensionName, $typeName, $id)->checkIn();
}
}
$this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false));
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Router\Route;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Associations controller class.
*
* @since 3.7.0
*/
class AssociationsController extends AdminController
{
/**
* 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 = ['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', [], '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')
);
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Controller;
use Joomla\CMS\MVC\Controller\BaseController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Component Controller
*
* @since 3.7.0
*/
class DisplayController extends BaseController
{
/**
* The default view.
*
* @var string
*
* @since 3.7.0
*/
protected $default_view = 'associations';
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Dispatcher;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Dispatcher\ComponentDispatcher;
use Joomla\CMS\Language\Text;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* ComponentDispatcher class for com_associations
*
* @since 4.0.0
*/
class Dispatcher extends ComponentDispatcher
{
/**
* 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);
}
}
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Field listing item languages
*
* @since 3.7.0
*/
class ItemlanguageField extends ListField
{
/**
* 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()->getInput();
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([0, 1], false);
$options = [];
// Each option has the format "<lang>|<id>", 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);
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Field;
use Joomla\CMS\Form\Field\GroupedlistField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* A drop down containing all component item types that implement associations.
*
* @since 3.7.0
*/
class ItemtypeField extends GroupedlistField
{
/**
* 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 = [];
$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);
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Field\Modal;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Supports a modal item picker.
*
* @since 3.7.0
*/
class AssociationField extends FormField
{
/**
* 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 = [];
$linkAssociations = 'index.php?option=com_associations&amp;view=associations&amp;layout=modal&amp;tmpl=component'
. '&amp;forcedItemType=' . Factory::getApplication()->getInput()->get('itemtype', '', 'string') . '&amp;function=jSelectAssociation_' . $this->id;
$linkAssociations .= "&amp;forcedLanguage=' + document.getElementById('target-association').getAttribute('data-language') + '";
$urlSelect = $linkAssociations . '&amp;' . Session::getFormToken() . '=1';
// Select custom association button
$html[] = '<button'
. ' type="button"'
. ' id="select-change"'
. ' class="btn btn-secondary' . ($value ? '' : ' hidden') . '"'
. ' data-bs-toggle="modal"'
. ' data-select="' . Text::_('COM_ASSOCIATIONS_SELECT_TARGET') . '"'
. ' data-change="' . Text::_('COM_ASSOCIATIONS_CHANGE_TARGET') . '"'
. ' data-bs-target="#associationSelect' . $this->id . 'Modal">'
. '<span class="icon-file" aria-hidden="true"></span> '
. '<span id="select-change-text"></span>'
. '</button>';
// Clear association button
$html[] = '<button'
. ' type="button"'
. ' class="btn btn-secondary' . ($value ? '' : ' hidden') . '"'
. ' onclick="return Joomla.submitbutton(\'undo-association\');"'
. ' id="remove-assoc">'
. '<span class="icon-times" aria-hidden="true"></span> ' . Text::_('JCLEAR')
. '</button>';
$html[] = '<input type="hidden" id="' . $this->id . '_id" name="' . $this->name . '" value="' . $value . '">';
// Select custom association modal
$html[] = HTMLHelper::_(
'bootstrap.renderModal',
'associationSelect' . $this->id . 'Modal',
[
'title' => Text::_('COM_ASSOCIATIONS_SELECT_TARGET'),
'backdrop' => 'static',
'url' => $urlSelect,
'height' => '400px',
'width' => '800px',
'bodyHeight' => 70,
'modalWidth' => 80,
'footer' => '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">'
. Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . '</button>',
]
);
return implode("\n", $html);
}
}

View File

@ -0,0 +1,671 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Helper;
use Joomla\CMS\Association\AssociationExtensionInterface;
use Joomla\CMS\Association\AssociationServiceInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Associations component helper.
*
* @since 3.7.0
*/
class AssociationsHelper extends ContentHelper
{
/**
* 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 = [];
/**
* 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 [];
}
// 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([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 = '<strong>' . Text::sprintf('JCATEGORY_SPRINTF', $categoryTitle) . '</strong> <br>';
} 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 = '<strong>' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $menutypeTitle) . '</strong><br>';
}
$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] = [];
$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 = [
'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 = '<strong>' . htmlspecialchars($language->title, ENT_QUOTES, 'UTF-8') . '</strong><br>'
. htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '<br><br>' . $additional;
$classes = 'badge ' . $labelClass;
$items[$langCode]['link'] = '<a href="' . $url . '" class="' . $classes . '">' . $text . '</a>'
. '<div role="tooltip">' . $tooltip . '</div>';
}
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 = [];
$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 = [];
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([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;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Model;
use Joomla\CMS\MVC\Model\ListModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of article records.
*
* @since 3.7.0
*/
class AssociationModel extends ListModel
{
/**
* 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 = [], $loadData = true)
{
// Get the form.
$form = $this->loadForm('com_associations.association', 'association', ['control' => 'jform', 'load_data' => $loadData]);
return !empty($form) ? $form : false;
}
}

View File

@ -0,0 +1,568 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Model;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\Table\Table;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Database\ParameterType;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of article records.
*
* @since 3.7.0
*/
class AssociationsModel 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.7
*/
public function __construct($config = [], MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'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->getInput()->get('forcedLanguage', '', 'cmd');
$forcedItemType = $app->getInput()->get('forcedItemType', '', 'string');
// Adjust the context to support modal layouts.
if ($layout = $app->getInput()->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 = $this->getCurrentUser();
$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 = [
$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', '\\Joomla\\CMS\\Table\\');
$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;
}
}

Some files were not shown because too many files have changed in this diff Show More