diff --git a/administrator/components/com_categories/Model/CategoryModel.php b/administrator/components/com_categories/Model/CategoryModel.php
index 1b44c6eee3bcb..7e0dc59787d2c 100644
--- a/administrator/components/com_categories/Model/CategoryModel.php
+++ b/administrator/components/com_categories/Model/CategoryModel.php
@@ -388,15 +388,9 @@ protected function preprocessForm(\JForm $form, $data, $group = 'content')
// Get the component form if it exists
$name = 'category' . ($section ? ('.' . $section) : '');
- // Looking first in the component forms folder
+ // Looking first in the component models/forms folder
$path = \JPath::clean(JPATH_ADMINISTRATOR . "/components/$component/forms/$name.xml");
- // Looking in the component models/forms folder (J! 3)
- if (!file_exists($path))
- {
- $path = \JPath::clean(JPATH_ADMINISTRATOR . "/components/$component/models/forms/$name.xml");
- }
-
// Old way: looking in the component folder
if (!file_exists($path))
{
diff --git a/administrator/components/com_content/Helper/ContentHelper.php b/administrator/components/com_content/Helper/ContentHelper.php
new file mode 100644
index 0000000000000..2f0b0c2c81b5c
--- /dev/null
+++ b/administrator/components/com_content/Helper/ContentHelper.php
@@ -0,0 +1,388 @@
+get('workflows_enable', 1))
+ {
+ \JHtmlSidebar::addEntry(
+ \JText::_('COM_CONTENT_SUBMENU_WORKFLOWS'),
+ 'index.php?option=com_workflow&extension=com_content',
+ $vName == 'workflows'
+ );
+
+ if ($vName == 'states' || $vName == 'transitions')
+ {
+ $app = Factory::getApplication();
+ $workflowID = $app->getUserStateFromRequest('filter.workflow_id', 'workflow_id', 1, 'int');
+
+ \JHtmlSidebar::addEntry(
+ '- ' . \JText::_('COM_WORKFLOW_STATES'),
+ 'index.php?option=com_workflow&view=states&workflow_id=' . $workflowID . "&extension=com_content",
+ $vName == 'states`'
+ );
+
+ \JHtmlSidebar::addEntry(
+ '- ' . \JText::_('COM_WORKFLOW_TRANSITIONS'),
+ 'index.php?option=com_workflow&view=transitions&workflow_id=' . $workflowID . "&extension=com_content",
+ $vName == 'transitions'
+ );
+ }
+ }
+
+ if (\JComponentHelper::isEnabled('com_fields') && \JComponentHelper::getParams('com_content')->get('custom_fields_enable', '1'))
+ {
+ \JHtmlSidebar::addEntry(
+ \JText::_('JGLOBAL_FIELDS'),
+ 'index.php?option=com_fields&context=com_content.article',
+ $vName == 'fields.fields'
+ );
+ \JHtmlSidebar::addEntry(
+ \JText::_('JGLOBAL_FIELD_GROUPS'),
+ 'index.php?option=com_fields&view=groups&context=com_content.article',
+ $vName == 'fields.groups'
+ );
+ }
+ }
+
+ /**
+ * Applies the content tag filters to arbitrary text as per settings for current user group
+ *
+ * @param text $text The string to filter
+ *
+ * @return string The filtered string
+ *
+ * @deprecated 4.0 Use \JComponentHelper::filterText() instead.
+ */
+ public static function filterText($text)
+ {
+ try
+ {
+ \JLog::add(
+ sprintf('%s() is deprecated. Use JComponentHelper::filterText() instead', __METHOD__),
+ \JLog::WARNING,
+ 'deprecated'
+ );
+ }
+ catch (\RuntimeException $exception)
+ {
+ // Informational log only
+ }
+
+ return \JComponentHelper::filterText($text);
+ }
+
+ /**
+ * Adds Count Items for Category Manager.
+ *
+ * @param \stdClass[] &$items The banner category objects
+ *
+ * @return \stdClass[]
+ *
+ * @since 3.5
+ */
+ public static function countItems(&$items)
+ {
+ $db = \JFactory::getDbo();
+
+ foreach ($items as $item)
+ {
+ $item->count_trashed = 0;
+ $item->count_unpublished = 0;
+ $item->count_published = 0;
+
+ $query = $db->getQuery(true);
+
+ $query ->select($db->qn('condition'))
+ ->select('COUNT(*) AS ' . $db->qn('count'))
+ ->from($db->qn('#__content', 'c'))
+ ->from($db->qn('#__workflow_states', 's'))
+ ->from($db->qn('#__workflow_associations', 'a'))
+ ->where($db->qn('a.item_id') . ' = ' . $db->qn('c.id'))
+ ->where($db->qn('s.id') . ' = ' . $db->qn('a.state_id'))
+ ->where('catid = ' . (int) $item->id)
+ ->where('a.extension = ' . $db->quote('com_content'))
+ ->group($db->qn('condition'));
+
+ $articles = $db->setQuery($query)->loadObjectList();
+
+ foreach ($articles as $article)
+ {
+ if ($article->condition == Workflow::PUBLISHED)
+ {
+ $item->count_published = $article->count;
+ }
+
+ if ($article->condition == Workflow::UNPUBLISHED)
+ {
+ $item->count_unpublished = $article->count;
+ }
+
+ if ($article->condition == Workflow::TRASHED)
+ {
+ $item->count_trashed = $article->count;
+ }
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Adds Count Items for Tag Manager.
+ *
+ * @param \stdClass[] &$items The content objects
+ * @param string $extension The name of the active view.
+ *
+ * @return \stdClass[]
+ *
+ * @since 3.6
+ */
+ public static function countTagItems(&$items, $extension)
+ {
+ $db = \JFactory::getDbo();
+ $parts = explode('.', $extension);
+ $section = null;
+
+ if (count($parts) > 1)
+ {
+ $section = $parts[1];
+ }
+
+ $join = $db->qn('#__content') . ' AS c ON ct.content_item_id=c.id';
+ $state = 'state';
+
+ if ($section === 'category')
+ {
+ $join = $db->qn('#__categories') . ' AS c ON ct.content_item_id=c.id';
+ $state = 'published as state';
+ }
+
+ foreach ($items as $item)
+ {
+ $item->count_trashed = 0;
+ $item->count_archived = 0;
+ $item->count_unpublished = 0;
+ $item->count_published = 0;
+ $query = $db->getQuery(true);
+ $query->select($state . ', count(*) AS count')
+ ->from($db->qn('#__contentitem_tag_map') . 'AS ct ')
+ ->where('ct.tag_id = ' . (int) $item->id)
+ ->where('ct.type_alias =' . $db->q($extension))
+ ->join('LEFT', $join)
+ ->group('state');
+ $db->setQuery($query);
+ $contents = $db->loadObjectList();
+
+ foreach ($contents as $content)
+ {
+ if ($content->state == 1)
+ {
+ $item->count_published = $content->count;
+ }
+
+ if ($content->state == 0)
+ {
+ $item->count_unpublished = $content->count;
+ }
+
+ if ($content->state == 2)
+ {
+ $item->count_archived = $content->count;
+ }
+
+ if ($content->state == -2)
+ {
+ $item->count_trashed = $content->count;
+ }
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Returns a valid section for articles. If it is not valid then null
+ * is returned.
+ *
+ * @param string $section The section to get the mapping for
+ *
+ * @return string|null The new section
+ *
+ * @since 3.7.0
+ */
+ public static function validateSection($section)
+ {
+ if (\JFactory::getApplication()->isClient('site'))
+ {
+ // On the front end we need to map some sections
+ switch ($section)
+ {
+ // Editing an article
+ case 'form':
+
+ // Category list view
+ case 'featured':
+ case 'category':
+ $section = 'article';
+ }
+ }
+
+ if ($section != 'article')
+ {
+ // We don't know other sections
+ return null;
+ }
+
+ return $section;
+ }
+
+ /**
+ * Returns valid contexts
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public static function getContexts()
+ {
+ \JFactory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR);
+
+ $contexts = array(
+ 'com_content.article' => \JText::_('COM_CONTENT'),
+ 'com_content.categories' => \JText::_('JCATEGORY')
+ );
+
+ return $contexts;
+ }
+
+ /**
+ * Check if state can be deleted
+ *
+ * @param int $stateID Id of state to delete
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public static function canDeleteState($stateID)
+ {
+ $db = \JFactory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select('id')
+ ->from($db->qn('#__content'))
+ ->where('state = ' . (int) $stateID);
+ $db->setQuery($query);
+ $states = $db->loadResult();
+
+ return empty($states);
+ }
+
+ /**
+ * Method to filter transitions by given id of state
+ *
+ * @param int $transitions Array of transitions
+ * @param int $pk Id of state
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public static function filterTransitions($transitions, $pk)
+ {
+ return array_values(
+ array_filter(
+ $transitions,
+ function ($var) use ($pk)
+ {
+ return $var['from_state_id'] == $pk;
+ }
+ )
+ );
+ }
+
+ /**
+ * Method to change state of multiple ids
+ *
+ * @param int $pks Array of IDs
+ * @param int $condition Condition of the workflow state
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public static function updateContentState($pks, $condition)
+ {
+ if (empty($pks))
+ {
+ return false;
+ }
+
+ try
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query->update($db->qn('#__content'))
+ ->set($db->qn('state') . '=' . (int) $condition)
+ ->where($db->qn('id') . ' IN (' . implode(', ', $pks) . ')');
+
+ $db->setQuery($query)->execute();
+ }
+ catch (\Exception $e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/administrator/components/com_content/Model/ArticleModel.php b/administrator/components/com_content/Model/ArticleModel.php
index c50de3440bad1..44d0565f828e3 100644
--- a/administrator/components/com_content/Model/ArticleModel.php
+++ b/administrator/components/com_content/Model/ArticleModel.php
@@ -11,9 +11,17 @@
defined('_JEXEC') or die;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Categories\Categories;
+use Joomla\CMS\Model\Form;
+use Joomla\Component\Content\Administrator\Helper\ContentHelper;
+use Joomla\Component\Workflow\Administrator\Helper\WorkflowHelper;
+use Joomla\Component\Workflow\Administrator\Table\StateTable;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\MVC\Model\AdminModel;
+use Joomla\CMS\Workflow\Workflow;
/**
* Item Model for an Article.
@@ -157,6 +165,50 @@ protected function batchCopy($value, $pks, $contexts)
return $newIds;
}
+ /**
+ * Batch change workflow state or current.
+ *
+ * @param integer $value The workflow state ID.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return mixed An array of new IDs on success, boolean false on failure.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function batchWorkflowState($value, $pks, $contexts)
+ {
+ $user = Factory::getUser();
+
+ if (!$user->authorise('core.admin', 'com_content'))
+ {
+ $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EXECUTE_TRANSITION'));
+ }
+
+ // Get state information
+ $state = new StateTable($this->_db);
+
+ if (empty($value) || !$state->load($value))
+ {
+ Factory::getApplication()->enqueueMessage(\JText::sprintf('JGLOBAL_BATCH_WORKFLOW_STATE_ROW_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ if (empty($pks))
+ {
+ Factory::getApplication()->enqueueMessage(\JText::sprintf('JGLOBAL_BATCH_WORKFLOW_STATE_ROW_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ $workflow = new Workflow(['extension' => 'com_content']);
+
+ // Update content state value and workflow associations
+ return ContentHelper::updateContentState($pks, $state->condition)
+ && $workflow->updateAssociations($pks, $value);
+ }
+
/**
* Method to test whether a record can be deleted.
*
@@ -170,7 +222,13 @@ protected function canDelete($record)
{
if (!empty($record->id))
{
- if ($record->state != -2)
+ $state = new StateTable($this->_db);
+
+ $workflow = new Workflow(['extension' => 'com_content']);
+
+ $assoc = $workflow->getAssociation($record->id);
+
+ if (!$state->load($assoc->state_id) || $state->condition != Workflow::TRASHED)
{
return false;
}
@@ -305,7 +363,7 @@ public function getItem($pk = null)
* @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 \JForm|boolean A \JForm object on success, false on failure
+ * @return Form|boolean A \JForm object on success, false on failure
*
* @since 1.6
*/
@@ -320,6 +378,8 @@ public function getForm($data = array(), $loadData = true)
}
$jinput = \JFactory::getApplication()->input;
+ $db = $this->getDbo();
+ $query = $db->getQuery(true);
/*
* The front end calls this model and uses a_id to avoid id clashes so we need to check for that first.
@@ -328,15 +388,28 @@ public function getForm($data = array(), $loadData = true)
$id = $jinput->get('a_id', $jinput->get('id', 0));
// Determine correct permissions to check.
- if ($this->getState('article.id'))
+ if ($id = $this->getState('article.id', $id))
{
- $id = $this->getState('article.id');
-
// Existing record. Can only edit in selected categories.
$form->setFieldAttribute('catid', 'action', 'core.edit');
// Existing record. Can only edit own articles in selected categories.
$form->setFieldAttribute('catid', 'action', 'core.edit.own');
+
+ $table = $this->getTable();
+
+ if ($table->load(array('id' => $id)))
+ {
+
+
+ $workflow = new Workflow(['extension' => 'com_content']);
+
+ // Transition field
+ $assoc = $workflow->getAssociation($table->id);
+
+ $form->setFieldAttribute('transition', 'workflow_state', (int) $assoc->state_id);
+ }
+
}
else
{
@@ -344,7 +417,7 @@ public function getForm($data = array(), $loadData = true)
$form->setFieldAttribute('catid', 'action', 'core.create');
}
- $user = \JFactory::getUser();
+ $user = \JFactory::getUser();
// Check for existing article.
// Modify the form based on Edit State access controls.
@@ -481,6 +554,8 @@ public function save($data)
{
$input = \JFactory::getApplication()->input;
$filter = \JFilterInput::getInstance();
+ $db = $this->getDbo();
+ $user = Factory::getUser();
if (isset($data['metadata']) && isset($data['metadata']['author']))
{
@@ -569,8 +644,68 @@ public function save($data)
$data['alias'] = '';
}
}
+ }
+
+ $workflowId = 0;
+ $stateId = 0;
+
+ // Set status depending on category
+ if (empty($data['id']))
+ {
+ $workflow = $this->getWorkflowByCategory($data['catid']);
+
+ if (empty($workflow->id))
+ {
+ $this->setError(\JText::_('COM_CONTENT_WORKFLOW_NOT_FOUND'));
+
+ return false;
+ }
+
+ $stateId = (int) $workflow->state_id;
+ $workflowId = (int) $workflow->id;
+
+ // B/C state
+ $data['state'] = (int) $workflow->condition;
+
+ // No transition for new articles
+ if (isset($data['transition']))
+ {
+ unset($data['transition']);
+ }
+ }
+ // Calculate new status depending on transition
+ elseif (!empty($data['transition']))
+ {
+ // Check if the user is allowed to execute this transition
+ if (!$user->authorise('core.execute.transition', 'com_content.transition.' . (int) $data['transition']))
+ {
+ $this->setError(JText::_('COM_CONTENT_WORKFLOW_TRANSITION_NOT_ALLOWED'));
+
+ return false;
+ }
+
+ // Set the new state
+ $query = $db->getQuery(true);
+
+ $query ->select($db->qn(['ws.id', 'ws.condition']))
+ ->from($db->qn('#__workflow_states', 'ws'))
+ ->from($db->qn('#__workflow_transitions', 'wt'))
+ ->where($db->qn('wt.to_state_id') . ' = ' . $db->qn('ws.id'))
+ ->where($db->qn('wt.id') . ' = ' . (int) $data['transition'])
+ ->where($db->qn('ws.published') . ' = 1')
+ ->where($db->qn('wt.published') . ' = 1');
+
+ $state = $db->setQuery($query)->loadObject();
+
+ if (empty($state->id))
+ {
+ $this->setError(JText::_('COM_CONTENT_WORKFLOW_TRANSITION_NOT_ALLOWED'));
+
+ return false;
+ }
+
+ $data['state'] = (int) $state->condition;
- $data['state'] = 0;
}
// Automatic handling of alias for empty fields
@@ -604,6 +739,8 @@ public function save($data)
}
}
+ $workflow = new Workflow(['extension' => 'com_content']);
+
if (parent::save($data))
{
if (isset($data['featured']))
@@ -611,6 +748,49 @@ public function save($data)
$this->featured($this->getState($this->getName() . '.id'), $data['featured']);
}
+ // Run the transition and update the workflow association
+ if (!empty($data['transition']))
+ {
+ $this->runTransition((int) $this->getState($this->getName() . '.id'), (int) $data['transition']);
+ }
+
+ // Let's check if we have workflow association (perhaps something went wrong before)
+ if (empty($stateId))
+ {
+ $assoc = $workflow->getAssociation($this->getState($this->getName() . '.id'));
+
+ // If not, reset the state and let's create the associations
+ if (empty($assoc->item_id))
+ {
+ $table = $this->getTable();
+
+ $table->load((int) $this->getState($this->getName() . '.id'));
+
+ $workflow = $this->getWorkflowByCategory($table->catid);
+
+ if (empty($workflow->id))
+ {
+ $this->setError(\JText::_('COM_CONTENT_WORKFLOW_NOT_FOUND'));
+
+ return false;
+ }
+
+ $stateId = (int) $workflow->state_id;
+ $workflowId = (int) $workflow->id;
+
+ // B/C state
+ $table->state = $workflow->condition;
+
+ $table->store();
+ }
+ }
+
+ // If we have a new state, create the workflow association
+ if (!empty($stateId))
+ {
+ $workflow->createAssociation($this->getState($this->getName() . '.id'), (int) $stateId);
+ }
+
return true;
}
@@ -841,8 +1021,126 @@ public function delete(&$pks)
->where('content_id IN (' . implode(',', $pks) . ')');
$db->setQuery($query);
$db->execute();
+
+ $workflow = new Workflow(['extension' => 'com_content']);
+
+ $workflow->deleteAssociation($pks);
}
return $return;
}
+
+ /**
+ * Load the assigned workflow information by a given category ID
+ *
+ * @param int $catId The give category
+ *
+ * @return integer|boolean If found, the workflow ID, otherwise false
+ */
+ protected function getWorkflowByCategory($catId)
+ {
+ $db = $this->getDbo();
+
+ $category = Categories::getInstance('content')->get($catId);
+
+ if (!empty($category->id) && !empty($category->params))
+ {
+ $catparams = new Registry($category->params);
+
+ // Recheck if the workflow still exists
+ if ($catparams->get('workflow_id') > 0)
+ {
+ $query = $db->getQuery(true);
+
+ $query ->select(
+ $db->qn(
+ [
+ 'w.id',
+ 'ws.condition'
+ ]
+ )
+ )
+ ->select($db->qn('ws.id', 'state_id'))
+ ->from($db->qn('#__workflow_states', 'ws'))
+ ->from($db->qn('#__workflows', 'w'))
+ ->where($db->qn('ws.default') . ' = 1')
+ ->where($db->qn('ws.workflow_id') . ' = ' . $db->qn('w.id'))
+ ->where($db->qn('w.published') . ' = 1')
+ ->where($db->qn('ws.published') . ' = 1')
+ ->where($db->qn('w.id') . ' = ' . (int) $catparams->get('workflow_id'));
+
+ $workflow = $db->setQuery($query)->loadObject();
+
+ if (!empty($workflow->id))
+ {
+ return $workflow;
+ }
+ }
+ }
+
+ // Use default workflow
+ $query = $db->getQuery(true);
+
+ $query ->select(
+ $db->qn(
+ [
+ 'w.id',
+ 'ws.condition'
+ ]
+ )
+ )
+ ->select($db->qn('ws.id', 'state_id'))
+ ->from($db->qn('#__workflow_states', 'ws'))
+ ->from($db->qn('#__workflows', 'w'))
+ ->where($db->qn('ws.default') . ' = 1')
+ ->where($db->qn('ws.workflow_id') . ' = ' . $db->qn('w.id'))
+ ->where($db->qn('w.published') . ' = 1')
+ ->where($db->qn('ws.published') . ' = 1')
+ ->where($db->qn('w.default') . ' = 1');
+
+ $workflow = $db->setQuery($query)->loadObject();
+
+ // Last check if we have a workflow ID
+ if (!empty($workflow->id))
+ {
+ return $workflow;
+ }
+
+ return false;
+ }
+
+ /**
+ * Runs transition for item.
+ *
+ * @param array $pk Id of article
+ * @param array $transition_id Id of transition
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function runTransition($pk, $transition_id)
+ {
+ $workflow = new Workflow(['extension' => 'com_content']);
+
+ $runTransaction = $workflow->executeTransition($pk, $transition_id);
+
+ if (!$runTransaction)
+ {
+ $this->setError(\JText::_('COM_CONTENT_ERROR_UPDATE_STATE'));
+
+ return false;
+ }
+
+ // B/C state change trigger for UCM
+ $context = $this->option . '.' . $this->name;
+
+ // Include the plugins for the change of state event.
+ \JPluginHelper::importPlugin($this->events_map['change_state']);
+
+ // Trigger the change state event.
+ \JFactory::getApplication()->triggerEvent($this->event_change_state, [$context, [$pk], $transition_id]);
+
+ return true;
+ }
}
diff --git a/administrator/components/com_content/Model/ArticlesModel.php b/administrator/components/com_content/Model/ArticlesModel.php
index 2ccf1941786c2..801d3fa97d98e 100644
--- a/administrator/components/com_content/Model/ArticlesModel.php
+++ b/administrator/components/com_content/Model/ArticlesModel.php
@@ -13,6 +13,8 @@
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Utilities\ArrayHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Workflow\Workflow;
/**
* Methods supporting a list of article records.
@@ -105,6 +107,9 @@ protected function populateState($ordering = 'a.id', $direction = 'desc')
$published = $this->getUserStateFromRequest($this->context . '.filter.published', 'filter_published', '');
$this->setState('filter.published', $published);
+ $condition = $this->getUserStateFromRequest($this->context . '.filter.condition', 'filter_condition', '');
+ $this->setState('filter.condition', $condition);
+
$level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level');
$this->setState('filter.level', $level);
@@ -216,6 +221,27 @@ protected function getListQuery()
$query->select('ua.name AS author_name')
->join('LEFT', '#__users AS ua ON ua.id = a.created_by');
+ // Join over the associations.
+ $query ->select($query->quoteName('wa.state_id', 'state_id'))
+ ->innerJoin($query->quoteName('#__workflow_associations', 'wa'))
+ ->where($query->quoteName('wa.item_id') . ' = ' . $query->quoteName('a.id'));
+
+ // Join over the states.
+ $query ->select(
+ $query->quoteName(
+ [
+ 'ws.title',
+ 'ws.condition'
+ ],
+ [
+ 'state_title',
+ 'state_condition'
+ ]
+ )
+ )
+ ->innerJoin($query->quoteName('#__workflow_states', 'ws'))
+ ->where($query->quoteName('ws.id') . ' = ' . $query->quoteName('wa.state_id'));
+
// Join on voting table
$associationsGroupBy = array(
'a.id',
@@ -246,7 +272,7 @@ protected function getListQuery()
if (\JPluginHelper::isEnabled('content', 'vote'))
{
- $query->select('COALESCE(NULLIF(ROUND(v.rating_sum / v.rating_count, 0), 0), 0) AS rating,
+ $query->select('COALESCE(NULLIF(ROUND(v.rating_sum / v.rating_count, 0), 0), 0) AS rating,
COALESCE(NULLIF(v.rating_count, 0), 0) as rating_count')
->join('LEFT', '#__content_rating AS v ON a.id = v.content_id');
@@ -284,17 +310,32 @@ protected function getListQuery()
}
// Filter by published state
- $published = (string) $this->getState('filter.published');
+ $workflowState = (string) $this->getState('filter.state');
- if (is_numeric($published))
+ if (is_numeric($workflowState))
{
- $query->where('a.state = ' . (int) $published);
+ $query->where('wa.state_id = ' . (int) $workflowState);
+ }
+
+ $condition = (string) $this->getState('filter.condition');
+
+ if (is_numeric($condition))
+ {
+ switch ((int) $condition)
+ {
+ case Workflow::PUBLISHED:
+ case Workflow::UNPUBLISHED:
+ case Workflow::TRASHED:
+ $query->where($db->qn('ws.condition') . ' = ' . (int) $condition);
+ }
}
- elseif ($published === '')
+ elseif (!is_numeric($workflowState))
{
- $query->where('(a.state = 0 OR a.state = 1)');
+ $query->where($db->qn('ws.condition') . ' IN (' . $query->quote(Workflow::PUBLISHED) . ',' . $query->quote(Workflow::UNPUBLISHED) . ')');
}
+ $query->where($db->qn('wa.extension') . '=' . $db->quote('com_content'));
+
// Filter by categories and by level
$categoryId = $this->getState('filter.category_id', array());
$level = $this->getState('filter.level');
@@ -410,6 +451,102 @@ protected function getListQuery()
return $query;
}
+ /**
+ * Method to get all transitions at once for all articles
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ 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();
+
+ $ids = ArrayHelper::getColumn($items, 'state_id');
+ $ids = ArrayHelper::toInteger($ids);
+ $ids = array_unique(array_filter($ids));
+
+ $this->cache[$store] = array();
+
+ try
+ {
+ if (count($ids))
+ {
+ Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
+
+ $query = $db->getQuery(true);
+
+ $select = $db->quoteName(
+ array(
+ 't.id',
+ 't.title',
+ 't.from_state_id',
+ 's.id',
+ 's.title',
+ 's.condition'
+ ),
+ array(
+ 'value',
+ 'text',
+ 'from_state_id',
+ 'state_id',
+ 'state_title',
+ 'state_condition'
+ )
+ );
+
+ $query->select($select)
+ ->from($db->quoteName('#__workflow_transitions', 't'))
+ ->leftJoin($db->quoteName('#__workflow_states', 's') . ' ON ' . $db->qn('t.from_state_id') . ' IN(' . implode(',', $ids) . ')')
+ ->where($db->quoteName('t.to_state_id') . ' = ' . $db->quoteName('s.id'))
+ ->where($db->quoteName('t.published') . ' = 1')
+ ->where($db->quoteName('s.published') . ' = 1')
+ ->order($db->qn('t.ordering'));
+
+ $transitions = $db->setQuery($query)->loadAssocList();
+
+ $workflow = new Workflow(['extension' => 'com_content']);
+
+ foreach ($transitions as $key => $transition)
+ {
+ if (!$user->authorise('core.execute.transition', 'com_content.transition.' . (int) $transition['value']))
+ {
+ unset($transitions[$key]);
+ }
+ else
+ {
+ // Update the transition text with final state value
+ $conditionName = $workflow->getConditionName($transition['state_condition']);
+
+ $transitions[$key]['text'] .= ' [' . \JText::_($conditionName) . ']';
+ }
+ }
+
+ $this->cache[$store] = $transitions;
+ }
+ }
+ catch (\RuntimeException $e)
+ {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $this->cache[$store];
+ }
+
/**
* Build a list of authors
*
diff --git a/administrator/components/com_content/Model/FeaturedModel.php b/administrator/components/com_content/Model/FeaturedModel.php
index 40111ab225057..c152bea1a18a8 100644
--- a/administrator/components/com_content/Model/FeaturedModel.php
+++ b/administrator/components/com_content/Model/FeaturedModel.php
@@ -111,6 +111,14 @@ protected function getListQuery()
$query->select('ua.name AS author_name')
->join('LEFT', '#__users AS ua ON ua.id = a.created_by');
+ // Join over the states.
+ $query->select('wa.state_id AS state_id')
+ ->join('LEFT', '#__workflow_associations AS wa ON wa.item_id = a.id');
+
+ // Join over the states.
+ $query->select('ws.title AS state_title, ws.condition AS state_condition')
+ ->join('LEFT', '#__workflow_states AS ws ON ws.id = wa.state_id');
+
// Join on voting table
if (\JPluginHelper::isEnabled('content', 'vote'))
{
@@ -133,16 +141,25 @@ protected function getListQuery()
}
// Filter by published state
- $published = $this->getState('filter.published');
+ $workflowState = (string) $this->getState('filter.state');
- if (is_numeric($published))
+ if (is_numeric($workflowState))
{
- $query->where('a.state = ' . (int) $published);
+ $query->where('wa.state_id = ' . (int) $workflowState);
}
- elseif ($published === '')
+
+ $condition = (string) $this->getState('filter.condition');
+
+ if (is_numeric($condition))
{
- $query->where('(a.state = 0 OR a.state = 1)');
+ $query->where($db->qn('ws.condition') . '=' . $db->quote($condition));
}
+ elseif (!is_numeric($workflowState))
+ {
+ $query->where($db->qn('ws.condition') . ' IN ("0","1")');
+ }
+
+ $query->where($db->qn('wa.extension') . '=' . $db->quote('com_content'));
// Filter by a single or group of categories.
$baselevel = 1;
diff --git a/administrator/components/com_content/View/Articles/HtmlView.php b/administrator/components/com_content/View/Articles/HtmlView.php
index 0e98e34190654..d3264b9881f11 100644
--- a/administrator/components/com_content/View/Articles/HtmlView.php
+++ b/administrator/components/com_content/View/Articles/HtmlView.php
@@ -12,8 +12,8 @@
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
-
-\JLoader::register('ContentHelper', JPATH_ADMINISTRATOR . '/components/com_content/helpers/content.php');
+use Joomla\Component\Content\Administrator\Helper\ContentHelper;
+use Joomla\CMS\Workflow\Workflow;
/**
* View class for a list of articles.
@@ -91,7 +91,7 @@ public function display($tpl = null)
{
if ($this->getLayout() !== 'modal')
{
- \ContentHelper::addSubmenu('articles');
+ ContentHelper::addSubmenu('articles');
}
$this->items = $this->get('Items');
@@ -100,6 +100,7 @@ public function display($tpl = null)
$this->authors = $this->get('Authors');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
+ $this->transitions = $this->get('Transitions');
$this->vote = \JPluginHelper::isEnabled('content', 'vote');
// Check for errors.
@@ -166,18 +167,15 @@ protected function addToolbar()
if ($canDo->get('core.edit.state'))
{
- \JToolbarHelper::publish('articles.publish', 'JTOOLBAR_PUBLISH', true);
- \JToolbarHelper::unpublish('articles.unpublish', 'JTOOLBAR_UNPUBLISH', true);
\JToolbarHelper::custom('articles.featured', 'featured.png', 'featured_f2.png', 'JFEATURE', true);
\JToolbarHelper::custom('articles.unfeatured', 'unfeatured.png', 'featured_f2.png', 'JUNFEATURE', true);
- \JToolbarHelper::archiveList('articles.archive');
\JToolbarHelper::checkin('articles.checkin');
}
// Add a batch button
if ($user->authorise('core.create', 'com_content')
&& $user->authorise('core.edit', 'com_content')
- && $user->authorise('core.edit.state', 'com_content'))
+ && $user->authorise('core.execute.transition', 'com_content'))
{
$title = \JText::_('JTOOLBAR_BATCH');
@@ -188,14 +186,10 @@ protected function addToolbar()
$bar->appendButton('Custom', $dhtml, 'batch');
}
- if ($this->state->get('filter.published') == -2 && $canDo->get('core.delete'))
+ if ($this->state->get('filter.condition') == Workflow::TRASHED && $canDo->get('core.delete'))
{
\JToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'articles.delete', 'JTOOLBAR_EMPTY_TRASH');
}
- elseif ($canDo->get('core.edit.state'))
- {
- \JToolbarHelper::trash('articles.trash');
- }
if ($user->authorise('core.admin', 'com_content') || $user->authorise('core.options', 'com_content'))
{
diff --git a/administrator/components/com_content/View/Featured/HtmlView.php b/administrator/components/com_content/View/Featured/HtmlView.php
index 97806770ad654..60ab919e0a0ea 100644
--- a/administrator/components/com_content/View/Featured/HtmlView.php
+++ b/administrator/components/com_content/View/Featured/HtmlView.php
@@ -97,6 +97,7 @@ public function display($tpl = null)
$this->authors = $this->get('Authors');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
+ $this->transitions = $this->get('Transitions');
$this->vote = \JPluginHelper::isEnabled('content', 'vote');
// Check for errors.
@@ -139,10 +140,7 @@ protected function addToolbar()
if ($canDo->get('core.edit.state'))
{
- \JToolbarHelper::publish('articles.publish', 'JTOOLBAR_PUBLISH', true);
- \JToolbarHelper::unpublish('articles.unpublish', 'JTOOLBAR_UNPUBLISH', true);
\JToolbarHelper::custom('articles.unfeatured', 'unfeatured.png', 'featured_f2.png', 'JUNFEATURE', true);
- \JToolbarHelper::archiveList('articles.archive');
\JToolbarHelper::checkin('articles.checkin');
}
@@ -150,10 +148,6 @@ protected function addToolbar()
{
\JToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'articles.delete', 'JTOOLBAR_EMPTY_TRASH');
}
- elseif ($canDo->get('core.edit.state'))
- {
- \JToolbarHelper::trash('articles.trash');
- }
if ($canDo->get('core.admin') || $canDo->get('core.options'))
{
diff --git a/administrator/components/com_content/access.xml b/administrator/components/com_content/access.xml
index ac2db9a908bdf..a75bbc9216795 100644
--- a/administrator/components/com_content/access.xml
+++ b/administrator/components/com_content/access.xml
@@ -10,6 +10,7 @@