+
diff --git a/administrator/components/com_content/forms/filter_articles.xml b/administrator/components/com_content/forms/filter_articles.xml
index 73c1bb84e6f34..6eea0efcc394d 100644
--- a/administrator/components/com_content/forms/filter_articles.xml
+++ b/administrator/components/com_content/forms/filter_articles.xml
@@ -133,6 +133,8 @@
+
+
diff --git a/administrator/components/com_content/forms/filter_drafts.xml b/administrator/components/com_content/forms/filter_drafts.xml
new file mode 100644
index 0000000000000..73c1bb84e6f34
--- /dev/null
+++ b/administrator/components/com_content/forms/filter_drafts.xml
@@ -0,0 +1,170 @@
+
+
diff --git a/administrator/components/com_content/src/Controller/ArticleController.php b/administrator/components/com_content/src/Controller/ArticleController.php
index 27a76274978ba..ee0cd96c7f9e3 100644
--- a/administrator/components/com_content/src/Controller/ArticleController.php
+++ b/administrator/components/com_content/src/Controller/ArticleController.php
@@ -1,4 +1,5 @@
app->setUserState('com_menus.edit.item', array(
- 'data' => $editState,
- 'type' => $type,
- 'link' => $link)
+ $this->app->setUserState(
+ 'com_menus.edit.item',
+ array(
+ 'data' => $editState,
+ 'type' => $type,
+ 'link' => $link
+ )
);
$this->setRedirect(Route::_('index.php?option=com_menus&view=item&client_id=0&menutype=mainmenu&layout=edit', false));
@@ -182,4 +188,33 @@ public function batch($model = null)
return parent::batch($model);
}
+
+ public function saveAsDraft()
+ {
+ $this->checkToken();
+
+ $user = $this->app->getIdentity();
+ $id = $this->input->get('id', 0, 'integer');
+
+ // $id = $this->input->data->id;
+
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list . $this->getRedirectToListAppend();
+
+ // Access checks.
+ if (!$user->authorise('core.edit.state', 'com_content.article.' . (int) $id))
+ {
+ // Prune items that you can't change.
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
+ }
+
+ // Get the model.
+ /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
+ $model = $this->getModel();
+
+ $model->storeHistory($id);
+ $model->storeDraft($id);
+
+ $message = Text::plural('COM_CONTENT_N_ITEMS_DRAFTED', 1);
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
}
diff --git a/administrator/components/com_content/src/Controller/ArticlesController.php b/administrator/components/com_content/src/Controller/ArticlesController.php
index a037239355c1e..cda9197886a4f 100644
--- a/administrator/components/com_content/src/Controller/ArticlesController.php
+++ b/administrator/components/com_content/src/Controller/ArticlesController.php
@@ -1,4 +1,5 @@
checkToken();
+
+ $user = $this->app->getIdentity();
+ $ids = $this->input->get('cid', array(), 'array');
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list . $this->getRedirectToListAppend();
+ $message = '';
+
+ // Access checks.
+ foreach ($ids as $i => $id)
+ {
+ if (!$user->authorise('core.edit.state', 'com_content.article.' . (int) $id))
+ {
+ // Prune items that you can't change.
+ unset($ids[$i]);
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
+ }
+ }
+
+ if (empty($ids))
+ {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'error');
+ }
+ else
+ {
+ // Get the model.
+ /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
+ /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
+ $model = $this->getModel();
+
+ foreach ($ids as $id)
+ {
+ $model->storeHistory($id);
+ $model->storeDraft($id);
+ }
+
+ $message = Text::plural('COM_CONTENT_N_ITEMS_DRAFTED', count($ids));
+ }
+
+ $message = Text::plural('COM_CONTENT_N_ITEMS_DRAFTED', count($ids));
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
}
diff --git a/administrator/components/com_content/src/Controller/DraftsController.php b/administrator/components/com_content/src/Controller/DraftsController.php
new file mode 100644
index 0000000000000..3d46630e02b15
--- /dev/null
+++ b/administrator/components/com_content/src/Controller/DraftsController.php
@@ -0,0 +1,314 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Content\Administrator\Controller;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Application\CMSApplication;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\Controller\AdminController;
+use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
+use Joomla\CMS\Response\JsonResponse;
+use Joomla\CMS\Router\Route;
+use Joomla\Input\Input;
+use Joomla\Utilities\ArrayHelper;
+
+/**
+ * Drafts list controller class.
+ *
+ * @since 1.6
+ */
+class DraftsController 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 JApplication for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // Articles default form can come from the articles or featured view.
+ // Adjust the redirect view on the value of 'view' in the request.
+ if ($this->input->get('view') == 'featured')
+ {
+ $this->view_list = 'featured';
+ }
+
+ $this->registerTask('unfeatured', 'featured');
+ }
+
+ /**
+ * Method to toggle the featured setting of a list of articles.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function featured()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $user = $this->app->getIdentity();
+ $ids = $this->input->get('cid', array(), 'array');
+ $values = array('featured' => 1, 'unfeatured' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list . $this->getRedirectToListAppend();
+
+ // Access checks.
+ foreach ($ids as $i => $id)
+ {
+ if (!$user->authorise('core.edit.state', 'com_content.article.' . (int) $id))
+ {
+ // Prune items that you can't change.
+ unset($ids[$i]);
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
+ }
+ }
+
+ if (empty($ids))
+ {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'error');
+ }
+ else
+ {
+ // Get the model.
+ /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
+ $model = $this->getModel();
+
+ // Publish the items.
+ if (!$model->featured($ids, $value))
+ {
+ $this->setRedirect(Route::_($redirectUrl, false), $model->getError(), 'error');
+
+ return;
+ }
+
+ if ($value == 1)
+ {
+ $message = Text::plural('COM_CONTENT_N_ITEMS_FEATURED', count($ids));
+ }
+ else
+ {
+ $message = Text::plural('COM_CONTENT_N_ITEMS_UNFEATURED', count($ids));
+ }
+ }
+
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config The array of possible config values. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Draft', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to get the number of published articles for quickicons
+ *
+ * @return string The JSON-encoded amount of published articles
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('drafts');
+
+ $model->setState('filter.published', 1);
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_CONTENT_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_CONTENT_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
+
+ /**
+ * Method to toggle the unsharing draft.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function unshareDrafts()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Get the input
+ $task = $this->getTask();
+ $pks = $this->input->post->get('cid', array(), 'array');
+ $value = 1;
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list;
+ $message = '';
+
+ /** @var \Joomla\Component\Content\Administrator\Model\DraftModel $model */
+ $model = $this->getModel();
+
+ // Publish the items.
+ if (!$model->unshare($pks))
+ {
+ $this->setRedirect(Route::_($redirectUrl, false), $model->getError(), 'error');
+
+ return;
+ }
+
+ // TODO: HERE properly messages
+ $message = Text::plural('COM_CONTENT_N_ITEMS_UNSHARED', count($pks));
+
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
+
+ public function shareDrafts()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Get the input
+ $task = $this->getTask();
+ $pks = $this->input->post->get('cid', array(), 'array');
+ $value = 1;
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list;
+ $message = '';
+
+ /** @var \Joomla\Component\Content\Administrator\Model\DraftModel $model */
+ $model = $this->getModel();
+
+ // Publish the items.
+ if (!$model->share($pks, $value))
+ {
+ $this->setRedirect(Route::_($redirectUrl, false), $model->getError(), 'error');
+
+ return;
+ }
+
+ // TODO: HERE properly messages
+ $message = Text::plural('COM_CONTENT_N_ITEMS_SHARED', count($pks));
+
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
+
+
+ public function deleteDrafts()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Get the input
+ $pks = $this->input->post->get('cid', array(), 'array');
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list;
+ $message = '';
+
+ /** @var \Joomla\Component\Content\Administrator\Model\DraftModel $model */
+ $model = $this->getModel();
+
+ // Publish the items.
+ foreach ($pks as $id)
+ {
+ if (!$model->delete($id))
+ {
+ $this->setRedirect(Route::_($redirectUrl, false), $model->getError(), 'error');
+
+ return;
+ }
+ }
+
+ $message = Text::plural('COM_CONTENT_N_ITEMS_DELETED', count($pks));
+
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
+
+
+ /**
+ * Method to toggle the featured setting of a list of articles.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function published()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $user = $this->app->getIdentity();
+ $ids = $this->input->get('cid', array(), 'array');
+ $values = array('featured' => 1, 'unfeatured' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list . $this->getRedirectToListAppend();
+
+ // Access checks.
+ foreach ($ids as $i => $id)
+ {
+ if (!$user->authorise('core.edit.state', 'com_content.article.' . (int) $id))
+ {
+ // Prune items that you can't change.
+ unset($ids[$i]);
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
+ }
+ }
+
+ if (empty($ids))
+ {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'error');
+ }
+ else
+ {
+ // Get the model.
+ /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
+ $model = $this->getModel();
+
+ // Publish the items.
+ if (!$model->featured($ids, $value))
+ {
+ $this->setRedirect(Route::_($redirectUrl, false), $model->getError(), 'error');
+
+ return;
+ }
+
+ if ($value == 1)
+ {
+ $message = Text::plural('COM_CONTENT_N_ITEMS_FEATURED', count($ids));
+ }
+ else
+ {
+ $message = Text::plural('COM_CONTENT_N_ITEMS_UNFEATURED', count($ids));
+ }
+ }
+
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
+}
diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php
index 84780cc8ec041..9517e76aa6212 100644
--- a/administrator/components/com_content/src/Extension/ContentComponent.php
+++ b/administrator/components/com_content/src/Extension/ContentComponent.php
@@ -62,6 +62,7 @@ class ContentComponent extends MVCComponent implements
/**
* The trashed condition
*
+ *
* @since 4.0.0
*/
const CONDITION_NAMES = [
@@ -69,6 +70,7 @@ class ContentComponent extends MVCComponent implements
self::CONDITION_UNPUBLISHED => 'JUNPUBLISHED',
self::CONDITION_ARCHIVED => 'JARCHIVED',
self::CONDITION_TRASHED => 'JTRASHED',
+ self::CONDITION_DRAFTED => 'JDRAFTED',
];
/**
@@ -99,6 +101,8 @@ class ContentComponent extends MVCComponent implements
*/
const CONDITION_TRASHED = -2;
+ const CONDITION_DRAFTED = -3;
+
/**
* Booting the extension. This is the function to set up the environment of the extension like
* registering new class loaders, etc.
diff --git a/administrator/components/com_content/src/Model/ArticleModel.php b/administrator/components/com_content/src/Model/ArticleModel.php
index bc9bd82c04633..e3cbcedbd2117 100644
--- a/administrator/components/com_content/src/Model/ArticleModel.php
+++ b/administrator/components/com_content/src/Model/ArticleModel.php
@@ -1,4 +1,5 @@
bind(':featuredUp', $featured->featured_up, $featured->featured_up ? ParameterType::STRING : ParameterType::NULL)
->bind(':featuredDown', $featured->featured_down, $featured->featured_down ? ParameterType::STRING : ParameterType::NULL);
- $db->setQuery($query);
- $db->execute();
+ $db->setQuery($query);
+ $db->execute();
}
}
@@ -291,6 +292,99 @@ protected function batchMove($value, $pks, $contexts)
return true;
}
+ public function storeDraft($pk)
+ {
+ try
+ {
+ $db = $this->getDbo();
+ $item_id = "com_content.article." . $pk;
+
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('h.version_id')
+ ]
+ )
+ ->from($db->quoteName('#__history', 'h'))
+ ->where($db->quoteName('item_id') . ' = :item_id')
+ ->bind(':item_id', $item_id, ParameterType::STRING)
+ ->order('version_id DESC')->setLimit(1);
+
+ $version_id = $db->setQuery($query)->loadResult();
+ $hashval = $version_id . uniqid();
+
+ $query = $db->getQuery(true)
+ ->insert($db->quoteName('#__draft'))
+ ->columns(
+ [
+ $db->quoteName('article_id'),
+ $db->quoteName('version_id'),
+ $db->quoteName('hashval'),
+ ]
+ )
+ ->values(':article_id, :version_id, :hashval')
+ ->bind(':article_id', $pk, ParameterType::INTEGER)
+ ->bind(':version_id', $version_id, ParameterType::INTEGER)
+ ->bind(':hashval', $hashval, ParameterType::STRING);
+ $db->setQuery($query)->execute();
+
+ return true;
+ }
+ catch (\Exception $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+ /**
+ * Batch move categories to a new category.
+ *
+ * @param integer $value The new category ID.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.8.6
+ */
+ public function storeHistory($pk)
+ {
+ // Set some needed variables.
+ $this->table = $this->getTable();
+ $this->tableClassName = get_class($this->table);
+
+ // Check that the row actually exists
+ if (!$this->table->load($pk))
+ {
+ if ($error = $this->table->getError())
+ {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ }
+ else
+ {
+ // Not fatal error
+ $this->setError(Text::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk));
+ }
+
+ // Store the row.
+ if (!$this->table->store())
+ {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
/**
* Method to test whether a record can be deleted.
*
@@ -630,7 +724,8 @@ protected function loadFormData()
$data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
}
- $data->set('access',
+ $data->set(
+ 'access',
$app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
);
}
@@ -682,6 +777,7 @@ public function validate($form, $data, $group = null)
*
* @since 1.6
*/
+
public function save($data)
{
$input = Factory::getApplication()->input;
@@ -694,6 +790,22 @@ public function save($data)
$data['metadata']['author'] = $filter->clean($data['metadata']['author'], 'TRIM');
}
+ if (!isset($data['draft']) || $data['state'] == -3)
+ {
+ if (!isset($data['draft']) || $data['state'] != 1)
+ {
+ $data['draft'] = 1;
+ }
+ }
+
+ if ($data['state'] != -3)
+ {
+ if ($data['state'] == 1)
+ {
+ $data['draft'] = 0;
+ }
+ }
+
if (isset($data['created_by_alias']))
{
$data['created_by_alias'] = $filter->clean($data['created_by_alias'], 'TRIM');
@@ -799,7 +911,7 @@ public function save($data)
}
// Automatic handling of alias for empty fields
- if (in_array($input->get('task'), array('apply', 'save', 'save2new')) && (!isset($data['id']) || (int) $data['id'] == 0))
+ if (in_array($input->get('task'), array('apply', 'save', 'save2new', 'saveAsDraft')) && (!isset($data['id']) || (int) $data['id'] == 0))
{
if ($data['alias'] == null)
{
@@ -1031,6 +1143,46 @@ public function featured($pks, $value = 0, $featuredUp = null, $featuredDown = n
return true;
}
+ public function draft($pks)
+ {
+ // Sanitize the ids.
+ $pks = (array) $pks;
+ $pks = ArrayHelper::toInteger($pks);
+ $context = $this->option . '.' . $this->name;
+
+ if (empty($pks))
+ {
+ $this->setError(Text::_('COM_CONTENT_NO_ITEM_SELECTED'));
+
+ return false;
+ }
+
+ try
+ {
+ $value = -3;
+
+ // Adjust the mapping table.
+ // Clear the existing features settings.
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__content'))
+ ->set($db->quoteName('state') . ' = :state')
+ ->whereIn($db->quoteName('id'), $pks)
+ ->bind(':state', $value, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ catch (\Exception $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
/**
* A protected method to get a set of ordering conditions.
*
diff --git a/administrator/components/com_content/src/Model/ArticlesModel.php b/administrator/components/com_content/src/Model/ArticlesModel.php
index e9b8e02a02a43..1fc0b771c62ae 100644
--- a/administrator/components/com_content/src/Model/ArticlesModel.php
+++ b/administrator/components/com_content/src/Model/ArticlesModel.php
@@ -1,4 +1,5 @@
quoteName('a.metadesc'),
$db->quoteName('a.metadata'),
$db->quoteName('a.version'),
+ $db->quoteName('a.draft'),
+ $db->quoteName('a.shared')
]
)
)
@@ -351,7 +356,7 @@ protected function getListQuery()
// Filter by featured.
$featured = (string) $this->getState('filter.featured');
- if (in_array($featured, ['0','1']))
+ if (in_array($featured, ['0', '1']))
{
$featured = (int) $featured;
$query->where($db->quoteName('a.featured') . ' = :featured')
@@ -618,7 +623,7 @@ public function getTransitions()
$query = $db->getQuery(true);
- $query ->select(
+ $query->select(
[
$db->quoteName('t.id', 'value'),
$db->quoteName('t.title', 'text'),
@@ -704,4 +709,5 @@ public function getItems()
return $items;
}
+
}
diff --git a/administrator/components/com_content/src/Model/DraftModel.php b/administrator/components/com_content/src/Model/DraftModel.php
new file mode 100644
index 0000000000000..bdb07c7693a36
--- /dev/null
+++ b/administrator/components/com_content/src/Model/DraftModel.php
@@ -0,0 +1,348 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Content\Administrator\Model;
+
+\defined('_JEXEC') or die;
+
+use \Datetime;
+use Joomla\CMS\Event\AbstractEvent;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Form\Form;
+use Joomla\CMS\Form\FormFactoryInterface;
+use Joomla\CMS\Helper\TagsHelper;
+use Joomla\CMS\Language\Associations;
+use Joomla\CMS\Language\LanguageHelper;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
+use Joomla\CMS\MVC\Model\AdminModel;
+use Joomla\CMS\MVC\Model\WorkflowBehaviorTrait;
+use Joomla\CMS\MVC\Model\WorkflowModelInterface;
+use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\String\PunycodeHelper;
+use Joomla\CMS\Table\Table;
+use Joomla\CMS\Table\TableInterface;
+use Joomla\CMS\Tag\TaggableTableInterface;
+use Joomla\CMS\UCM\UCMType;
+use Joomla\CMS\Versioning\VersionableModelTrait;
+use Joomla\CMS\Workflow\Workflow;
+use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper;
+use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
+use Joomla\Database\ParameterType;
+use Joomla\Registry\Registry;
+use Joomla\Utilities\ArrayHelper;
+
+/**
+ * Item Model for a Draft.
+ *
+ * @since 1.6
+ */
+
+class DraftModel extends AdminModel implements WorkflowModelInterface
+{
+ use WorkflowBehaviorTrait, VersionableModelTrait;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_CONTENT';
+
+ /**
+ * The type alias for this content type (for example, 'com_content.draft').
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = 'com_content.draft';
+
+ /**
+ * The context used for the associations table
+ *
+ * @var string
+ * @since 3.4.4
+ */
+ protected $associationsContext = 'com_content.item';
+
+ /**
+ * The event to trigger before changing featured status one or more items.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $event_before_change_featured = null;
+
+ /**
+ * The event to trigger after changing featured status one or more items.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $event_after_change_featured = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param FormFactoryInterface $formFactory The form factory.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, FormFactoryInterface $formFactory = null)
+ {
+ $config['events_map'] = $config['events_map'] ?? [];
+
+ $config['events_map'] = array_merge(
+ ['featured' => 'content'],
+ $config['events_map']
+ );
+
+ parent::__construct($config, $factory, $formFactory);
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $app = Factory::getApplication();
+
+ // Get the form.
+ $form = $this->loadForm('com_content.draft', 'draft', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form))
+ {
+ return false;
+ }
+
+ // Object uses for checking edit state permission of draft
+ $record = new \stdClass;
+
+ // Get ID of the draft from input, for frontend, we use a_id while backend uses id
+ $draftIdFromInput = (int) $app->input->getInt('a_id') ?: $app->input->getInt('id', 0);
+
+ // On edit draft, we get ID of draft from draft.id state, but on save, we use data from input
+ $id = (int) $this->getState('draft.id', $draftIdFromInput);
+
+ $record->id = $id;
+
+ // For new drafts we load the potential state + associations
+ if ($id == 0 && $formField = $form->getField('catid'))
+ {
+ $assignedCatids = $data['catid'] ?? $form->getValue('catid');
+
+ $assignedCatids = is_array($assignedCatids)
+ ? (int) reset($assignedCatids)
+ : (int) $assignedCatids;
+
+ // Try to get the category from the category field
+ if (empty($assignedCatids))
+ {
+ $assignedCatids = $formField->getAttribute('default', null);
+
+ if (!$assignedCatids)
+ {
+ // Choose the first category available
+ $catOptions = $formField->options;
+
+ if ($catOptions && !empty($catOptions[0]->value))
+ {
+ $assignedCatids = (int) $catOptions[0]->value;
+ }
+ }
+ }
+
+ // Activate the reload of the form when category is changed
+ $form->setFieldAttribute('catid', 'refresh-enabled', true);
+ $form->setFieldAttribute('catid', 'refresh-cat-id', $assignedCatids);
+ $form->setFieldAttribute('catid', 'refresh-section', 'draft');
+
+ // Store ID of the category uses for edit state permission check
+ $record->catid = $assignedCatids;
+ }
+ else
+ {
+ // Get the category which the draft is being added to
+ if (!empty($data['catid']))
+ {
+ $catId = (int) $data['catid'];
+ }
+ else
+ {
+ $catIds = $form->getValue('catid');
+
+ $catId = is_array($catIds)
+ ? (int) reset($catIds)
+ : (int) $catIds;
+
+ if (!$catId)
+ {
+ $catId = (int) $form->getFieldAttribute('catid', 'default', 0);
+ }
+ }
+
+ $record->catid = $catId;
+ }
+
+ // Modify the form based on Edit State access controls.
+ if (!$this->canEditState($record))
+ {
+ // Disable fields for display.
+ $form->setFieldAttribute('featured', 'disabled', 'true');
+ $form->setFieldAttribute('featured_up', 'disabled', 'true');
+ $form->setFieldAttribute('featured_down', 'disabled', 'true');
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('publish_up', 'disabled', 'true');
+ $form->setFieldAttribute('publish_down', 'disabled', 'true');
+ $form->setFieldAttribute('state', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is an draft you can edit.
+ $form->setFieldAttribute('featured', 'filter', 'unset');
+ $form->setFieldAttribute('featured_up', 'filter', 'unset');
+ $form->setFieldAttribute('featured_down', 'filter', 'unset');
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('publish_up', 'filter', 'unset');
+ $form->setFieldAttribute('publish_down', 'filter', 'unset');
+ $form->setFieldAttribute('state', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_by user if not allowed to access com_users.
+ if (!Factory::getUser()->authorise('core.manage', 'com_users'))
+ {
+ $form->setFieldAttribute('created_by', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to unshare drafts.
+ *
+ * @param array $pks The ids of the items to toggle.
+ * @param integer $value The value to toggle to.
+ * @param string|Date $featuredUp The date which item featured up.
+ * @param string|Date $featuredDown The date which item featured down.
+ *
+ * @return boolean True on success.
+ */
+ public function unshare($pks)
+ {
+ // Sanitize the ids.
+ $pks = (array) $pks;
+
+ if (empty($pks))
+ {
+ $this->setError(Text::_('COM_CONTENT_NO_ITEM_SELECTED'));
+
+ return false;
+ }
+
+ try
+ {
+ $value = 0;
+
+ // Adjust the mapping table.
+ // Clear the existing features settings.
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__draft'))
+ ->set($db->quoteName('state') . ' = :state')
+ ->set($db->quoteName('shared_date') . ' = NULL')
+ ->whereIn($db->quoteName('hashval'), $pks, ParameterType::STRING)
+ ->bind(':state', $value, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ catch (\Exception $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public function share($pks)
+ {
+ // Sanitize the ids.
+ if (empty($pks))
+ {
+ $this->setError(Text::_('COM_CONTENT_NO_ITEM_SELECTED'));
+
+ return false;
+ }
+
+ try
+ {
+ $value = 1;
+
+ // Adjust the mapping table.
+ // Clear the existing features settings.
+ $now = new DateTime;
+ $date_sql = $now->format('Y-m-d H:i:s');
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__draft'))
+ ->set($db->quoteName('state') . ' = :state')
+ ->set($db->quoteName('shared_date') . ' = :date')
+ ->whereIn($db->quoteName('hashval'), $pks, ParameterType::STRING)
+ ->bind(':state', $value, ParameterType::INTEGER)
+ ->bind(':date', $date_sql, ParameterType::STRING);
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ catch (\Exception $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public function delete($pks)
+ {
+ // Sanitize the ids.
+ try
+ {
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__draft'))
+ ->where($db->quoteName('hashval') . ' = :hashval')
+ ->bind(':hashval', $pks, ParameterType::STRING);
+ $db->setQuery($query);
+ $db->execute();
+ }
+ catch (\Exception $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/administrator/components/com_content/src/Model/DraftsModel.php b/administrator/components/com_content/src/Model/DraftsModel.php
new file mode 100644
index 0000000000000..de8a519876d3b
--- /dev/null
+++ b/administrator/components/com_content/src/Model/DraftsModel.php
@@ -0,0 +1,388 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Content\Administrator\Model;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Associations;
+use Joomla\CMS\MVC\Model\ListModel;
+use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\Table\Table;
+use Joomla\Component\Content\Administrator\Extension\ContentComponent;
+use Joomla\Database\ParameterType;
+use Joomla\Registry\Registry;
+use Joomla\Utilities\ArrayHelper;
+
+/**
+ * Methods supporting a list of article records.
+ *
+ * @since 1.6
+ */
+class DraftsModel extends ListModel
+{
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 1.6
+ * @see \Joomla\CMS\MVC\Controller\BaseController
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields']))
+ {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'alias', 'a.alias',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'catid', 'a.catid', 'category_title',
+ 'state', 'a.state',
+ 'access', 'a.access', 'access_level',
+ 'created', 'a.created',
+ 'modified', 'a.modified',
+ 'created_by', 'a.created_by',
+ 'created_by_alias', 'a.created_by_alias',
+ 'ordering', 'a.ordering',
+ 'featured', 'a.featured',
+ 'featured_up', 'fp.featured_up',
+ 'featured_down', 'fp.featured_down',
+ 'language', 'a.language',
+ 'hits', 'a.hits',
+ 'publish_up', 'a.publish_up',
+ 'publish_down', 'a.publish_down',
+ 'published', 'a.published',
+ 'author_id',
+ 'category_id',
+ 'level',
+ 'tag',
+ 'rating_count', 'rating',
+ 'stage', 'wa.stage_id',
+ 'ws.title'
+ );
+
+ if (Associations::isEnabled())
+ {
+ $config['filter_fields'][] = 'association';
+ }
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Get the filter form
+ *
+ * @param array $data data
+ * @param boolean $loadData load current data
+ *
+ * @return Form|null The \JForm object or null if the form can't be found
+ *
+ * @since 3.2
+ */
+ public function getFilterForm($data = array(), $loadData = true)
+ {
+ $form = parent::getFilterForm($data, $loadData);
+
+ $params = ComponentHelper::getParams('com_content');
+
+ if (!$params->get('workflow_enabled'))
+ {
+ $form->removeField('stage', 'filter');
+ }
+ else
+ {
+ $ordering = $form->getField('fullordering', 'list');
+
+ $ordering->addOption('JSTAGE_ASC', ['value' => 'ws.title ASC']);
+ $ordering->addOption('JSTAGE_DESC', ['value' => 'ws.title DESC']);
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.id', $direction = 'desc')
+ {
+ $app = Factory::getApplication();
+
+ $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout'))
+ {
+ $this->context .= '.' . $layout;
+ }
+
+ // Adjust the context to support forced languages.
+ if ($forcedLanguage)
+ {
+ $this->context .= '.' . $forcedLanguage;
+ }
+
+ $search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
+ $this->setState('filter.search', $search);
+
+ $featured = $this->getUserStateFromRequest($this->context . '.filter.featured', 'filter_featured', '');
+ $this->setState('filter.featured', $featured);
+
+ $published = $this->getUserStateFromRequest($this->context . '.filter.published', 'filter_published', '');
+ $this->setState('filter.published', $published);
+
+ $level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level');
+ $this->setState('filter.level', $level);
+
+ $language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '');
+ $this->setState('filter.language', $language);
+
+ $formSubmited = $app->input->post->get('form_submited');
+
+ // Gets the value of a user state variable and sets it in the session
+ $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access');
+ $this->getUserStateFromRequest($this->context . '.filter.author_id', 'filter_author_id');
+ $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id');
+ $this->getUserStateFromRequest($this->context . '.filter.tag', 'filter_tag', '');
+
+ if ($formSubmited)
+ {
+ $access = $app->input->post->get('access');
+ $this->setState('filter.access', $access);
+
+ $authorId = $app->input->post->get('author_id');
+ $this->setState('filter.author_id', $authorId);
+
+ $categoryId = $app->input->post->get('category_id');
+ $this->setState('filter.category_id', $categoryId);
+
+ $tag = $app->input->post->get('tag');
+ $this->setState('filter.tag', $tag);
+ }
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+
+ // Force a language
+ if (!empty($forcedLanguage))
+ {
+ $this->setState('filter.language', $forcedLanguage);
+ $this->setState('filter.forcedLanguage', $forcedLanguage);
+ }
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . serialize($this->getState('filter.access'));
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . serialize($this->getState('filter.category_id'));
+ $id .= ':' . serialize($this->getState('filter.author_id'));
+ $id .= ':' . $this->getState('filter.language');
+ $id .= ':' . serialize($this->getState('filter.tag'));
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDbo();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ [
+ $db->quoteName('a.article_id', 'id'),
+ $db->quoteName('a.state'),
+ $db->quoteName('a.hashval'),
+ $db->quoteName('a.shared_date'),
+ $db->quoteName('b.title'),
+ $db->quoteName('b.alias'),
+ $db->quoteName('h.version_id'),
+ $db->quoteName('c.title', 'category_title')
+ ]
+ )
+ ->from($db->quoteName('#__draft', 'a'))
+ ->join('INNER', $db->quoteName('#__content', 'b'), $db->quoteName('a.article_id') . ' = ' . $db->quoteName('b.id'))
+ ->join('INNER', $db->quoteName('#__history', 'h'), $db->quoteName('a.version_id') . ' = ' . $db->quoteName('h.version_id'))
+ ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('b.catid'));
+
+ return $query;
+ }
+
+ /**
+ * Method to get all transitions at once for all articles
+ *
+ * @return array|boolean
+ *
+ * @since 4.0.0
+ */
+ public function getTransitions()
+ {
+ // Get a storage key.
+ $store = $this->getStoreId('getTransitions');
+
+ // Try to load the data from internal storage.
+ if (isset($this->cache[$store]))
+ {
+ return $this->cache[$store];
+ }
+
+ $db = $this->getDbo();
+ $user = Factory::getUser();
+
+ $items = $this->getItems();
+
+ if ($items === false)
+ {
+ return false;
+ }
+
+ $stage_ids = ArrayHelper::getColumn($items, 'stage_id');
+ $stage_ids = ArrayHelper::toInteger($stage_ids);
+ $stage_ids = array_values(array_unique(array_filter($stage_ids)));
+
+ $workflow_ids = ArrayHelper::getColumn($items, 'workflow_id');
+ $workflow_ids = ArrayHelper::toInteger($workflow_ids);
+ $workflow_ids = array_values(array_unique(array_filter($workflow_ids)));
+
+ $this->cache[$store] = array();
+
+ try
+ {
+ if (count($stage_ids) || count($workflow_ids))
+ {
+ Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
+
+ $query = $db->getQuery(true);
+
+ $query->select(
+ [
+ $db->quoteName('t.id', 'value'),
+ $db->quoteName('t.title', 'text'),
+ $db->quoteName('t.from_stage_id'),
+ $db->quoteName('t.to_stage_id'),
+ $db->quoteName('s.id', 'stage_id'),
+ $db->quoteName('s.title', 'stage_title'),
+ $db->quoteName('t.workflow_id'),
+ ]
+ )
+ ->from($db->quoteName('#__workflow_transitions', 't'))
+ ->innerJoin(
+ $db->quoteName('#__workflow_stages', 's'),
+ $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id')
+ )
+ ->where(
+ [
+ $db->quoteName('t.published') . ' = 1',
+ $db->quoteName('s.published') . ' = 1',
+ ]
+ )
+ ->order($db->quoteName('t.ordering'));
+
+ $where = [];
+
+ if (count($stage_ids))
+ {
+ $where[] = $db->quoteName('t.from_stage_id') . ' IN (' . implode(',', $query->bindArray($stage_ids)) . ')';
+ }
+
+ if (count($workflow_ids))
+ {
+ $where[] = '(' . $db->quoteName('t.from_stage_id') . ' = -1 AND ' . $db->quoteName('t.workflow_id') . ' IN (' . implode(',', $query->bindArray($workflow_ids)) . '))';
+ }
+
+ $query->where('((' . implode(') OR (', $where) . '))');
+
+ $transitions = $db->setQuery($query)->loadAssocList();
+
+ foreach ($transitions as $key => $transition)
+ {
+ if (!$user->authorise('core.execute.transition', 'com_content.transition.' . (int) $transition['value']))
+ {
+ unset($transitions[$key]);
+ }
+ }
+
+ $this->cache[$store] = $transitions;
+ }
+ }
+ catch (\RuntimeException $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $this->cache[$store];
+ }
+
+ /**
+ * Method to get a list of articles.
+ * Overridden to add item type alias.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 4.0.0
+ */
+ public function getItems()
+ {
+ $items = parent::getItems();
+
+ foreach ($items as $item)
+ {
+ $item->typeAlias = 'com_content.article';
+
+ if (isset($item->metadata))
+ {
+ $registry = new Registry($item->metadata);
+ $item->metadata = $registry->toArray();
+ }
+ }
+
+ return $items;
+ }
+}
diff --git a/administrator/components/com_content/src/Table/DraftTable.php b/administrator/components/com_content/src/Table/DraftTable.php
new file mode 100644
index 0000000000000..4cf84b4cc9122
--- /dev/null
+++ b/administrator/components/com_content/src/Table/DraftTable.php
@@ -0,0 +1,22 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Content\Administrator\Table;
+
+\defined('JPATH_PLATFORM') or die;
+
+/**
+ * Draft table
+ *
+ * @since 1.5
+ */
+class DraftTable extends \JTableContent
+{
+}
diff --git a/administrator/components/com_content/src/View/Article/HtmlView.php b/administrator/components/com_content/src/View/Article/HtmlView.php
index 93d056e9ecc70..35012f409ae58 100644
--- a/administrator/components/com_content/src/View/Article/HtmlView.php
+++ b/administrator/components/com_content/src/View/Article/HtmlView.php
@@ -1,4 +1,5 @@
save2new('article.save2new');
+
+ if (!empty($this->item->id))
+ {
+ $childBar->saveAsDraft('article.saveAsDraft');
+ }
}
);
@@ -200,6 +206,7 @@ function (Toolbar $childBar) use ($checkedOut, $itemEditable, $canDo, $user)
if ($canDo->get('core.create'))
{
$childBar->save2copy('article.save2copy');
+ $childBar->saveAsDraft('article.saveAsDraft');
}
}
);
@@ -232,6 +239,13 @@ function (Toolbar $childBar) use ($checkedOut, $itemEditable, $canDo, $user)
}
}
+ if (!empty($this->item->id))
+ {
+ $toolbar->standardButton('article.shareAsDraft', "JTOOLBAR_SHARE_AS_DRAFT")
+ ->icon('icon-project-diagram')
+ ->task('article.saveAsDraft');
+ }
+
$toolbar->divider();
$toolbar->help('JHELP_CONTENT_ARTICLE_MANAGER_EDIT');
}
diff --git a/administrator/components/com_content/src/View/Articles/HtmlView.php b/administrator/components/com_content/src/View/Articles/HtmlView.php
index a829d46dbba15..fecb0f49fa342 100644
--- a/administrator/components/com_content/src/View/Articles/HtmlView.php
+++ b/administrator/components/com_content/src/View/Articles/HtmlView.php
@@ -242,6 +242,12 @@ protected function addToolbar()
}
}
+ // TODO: HERE
+ $toolbar->saveAsDraft('com_content')
+ ->icon('icon-project-diagram')
+ ->task('articles.saveAsDraft')
+ ->listCheck(true);
+
if (!$this->isEmptyState && $this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete'))
{
$toolbar->delete('articles.delete')
diff --git a/administrator/components/com_content/src/View/Draft/HtmlView.php b/administrator/components/com_content/src/View/Draft/HtmlView.php
new file mode 100644
index 0000000000000..fa702f5257d15
--- /dev/null
+++ b/administrator/components/com_content/src/View/Draft/HtmlView.php
@@ -0,0 +1,117 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Content\Administrator\View\Article;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Helper\ContentHelper;
+use Joomla\CMS\Language\Associations;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\View\GenericDataException;
+use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
+use Joomla\CMS\Router\Route;
+use Joomla\CMS\Toolbar\Toolbar;
+use Joomla\CMS\Toolbar\ToolbarHelper;
+use Joomla\Component\Content\Site\Helper\RouteHelper;
+
+/**
+ * View to edit an draft.
+ *
+ * @since 1.6
+ */
+class HtmlView extends BaseHtmlView
+{
+ /**
+ * The \JForm object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var object
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var \JObject
+ */
+ protected $canDo;
+
+ /**
+ * Pagebreak TOC alias
+ *
+ * @var string
+ */
+ protected $eName;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ if ($this->getLayout() == 'pagebreak')
+ {
+ parent::display($tpl);
+
+ return;
+ }
+
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+ $this->canDo = ContentHelper::getActions('com_content', 'draft', $this->item->id);
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors')))
+ {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // If we are forcing a language in modal (used for associations).
+ if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd'))
+ {
+ // Set the language field to the forcedLanguage and disable changing it.
+ $this->form->setValue('language', null, $forcedLanguage);
+ $this->form->setFieldAttribute('language', 'readonly', 'true');
+
+ // Only allow to select categories with All language or with the forced language.
+ $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
+
+ // Only allow to select tags with All language or with the forced language.
+ $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+}
diff --git a/administrator/components/com_content/src/View/Drafts/HtmlView.php b/administrator/components/com_content/src/View/Drafts/HtmlView.php
new file mode 100644
index 0000000000000..ad56fb231ddb4
--- /dev/null
+++ b/administrator/components/com_content/src/View/Drafts/HtmlView.php
@@ -0,0 +1,201 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Content\Administrator\View\Drafts;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Multilanguage;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\View\GenericDataException;
+use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
+use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\Toolbar\Toolbar;
+use Joomla\CMS\Toolbar\ToolbarHelper;
+use Joomla\Component\Content\Administrator\Extension\ContentComponent;
+use Joomla\Component\Content\Administrator\Helper\ContentHelper;
+
+/**
+ * View class for a list of drafts.
+ *
+ * @since 1.6
+ */
+class HtmlView extends BaseHtmlView
+{
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \JPagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \JObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \JForm
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ */
+ public $activeFilters;
+
+ /**
+ * All transition, which can be executed of one if the items
+ *
+ * @var array
+ */
+ protected $transitions = [];
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
+ {
+ $this->setLayout('emptystate');
+ }
+
+ if (ComponentHelper::getParams('com_content')->get('workflow_enabled'))
+ {
+ PluginHelper::importPlugin('workflow');
+
+ $this->transitions = $this->get('Transitions');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors')) || $this->transitions === false)
+ {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // We don't need toolbar in the modal window.
+ if ($this->getLayout() !== 'modal')
+ {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled())
+ {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ }
+ else
+ {
+ // In article associations modal we need to remove language filter if forcing a language.
+ // We also need to change the category filter to show show categories with All or the forced language.
+ if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD'))
+ {
+ // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
+ $languageXml = new \SimpleXMLElement('');
+ $this->filterForm->setField($languageXml, 'filter', true);
+
+ // Also, unset the active language filter so the search tools is not open by default with this filter.
+ unset($this->activeFilters['language']);
+
+ // One last changes needed is to change the category filter to just show categories with All language or with the forced language.
+ $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_content', 'category', $this->state->get('filter.category_id'));
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_CONTENT_DRAFTS_TITLE'), 'copy article');
+
+ // TODO: HERE
+ $toolbar->unshare('com_content')
+ ->text('JTOOLBAR_UNSHARE')
+ ->icon('icon-eye-slash')
+ ->task('drafts.unshareDrafts')
+ ->listCheck(true);
+
+ $toolbar->save('com_content')
+ ->text('JTOOLBAR_SHARE')
+ ->icon('icon-eye')
+ ->task('drafts.shareDrafts')
+ ->listCheck(true);
+
+ $toolbar->cancel('com_content')
+ ->text('JTOOLBAR_DELETE')
+ ->icon('icon-trash')
+ ->task('drafts.deleteDrafts')
+ ->listCheck(true);
+
+ if (!$this->isEmptyState && $this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete'))
+ {
+ $toolbar->delete('articles.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($user->authorise('core.admin', 'com_content') || $user->authorise('core.options', 'com_content'))
+ {
+ $toolbar->preferences('com_content');
+ }
+
+ $toolbar->help('JHELP_CONTENT_ARTICLE_MANAGER');
+ }
+}
diff --git a/administrator/components/com_content/tmpl/articles/default.php b/administrator/components/com_content/tmpl/articles/default.php
index d3fa1fb904e6d..6726ee08b55c6 100644
--- a/administrator/components/com_content/tmpl/articles/default.php
+++ b/administrator/components/com_content/tmpl/articles/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = $this->document->getWebAssetManager();
-$wa->getRegistry()->addExtensionRegistryFile('com_workflow');
-$wa->useScript('com_workflow.admin-items-workflow-buttons')
- ->addInlineScript($js, [], ['type' => 'module']);
+ $wa->getRegistry()->addExtensionRegistryFile('com_workflow');
+ $wa->useScript('com_workflow.admin-items-workflow-buttons')
+ ->addInlineScript($js, [], ['type' => 'module']);
-$workflow_state = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.state', 'com_content.article');
-$workflow_featured = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article');
+ $workflow_state = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.state', 'com_content.article');
+ $workflow_featured = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article');
endif;
@@ -121,9 +122,9 @@
-
-
- |
+
+
+ |
@@ -134,6 +135,7 @@
|
|
+
|
@@ -171,228 +173,231 @@
- class="js-draggable" data-url="" data-direction="" data-nested="true">
- items as $i => $item) :
- $item->max_ordering = 0;
- $canEdit = $user->authorise('core.edit', 'com_content.article.' . $item->id);
- $canCheckin = $user->authorise('core.manage', 'com_checkin') || $item->checked_out == $userId || is_null($item->checked_out);
- $canEditOwn = $user->authorise('core.edit.own', 'com_content.article.' . $item->id) && $item->created_by == $userId;
- $canChange = $user->authorise('core.edit.state', 'com_content.article.' . $item->id) && $canCheckin;
- $canEditCat = $user->authorise('core.edit', 'com_content.category.' . $item->catid);
- $canEditOwnCat = $user->authorise('core.edit.own', 'com_content.category.' . $item->catid) && $item->category_uid == $userId;
- $canEditParCat = $user->authorise('core.edit', 'com_content.category.' . $item->parent_category_id);
- $canEditOwnParCat = $user->authorise('core.edit.own', 'com_content.category.' . $item->parent_category_id) && $item->parent_category_uid == $userId;
+ class="js-draggable" data-url="" data-direction="" data-nested="true" >
+ items as $i => $item) :
+ $item->max_ordering = 0;
+ $canEdit = $user->authorise('core.edit', 'com_content.article.' . $item->id);
+ $canCheckin = $user->authorise('core.manage', 'com_checkin') || $item->checked_out == $userId || is_null($item->checked_out);
+ $canEditOwn = $user->authorise('core.edit.own', 'com_content.article.' . $item->id) && $item->created_by == $userId;
+ $canChange = $user->authorise('core.edit.state', 'com_content.article.' . $item->id) && $canCheckin;
+ $canEditCat = $user->authorise('core.edit', 'com_content.category.' . $item->catid);
+ $canEditOwnCat = $user->authorise('core.edit.own', 'com_content.category.' . $item->catid) && $item->category_uid == $userId;
+ $canEditParCat = $user->authorise('core.edit', 'com_content.category.' . $item->parent_category_id);
+ $canEditOwnParCat = $user->authorise('core.edit.own', 'com_content.category.' . $item->parent_category_id) && $item->parent_category_uid == $userId;
- $transitions = ContentHelper::filterTransitions($this->transitions, (int) $item->stage_id, (int) $item->workflow_id);
+ $transitions = ContentHelper::filterTransitions($this->transitions, (int) $item->stage_id, (int) $item->workflow_id);
- $transition_ids = ArrayHelper::getColumn($transitions, 'value');
- $transition_ids = ArrayHelper::toInteger($transition_ids);
+ $transition_ids = ArrayHelper::getColumn($transitions, 'value');
+ $transition_ids = ArrayHelper::toInteger($transition_ids);
?>
-
-
- id, false, 'cid', 'cb', $item->title); ?>
- |
-
-
-
-
-
-
-
-
- |
-
-
- $transitions,
- 'title' => Text::_($item->stage_title),
- 'tip_content' => Text::sprintf('JWORKFLOW', Text::_($item->workflow_title)),
- 'id' => 'workflow-' . $item->id,
- 'task' => 'articles.runTransition'
- ];
+ |
+
+ id, false, 'cid', 'cb', $item->title); ?>
+ |
+
+
+
+
+
+
+
+
+ |
+
+
+ $transitions,
+ 'title' => Text::_($item->stage_title),
+ 'tip_content' => Text::sprintf('JWORKFLOW', Text::_($item->workflow_title)),
+ 'id' => 'workflow-' . $item->id,
+ 'task' => 'articles.runTransition'
+ ];
- echo (new TransitionButton($options))
- ->render(0, $i);
- ?>
- |
-
-
- 'articles.',
- 'disabled' => $workflow_featured || !$canChange,
- 'id' => 'featured-' . $item->id
- ];
+ echo (new TransitionButton($options))
+ ->render(0, $i);
+ ?>
+ |
+
+
+ 'articles.',
+ 'disabled' => $workflow_featured || !$canChange,
+ 'id' => 'featured-' . $item->id
+ ];
- echo (new FeaturedButton)
- ->render((int) $item->featured, $i, $options, $item->featured_up, $item->featured_down);
- ?>
- |
-
- 'articles.',
- 'disabled' => $workflow_state || !$canChange,
- 'id' => 'state-' . $item->id
- ];
+ echo (new FeaturedButton)
+ ->render((int) $item->featured, $i, $options, $item->featured_up, $item->featured_down);
+ ?>
+ |
+
+ 'articles.',
+ 'disabled' => $workflow_state || !$canChange,
+ 'id' => 'state-' . $item->id
+ ];
- echo (new PublishedButton)->render((int) $item->state, $i, $options, $item->publish_up, $item->publish_down);
- ?>
- |
-
-
- checked_out) : ?>
- editor, $item->checked_out_time, 'articles.', $canCheckin); ?>
-
-
-
- escape($item->title); ?>
-
- escape($item->title); ?>
-
-
- note)) : ?>
- escape($item->alias)); ?>
+ echo (new PublishedButton)->render((int) $item->state, $i, $options, $item->publish_up, $item->publish_down);
+ ?>
+
+
+
+ checked_out) : ?>
+ editor, $item->checked_out_time, 'articles.', $canCheckin); ?>
+
+
+
+ escape($item->title); ?>
- escape($item->alias), $this->escape($item->note)); ?>
+ escape($item->title); ?>
-
-
- parent_category_id . '&extension=com_content');
- $CurrentCatUrl = Route::_('index.php?option=com_categories&task=category.edit&id=' . $item->catid . '&extension=com_content');
- $EditCatTxt = Text::_('COM_CONTENT_EDIT_CATEGORY');
- echo Text::_('JCATEGORY') . ': ';
- if ($item->category_level != '1') :
- if ($item->parent_category_level != '1') :
- echo ' » ';
- endif;
- endif;
- if (Factory::getLanguage()->isRtl())
- {
- if ($canEditCat || $canEditOwnCat) :
- echo ' ';
- endif;
- echo $this->escape($item->category_title);
- if ($canEditCat || $canEditOwnCat) :
- echo '';
- endif;
+
+ note)) : ?>
+ escape($item->alias)); ?>
+
+ escape($item->alias), $this->escape($item->note)); ?>
+
+
+
-
- |
-
- escape($item->access_level); ?>
- |
-
- created_by != 0) : ?>
-
- escape($item->author_name); ?>
-
-
-
-
- created_by_alias) : ?>
- escape($item->created_by_alias)); ?>
-
- |
-
-
- association) : ?>
- id); ?>
-
- |
-
-
+ |
+
-
+ escape($item->access_level); ?>
|
-
-
- {$orderingColumn};
- echo $date > 0 ? HTMLHelper::_('date', $date, Text::_('DATE_FORMAT_LC4')) : '-';
- ?>
- |
- hits) : ?>
-
-
- hits; ?>
-
+ |
+ created_by != 0) : ?>
+
+ escape($item->author_name); ?>
+
+
+
+
+ created_by_alias) : ?>
+ escape($item->created_by_alias)); ?>
+
|
-
- vote) : ?>
-
-
- rating_count; ?>
-
+
+ |
+ association) : ?>
+ id); ?>
+
+ |
+
+
+
+
+ |
+
+
+ {$orderingColumn};
+ echo $date > 0 ? HTMLHelper::_('date', $date, Text::_('DATE_FORMAT_LC4')) : '-';
+ ?>
|
-
-
- rating; ?>
-
+ hits) : ?>
+ |
+
+ hits; ?>
+
+ |
+
+ vote) : ?>
+
+
+ rating_count; ?>
+
+ |
+
+
+ rating; ?>
+
+ |
+
+
+ id; ?>
|
-
-
- id; ?>
- |
-
-
-
+
+
+
-
+
pagination->getListFooter(); ?>
-
- authorise('core.create', 'com_content')
+
+ authorise('core.create', 'com_content')
&& $user->authorise('core.edit', 'com_content')
- && $user->authorise('core.edit.state', 'com_content')) : ?>
+ && $user->authorise('core.edit.state', 'com_content')
+ ) : ?>
-
+
@@ -415,4 +420,4 @@
-
+
\ No newline at end of file
diff --git a/administrator/components/com_content/tmpl/articles/default_batch_body.php b/administrator/components/com_content/tmpl/articles/default_batch_body.php
index eaa3e08bbea6a..ea296d74016e3 100644
--- a/administrator/components/com_content/tmpl/articles/default_batch_body.php
+++ b/administrator/components/com_content/tmpl/articles/default_batch_body.php
@@ -1,4 +1,5 @@
\ No newline at end of file
diff --git a/administrator/components/com_content/tmpl/draft/edit.php b/administrator/components/com_content/tmpl/draft/edit.php
new file mode 100644
index 0000000000000..1d0101ea48a36
--- /dev/null
+++ b/administrator/components/com_content/tmpl/draft/edit.php
@@ -0,0 +1,15 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+/** @var \Joomla\Component\Content\Administrator\View\Article\HtmlView $this */
+
+defined('_JEXEC') or die;
+
+echo "draft view";
diff --git a/administrator/components/com_content/tmpl/draft/edit.xml b/administrator/components/com_content/tmpl/draft/edit.xml
new file mode 100644
index 0000000000000..f5efd601bb092
--- /dev/null
+++ b/administrator/components/com_content/tmpl/draft/edit.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_content/tmpl/draft/modal.php b/administrator/components/com_content/tmpl/draft/modal.php
new file mode 100644
index 0000000000000..9147d5b5ed403
--- /dev/null
+++ b/administrator/components/com_content/tmpl/draft/modal.php
@@ -0,0 +1,15 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+?>
+
diff --git a/administrator/components/com_content/tmpl/draft/pagebreak.php b/administrator/components/com_content/tmpl/draft/pagebreak.php
new file mode 100644
index 0000000000000..53a8211f88bf3
--- /dev/null
+++ b/administrator/components/com_content/tmpl/draft/pagebreak.php
@@ -0,0 +1,48 @@
+
+ * @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;
+
+/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
+$wa = $this->document->getWebAssetManager();
+$wa->useScript('com_content.admin-article-pagebreak');
+
+$this->eName = Factory::getApplication()->input->getCmd('e_name', '');
+$this->eName = preg_replace('#[^A-Z0-9\-\_\[\]]#i', '', $this->eName);
+$this->document->setTitle(Text::_('COM_CONTENT_PAGEBREAK_DOC_TITLE'));
+
+?>
+
diff --git a/administrator/components/com_content/tmpl/drafts/default.php b/administrator/components/com_content/tmpl/drafts/default.php
new file mode 100644
index 0000000000000..e356be702bf18
--- /dev/null
+++ b/administrator/components/com_content/tmpl/drafts/default.php
@@ -0,0 +1,204 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Button\PublishedButton;
+use Joomla\CMS\Factory;
+use Joomla\CMS\HTML\HTMLHelper;
+use Joomla\CMS\Language\Associations;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\Layout\LayoutHelper;
+use Joomla\CMS\Router\Route;
+use Joomla\CMS\Session\Session;
+
+HTMLHelper::_('behavior.multiselect');
+
+$app = Factory::getApplication();
+$user = Factory::getUser();
+$userId = $user->get('id');
+$listOrder = $this->escape($this->state->get('list.ordering'));
+$listDirn = $this->escape($this->state->get('list.direction'));
+$saveOrder = $listOrder == 'a.ordering';
+
+if (strpos($listOrder, 'publish_up') !== false)
+{
+ $orderingColumn = 'publish_up';
+}
+elseif (strpos($listOrder, 'publish_down') !== false)
+{
+ $orderingColumn = 'publish_down';
+}
+elseif (strpos($listOrder, 'modified') !== false)
+{
+ $orderingColumn = 'modified';
+}
+else
+{
+ $orderingColumn = 'created';
+}
+
+if ($saveOrder && !empty($this->items))
+{
+ $saveOrderingUrl = 'index.php?option=com_content&task=articles.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
+}
+
+$assoc = Associations::isEnabled();
+?>
+
+
\ No newline at end of file
diff --git a/administrator/components/com_content/tmpl/drafts/default.xml b/administrator/components/com_content/tmpl/drafts/default.xml
new file mode 100644
index 0000000000000..d49551000236a
--- /dev/null
+++ b/administrator/components/com_content/tmpl/drafts/default.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_content/tmpl/drafts/default_batch_body.php b/administrator/components/com_content/tmpl/drafts/default_batch_body.php
new file mode 100644
index 0000000000000..eaa3e08bbea6a
--- /dev/null
+++ b/administrator/components/com_content/tmpl/drafts/default_batch_body.php
@@ -0,0 +1,59 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Multilanguage;
+use Joomla\CMS\Layout\LayoutHelper;
+
+$params = ComponentHelper::getParams('com_content');
+
+$published = (int) $this->state->get('filter.published');
+
+$user = Factory::getUser();
+?>
+
+
+
+
+ = 0) : ?>
+
+
+
+ authorise('core.admin', 'com_content') && $params->get('workflow_enabled')) : ?>
+
+
+
+
diff --git a/administrator/components/com_content/tmpl/drafts/default_batch_footer.php b/administrator/components/com_content/tmpl/drafts/default_batch_footer.php
new file mode 100644
index 0000000000000..3fe481e8ec80c
--- /dev/null
+++ b/administrator/components/com_content/tmpl/drafts/default_batch_footer.php
@@ -0,0 +1,23 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Language\Text;
+
+/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
+$wa = $this->document->getWebAssetManager();
+$wa->useScript('com_content.admin-articles-batch');
+
+?>
+
+
diff --git a/administrator/components/com_contenthistory/tmpl/history/modal.php b/administrator/components/com_contenthistory/tmpl/history/modal.php
index 10a98ea2c238d..0955caa111035 100644
--- a/administrator/components/com_contenthistory/tmpl/history/modal.php
+++ b/administrator/components/com_contenthistory/tmpl/history/modal.php
@@ -49,6 +49,10 @@
|
+
+
+
+ |
|
@@ -79,6 +83,9 @@
version_note); ?>
|
+
+ Draft
+ |
keep_forever) : ?>
|