diff --git a/administrator/components/com_banners/src/Model/BannerModel.php b/administrator/components/com_banners/src/Model/BannerModel.php
index 03e01ef025a29..9eab11d30dd15 100644
--- a/administrator/components/com_banners/src/Model/BannerModel.php
+++ b/administrator/components/com_banners/src/Model/BannerModel.php
@@ -16,6 +16,7 @@
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper;
use Joomla\Database\ParameterType;
@@ -29,7 +30,7 @@
*
* @since 1.6
*/
-class BannerModel extends AdminModel
+class BannerModel extends AdminModel implements VersionableModelInterface
{
use VersionableModelTrait;
diff --git a/administrator/components/com_banners/src/Model/ClientModel.php b/administrator/components/com_banners/src/Model/ClientModel.php
index 6378292da6ab2..fb672b14f9487 100644
--- a/administrator/components/com_banners/src/Model/ClientModel.php
+++ b/administrator/components/com_banners/src/Model/ClientModel.php
@@ -13,6 +13,7 @@
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Table\Table;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
// phpcs:disable PSR1.Files.SideEffects
@@ -24,7 +25,7 @@
*
* @since 1.6
*/
-class ClientModel extends AdminModel
+class ClientModel extends AdminModel implements VersionableModelInterface
{
use VersionableModelTrait;
diff --git a/administrator/components/com_categories/src/Model/CategoryModel.php b/administrator/components/com_categories/src/Model/CategoryModel.php
index bb2c26b083b21..b4fb1b5290a27 100644
--- a/administrator/components/com_categories/src/Model/CategoryModel.php
+++ b/administrator/components/com_categories/src/Model/CategoryModel.php
@@ -26,6 +26,7 @@
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Category;
use Joomla\CMS\UCM\UCMType;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper;
use Joomla\Database\ParameterType;
@@ -43,7 +44,7 @@
*
* @since 1.6
*/
-class CategoryModel extends AdminModel
+class CategoryModel extends AdminModel implements VersionableModelInterface
{
use VersionableModelTrait;
diff --git a/administrator/components/com_contact/src/Model/ContactModel.php b/administrator/components/com_contact/src/Model/ContactModel.php
index 3c0128e293cba..f5d884f848622 100644
--- a/administrator/components/com_contact/src/Model/ContactModel.php
+++ b/administrator/components/com_contact/src/Model/ContactModel.php
@@ -18,6 +18,7 @@
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\String\PunycodeHelper;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper;
use Joomla\Database\ParameterType;
@@ -33,7 +34,7 @@
*
* @since 1.6
*/
-class ContactModel extends AdminModel
+class ContactModel extends AdminModel implements VersionableModelInterface
{
use VersionableModelTrait;
diff --git a/administrator/components/com_content/src/Model/ArticleModel.php b/administrator/components/com_content/src/Model/ArticleModel.php
index 48862d40276f9..106a7139a7634 100644
--- a/administrator/components/com_content/src/Model/ArticleModel.php
+++ b/administrator/components/com_content/src/Model/ArticleModel.php
@@ -29,6 +29,7 @@
use Joomla\CMS\Table\TableInterface;
use Joomla\CMS\Tag\TaggableTableInterface;
use Joomla\CMS\UCM\UCMType;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
use Joomla\CMS\Workflow\Workflow;
use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper;
@@ -49,7 +50,7 @@
* @since 1.6
*/
-class ArticleModel extends AdminModel implements WorkflowModelInterface
+class ArticleModel extends AdminModel implements WorkflowModelInterface, VersionableModelInterface
{
use WorkflowBehaviorTrait;
use VersionableModelTrait;
diff --git a/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php b/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php
index 255e527846da2..ecab911f422ed 100644
--- a/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php
+++ b/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php
@@ -18,6 +18,7 @@
use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
use Joomla\Filesystem\Path;
+use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
@@ -75,8 +76,18 @@ public static function decodeFields($jsonString)
if (\is_object($object)) {
foreach ($object as $name => $value) {
- if (!\is_null($value) && $subObject = json_decode($value)) {
- $object->$name = $subObject;
+ if (!\is_null($value)) {
+ if (\is_object($value)) {
+ $object->$name = ArrayHelper::fromObject($value);
+ continue;
+ }
+
+ if (str_starts_with($value, '{')) {
+ $object->$name = json_decode($value);
+ continue;
+ }
+
+ $object->$name = $value;
}
}
}
diff --git a/administrator/components/com_contenthistory/tmpl/compare/compare.php b/administrator/components/com_contenthistory/tmpl/compare/compare.php
index 8f855b92c1ad8..56dc248d236f3 100644
--- a/administrator/components/com_contenthistory/tmpl/compare/compare.php
+++ b/administrator/components/com_contenthistory/tmpl/compare/compare.php
@@ -12,6 +12,7 @@
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
+use Joomla\Utilities\ArrayHelper;
/** @var \Joomla\Component\Contenthistory\Administrator\View\Compare\HtmlView $this */
@@ -19,8 +20,8 @@
$version2 = $this->items[0];
$version1 = $this->items[1];
-$object1 = $version1->data;
-$object2 = $version2->data;
+$object1 = ArrayHelper::fromObject($version1->data);
+$object2 = ArrayHelper::fromObject($version2->data);
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
@@ -43,40 +44,70 @@
- $value) : ?>
- value) && isset($object2->$name->value) && $value->value != $object2->$name->value) : ?>
- value)) : ?>
-
- |
- label; ?>
- |
-
- value as $subName => $subValue) : ?>
- $name->value->$subName->value ?? ''; ?>
- value || $newSubValue) : ?>
- value != $newSubValue) : ?>
+ $value1) : ?>
+
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
- | label; ?> |
- value, ENT_COMPAT, 'UTF-8'); ?> |
- |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
| |
-
+
+
+
+ |
+
+ |
+
+ |
+
+ |
+ |
+
-
-
-
- |
- label; ?>
- |
- value); ?> |
- $name->value = is_object($object2->$name->value) ? json_encode($object2->$name->value) : $object2->$name->value; ?>
- $name->value, ENT_COMPAT, 'UTF-8'); ?> |
- |
-
+
-
-
+
diff --git a/administrator/components/com_contenthistory/tmpl/preview/preview.php b/administrator/components/com_contenthistory/tmpl/preview/preview.php
index fca9e66163ee0..ff46dd7cc5788 100644
--- a/administrator/components/com_contenthistory/tmpl/preview/preview.php
+++ b/administrator/components/com_contenthistory/tmpl/preview/preview.php
@@ -51,14 +51,27 @@
value = (\is_object($subValue->value) || \is_array($subValue->value)) ? \json_encode($subValue->value, \JSON_UNESCAPED_UNICODE) : $subValue->value; ?>
| label; ?> |
- value; ?> |
+
+ value)) : ?>
+ value); ?>
+
+ value; ?>
+
+ |
+
| label; ?> |
- value; ?> |
+
+ value)) : ?>
+ value); ?>
+
+ value; ?>
+
+ |
diff --git a/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php b/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php
index 193171d9d12ef..e710ec0c7e5c1 100644
--- a/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php
+++ b/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php
@@ -17,6 +17,7 @@
use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\MVC\Model\AdminModel;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper;
use Joomla\Registry\Registry;
@@ -30,7 +31,7 @@
*
* @since 1.6
*/
-class NewsfeedModel extends AdminModel
+class NewsfeedModel extends AdminModel implements VersionableModelInterface
{
use VersionableModelTrait;
diff --git a/administrator/components/com_tags/src/Model/TagModel.php b/administrator/components/com_tags/src/Model/TagModel.php
index b4e1901670743..f1b8b51b2bd89 100644
--- a/administrator/components/com_tags/src/Model/TagModel.php
+++ b/administrator/components/com_tags/src/Model/TagModel.php
@@ -15,6 +15,7 @@
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
@@ -28,7 +29,7 @@
*
* @since 3.1
*/
-class TagModel extends AdminModel
+class TagModel extends AdminModel implements VersionableModelInterface
{
use VersionableModelTrait;
diff --git a/administrator/components/com_users/src/Model/NoteModel.php b/administrator/components/com_users/src/Model/NoteModel.php
index 91443ab089e46..4f820fd251488 100644
--- a/administrator/components/com_users/src/Model/NoteModel.php
+++ b/administrator/components/com_users/src/Model/NoteModel.php
@@ -13,6 +13,7 @@
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableModelTrait;
// phpcs:disable PSR1.Files.SideEffects
@@ -24,7 +25,7 @@
*
* @since 2.5
*/
-class NoteModel extends AdminModel
+class NoteModel extends AdminModel implements VersionableModelInterface
{
use VersionableModelTrait;
diff --git a/administrator/language/en-GB/joomla.ini b/administrator/language/en-GB/joomla.ini
index db5d787f4508d..d91cba72dd62b 100644
--- a/administrator/language/en-GB/joomla.ini
+++ b/administrator/language/en-GB/joomla.ini
@@ -308,6 +308,7 @@ JFIELD_PLG_SEARCH_SEARCHLIMIT_LABEL="Search Limit"
JFIELD_PUBLISHED_DESC="Set publication status."
JFIELD_READMORE_DESC="Add a custom text instead of Read More."
JFIELD_READMORE_LABEL="Read More Text"
+JFIELD_RULES_LABEL="Group Permissions"
JFIELD_SPACER_LABEL="
"
JFIELD_TITLE_DESC="Title"
JFIELD_VERSION_HISTORY_DESC="This button allows you to open a window to view older versions of this item."
diff --git a/libraries/src/MVC/Model/AdminModel.php b/libraries/src/MVC/Model/AdminModel.php
index 52e1a414efbaa..1d4c7fd8264c9 100644
--- a/libraries/src/MVC/Model/AdminModel.php
+++ b/libraries/src/MVC/Model/AdminModel.php
@@ -25,6 +25,8 @@
use Joomla\CMS\Table\TableInterface;
use Joomla\CMS\Tag\TaggableTableInterface;
use Joomla\CMS\UCM\UCMType;
+use Joomla\CMS\Versioning\VersionableModelInterface;
+use Joomla\CMS\Versioning\Versioning;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
@@ -1443,6 +1445,19 @@ public function save($data)
}
}
+ if ($this instanceof VersionableModelInterface) {
+ // Merge table data and data so that we write all data to the history
+ $tableData = ArrayHelper::fromObject($table);
+
+ $historyData = array_merge($tableData, $data);
+
+ // We have to set the key for new items, would be always 0 otherwise
+ $historyData[$key] = $this->getState($this->getName() . '.id');
+
+
+ $this->saveHistory($historyData, $context);
+ }
+
if ($app->getInput()->get('task') == 'editAssociations') {
return $this->redirectToAssociations($data);
}
@@ -1730,4 +1745,25 @@ protected function redirectToAssociations($data)
return true;
}
+
+ /**
+ * Method to save the history.
+ *
+ * @param array $data The form data.
+ * @param string $context The model context.
+ *
+ * @return boolean True on success, False on error.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function saveHistory(array $data, string $context)
+ {
+ $id = $this->getState($this->getName() . '.id');
+
+ $versionNote = \array_key_exists('version_note', $data) ? $data['version_note'] : '';
+
+ $result = Versioning::store($context, $id, ArrayHelper::toObject($data), $versionNote);
+
+ return $result;
+ }
}
diff --git a/libraries/src/Versioning/VersionableControllerTrait.php b/libraries/src/Versioning/VersionableControllerTrait.php
index 8e9d8a1e7eeaf..1acf83d697116 100644
--- a/libraries/src/Versioning/VersionableControllerTrait.php
+++ b/libraries/src/Versioning/VersionableControllerTrait.php
@@ -36,9 +36,9 @@ public function loadhistory()
$table = $model->getTable();
$historyId = $this->input->getInt('version_id', null);
- if (!$model->loadhistory($historyId, $table)) {
- $this->setMessage($model->getError(), 'error');
+ $id = $model->getItemIdFromHistory($historyId);
+ if (false === $id) {
$this->setRedirect(
Route::_(
'index.php?option=' . $this->option . '&view=' . $this->view_list
@@ -51,17 +51,13 @@ public function loadhistory()
}
// Determine the name of the primary key for the data.
- if (empty($key)) {
- $key = $table->getKeyName();
- }
-
- $recordId = $table->$key;
+ $key = $table->getKeyName();
// To avoid data collisions the urlVar may be different from the primary key.
$urlVar = empty($this->urlVar) ? $key : $this->urlVar;
// Access check.
- if (!$this->allowEdit([$key => $recordId], $key)) {
+ if (!$this->allowEdit([$key => $id], $key)) {
$this->setMessage(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
$this->setRedirect(
@@ -79,13 +75,21 @@ public function loadhistory()
$this->setRedirect(
Route::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
- . $this->getRedirectToItemAppend($recordId, $urlVar),
+ . $this->getRedirectToItemAppend($id, $urlVar),
false
)
);
- if (!$table->check() || !$table->store()) {
- $this->setMessage($table->getError(), 'error');
+ if (!$model->loadhistory($historyId, $table)) {
+ $this->setMessage($model->getError(), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . $this->getRedirectToListAppend(),
+ false
+ )
+ );
return false;
}
diff --git a/libraries/src/Versioning/VersionableModelInterface.php b/libraries/src/Versioning/VersionableModelInterface.php
new file mode 100644
index 0000000000000..010868ff52c4d
--- /dev/null
+++ b/libraries/src/Versioning/VersionableModelInterface.php
@@ -0,0 +1,34 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\Versioning;
+
+// phpcs:disable PSR1.Files.SideEffects
+
+\defined('_JEXEC') or die;
+// phpcs:enable PSR1.Files.SideEffects
+
+/**
+ * Interface for a versionable model.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+interface VersionableModelInterface
+{
+ /**
+ * Method to load a row for editing from the version history table.
+ *
+ * @param integer $historyId Key to the version history table.
+ *
+ * @return boolean False on failure or error, true otherwise.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function loadHistory(int $historyId);
+}
diff --git a/libraries/src/Versioning/VersionableModelTrait.php b/libraries/src/Versioning/VersionableModelTrait.php
index 8cdae9be05ddd..916ee5d086ad5 100644
--- a/libraries/src/Versioning/VersionableModelTrait.php
+++ b/libraries/src/Versioning/VersionableModelTrait.php
@@ -9,9 +9,8 @@
namespace Joomla\CMS\Versioning;
-use Joomla\CMS\Language\Text;
+use Joomla\CMS\Factory;
use Joomla\CMS\Table\ContentHistory;
-use Joomla\CMS\Table\Table;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
@@ -26,58 +25,95 @@
trait VersionableModelTrait
{
/**
- * Method to load a row for editing from the version history table.
+ * Method to get the item id from the version history table.
*
- * @param integer $versionId Key to the version history table.
- * @param Table $table Content table object being loaded.
+ * @param integer $historyId Key to the version history table.
*
- * @return boolean False on failure or error, true otherwise.
+ * @return integer False on failure or error, id otherwise.
*
- * @since 4.0.0
+ * @since __DEPLOY_VERSION__
*/
- public function loadHistory($versionId, Table $table)
+ public function getItemIdFromHistory($historyId)
{
- // Only attempt to check the row in if it exists, otherwise do an early exit.
- if (!$versionId) {
+ $rowArray = $this->getHistoryData($historyId);
+
+ if (false === $rowArray) {
return false;
}
- // Get an instance of the row to checkout.
- $historyTable = new ContentHistory($this->getDbo());
+ $table = $this->getTable();
+ $key = $table->getKeyName();
+
+ if (isset($rowArray[$key])) {
+ return $rowArray[$key];
+ }
- if (!$historyTable->load($versionId)) {
- $this->setError($historyTable->getError());
+ return false;
+ }
+ /**
+ * Method to get the version data from the version history table.
+ *
+ * @param integer $historyId Key to the version history table.
+ *
+ * @return mixed False on failure or error, data otherwise.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function getHistoryData($historyId)
+ {
+ // Get an instance of the row to checkout.
+ $historyTable = new ContentHistory($this->getDatabase());
+
+ if (!$historyTable->load($historyId)) {
return false;
}
- $typeAlias = explode('.', $historyTable->item_id);
- array_pop($typeAlias);
-
$rowArray = ArrayHelper::fromObject(json_decode($historyTable->version_data));
- $key = $table->getKeyName();
+ return $rowArray;
+ }
- if (implode('.', $typeAlias) != $this->typeAlias) {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_HISTORY_ID_MISMATCH'));
+ /**
+ * Method to get a version history table.
+ *
+ * @param integer $historyId Key to the version history table.
+ *
+ * @return mixed False on failure or error, table otherwise.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function getHistoryTable($historyId)
+ {
+ if (empty($historyId)) {
+ return false;
+ }
- if (isset($rowArray[$key])) {
- $table->checkIn($rowArray[$key]);
- }
+ // Get an instance of the row to checkout.
+ $historyTable = new ContentHistory($this->getDatabase());
+ if (!$historyTable->load($historyId)) {
return false;
}
- $this->setState('save_date', $historyTable->save_date);
- $this->setState('version_note', $historyTable->version_note);
+ return $historyTable;
+ }
- /**
- * Load data from current version before replacing it with data from history to avoid error
- * if there are some required keys missing in the history data
- */
+ /**
+ * Method to load a row for editing from the version history table.
+ *
+ * @param integer $historyId Key to the version history table.
+ *
+ * @return boolean False on failure or error, true otherwise.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function loadHistory(int $historyId)
+ {
+ $rowArray = $this->getHistoryData($historyId);
- if (isset($rowArray[$key])) {
- $table->load($rowArray[$key]);
+ if (false === $rowArray) {
+ return false;
}
// Fix null ordering when restoring history
@@ -85,6 +121,16 @@ public function loadHistory($versionId, Table $table)
$rowArray['ordering'] = 0;
}
- return $table->bind($rowArray);
+ [$extension, $type] = explode('.', $this->typeAlias);
+
+ $app = Factory::getApplication();
+ $app->setUserState($extension . '.edit.' . $type . '.data', $rowArray);
+
+ $historyTable = $this->getHistoryTable($historyId);
+
+ $this->setState('save_date', $historyTable->save_date);
+ $this->setState('version_note', $historyTable->version_note);
+
+ return true;
}
}
diff --git a/plugins/behaviour/versionable/src/Extension/Versionable.php b/plugins/behaviour/versionable/src/Extension/Versionable.php
index 3f5f5bc3b974d..e1db352945e73 100644
--- a/plugins/behaviour/versionable/src/Extension/Versionable.php
+++ b/plugins/behaviour/versionable/src/Extension/Versionable.php
@@ -15,6 +15,7 @@
use Joomla\CMS\Event\Table\BeforeDeleteEvent;
use Joomla\CMS\Helper\CMSHelper;
use Joomla\CMS\Plugin\CMSPlugin;
+use Joomla\CMS\Versioning\VersionableModelInterface;
use Joomla\CMS\Versioning\VersionableTableInterface;
use Joomla\CMS\Versioning\Versioning;
use Joomla\Event\DispatcherInterface;
@@ -97,21 +98,33 @@ public function onTableAfterStore(AfterStoreEvent $event)
// Extract arguments
/** @var VersionableTableInterface $table */
$table = $event['subject'];
+
+ // We need to check this first because getTypeAlias is only available when VersionableTableInterface is implemented
+ if (!$table instanceof VersionableTableInterface) {
+ return;
+ }
+
$result = $event['result'];
+ $typeAlias = $table->getTypeAlias();
+ [$component, $modelName] = explode('.', $typeAlias);
+
+ $model = $this->getApplication()->bootComponent($component)->getMVCFactory()->createModel($modelName, 'Administrator');
+
+ if ($model instanceof VersionableModelInterface) {
+ return;
+ }
+
if (!$result) {
return;
}
- if (!(\is_object($table) && $table instanceof VersionableTableInterface)) {
+ if (!(\is_object($table))) {
return;
}
// Get the Tags helper and assign the parsed alias
- $typeAlias = $table->getTypeAlias();
- $aliasParts = explode('.', $typeAlias);
-
- if ($aliasParts[0] === '' || !ComponentHelper::getParams($aliasParts[0])->get('save_history', 0)) {
+ if ($component === '' || !ComponentHelper::getParams($component)->get('save_history', 0)) {
return;
}