primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_contenthistory</name>
<author>Joomla! Project</author>
<creationDate>2013-05</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_CONTENTHISTORY_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Component\Contenthistory</namespace>
<files folder="site">
<folder>src</folder>
</files>
<media destination="com_contenthistory" folder="media">
<folder>js</folder>
</media>
<administration>
<files folder="admin">
<filename>contenthistory.xml</filename>
<folder>helpers</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_contenthistory.ini</language>
<language tag="en-GB">language/en-GB/com_contenthistory.sys.ini</language>
</languages>
</administration>
</extension>

View File

@ -0,0 +1,27 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Categories helper.
*
* @since 3.2
*
* @deprecated 4.3 will be removed in 6.0
* Use \Joomla\Component\Contenthistory\Administrator\Helper\ContenthistoryHelper instead
*/
class ContenthistoryHelper extends \Joomla\Component\Contenthistory\Administrator\Helper\ContenthistoryHelper
{
}

View File

@ -0,0 +1,53 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @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 content history 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\\Contenthistory'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Contenthistory'));
$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,26 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\Controller;
use Joomla\CMS\MVC\Controller\BaseController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Contenthistory Controller
*
* @since 3.2
*/
class DisplayController extends BaseController
{
}

View File

@ -0,0 +1,96 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Contenthistory list controller class.
*
* @since 3.2
*/
class HistoryController extends AdminController
{
/**
* Proxy for getModel.
*
* @param string $name The name of the model
* @param string $prefix The prefix for the model
* @param array $config An additional array of parameters
*
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model
*
* @since 3.2
*/
public function getModel($name = 'History', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
/**
* Toggles the keep forever value for one or more history rows. If it was Yes, changes to No. If No, changes to Yes.
*
* @return void
*
* @since 3.2
*/
public function keep()
{
$this->checkToken();
// Get items to toggle keep forever from the request.
$cid = (array) $this->input->get('cid', [], 'int');
// Remove zero values resulting from input filter
$cid = array_filter($cid);
if (empty($cid)) {
$this->app->enqueueMessage(Text::_('COM_CONTENTHISTORY_NO_ITEM_SELECTED'), 'warning');
} else {
// Get the model.
$model = $this->getModel();
// Toggle keep forever status of the selected items.
if ($model->keep($cid)) {
$this->setMessage(Text::plural('COM_CONTENTHISTORY_N_ITEMS_KEEP_TOGGLE', \count($cid)));
} else {
$this->setMessage($model->getError(), 'error');
}
}
$this->setRedirect(
Route::_(
'index.php?option=com_contenthistory&view=history&layout=modal&tmpl=component&item_id='
. $this->input->getCmd('item_id') . '&' . Session::getFormToken() . '=1',
false
)
);
}
/**
* Gets the URL arguments to append to a list redirect.
*
* @return string The arguments to append to the redirect URL.
*
* @since 4.0.0
*/
protected function getRedirectToListAppend()
{
return '&layout=modal&tmpl=component&item_id=' . $this->input->get('item_id') . '&' . Session::getFormToken() . '=1';
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\Controller;
use Joomla\CMS\MVC\Controller\BaseController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Contenthistory list controller class.
*
* @since 3.2
*/
class PreviewController extends BaseController
{
/**
* Proxy for getModel.
*
* @param string $name The name of the model
* @param string $prefix The prefix for the model
* @param array $config An additional array of parameters
*
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model
*
* @since 3.2
*/
public function getModel($name = 'Preview', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @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\Contenthistory\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_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()
{
// Check the user has permission to access this component if in the backend
if ($this->app->getIdentity()->guest) {
throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
}
}
}

View File

@ -0,0 +1,363 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @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\Contenthistory\Administrator\Helper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Table\ContentHistory;
use Joomla\CMS\Table\ContentType;
use Joomla\CMS\Table\Table;
use Joomla\Database\ParameterType;
use Joomla\Filesystem\Folder;
use Joomla\Filesystem\Path;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Categories helper.
*
* @since 3.2
*/
class ContenthistoryHelper
{
/**
* Method to put all field names, including nested ones, in a single array for easy lookup.
*
* @param \stdClass $object Standard class object that may contain one level of nested objects.
*
* @return array Associative array of all field names, including ones in a nested object.
*
* @since 3.2
*/
public static function createObjectArray($object)
{
$result = [];
if ($object === null) {
return $result;
}
foreach ($object as $name => $value) {
$result[$name] = $value;
if (\is_object($value)) {
foreach ($value as $subName => $subValue) {
$result[$subName] = $subValue;
}
}
}
return $result;
}
/**
* Method to decode JSON-encoded fields in a standard object. Used to unpack JSON strings in the content history data column.
*
* @param string $jsonString JSON String to convert to an object.
*
* @return \stdClass Object with any JSON-encoded fields unpacked.
*
* @since 3.2
*/
public static function decodeFields($jsonString)
{
$object = json_decode($jsonString);
if (\is_object($object)) {
foreach ($object as $name => $value) {
if (!\is_null($value) && $subObject = json_decode($value)) {
$object->$name = $subObject;
}
}
}
return $object;
}
/**
* Method to get field labels for the fields in the JSON-encoded object.
* First we see if we can find translatable labels for the fields in the object.
* We translate any we can find and return an array in the format object->name => label.
*
* @param \stdClass $object Standard class object in the format name->value.
* @param ContentType $typesTable Table object with content history options.
*
* @return \stdClass Contains two associative arrays.
* $formValues->labels in the format name => label (for example, 'id' => 'Article ID').
* $formValues->values in the format name => value (for example, 'state' => 'Published'.
* This translates the text from the selected option in the form.
*
* @since 3.2
*/
public static function getFormValues($object, ContentType $typesTable)
{
$labels = [];
$values = [];
$expandedObjectArray = static::createObjectArray($object);
static::loadLanguageFiles($typesTable->type_alias);
if ($formFile = static::getFormFile($typesTable)) {
if ($xml = simplexml_load_file($formFile)) {
// Now we need to get all of the labels from the form
$fieldArray = $xml->xpath('//field');
$fieldArray = array_merge($fieldArray, $xml->xpath('//fields'));
foreach ($fieldArray as $field) {
if ($label = (string) $field->attributes()->label) {
$labels[(string) $field->attributes()->name] = Text::_($label);
}
}
// Get values for any list type fields
$listFieldArray = $xml->xpath('//field[@type="list" or @type="radio"]');
foreach ($listFieldArray as $field) {
$name = (string) $field->attributes()->name;
if (isset($expandedObjectArray[$name])) {
$optionFieldArray = $field->xpath('option[@value="' . $expandedObjectArray[$name] . '"]');
$valueText = null;
if (\is_array($optionFieldArray) && \count($optionFieldArray)) {
$valueText = trim((string) $optionFieldArray[0]);
}
$values[(string) $field->attributes()->name] = Text::_($valueText);
}
}
}
}
$result = new \stdClass();
$result->labels = $labels;
$result->values = $values;
return $result;
}
/**
* Method to get the XML form file for this component. Used to get translated field names for history preview.
*
* @param ContentType $typesTable Table object with content history options.
*
* @return mixed \JModel object if successful, false if no model found.
*
* @since 3.2
*/
public static function getFormFile(ContentType $typesTable)
{
// First, see if we have a file name in the $typesTable
$options = json_decode($typesTable->content_history_options);
if (\is_object($options) && isset($options->formFile) && is_file(JPATH_ROOT . '/' . $options->formFile)) {
$result = JPATH_ROOT . '/' . $options->formFile;
} else {
$aliasArray = explode('.', $typesTable->type_alias);
$component = ($aliasArray[1] == 'category') ? 'com_categories' : $aliasArray[0];
$path = Folder::makeSafe(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/');
array_shift($aliasArray);
$file = File::makeSafe(implode('.', $aliasArray) . '.xml');
$result = is_file($path . $file) ? $path . $file : false;
}
return $result;
}
/**
* Method to query the database using values from lookup objects.
*
* @param \stdClass $lookup The std object with the values needed to do the query.
* @param mixed $value The value used to find the matching title or name. Typically the id.
*
* @return mixed Value from database (for example, name or title) on success, false on failure.
*
* @since 3.2
*/
public static function getLookupValue($lookup, $value)
{
$result = false;
if (isset($lookup->sourceColumn) && isset($lookup->targetTable) && isset($lookup->targetColumn) && isset($lookup->displayColumn)) {
$db = Factory::getDbo();
$value = (int) $value;
$query = $db->getQuery(true);
$query->select($db->quoteName($lookup->displayColumn))
->from($db->quoteName($lookup->targetTable))
->where($db->quoteName($lookup->targetColumn) . ' = :value')
->bind(':value', $value, ParameterType::INTEGER);
$db->setQuery($query);
try {
$result = $db->loadResult();
} catch (\Exception $e) {
// Ignore any errors and just return false
return false;
}
}
return $result;
}
/**
* Method to remove fields from the object based on values entered in the #__content_types table.
*
* @param \stdClass $object Object to be passed to view layout file.
* @param ContentType $typeTable Table object with content history options.
*
* @return \stdClass object with hidden fields removed.
*
* @since 3.2
*/
public static function hideFields($object, ContentType $typeTable)
{
if ($options = json_decode($typeTable->content_history_options)) {
if (isset($options->hideFields) && \is_array($options->hideFields)) {
foreach ($options->hideFields as $field) {
unset($object->$field);
}
}
}
return $object;
}
/**
* Method to load the language files for the component whose history is being viewed.
*
* @param string $typeAlias The type alias, for example 'com_content.article'.
*
* @return void
*
* @since 3.2
*/
public static function loadLanguageFiles($typeAlias)
{
$aliasArray = explode('.', $typeAlias);
if (\is_array($aliasArray) && \count($aliasArray) == 2) {
$component = ($aliasArray[1] == 'category') ? 'com_categories' : $aliasArray[0];
$lang = Factory::getLanguage();
/**
* Loading language file from the administrator/language directory then
* loading language file from the administrator/components/extension/language directory
*/
$lang->load($component, JPATH_ADMINISTRATOR)
|| $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
// Force loading of backend global language file
$lang->load('joomla', Path::clean(JPATH_ADMINISTRATOR));
}
}
/**
* Method to create object to pass to the layout. Format is as follows:
* field is std object with name, value.
*
* Value can be a std object with name, value pairs.
*
* @param \stdClass $object The std object from the JSON string. Can be nested 1 level deep.
* @param \stdClass $formValues Standard class of label and value in an associative array.
*
* @return \stdClass Object with translated labels where available
*
* @since 3.2
*/
public static function mergeLabels($object, $formValues)
{
$result = new \stdClass();
if ($object === null) {
return $result;
}
$labelsArray = $formValues->labels;
$valuesArray = $formValues->values;
foreach ($object as $name => $value) {
$result->$name = new \stdClass();
$result->$name->name = $name;
$result->$name->value = $valuesArray[$name] ?? $value;
$result->$name->label = $labelsArray[$name] ?? $name;
if (\is_object($value)) {
$subObject = new \stdClass();
foreach ($value as $subName => $subValue) {
$subObject->$subName = new \stdClass();
$subObject->$subName->name = $subName;
$subObject->$subName->value = $valuesArray[$subName] ?? $subValue;
$subObject->$subName->label = $labelsArray[$subName] ?? $subName;
$result->$name->value = $subObject;
}
}
}
return $result;
}
/**
* Method to prepare the object for the preview and compare views.
*
* @param ContentHistory $table Table object loaded with data.
*
* @return \stdClass Object ready for the views.
*
* @since 3.2
*/
public static function prepareData(ContentHistory $table)
{
$object = static::decodeFields($table->version_data);
$typesTable = Table::getInstance('ContentType', 'Joomla\\CMS\\Table\\');
$typeAlias = explode('.', $table->item_id);
array_pop($typeAlias);
$typesTable->load(['type_alias' => implode('.', $typeAlias)]);
$formValues = static::getFormValues($object, $typesTable);
$object = static::mergeLabels($object, $formValues);
$object = static::hideFields($object, $typesTable);
$object = static::processLookupFields($object, $typesTable);
return $object;
}
/**
* Method to process any lookup values found in the content_history_options column for this table.
* This allows category title and user name to be displayed instead of the id column.
*
* @param \stdClass $object The std object from the JSON string. Can be nested 1 level deep.
* @param ContentType $typesTable Table object loaded with data.
*
* @return \stdClass Object with lookup values inserted.
*
* @since 3.2
*/
public static function processLookupFields($object, ContentType $typesTable)
{
if ($options = json_decode($typesTable->content_history_options)) {
if (isset($options->displayLookup) && \is_array($options->displayLookup)) {
foreach ($options->displayLookup as $lookup) {
$sourceColumn = $lookup->sourceColumn ?? false;
$sourceValue = $object->$sourceColumn->value ?? false;
if ($sourceColumn && $sourceValue && ($lookupValue = static::getLookupValue($lookup, $sourceValue))) {
$object->$sourceColumn->value = $lookupValue;
}
}
}
}
return $object;
}
}

View File

@ -0,0 +1,185 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\Model;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\Table\ContentHistory;
use Joomla\CMS\Table\ContentType;
use Joomla\CMS\Table\Table;
use Joomla\Component\Contenthistory\Administrator\Helper\ContenthistoryHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of contenthistory records.
*
* @since 3.2
*/
class CompareModel extends ListModel
{
/**
* Method to get a version history row.
*
* @return array|boolean On success, array of populated tables. False on failure.
*
* @since 3.2
*
* @throws NotAllowed Thrown if not authorised to edit an item
*/
public function getItems()
{
$input = Factory::getApplication()->getInput();
/** @var ContentHistory $table1 */
$table1 = $this->getTable('ContentHistory');
/** @var ContentHistory $table2 */
$table2 = $this->getTable('ContentHistory');
$id1 = $input->getInt('id1');
$id2 = $input->getInt('id2');
if (!$id1 || \is_array($id1) || !$id2 || \is_array($id2)) {
$this->setError(Text::_('COM_CONTENTHISTORY_ERROR_INVALID_ID'));
return false;
}
$result = [];
if (!$table1->load($id1) || !$table2->load($id2)) {
$this->setError(Text::_('COM_CONTENTHISTORY_ERROR_VERSION_NOT_FOUND'));
// Assume a failure to load the content means broken data, abort mission
return false;
}
// Get the first history record's content type record so we can check ACL
/** @var ContentType $contentTypeTable */
$contentTypeTable = $this->getTable('ContentType');
$typeAlias = explode('.', $table1->item_id);
array_pop($typeAlias);
$typeAlias = implode('.', $typeAlias);
if (!$contentTypeTable->load(['type_alias' => $typeAlias])) {
$this->setError(Text::_('COM_CONTENTHISTORY_ERROR_FAILED_LOADING_CONTENT_TYPE'));
// Assume a failure to load the content type means broken data, abort mission
return false;
}
$user = $this->getCurrentUser();
// Access check
if (!$user->authorise('core.edit', $table1->item_id) && !$this->canEdit($table1)) {
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$nullDate = $this->getDatabase()->getNullDate();
foreach ([$table1, $table2] as $table) {
$object = new \stdClass();
$object->data = ContenthistoryHelper::prepareData($table);
$object->version_note = $table->version_note;
// Let's use custom calendars when present
$object->save_date = HTMLHelper::_('date', $table->save_date, Text::_('DATE_FORMAT_LC6'));
$dateProperties = [
'modified_time',
'created_time',
'modified',
'created',
'checked_out_time',
'publish_up',
'publish_down',
];
foreach ($dateProperties as $dateProperty) {
if (
property_exists($object->data, $dateProperty)
&& $object->data->$dateProperty->value !== null
&& $object->data->$dateProperty->value !== $nullDate
) {
$object->data->$dateProperty->value = HTMLHelper::_(
'date',
$object->data->$dateProperty->value,
Text::_('DATE_FORMAT_LC6')
);
}
}
$result[] = $object;
}
return $result;
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $type The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return Table A Table object
*
* @since 3.2
*/
public function getTable($type = 'Contenthistory', $prefix = 'Joomla\\CMS\\Table\\', $config = [])
{
return Table::getInstance($type, $prefix, $config);
}
/**
* Method to test whether a record is editable
*
* @param ContentHistory $record A Table object.
*
* @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
*
* @since 3.6
*/
protected function canEdit($record)
{
$result = false;
if (!empty($record->item_id)) {
/**
* Make sure user has edit privileges for this content item. Note that we use edit permissions
* for the content item, not delete permissions for the content history row.
*/
$user = $this->getCurrentUser();
$result = $user->authorise('core.edit', $record->item_id);
// Finally try session (this catches edit.own case too)
if (!$result) {
/** @var ContentType $contentTypeTable */
$contentTypeTable = $this->getTable('ContentType');
$typeAlias = explode('.', $record->item_id);
$id = array_pop($typeAlias);
$typeAlias = implode('.', $typeAlias);
$contentTypeTable->load(['type_alias' => $typeAlias]);
$typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
$result = \in_array((int) $id, $typeEditables);
}
}
return $result;
}
}

View File

@ -0,0 +1,395 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\Model;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\CMSHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\Table\ContentHistory;
use Joomla\CMS\Table\ContentType;
use Joomla\CMS\Table\Table;
use Joomla\Database\ParameterType;
use Joomla\Database\QueryInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of contenthistory records.
*
* @since 3.2
*/
class HistoryModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.2
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'version_id',
'h.version_id',
'version_note',
'h.version_note',
'save_date',
'h.save_date',
'editor_user_id',
'h.editor_user_id',
];
}
parent::__construct($config, $factory);
}
/**
* Method to test whether a record is editable
*
* @param ContentHistory $record A Table object.
*
* @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
*
* @since 3.2
*/
protected function canEdit($record)
{
if (empty($record->item_id)) {
return false;
}
/**
* Make sure user has edit privileges for this content item. Note that we use edit permissions
* for the content item, not delete permissions for the content history row.
*/
$user = $this->getCurrentUser();
if ($user->authorise('core.edit', $record->item_id)) {
return true;
}
// Finally try session (this catches edit.own case too)
/** @var ContentType $contentTypeTable */
$contentTypeTable = $this->getTable('ContentType');
$typeAlias = explode('.', $record->item_id);
$id = array_pop($typeAlias);
$typeAlias = implode('.', $typeAlias);
$contentTypeTable->load(['type_alias' => $typeAlias]);
$typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
$result = \in_array((int) $id, $typeEditables);
return $result;
}
/**
* Method to test whether a history record can be deleted. Note that we check whether we have edit permissions
* for the content item row.
*
* @param ContentHistory $record A Table object.
*
* @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
*
* @since 3.6
*/
protected function canDelete($record)
{
return $this->canEdit($record);
}
/**
* Method to delete one or more records from content history table.
*
* @param array $pks An array of record primary keys.
*
* @return boolean True if successful, false if an error occurs.
*
* @since 3.2
*/
public function delete(&$pks)
{
$pks = (array) $pks;
$table = $this->getTable();
// Iterate the items to delete each one.
foreach ($pks as $i => $pk) {
if ($table->load($pk)) {
if ((int) $table->keep_forever === 1) {
unset($pks[$i]);
continue;
}
if ($this->canEdit($table)) {
if (!$table->delete($pk)) {
$this->setError($table->getError());
return false;
}
} else {
// Prune items that you can't change.
unset($pks[$i]);
$error = $this->getError();
if ($error) {
try {
Log::add($error, Log::WARNING, 'jerror');
} catch (\RuntimeException $exception) {
Factory::getApplication()->enqueueMessage($error, 'warning');
}
return false;
}
try {
Log::add(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), Log::WARNING, 'jerror');
} catch (\RuntimeException $exception) {
Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 'warning');
}
return false;
}
} else {
$this->setError($table->getError());
return false;
}
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Method to get an array of data items.
*
* @return mixed An array of data items on success, false on failure.
*
* @since 3.4.5
*
* @throws NotAllowed Thrown if not authorised to edit an item
*/
public function getItems()
{
$items = parent::getItems();
$user = $this->getCurrentUser();
if ($items === false) {
return false;
}
// This should be an array with at least one element
if (!\is_array($items) || !isset($items[0])) {
return $items;
}
// Access check
if (!$user->authorise('core.edit', $items[0]->item_id) && !$this->canEdit($items[0])) {
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
return $items;
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $type The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return Table A Table object
*
* @since 3.2
*/
public function getTable($type = 'ContentHistory', $prefix = 'Joomla\\CMS\\Table\\', $config = [])
{
return Table::getInstance($type, $prefix, $config);
}
/**
* Method to toggle on and off the keep forever value for one or more records from content history table.
*
* @param array $pks An array of record primary keys.
*
* @return boolean True if successful, false if an error occurs.
*
* @since 3.2
*/
public function keep(&$pks)
{
$pks = (array) $pks;
$table = $this->getTable();
// Iterate the items to delete each one.
foreach ($pks as $i => $pk) {
if ($table->load($pk)) {
if ($this->canEdit($table)) {
$table->keep_forever = $table->keep_forever ? 0 : 1;
if (!$table->store()) {
$this->setError($table->getError());
return false;
}
} else {
// Prune items that you can't change.
unset($pks[$i]);
$error = $this->getError();
if ($error) {
try {
Log::add($error, Log::WARNING, 'jerror');
} catch (\RuntimeException $exception) {
Factory::getApplication()->enqueueMessage($error, 'warning');
}
return false;
}
try {
Log::add(Text::_('COM_CONTENTHISTORY_ERROR_KEEP_NOT_PERMITTED'), Log::WARNING, 'jerror');
} catch (\RuntimeException $exception) {
Factory::getApplication()->enqueueMessage(Text::_('COM_CONTENTHISTORY_ERROR_KEEP_NOT_PERMITTED'), 'warning');
}
return false;
}
} else {
$this->setError($table->getError());
return false;
}
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 3.2
*/
protected function populateState($ordering = 'h.save_date', $direction = 'DESC')
{
$input = Factory::getApplication()->getInput();
$itemId = $input->get('item_id', '', 'string');
$this->setState('item_id', $itemId);
$this->setState('sha1_hash', $this->getSha1Hash());
// Load the parameters.
$params = ComponentHelper::getParams('com_contenthistory');
$this->setState('params', $params);
// List state information.
parent::populateState($ordering, $direction);
}
/**
* Build an SQL query to load the list data.
*
* @return QueryInterface
*
* @since 3.2
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
$itemId = $this->getState('item_id');
// Select the required fields from the table.
$query->select(
$this->getState(
'list.select',
[
$db->quoteName('h.version_id'),
$db->quoteName('h.item_id'),
$db->quoteName('h.version_note'),
$db->quoteName('h.save_date'),
$db->quoteName('h.editor_user_id'),
$db->quoteName('h.character_count'),
$db->quoteName('h.sha1_hash'),
$db->quoteName('h.version_data'),
$db->quoteName('h.keep_forever'),
]
)
)
->from($db->quoteName('#__history', 'h'))
->where($db->quoteName('h.item_id') . ' = :itemid')
->bind(':itemid', $itemId, ParameterType::STRING)
// Join over the users for the editor
->select($db->quoteName('uc.name', 'editor'))
->join(
'LEFT',
$db->quoteName('#__users', 'uc'),
$db->quoteName('uc.id') . ' = ' . $db->quoteName('h.editor_user_id')
);
// Add the list ordering clause.
$orderCol = $this->state->get('list.ordering');
$orderDirn = $this->state->get('list.direction');
$query->order($db->quoteName($orderCol) . $orderDirn);
return $query;
}
/**
* Get the sha1 hash value for the current item being edited.
*
* @return string sha1 hash of row data
*
* @since 3.2
*/
protected function getSha1Hash()
{
$result = false;
$item_id = Factory::getApplication()->getInput()->getCmd('item_id', '');
$typeAlias = explode('.', $item_id);
Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/' . $typeAlias[0] . '/tables');
$typeTable = $this->getTable('ContentType');
$typeTable->load(['type_alias' => $typeAlias[0] . '.' . $typeAlias[1]]);
$contentTable = $typeTable->getContentTable();
if ($contentTable && $contentTable->load($typeAlias[2])) {
$helper = new CMSHelper();
$dataObject = $helper->getDataObject($contentTable);
$result = $this->getTable('ContentHistory')->getSha1(json_encode($dataObject), $typeTable);
}
return $result;
}
}

View File

@ -0,0 +1,150 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\Model;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\ItemModel;
use Joomla\CMS\Table\ContentHistory;
use Joomla\CMS\Table\ContentType;
use Joomla\CMS\Table\Table;
use Joomla\Component\Contenthistory\Administrator\Helper\ContenthistoryHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of contenthistory records.
*
* @since 3.2
*/
class PreviewModel extends ItemModel
{
/**
* Method to get a version history row.
*
* @param integer $pk The id of the item
*
* @return \stdClass|boolean On success, standard object with row data. False on failure.
*
* @since 3.2
*
* @throws NotAllowed Thrown if not authorised to edit an item
*/
public function getItem($pk = null)
{
/** @var ContentHistory $table */
$table = $this->getTable('ContentHistory');
$versionId = Factory::getApplication()->getInput()->getInt('version_id');
if (!$versionId || \is_array($versionId) || !$table->load($versionId)) {
return false;
}
$user = $this->getCurrentUser();
// Access check
if (!$user->authorise('core.edit', $table->item_id) && !$this->canEdit($table)) {
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$result = new \stdClass();
$result->version_note = $table->version_note;
$result->data = ContenthistoryHelper::prepareData($table);
// Let's use custom calendars when present
$result->save_date = HTMLHelper::_('date', $table->save_date, Text::_('DATE_FORMAT_LC6'));
$dateProperties = [
'modified_time',
'created_time',
'modified',
'created',
'checked_out_time',
'publish_up',
'publish_down',
];
$nullDate = $this->getDatabase()->getNullDate();
foreach ($dateProperties as $dateProperty) {
if (
property_exists($result->data, $dateProperty)
&& $result->data->$dateProperty->value !== null
&& $result->data->$dateProperty->value !== $nullDate
) {
$result->data->$dateProperty->value = HTMLHelper::_(
'date',
$result->data->$dateProperty->value,
Text::_('DATE_FORMAT_LC6')
);
}
}
return $result;
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $type The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return Table A Table object
*
* @since 3.2
*/
public function getTable($type = 'ContentHistory', $prefix = 'Joomla\\CMS\\Table\\', $config = [])
{
return Table::getInstance($type, $prefix, $config);
}
/**
* Method to test whether a record is editable
*
* @param ContentHistory $record A Table object.
*
* @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
*
* @since 3.6
*/
protected function canEdit($record)
{
$result = false;
if (!empty($record->item_id)) {
/**
* Make sure user has edit privileges for this content item. Note that we use edit permissions
* for the content item, not delete permissions for the content history row.
*/
$user = $this->getCurrentUser();
$result = $user->authorise('core.edit', $record->item_id);
// Finally try session (this catches edit.own case too)
if (!$result) {
/** @var ContentType $contentTypeTable */
$contentTypeTable = $this->getTable('ContentType');
$typeAlias = explode('.', $record->item_id);
$id = array_pop($typeAlias);
$typeAlias = implode('.', $typeAlias);
$typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
$result = \in_array((int) $id, $typeEditables);
}
}
return $result;
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\View\Compare;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of contenthistory.
*
* @since 3.2
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*/
protected $items;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 3.2
*/
public function display($tpl = null)
{
$this->state = $this->get('State');
$this->items = $this->get('Items');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
parent::display($tpl);
}
}

View File

@ -0,0 +1,148 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\View\History;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarFactoryInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of contenthistory.
*
* @since 3.2
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*/
protected $items;
/**
* The model state
*
* @var Pagination
*/
protected $pagination;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* The toolbar for the history modal. Note this is rendered inside the modal rather than using the regular module
*
* @var Toolbar
*/
protected $toolbar;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 3.2
*/
public function display($tpl = null)
{
$this->state = $this->get('State');
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->toolbar = $this->addToolbar();
parent::display($tpl);
}
/**
* Add the page toolbar.
*
* @return Toolbar
*
* @since 4.0.0
*/
protected function addToolbar(): Toolbar
{
/** @var Toolbar $toolbar */
$toolbar = Factory::getContainer()->get(ToolbarFactoryInterface::class)->createToolbar('toolbar');
// Cache a session token for reuse throughout.
$token = Session::getFormToken();
// Clean up input to ensure a clean url.
$filter = InputFilter::getInstance();
$aliasArray = explode('.', $this->state->item_id);
if ($aliasArray[1] === 'category') {
$option = 'com_categories';
$append = '&amp;extension=' . $filter->clean($aliasArray[0], 'cmd');
} else {
$option = $aliasArray[0];
$append = '';
}
$task = $filter->clean($aliasArray[1], 'cmd') . '.loadhistory';
// Build the final urls.
$loadUrl = Route::_('index.php?option=' . $filter->clean($option, 'cmd') . $append . '&amp;task=' . $task . '&amp;' . $token . '=1');
$previewUrl = Route::_('index.php?option=com_contenthistory&view=preview&layout=preview&tmpl=component&' . $token . '=1');
$compareUrl = Route::_('index.php?option=com_contenthistory&view=compare&layout=compare&tmpl=component&' . $token . '=1');
$toolbar->basicButton('load', 'COM_CONTENTHISTORY_BUTTON_LOAD')
->attributes(['data-url' => $loadUrl])
->icon('icon-upload')
->buttonClass('btn btn-success')
->listCheck(true);
$toolbar->basicButton('preview', 'COM_CONTENTHISTORY_BUTTON_PREVIEW')
->attributes(['data-url' => $previewUrl])
->icon('icon-search')
->listCheck(true);
$toolbar->basicButton('compare', 'COM_CONTENTHISTORY_BUTTON_COMPARE')
->attributes(['data-url' => $compareUrl])
->icon('icon-search-plus')
->listCheck(true);
$toolbar->basicButton('keep', 'COM_CONTENTHISTORY_BUTTON_KEEP', 'history.keep')
->icon('icon-lock')
->listCheck(true);
$toolbar->basicButton('delete', 'COM_CONTENTHISTORY_BUTTON_DELETE', 'history.delete')
->buttonClass('btn btn-danger')
->icon('icon-times')
->listCheck(true);
return $toolbar;
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Contenthistory\Administrator\View\Preview;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of contenthistory.
*
* @since 1.5
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var \stdClass|false
*/
protected $item;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 3.2
*/
public function display($tpl = null)
{
$this->state = $this->get('State');
$this->item = $this->get('Item');
if (false === $this->item) {
$this->getLanguage()->load('com_content', JPATH_SITE, null, true);
throw new \Exception(Text::_('COM_CONTENT_ERROR_ARTICLE_NOT_FOUND'), 404);
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
parent::display($tpl);
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @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;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
/** @var \Joomla\Component\Contenthistory\Administrator\View\Compare\HtmlView $this */
Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
$version2 = $this->items[0];
$version1 = $this->items[1];
$object1 = $version1->data;
$object2 = $version2->data;
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('com_contenthistory.admin-compare-compare');
?>
<div role="main">
<h1 class="mb-3"><?php echo Text::_('COM_CONTENTHISTORY_COMPARE_TITLE'); ?></h1>
<table id="diff" class="table">
<caption class="visually-hidden">
<?php echo Text::_('COM_CONTENTHISTORY_COMPARE_CAPTION'); ?>
</caption>
<thead>
<tr>
<th scope="col" class="w-25"><?php echo Text::_('COM_CONTENTHISTORY_PREVIEW_FIELD'); ?></th>
<th scope="col"><?php echo Text::_('COM_CONTENTHISTORY_COMPARE_OLD'); ?></th>
<th scope="col"><?php echo Text::_('COM_CONTENTHISTORY_COMPARE_NEW'); ?></th>
<th scope="col"><?php echo Text::_('COM_CONTENTHISTORY_COMPARE_DIFF'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($object1 as $name => $value) : ?>
<?php if (isset($value->value) && isset($object2->$name->value) && $value->value != $object2->$name->value) : ?>
<?php if (is_object($value->value)) : ?>
<tr>
<td colspan="4">
<strong><?php echo $value->label; ?></strong>
</td>
</tr>
<?php foreach ($value->value as $subName => $subValue) : ?>
<?php $newSubValue = $object2->$name->value->$subName->value ?? ''; ?>
<?php if ($subValue->value || $newSubValue) : ?>
<?php if ($subValue->value != $newSubValue) : ?>
<tr>
<th scope="row"><em>&nbsp;&nbsp;<?php echo $subValue->label; ?></em></th>
<td class="original"><?php echo htmlspecialchars($subValue->value, ENT_COMPAT, 'UTF-8'); ?></td>
<td class="changed" ><?php echo htmlspecialchars($newSubValue, ENT_COMPAT, 'UTF-8'); ?></td>
<td class="diff">&nbsp;</td>
</tr>
<?php endif; ?>
<?php endif; ?>
<?php endforeach; ?>
<?php else : ?>
<tr>
<th scope="row">
<?php echo $value->label; ?>
</th>
<td class="original"><?php echo htmlspecialchars($value->value); ?></td>
<?php $object2->$name->value = is_object($object2->$name->value) ? json_encode($object2->$name->value) : $object2->$name->value; ?>
<td class="changed"><?php echo htmlspecialchars($object2->$name->value, ENT_COMPAT, 'UTF-8'); ?></td>
<td class="diff">&nbsp;</td>
</tr>
<?php endif; ?>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>

View File

@ -0,0 +1,124 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @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;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
/** @var \Joomla\Component\Contenthistory\Administrator\View\History\HtmlView $this */
Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
$hash = $this->state->get('sha1_hash');
$formUrl = 'index.php?option=com_contenthistory&view=history&layout=modal&tmpl=component&item_id=' . $this->state->get('item_id') . '&' . Session::getFormToken() . '=1';
Text::script('COM_CONTENTHISTORY_BUTTON_SELECT_ONE_VERSION', true);
Text::script('COM_CONTENTHISTORY_BUTTON_SELECT_TWO_VERSIONS', true);
Text::script('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST');
$inlineJS = <<<JS
document.querySelectorAll('.js-link-open-window').forEach((link) => link.addEventListener('click', (e) => {
e.preventDefault();
window.open(link.dataset.url, 'win2', 'width=800,height=600,resizable=yes,scrollbars=yes')
}));
JS;
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('multiselect')
->useScript('com_contenthistory.admin-history-modal')
->useScript('list-view')
->addInlineScript($inlineJS, [], ['type' => 'module']);
?>
<div class="container-popup">
<div id="subhead" class="subhead noshadow mb-3">
<?php echo $this->toolbar->render(); ?>
</div>
<form action="<?php echo Route::_($formUrl); ?>" method="post" name="adminForm" id="adminForm">
<table class="table table-sm">
<caption class="visually-hidden">
<?php echo Text::_('COM_CONTENTHISTORY_VERSION_CAPTION'); ?>
</caption>
<thead>
<tr>
<td class="w-1 text-center">
<input class="form-check-input" type="checkbox" name="checkall-toggle" value="" title="<?php echo Text::_('JGLOBAL_CHECK_ALL'); ?>" onclick="Joomla.checkAll(this)">
</td>
<th scope="col" class="w-15">
<?php echo Text::_('JDATE'); ?>
</th>
<th scope="col" class="w-15 d-none d-md-table-cell">
<?php echo Text::_('COM_CONTENTHISTORY_VERSION_NOTE'); ?>
</th>
<th scope="col" class="w-10">
<?php echo Text::_('COM_CONTENTHISTORY_KEEP_VERSION'); ?>
</th>
<th scope="col" class="w-15 d-none d-md-table-cell">
<?php echo Text::_('JAUTHOR'); ?>
</th>
<th scope="col" class="w-10 text-end">
<?php echo Text::_('COM_CONTENTHISTORY_CHARACTER_COUNT'); ?>
</th>
</tr>
</thead>
<tbody>
<?php $i = 0; ?>
<?php foreach ($this->items as $item) : ?>
<tr class="row<?php echo $i % 2; ?>">
<td class="text-center">
<?php echo HTMLHelper::_('grid.id', $i, $item->version_id, false, 'cid', 'cb', $item->save_date); ?>
</td>
<th scope="row">
<a href="#" class="js-link-open-window save-date" data-url="<?php echo Route::_('index.php?option=com_contenthistory&view=preview&layout=preview&tmpl=component&' . Session::getFormToken() . '=1&version_id=' . $item->version_id); ?>">
<?php echo HTMLHelper::_('date', $item->save_date, Text::_('DATE_FORMAT_LC6')); ?>
</a>
<?php if ($item->sha1_hash == $hash) : ?>
<span class="icon-star" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('JCURRENT'); ?></span>
<?php endif; ?>
</th>
<td class="d-none d-md-table-cell">
<?php echo htmlspecialchars($item->version_note); ?>
</td>
<td>
<?php if ($item->keep_forever) : ?>
<button type="button" class="js-grid-item-action btn btn-secondary btn-sm" data-item-id="cb<?php echo $i; ?>" data-item-task="history.keep">
<?php echo Text::_('JYES'); ?>
&nbsp;<span class="icon-lock" aria-hidden="true"></span>
</button>
<?php else : ?>
<button type="button" class="js-grid-item-action btn btn-secondary btn-sm" data-item-id="cb<?php echo $i; ?>" data-item-task="history.keep">
<?php echo Text::_('JNO'); ?>
</button>
<?php endif; ?>
</td>
<td class="d-none d-md-table-cell">
<?php echo empty($item->editor) ? $item->editor_user_id : htmlspecialchars($item->editor); ?>
</td>
<td class="text-end">
<?php echo number_format((int) $item->character_count, 0, Text::_('DECIMALS_SEPARATOR'), Text::_('THOUSANDS_SEPARATOR')); ?>
</td>
</tr>
<?php $i++; ?>
<?php endforeach; ?>
</tbody>
</table>
<?php /* load the pagination. */ ?>
<?php echo $this->pagination->getListFooter(); ?>
<input type="hidden" name="task" value="">
<input type="hidden" name="boxchecked" value="0">
<?php echo HTMLHelper::_('form.token'); ?>
</form>
</div>

View File

@ -0,0 +1,67 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_contenthistory
*
* @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;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
/** @var \Joomla\Component\Contenthistory\Administrator\View\Preview\HtmlView $this */
Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
?>
<div role="main">
<h1>
<?php echo Text::sprintf('COM_CONTENTHISTORY_PREVIEW_SUBTITLE_DATE', $this->item->save_date); ?>
</h1>
<?php if ($this->item->version_note) : ?>
<h2>
<?php echo Text::sprintf('COM_CONTENTHISTORY_PREVIEW_SUBTITLE', $this->item->version_note); ?>
</h2>
<?php endif; ?>
<table class="table">
<caption class="visually-hidden">
<?php echo Text::_('COM_CONTENTHISTORY_PREVIEW_CAPTION'); ?>
</caption>
<thead>
<tr>
<th class="w-25" scope="col"><?php echo Text::_('COM_CONTENTHISTORY_PREVIEW_FIELD'); ?></th>
<th scope="col"><?php echo Text::_('COM_CONTENTHISTORY_PREVIEW_VALUE'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->item->data as $name => $value) : ?>
<?php if (is_object($value->value)) : ?>
<tr>
<td colspan="2">
<?php echo $value->label; ?>
</td>
</tr>
<?php foreach ($value->value as $subName => $subValue) : ?>
<?php if ($subValue && isset($subValue->value)) : ?>
<?php $subValue->value = (\is_object($subValue->value) || \is_array($subValue->value)) ? \json_encode($subValue->value, \JSON_UNESCAPED_UNICODE) : $subValue->value; ?>
<tr>
<th scope="row"><em>&nbsp;&nbsp;<?php echo $subValue->label; ?></em></th>
<td><?php echo $subValue->value; ?></td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
<?php else : ?>
<tr>
<th scope="row"><?php echo $value->label; ?></th>
<td><?php echo $value->value; ?></td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>