diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f6e0dcfc511fd..112744b894a2e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -54,3 +54,18 @@ plugins/system/webauthn/* @nikosdion media/plg_system_webauthn/* @nikosdion language/administrator/en-GB/en-GB.plg_system_webauthn.ini @nikosdion language/administrator/en-GB/en-GB.plg_system_webauthn.sys.ini @nikosdion + +# Workflow +administrator/components/com_workflow/* @bembelimen @hleithner +administrator/language/en-GB/com_workflow.ini @bembelimen @hleithner +administrator/language/en-GB/com_workflow.sys.ini @bembelimen @hleithner +administrator/language/en-GB/plg_workflow_publishing.ini @bembelimen @hleithner +administrator/language/en-GB/plg_workflow_publishing.sys.ini @bembelimen @hleithner +libraries/src/Form/Field/TransitionField.php @bembelimen @hleithner +libraries/src/Form/Field/Workflow* @bembelimen @hleithner +libraries/src/HTML/Helpers/Workflow* @bembelimen @hleithner +libraries/src/MVC/Model/Workflow* @bembelimen @hleithner +libraries/src/MVC/Model/Workflow* @bembelimen @hleithner +libraries/src/Workflow/* @bembelimen @hleithner +build/media_source/com_workflow/* @bembelimen @hleithner +plugins/workflow/* @bembelimen @hleithner diff --git a/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-05-15.sql b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-05-15.sql index 7bbedc437e6d4..b0c2b3115e08c 100644 --- a/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-05-15.sql +++ b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-05-15.sql @@ -10,7 +10,6 @@ CREATE TABLE IF NOT EXISTS `#__workflows` ( `description` text NOT NULL, `extension` varchar(50) NOT NULL, `default` tinyint(1) NOT NULL DEFAULT 0, - `core` tinyint(1) NOT NULL DEFAULT 0, `ordering` int(11) NOT NULL DEFAULT 0, `created` datetime NOT NULL, `created_by` int(10) NOT NULL DEFAULT 0, @@ -34,8 +33,8 @@ CREATE TABLE IF NOT EXISTS `#__workflows` ( -- Dumping data for table `#__workflows` -- -INSERT INTO `#__workflows` (`id`, `asset_id`, `published`, `title`, `description`, `extension`, `default`, `core`,`ordering`, `created`, `created_by`, `modified`, `modified_by`, `checked_out_time`, `checked_out`) VALUES -(1, 0, 1, 'COM_WORKFLOW_DEFAULT_WORKFLOW', '', 'com_content', 1, 1, 1, CURRENT_TIMESTAMP(), 0, CURRENT_TIMESTAMP(), 0, NULL, 0); +INSERT INTO `#__workflows` (`id`, `asset_id`, `published`, `title`, `description`, `extension`, `default`, `ordering`, `created`, `created_by`, `modified`, `modified_by`, `checked_out_time`, `checked_out`) VALUES +(1, 0, 1, 'COM_WORKFLOW_BASIC_WORKFLOW', '', 'com_content.article', 1, 1, CURRENT_TIMESTAMP(), 0, CURRENT_TIMESTAMP(), 0, NULL, 0); -- -- Table structure for table `#__workflow_associations` @@ -64,7 +63,6 @@ CREATE TABLE IF NOT EXISTS `#__workflow_stages` ( `published` tinyint(1) NOT NULL DEFAULT 0, `title` varchar(255) NOT NULL, `description` text NOT NULL, - `condition` int(10) DEFAULT 0, `default` tinyint(1) NOT NULL DEFAULT 0, `checked_out_time` datetime, `checked_out` int(10) NOT NULL DEFAULT 0, @@ -80,11 +78,8 @@ CREATE TABLE IF NOT EXISTS `#__workflow_stages` ( -- Dumping data for table `#__workflow_stages` -- -INSERT INTO `#__workflow_stages` (`id`, `asset_id`, `ordering`, `workflow_id`, `published`, `title`, `description`, `condition`, `default`, `checked_out_time`, `checked_out`) VALUES -(1, 0, 1, 1, 1, 'JUNPUBLISHED', '', 0, 1, NULL, 0), -(2, 0, 2, 1, 1, 'JPUBLISHED', '', 1, 0, NULL, 0), -(3, 0, 3, 1, 1, 'JTRASHED', '', -2, 0, NULL, 0), -(4, 0, 4, 1, 1, 'JARCHIVED', '', 2, 0, NULL, 0); +INSERT INTO `#__workflow_stages` (`id`, `asset_id`, `ordering`, `workflow_id`, `published`, `title`, `description`, `default`, `checked_out_time`, `checked_out`) VALUES +(1, 0, 1, 1, 1, 'COM_WORKFLOW_BASIC_STAGE', '', 1, NULL, 0); -- -- Table structure for table `#__workflow_transitions` @@ -100,6 +95,7 @@ CREATE TABLE IF NOT EXISTS `#__workflow_transitions` ( `description` text NOT NULL, `from_stage_id` int(10) NOT NULL, `to_stage_id` int(10) NOT NULL, + `options` text NOT NULL, `checked_out_time` datetime, `checked_out` int(10) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), @@ -115,21 +111,31 @@ CREATE TABLE IF NOT EXISTS `#__workflow_transitions` ( -- Dumping data for table `#__workflow_transitions` -- -INSERT INTO `#__workflow_transitions` (`id`, `asset_id`, `published`, `ordering`, `workflow_id`, `title`, `description`, `from_stage_id`, `to_stage_id`, `checked_out_time`, `checked_out`) VALUES -(1, 0, 1, 1, 1, 'Unpublish', '', -1, 1, NULL, 0), -(2, 0, 1, 2, 1, 'Publish', '', -1, 2, NULL, 0), -(3, 0, 1, 3, 1, 'Trash', '', -1, 3, NULL, 0), -(4, 0, 1, 4, 1, 'Archive', '', -1, 4, NULL, 0); +INSERT INTO `#__workflow_transitions` (`id`, `asset_id`, `published`, `ordering`, `workflow_id`, `title`, `description`, `from_stage_id`, `to_stage_id`, `options`, `checked_out_time`, `checked_out`) VALUES +(1, 0, 1, 1, 1, 'Unpublish', '', -1, 1, '{"publishing":"0"}', NULL, 0), +(2, 0, 1, 2, 1, 'Publish', '', -1, 1, '{"publishing":"1"}', NULL, 0), +(3, 0, 1, 3, 1, 'Trash', '', -1, 1, '{"publishing":"-2"}', NULL, 0), +(4, 0, 1, 4, 1, 'Archive', '', -1, 1, '{"publishing":"2"}', NULL, 0), +(5, 0, 1, 5, 1, 'Feature', '', -1, 1, '{"featuring":"1"}', NULL, 0), +(6, 0, 1, 6, 1, 'Unfeature', '', -1, 1, '{"featuring":"0"}', NULL, 0), +(7, 0, 1, 7, 1, 'Publish & Feature', '', -1, 1, '{"publishing":"1","featuring":"1"}', NULL, 0); -- -- Creating extension entry -- +-- Note that the old pseudo null dates have to be used for the `checked_out_time` +-- column because the conversion to real null dates will be done with a later +-- update SQL script. +-- INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `checked_out`, `checked_out_time`, `ordering`, `state`) VALUES -(0, 'com_workflow', 'component', 'com_workflow', '', 1, 1, 0, 0, '', '{}', 0, '0000-00-00 00:00:00', 0, 0); +(0, 'com_workflow', 'component', 'com_workflow', '', 1, 1, 0, 1, '', '{}', 0, '0000-00-00 00:00:00', 0, 0), +(0, 'plg_workflow_publishing', 'plugin', 'publishing', 'workflow', 0, 1, 1, 0, '', '{}', 0, '0000-00-00 00:00:00', 0, 0), +(0, 'plg_workflow_featuring', 'plugin', 'featuring', 'workflow', 0, 1, 1, 0, '', '{}', 0, '0000-00-00 00:00:00', 0, 0), +(0, 'plg_workflow_notification', 'plugin', 'notification', 'workflow', 0, 1, 1, 0, '', '{}', 0, '0000-00-00 00:00:00', 0, 0); -- -- Creating Associations for existing content -- INSERT INTO `#__workflow_associations` (`item_id`, `stage_id`, `extension`) -SELECT `id`, CASE WHEN `state` = -2 THEN 3 WHEN `state` = 0 THEN 1 WHEN `state` = 2 THEN 4 ELSE 2 END, 'com_content' FROM `#__content`; +SELECT `id`, 1, 'com_content.article' FROM `#__content`; diff --git a/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-05-15.sql b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-05-15.sql index a41ff39e4f764..ec7f6c3508ae3 100644 --- a/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-05-15.sql +++ b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-05-15.sql @@ -10,7 +10,6 @@ CREATE TABLE IF NOT EXISTS "#__workflows" ( "description" text NOT NULL, "extension" varchar(50) NOT NULL, "default" smallint NOT NULL DEFAULT 0, - "core" smallint NOT NULL DEFAULT 0, "ordering" bigint NOT NULL DEFAULT 0, "created" timestamp without time zone NOT NULL, "created_by" bigint DEFAULT 0 NOT NULL, @@ -31,8 +30,8 @@ CREATE INDEX "#__workflows_idx_modified" ON "#__workflows" ("modified"); CREATE INDEX "#__workflows_idx_modified_by" ON "#__workflows" ("modified_by"); CREATE INDEX "#__workflows_idx_checked_out" ON "#__workflows" ("checked_out"); -INSERT INTO "#__workflows" ("id", "asset_id", "published", "title", "description", "extension", "default", "core", "ordering", "created", "created_by", "modified", "modified_by", "checked_out_time", "checked_out") VALUES -(1, 0, 1, 'COM_WORKFLOW_DEFAULT_WORKFLOW', '', 'com_content', 1, 1, 1, CURRENT_TIMESTAMP, 0, CURRENT_TIMESTAMP, 0, NULL, 0); +INSERT INTO "#__workflows" ("id", "asset_id", "published", "title", "description", "extension", "default", "ordering", "created", "created_by", "modified", "modified_by", "checked_out_time", "checked_out") VALUES +(1, 0, 1, 'COM_WORKFLOW_BASIC_WORKFLOW', '', 'com_content.article', 1, 1, CURRENT_TIMESTAMP, 0, CURRENT_TIMESTAMP, 0, NULL, 0); -- -- Table structure for table "#__workflow_associations" @@ -64,7 +63,6 @@ CREATE TABLE IF NOT EXISTS "#__workflow_stages" ( "published" smallint NOT NULL DEFAULT 0, "title" varchar(255) NOT NULL, "description" text NOT NULL, - "condition" bigint DEFAULT 0 NOT NULL, "default" smallint NOT NULL DEFAULT 0, "checked_out_time" timestamp without time zone, "checked_out" bigint DEFAULT 0 NOT NULL, @@ -80,11 +78,8 @@ CREATE INDEX "#__workflow_stages_idx_checked_out" ON "#__workflow_stages" ("chec -- Dumping data for table "#__workflow_stages" -- -INSERT INTO "#__workflow_stages" ("id", "asset_id", "ordering", "workflow_id", "published", "title", "description", "condition", "default", "checked_out_time", "checked_out") VALUES -(1, 0, 1, 1, 1, 'JUNPUBLISHED', '', 0, 1, NULL, 0), -(2, 0, 2, 1, 1, 'JPUBLISHED', '', 1, 0, NULL, 0), -(3, 0, 3, 1, 1, 'JTRASHED', '', -2, 0, NULL, 0), -(4, 0, 4, 1, 1, 'JARCHIVED', '', 2, 0, NULL, 0); +INSERT INTO "#__workflow_stages" ("id", "asset_id", "ordering", "workflow_id", "published", "title", "description", "default", "checked_out_time", "checked_out") VALUES +(1, 0, 1, 1, 1, 'COM_WORKFLOW_BASIC_STAGE', '', 1, NULL, 0); -- -- Table structure for table "#__workflow_transitions" @@ -100,6 +95,7 @@ CREATE TABLE IF NOT EXISTS "#__workflow_transitions" ( "description" text NOT NULL, "from_stage_id" bigint DEFAULT 0 NOT NULL, "to_stage_id" bigint DEFAULT 0 NOT NULL, + "options" text NOT NULL, "checked_out_time" timestamp without time zone, "checked_out" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id") @@ -111,21 +107,31 @@ CREATE INDEX "#__workflow_transitions_idx_to_stage_id" ON "#__workflow_transitio CREATE INDEX "#__workflow_transitions_idx_workflow_id" ON "#__workflow_transitions" ("workflow_id"); CREATE INDEX "#__workflow_transitions_idx_checked_out" ON "#__workflow_transitions" ("checked_out"); -INSERT INTO "#__workflow_transitions" ("id", "asset_id", "published", "ordering", "workflow_id", "title", "description", "from_stage_id", "to_stage_id", "checked_out_time", "checked_out") VALUES -(1, 0, 1, 1, 1, 'Unpublish', '', -1, 1, NULL, 0), -(2, 0, 1, 2, 1, 'Publish', '', -1, 2, NULL, 0), -(3, 0, 1, 3, 1, 'Trash', '', -1, 3, NULL, 0), -(4, 0, 1, 4, 1, 'Archive', '', -1, 4, NULL, 0); +INSERT INTO "#__workflow_transitions" ("id", "asset_id", "published", "ordering", "workflow_id", "title", "description", "from_stage_id", "to_stage_id", "options", "checked_out_time", "checked_out") VALUES +(1, 0, 1, 1, 1, 'Unpublish', '', -1, 1, '{"publishing":"0"}', NULL, 0), +(2, 0, 1, 2, 1, 'Publish', '', -1, 1, '{"publishing":"1"}', NULL, 0), +(3, 0, 1, 3, 1, 'Trash', '', -1, 1, '{"publishing":"-2"}', NULL, 0), +(4, 0, 1, 4, 1, 'Archive', '', -1, 1, '{"publishing":"2"}', NULL, 0), +(5, 0, 1, 5, 1, 'Feature', '', -1, 1, '{"featuring":"1"}', NULL, 0), +(6, 0, 1, 6, 1, 'Unfeature', '', -1, 1, '{"featuring":"0"}', NULL, 0), +(7, 0, 1, 7, 1, 'Publish & Feature', '', -1, 1, '{"publishing":"1","featuring":"1"}', NULL, 0); -- -- Creating extension entry -- +-- Note that the old pseudo null dates have to be used for the "checked_out_time" +-- column because the conversion to real null dates will be done with a later +-- update SQL script. +-- INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "manifest_cache", "params", "checked_out", "checked_out_time", "ordering", "state") VALUES -(0, 'com_workflow', 'component', 'com_workflow', '', 1, 1, 0, 0, '', '{}', 0, '1970-01-01 00:00:00', 0, 0); +(0, 'com_workflow', 'component', 'com_workflow', '', 1, 1, 0, 1, '', '{}', 0, '1970-01-01 00:00:00', 0, 0), +(0, 'plg_workflow_publishing', 'plugin', 'publishing', 'workflow', 0, 1, 1, 0, '', '{}', 0, '1970-01-01 00:00:00', 0, 0), +(0, 'plg_workflow_featuring', 'plugin', 'featuring', 'workflow', 0, 1, 1, 0, '', '{}', 0, '1970-01-01 00:00:00', 0, 0), +(0, 'plg_workflow_notification', 'plugin', 'notification', 'workflow', 0, 1, 1, 0, '', '{}', 0, '1970-01-01 00:00:00', 0, 0); -- -- Creating Associations for existing content -- INSERT INTO "#__workflow_associations" ("item_id", "stage_id", "extension") -SELECT "id", CASE WHEN "state" = -2 THEN 3 WHEN "state" = 0 THEN 1 WHEN "state" = 2 THEN 4 ELSE 2 END, 'com_content' FROM "#__content"; +SELECT "id", 1, 'com_content.article' FROM "#__content"; diff --git a/administrator/components/com_content/config.xml b/administrator/components/com_content/config.xml index 67901887c1ead..62b34237ba614 100644 --- a/administrator/components/com_content/config.xml +++ b/administrator/components/com_content/config.xml @@ -1039,13 +1039,17 @@ - + +
diff --git a/administrator/components/com_content/forms/article.xml b/administrator/components/com_content/forms/article.xml index ec4a92fd4e4e8..8e9aa6d68f135 100644 --- a/administrator/components/com_content/forms/article.xml +++ b/administrator/components/com_content/forms/article.xml @@ -68,11 +68,18 @@ /> + name="state" + type="list" + label="JSTATUS" + class="custom-select-color-state" + size="1" + default="1" + > + + + + + + + + + JYES - - - - - + - + true, + 'core.state' => true, + ]; + /** * The trashed condition * @@ -171,6 +177,24 @@ public function getContexts(): array return $contexts; } + /** + * Returns valid contexts + * + * @return array + * + * @since 4.0.0 + */ + public function getWorkflowContexts(): array + { + Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR); + + $contexts = array( + 'com_content.article' => Text::_('COM_CONTENT') + ); + + return $contexts; + } + /** * Returns the table for the count items functions for the given section. * @@ -199,6 +223,38 @@ public function getWorkflowTableBySection(?string $section = null): string return '#__content'; } + /** + * Returns the model name, based on the context + * + * @param string $context The context of the workflow + * + * @return string + */ + public function getModelName($context): string + { + $parts = explode('.', $context); + + if (count($parts) < 2) + { + return ''; + } + + array_shift($parts); + + $modelname = array_shift($parts); + + if ($modelname === 'article' && Factory::getApplication()->isClient('site')) + { + return 'Form'; + } + elseif ($modelname === 'featured' && Factory::getApplication()->isClient('administrator')) + { + return 'Article'; + } + + return ucfirst($modelname); + } + /** * Method to filter transitions by given id of state. * @@ -228,7 +284,7 @@ public function countItems(array $items, string $section) { $config = (object) array( 'related_tbl' => 'content', - 'state_col' => 'condition', + 'state_col' => 'state', 'group_col' => 'catid', 'relation_type' => 'category_or_group', 'uses_workflows' => true, @@ -277,19 +333,4 @@ public function prepareForm(Form $form, $data) { ContentHelper::onPrepareForm($form, $data); } - - /** - * Method to change state of multiple ids - * - * @param array $pks Array of IDs - * @param int $condition Condition of the workflow state - * - * @return boolean - * - * @since 4.0.0 - */ - public static function updateContentState(array $pks, int $condition): bool - { - return ContentHelper::updateContentState($pks, $condition); - } } diff --git a/administrator/components/com_content/src/Helper/ContentHelper.php b/administrator/components/com_content/src/Helper/ContentHelper.php index f5ffc7d293d5e..664b098f2527a 100644 --- a/administrator/components/com_content/src/Helper/ContentHelper.php +++ b/administrator/components/com_content/src/Helper/ContentHelper.php @@ -67,49 +67,12 @@ public static function filterTransitions(array $transitions, int $pk, int $workf $transitions, function ($var) use ($pk, $workflow_id) { - return in_array($var['from_stage_id'], [-1, $pk]) && $var['to_stage_id'] != $pk && $workflow_id == $var['workflow_id']; + return in_array($var['from_stage_id'], [-1, $pk]) && $workflow_id == $var['workflow_id']; } ) ); } - /** - * Method to change state of multiple ids - * - * @param array $pks Array of IDs - * @param int $condition Condition of the workflow state - * - * @return boolean - * - * @since 4.0.0 - */ - public static function updateContentState(array $pks, int $condition): bool - { - if (empty($pks)) - { - return false; - } - - try - { - $db = Factory::getDbo(); - $query = $db->getQuery(true); - - $query->update($db->quoteName('#__content')) - ->set($db->quoteName('state') . ' = :condition') - ->whereIn($db->quoteName('id'), $pks) - ->bind(':condition', $condition, ParameterType::INTEGER); - - $db->setQuery($query)->execute(); - } - catch (\Exception $e) - { - return false; - } - - return true; - } - /** * Prepares a form * diff --git a/administrator/components/com_content/src/Model/ArticleModel.php b/administrator/components/com_content/src/Model/ArticleModel.php index 1b0142c7a5c04..042f121847689 100644 --- a/administrator/components/com_content/src/Model/ArticleModel.php +++ b/administrator/components/com_content/src/Model/ArticleModel.php @@ -11,14 +11,19 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; 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\Log\Log; +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\Category; @@ -30,7 +35,6 @@ use Joomla\Component\Content\Administrator\Extension\ContentComponent; use Joomla\Component\Content\Administrator\Helper\ContentHelper; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; -use Joomla\Component\Workflow\Administrator\Table\StageTable; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; @@ -41,8 +45,10 @@ * @since 1.6 */ -class ArticleModel extends AdminModel +class ArticleModel extends AdminModel implements WorkflowModelInterface { + use WorkflowBehaviorTrait; + /** * The prefix to use with controller messages. * @@ -67,6 +73,52 @@ class ArticleModel extends AdminModel */ protected $associationsContext = 'com_content.item'; + /** + * The event to trigger before changing featured status one or more items. + * + * @var string + * @since 4.0 + */ + protected $event_before_change_featured = null; + + /** + * The event to trigger after changing featured status one or more items. + * + * @var string + * @since 4.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); + + // Set the featured status change events + $this->event_before_change_featured = $config['event_before_change_featured'] ?? $this->event_before_change_featured; + $this->event_before_change_featured = $this->event_before_change_featured ?? 'onContentBeforeChangeFeatured'; + $this->event_after_change_featured = $config['event_after_change_featured'] ?? $this->event_after_change_featured; + $this->event_after_change_featured = $this->event_after_change_featured ?? 'onContentAfterChangeFeatured'; + + $this->setUpWorkflow('com_content.article'); + } + /** * Function that can be overriden to do any data cleanup after batch copying data * @@ -111,12 +163,7 @@ protected function cleanupPostBatchCopy(TableInterface $table, $newId, $oldId) } } - // Copy workflow association - $workflow = new Workflow(['extension' => 'com_content']); - - $assoc = $workflow->getAssociation((int) $oldId); - - $workflow->createAssociation((int) $newId, (int) $assoc->stage_id); + $this->workflowCleanupBatchMove($oldId, $newId); // Register FieldsHelper \JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php'); @@ -140,50 +187,6 @@ protected function cleanupPostBatchCopy(TableInterface $table, $newId, $oldId) Factory::getApplication()->triggerEvent('onContentAfterSave', array('com_content.article', &$this->table, false, $fieldsData)); } - /** - * Batch change workflow stage or current. - * - * @param integer $value The workflow stage 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 4.0.0 - */ - protected function batchWorkflowStage(int $value, array $pks, array $contexts) - { - $user = Factory::getUser(); - - if (!$user->authorise('core.admin', 'com_content')) - { - $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EXECUTE_TRANSITION')); - } - - // Get workflow stage information - $stage = new StageTable($this->_db); - - if (empty($value) || !$stage->load($value)) - { - Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_BATCH_WORKFLOW_STAGE_ROW_NOT_FOUND'), 'error'); - - return false; - } - - if (empty($pks)) - { - Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_BATCH_WORKFLOW_STAGE_ROW_NOT_FOUND'), 'error'); - - return false; - } - - $workflow = new Workflow(['extension' => 'com_content']); - - // Update content state value and workflow associations - return ContentHelper::updateContentState($pks, (int) $stage->condition) - && $workflow->updateAssociations($pks, $value); - } - /** * Batch move categories to a new category. * @@ -298,23 +301,12 @@ protected function batchMove($value, $pks, $contexts) */ protected function canDelete($record) { - if (!empty($record->id)) + if (empty($record->id) || ($record->state != -2 && !Factory::getApplication()->isClient('api'))) { - $stage = new StageTable($this->getDbo()); - - $workflow = new Workflow(['extension' => 'com_content']); - - $assoc = $workflow->getAssociation((int) $record->id); - - if (!$stage->load($assoc->stage_id) || ($stage->condition != ContentComponent::CONDITION_TRASHED && !Factory::getApplication()->isClient('api'))) - { - return false; - } - - return Factory::getUser()->authorise('core.delete', 'com_content.article.' . (int) $record->id); + return false; } - return false; + return Factory::getUser()->authorise('core.delete', 'com_content.article.' . (int) $record->id); } /** @@ -390,114 +382,9 @@ protected function prepareTable($table) */ public function publish(&$pks, $value = 1) { - $input = Factory::getApplication()->input; - - $user = Factory::getUser(); - $table = $this->getTable(); - $pks = (array) $pks; - $value = (int) $value; - - $itrans = $input->get('publish_transitions', [], 'array'); - - // Include the plugins for the change of state event. - PluginHelper::importPlugin($this->events_map['change_state']); - - $db = $this->getDbo(); - $query = $db->getQuery(true); - - $query->select( - [ - $db->quoteName('wt.id'), - $db->quoteName('wa.item_id'), - ] - ) - ->from( - [ - $db->quoteName('#__workflow_transitions', 'wt'), - $db->quoteName('#__workflow_stages', 'ws'), - $db->quoteName('#__workflow_stages', 'ws2'), - $db->quoteName('#__workflow_associations', 'wa'), - ] - ) - ->where( - [ - $db->quoteName('wt.to_stage_id') . ' = ' . $db->quoteName('ws.id'), - $db->quoteName('wa.stage_id') . ' = ' . $db->quoteName('ws2.id'), - $db->quoteName('wt.workflow_id') . ' = ' . $db->quoteName('ws.workflow_id'), - $db->quoteName('wt.workflow_id') . ' = ' . $db->quoteName('ws2.workflow_id'), - $db->quoteName('wt.to_stage_id') . ' != ' . $db->quoteName('wa.stage_id'), - $db->quoteName('wa.extension') . ' = ' . $db->quote('com_content'), - $db->quoteName('ws.condition') . ' = :condition', - ] - ) - ->extendWhere( - 'AND', - [ - $db->quoteName('wt.from_stage_id') . ' = -1', - $db->quoteName('wt.from_stage_id') . ' = ' . $db->quoteName('wa.stage_id'), - ], - 'OR' - ) - ->whereIn($db->quoteName('wa.item_id'), $pks) - ->bind(':condition', $value, ParameterType::INTEGER); - - $transitions = $db->setQuery($query)->loadObjectList(); - - $items = []; - - foreach ($transitions as $transition) - { - if ($user->authorise('core.execute.transition', 'com_content.transition.' . $transition->id)) - { - if (!isset($itrans[$transition->item_id]) || $itrans[$transition->item_id] == $transition->id) - { - $items[$transition->item_id] = (int) $transition->id; - } - } - } - - // Access checks. - foreach ($pks as $i => $pk) - { - $table->reset(); - - if ($table->load($pk)) - { - if (!isset($items[$pk])) - { - // Prune items that you can't change. - unset($pks[$i]); - - Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror'); - - return false; - } - - // If the table is checked out by another user, drop it and report to the user trying to change its state. - if ($table->hasField('checked_out') && $table->checked_out && ($table->checked_out != $user->id)) - { - Log::add(Text::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'), Log::WARNING, 'jerror'); - - // Prune items that you can't change. - unset($pks[$i]); - - return false; - } - } - } - - foreach ($pks as $i => $pk) - { - if (!$this->runTransition($pk, $items[$pk])) - { - return false; - } - } + $this->workflowBeforeStageChange(); - // Clear the component's cache - $this->cleanCache(); - - return true; + return parent::publish($pks, $value); } /** @@ -628,18 +515,6 @@ public function getForm($data = array(), $loadData = true) $form->setFieldAttribute('catid', 'filter', 'unset'); } } - - $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_stage', (int) $assoc->stage_id); - } } else { @@ -675,10 +550,6 @@ public function getForm($data = array(), $loadData = true) $form->setFieldAttribute('catid', 'refresh-enabled', true); $form->setFieldAttribute('catid', 'refresh-cat-id', $assignedCatids); $form->setFieldAttribute('catid', 'refresh-section', 'article'); - - $workflow = $this->getWorkflowByCategory($assignedCatids); - - $form->setFieldAttribute('transition', 'workflow_stage', (int) $workflow->stage_id); } } @@ -840,6 +711,8 @@ public function save($data) $data['images'] = (string) $registry; } + $this->workflowBeforeSave(); + // Create new category, if needed. $createCategory = true; @@ -924,70 +797,6 @@ public function save($data) } } - $stageId = 0; - - // Set status depending on category - if (empty($data['id'])) - { - $workflow = $this->getWorkflowByCategory($data['catid']); - - if (empty($workflow->id)) - { - $this->setError(Text::_('COM_CONTENT_WORKFLOW_NOT_FOUND')); - - return false; - } - - $stageId = (int) $workflow->stage_id; - - // B/C state - $data['state'] = (int) $workflow->condition; - } - - // Calculate new status depending on transition - if (!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(Text::_('COM_CONTENT_WORKFLOW_TRANSITION_NOT_ALLOWED')); - - return false; - } - - // Set the new state - $query = $db->getQuery(true); - $transition = (int) $data['transition']; - - $query->select($db->quoteName(['ws.id', 'ws.condition'])) - ->from( - [ - $db->quoteName('#__workflow_stages', 'ws'), - $db->quoteName('#__workflow_transitions', 'wt'), - ] - ) - ->where( - [ - $db->quoteName('wt.to_stage_id') . ' = ' . $db->quoteName('ws.id'), - $db->quoteName('wt.id') . ' = :transition', - $db->quoteName('ws.published') . ' = 1', - $db->quoteName('wt.published') . ' = 1', - ] - ) - ->bind(':transition', $transition, ParameterType::INTEGER); - - $stage = $db->setQuery($query)->loadObject(); - - if (empty($stage->id)) - { - $this->setError(Text::_('COM_CONTENT_WORKFLOW_TRANSITION_NOT_ALLOWED')); - - return false; - } - - $data['state'] = (int) $stage->condition; - } - // Automatic handling of alias for empty fields if (in_array($input->get('task'), array('apply', 'save', 'save2new')) && (!isset($data['id']) || (int) $data['id'] == 0)) { @@ -1019,11 +828,10 @@ public function save($data) } } - $workflow = new Workflow(['extension' => 'com_content']); - if (parent::save($data)) { - if (isset($data['featured'])) + // Check if featured is set and if not managed by workflow + if (isset($data['featured']) && !$this->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article')) { if (!$this->featured( $this->getState($this->getName() . '.id'), @@ -1036,47 +844,7 @@ public function save($data) } } - // Let's check if we have workflow association (perhaps something went wrong before) - if (empty($stageId)) - { - $assoc = $workflow->getAssociation((int) $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((int) $table->catid); - - if (empty($workflow->id)) - { - $this->setError(Text::_('COM_CONTENT_WORKFLOW_NOT_FOUND')); - - return false; - } - - $stageId = (int) $workflow->stage_id; - - // B/C state - $table->state = $workflow->condition; - - $table->store(); - } - } - - // If we have a new state, create the workflow association - if (!empty($stageId)) - { - $workflow->createAssociation((int) $this->getState($this->getName() . '.id'), (int) $stageId); - } - - // Run the transition and update the workflow association - if (!empty($data['transition'])) - { - $this->runTransition((int) $this->getState($this->getName() . '.id'), (int) $data['transition']); - } + $this->workflowAfterSave($data); return true; } @@ -1097,9 +865,15 @@ public function save($data) public function featured($pks, $value = 0, $featuredUp = null, $featuredDown = null) { // Sanitize the ids. - $pks = (array) $pks; - $pks = ArrayHelper::toInteger($pks); - $value = (int) $value; + $pks = (array) $pks; + $pks = ArrayHelper::toInteger($pks); + $value = (int) $value; + $context = $this->option . '.' . $this->name; + + $this->workflowBeforeStageChange(); + + // Include the plugins for the change of state event. + PluginHelper::importPlugin($this->events_map['featured']); // Convert empty strings to null for the query. if ($featuredUp === '') @@ -1121,6 +895,16 @@ public function featured($pks, $value = 0, $featuredUp = null, $featuredDown = n $table = $this->getTable('Featured', 'Administrator'); + // Trigger the before change state event. + $result = Factory::getApplication()->triggerEvent($this->event_before_change_featured, array($context, $pks, $value)); + + if (\in_array(false, $result, true)) + { + $this->setError($table->getError()); + + return false; + } + try { $db = $this->getDbo(); @@ -1214,6 +998,16 @@ public function featured($pks, $value = 0, $featuredUp = null, $featuredDown = n $table->reorder(); + // Trigger the change state event. + $result = Factory::getApplication()->triggerEvent($this->event_after_change_featured, array($context, $pks, $value)); + + if (\in_array(false, $result, true)) + { + $this->setError($table->getError()); + + return false; + } + $this->cleanCache(); return true; @@ -1288,6 +1082,8 @@ protected function preprocessForm(Form $form, $data, $group = 'content') } } + $this->workflowPreprocessForm($form, $data); + parent::preprocessForm($form, $data, $group); } @@ -1359,9 +1155,7 @@ public function delete(&$pks) $db->setQuery($query); $db->execute(); - $workflow = new Workflow(['extension' => 'com_content']); - - $workflow->deleteAssociation($pks); + $this->workflow->deleteAssociation($pks); } return $return; @@ -1374,147 +1168,4 @@ public function delete(&$pks) * * @return integer|boolean If found, the workflow ID, otherwise false */ - protected function getWorkflowByCategory(int $catId) - { - $db = $this->getDbo(); - - // Search categories and parents (if requested) for a workflow - $category = new Category($db); - - $categories = array_reverse($category->getPath($catId)); - - $workflow_id = 0; - - foreach ($categories as $cat) - { - $cat->params = new Registry($cat->params); - - $workflow_id = $cat->params->get('workflow_id'); - - if ($workflow_id == 'inherit') - { - $workflow_id = 0; - - continue; - } - elseif ($workflow_id == 'use_default') - { - $workflow_id = 0; - - break; - } - elseif ($workflow_id > 0) - { - break; - } - } - - // Check if the workflow exists - if ($workflow_id = (int) $workflow_id) - { - $query = $db->getQuery(true); - - $query->select( - [ - $db->quoteName('w.id'), - $db->quoteName('ws.condition'), - $db->quoteName('ws.id', 'stage_id'), - ] - ) - ->from( - [ - $db->quoteName('#__workflow_stages', 'ws'), - $db->quoteName('#__workflows', 'w'), - ] - ) - ->where( - [ - $db->quoteName('ws.workflow_id') . ' = ' . $db->quoteName('w.id'), - $db->quoteName('ws.default') . ' = 1', - $db->quoteName('w.published') . ' = 1', - $db->quoteName('ws.published') . ' = 1', - $db->quoteName('w.id') . ' = :workflowId', - ] - ) - ->bind(':workflowId', $workflow_id, ParameterType::INTEGER); - - $workflow = $db->setQuery($query)->loadObject(); - - if (!empty($workflow->id)) - { - return $workflow; - } - } - - // Use default workflow - $query = $db->getQuery(true); - - $query->select( - [ - $db->quoteName('w.id'), - $db->quoteName('ws.condition'), - $db->quoteName('ws.id', 'stage_id'), - ] - ) - ->from( - [ - $db->quoteName('#__workflow_stages', 'ws'), - $db->quoteName('#__workflows', 'w'), - ] - ) - ->where( - [ - $db->quoteName('ws.default') . ' = 1', - $db->quoteName('ws.workflow_id') . ' = ' . $db->quoteName('w.id'), - $db->quoteName('w.published') . ' = 1', - $db->quoteName('ws.published') . ' = 1', - $db->quoteName('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 integer $pk Id of article - * @param integer $transition_id Id of transition - * - * @return boolean - * - * @since 4.0.0 - */ - public function runTransition(int $pk, int $transition_id): bool - { - $workflow = new Workflow(['extension' => 'com_content']); - - $runTransaction = $workflow->executeTransition([$pk], $transition_id); - - if (!$runTransaction) - { - $this->setError(Text::_('COM_CONTENT_ERROR_UPDATE_STAGE')); - - return false; - } - - // B/C state change trigger for UCM - $context = $this->option . '.' . $this->name; - - // Include the plugins for the change of stage event. - PluginHelper::importPlugin($this->events_map['change_state']); - - // Trigger the change stage event. - Factory::getApplication()->triggerEvent($this->event_change_state, [$context, [$pk], $workflow->getConditionForTransition($transition_id)]); - - return true; - } } diff --git a/administrator/components/com_content/src/Model/ArticlesModel.php b/administrator/components/com_content/src/Model/ArticlesModel.php index 77606720577c5..9b26569f5db0a 100644 --- a/administrator/components/com_content/src/Model/ArticlesModel.php +++ b/administrator/components/com_content/src/Model/ArticlesModel.php @@ -11,6 +11,7 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Text; @@ -68,8 +69,8 @@ public function __construct($config = array()) 'level', 'tag', 'rating_count', 'rating', - 'condition', - 'stage', + 'stage', 'wa.stage_id', + 'ws.title' ); if (Associations::isEnabled()) @@ -81,6 +82,37 @@ public function __construct($config = array()) 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. * @@ -120,9 +152,6 @@ 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); @@ -203,6 +232,8 @@ protected function getListQuery() $query = $db->getQuery(true); $user = Factory::getUser(); + $params = ComponentHelper::getParams('com_content'); + // Select the required fields from the table. $query->select( $this->getState( @@ -252,12 +283,12 @@ protected function getListQuery() $db->quoteName('ua.name', 'author_name'), $db->quoteName('wa.stage_id', 'stage_id'), $db->quoteName('ws.title', 'stage_title'), - $db->quoteName('ws.condition', 'stage_condition'), $db->quoteName('ws.workflow_id', 'workflow_id'), + $db->quoteName('w.title', 'workflow_title'), ] ) ->from($db->quoteName('#__content', 'a')) - ->where($db->quoteName('wa.extension') . ' = ' . $db->quote('com_content')) + ->where($db->quoteName('wa.extension') . ' = ' . $db->quote('com_content.article')) ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language')) ->join('LEFT', $db->quoteName('#__content_frontpage', 'fp'), $db->quoteName('fp.content_id') . ' = ' . $db->quoteName('a.id')) ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out')) @@ -266,7 +297,8 @@ protected function getListQuery() ->join('LEFT', $db->quoteName('#__categories', 'parent'), $db->quoteName('parent.id') . ' = ' . $db->quoteName('c.parent_id')) ->join('LEFT', $db->quoteName('#__users', 'ua'), $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by')) ->join('INNER', $db->quoteName('#__workflow_associations', 'wa'), $db->quoteName('wa.item_id') . ' = ' . $db->quoteName('a.id')) - ->join('INNER', $db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('ws.id') . ' = ' . $db->quoteName('wa.stage_id')); + ->join('INNER', $db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('ws.id') . ' = ' . $db->quoteName('wa.stage_id')) + ->join('INNER', $db->quoteName('#__workflows', 'w'), $db->quoteName('w.id') . ' = ' . $db->quoteName('ws.workflow_id')); if (PluginHelper::isEnabled('content', 'vote')) { @@ -333,27 +365,27 @@ protected function getListQuery() // Filter by published state $workflowStage = (string) $this->getState('filter.stage'); - if (is_numeric($workflowStage)) + if ($params->get('workflow_enabled') && is_numeric($workflowStage)) { $workflowStage = (int) $workflowStage; $query->where($db->quoteName('wa.stage_id') . ' = :stage') ->bind(':stage', $workflowStage, ParameterType::INTEGER); } - $condition = (string) $this->getState('filter.condition'); + $published = (string) $this->getState('filter.published'); - if ($condition !== '*') + if ($published !== '*') { - if (is_numeric($condition)) + if (is_numeric($published)) { - $condition = (int) $condition; - $query->where($db->quoteName('ws.condition') . ' = :condition') - ->bind(':condition', $condition, ParameterType::INTEGER); + $state = (int) $published; + $query->where($db->quoteName('a.state') . ' = :state') + ->bind(':state', $published, ParameterType::INTEGER); } elseif (!is_numeric($workflowStage)) { $query->whereIn( - $db->quoteName('ws.condition'), + $db->quoteName('a.state'), [ ContentComponent::CONDITION_PUBLISHED, ContentComponent::CONDITION_UNPUBLISHED, @@ -558,23 +590,25 @@ public function getTransitions() return false; } - $ids = array_column($items, 'stage_id'); - $ids = ArrayHelper::toInteger($ids); - $ids = array_values(array_unique(array_filter($ids))); + $stage_ids = ArrayHelper::getColumn($items, 'stage_id'); + $stage_ids = ArrayHelper::toInteger($stage_ids); + $stage_ids = array_values(array_unique(array_filter($stage_ids))); - $ids[] = -1; + $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($ids)) + if (count($stage_ids) || count($workflow_ids)) { Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR); $query = $db->getQuery(true); - $query->select( + $query ->select( [ $db->quoteName('t.id', 'value'), $db->quoteName('t.title', 'text'), @@ -582,28 +616,39 @@ public function getTransitions() $db->quoteName('t.to_stage_id'), $db->quoteName('s.id', 'stage_id'), $db->quoteName('s.title', 'stage_title'), - $db->quoteName('s.condition', 'stage_condition'), - $db->quoteName('s.workflow_id'), + $db->quoteName('t.workflow_id'), ] ) ->from($db->quoteName('#__workflow_transitions', 't')) - ->join( - 'LEFT', + ->innerJoin( $db->quoteName('#__workflow_stages', 's'), - $db->quoteName('t.from_stage_id') . ' IN (' . implode(',', $query->bindArray($ids)) . ')' + $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id') ) ->where( [ - $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id'), $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(); - $workflow = new Workflow(['extension' => 'com_content']); + $workflow = new Workflow(['extension' => 'com_content.article']); foreach ($transitions as $key => $transition) { @@ -611,13 +656,6 @@ public function getTransitions() { unset($transitions[$key]); } - else - { - // Update the transition text with final state value - $conditionName = $workflow->getConditionName((int) $transition['stage_condition']); - - $transitions[$key]['text'] .= ' [' . Text::_($conditionName) . ']'; - } } $this->cache[$store] = $transitions; diff --git a/administrator/components/com_content/src/Model/FeaturedModel.php b/administrator/components/com_content/src/Model/FeaturedModel.php index 47049c8d02ca6..9735baece3fe0 100644 --- a/administrator/components/com_content/src/Model/FeaturedModel.php +++ b/administrator/components/com_content/src/Model/FeaturedModel.php @@ -56,8 +56,7 @@ public function __construct($config = array()) 'level', 'tag', 'rating_count', 'rating', - 'condition', - 'stage', + 'ws.title', ); } diff --git a/administrator/components/com_content/src/View/Articles/HtmlView.php b/administrator/components/com_content/src/View/Articles/HtmlView.php index ca36533b58a63..a66139ec85c9a 100644 --- a/administrator/components/com_content/src/View/Articles/HtmlView.php +++ b/administrator/components/com_content/src/View/Articles/HtmlView.php @@ -11,6 +11,7 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; @@ -64,6 +65,13 @@ class HtmlView extends BaseHtmlView */ public $activeFilters; + /** + * All transition, which can be executed of one if the items + * + * @var array + */ + protected $transitions = []; + /** * Display the view * @@ -78,9 +86,15 @@ public function display($tpl = null) $this->state = $this->get('State'); $this->filterForm = $this->get('FilterForm'); $this->activeFilters = $this->get('ActiveFilters'); - $this->transitions = $this->get('Transitions'); $this->vote = PluginHelper::isEnabled('content', 'vote'); + 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) { @@ -117,51 +131,6 @@ public function display($tpl = null) } } - $transitions = [ - 'publish' => [], - 'unpublish' => [], - 'archive' => [], - 'trash' => [] - ]; - - foreach ($this->transitions as $transition) - { - switch ($transition['stage_condition']) - { - case ContentComponent::CONDITION_PUBLISHED: - $transitions['publish'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - - case ContentComponent::CONDITION_UNPUBLISHED: - $transitions['unpublish'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - - case ContentComponent::CONDITION_ARCHIVED: - $transitions['archive'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - - case ContentComponent::CONDITION_TRASHED: - $transitions['trash'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - } - } - - $this->document->addScriptOptions('articles.transitions', $transitions); - - $articles = []; - - foreach ($this->items as $item) - { - $articles['article-' . (int) $item->id] = Text::sprintf('COM_CONTENT_STAGE_ARTICLE_TITLE', $this->escape($item->title), (int) $item->id); - } - - $this->document->addScriptOptions('articles.items', $articles); - - Text::script('COM_CONTENT_ERROR_CANNOT_PUBLISH'); - Text::script('COM_CONTENT_ERROR_CANNOT_UNPUBLISH'); - Text::script('COM_CONTENT_ERROR_CANNOT_TRASH'); - Text::script('COM_CONTENT_ERROR_CANNOT_ARCHIVE'); - return parent::display($tpl); } @@ -187,7 +156,7 @@ protected function addToolbar() $toolbar->addNew('article.add'); } - if ($canDo->get('core.edit.state') || $canDo->get('core.execute.transition')) + if ($canDo->get('core.edit.state') || count($this->transitions)) { $dropdown = $toolbar->dropdownButton('status-group') ->text('JTOOLBAR_CHANGE_STATUS') @@ -198,15 +167,35 @@ protected function addToolbar() $childBar = $dropdown->getChildToolbar(); - if ($canDo->get('core.execute.transition')) + if (count($this->transitions)) { - $childBar->publish('articles.publish')->listCheck(true); - - $childBar->unpublish('articles.unpublish')->listCheck(true); + $childBar->separatorButton('transition-headline') + ->text('COM_CONTENT_RUN_TRANSITIONS') + ->buttonClass('text-center py-2 h3'); + + $cmd = "Joomla.submitbutton('articles.runTransition');"; + $messages = "{error: [Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST')]}"; + $alert = 'Joomla.renderMessages(' . $messages . ')'; + $cmd = 'if (document.adminForm.boxchecked.value == 0) { ' . $alert . ' } else { ' . $cmd . ' }'; + + foreach ($this->transitions as $transition) + { + $childBar->standardButton('transition') + ->text($transition['text']) + ->buttonClass('transition-' . (int) $transition['value']) + ->icon('fas fa-project-diagram') + ->onclick('document.adminForm.transition_id.value=' . (int) $transition['value'] . ';' . $cmd); + } + + $childBar->separatorButton('transition-separator'); } if ($canDo->get('core.edit.state')) { + $childBar->publish('articles.publish')->listCheck(true); + + $childBar->unpublish('articles.unpublish')->listCheck(true); + $childBar->standardButton('featured') ->text('JFEATURE') ->task('articles.featured') @@ -216,20 +205,11 @@ protected function addToolbar() ->text('JUNFEATURE') ->task('articles.unfeatured') ->listCheck(true); - } - if ($canDo->get('core.execute.transition')) - { $childBar->archive('articles.archive')->listCheck(true); - } - if ($canDo->get('core.edit.state')) - { $childBar->checkin('articles.checkin')->listCheck(true); - } - if ($canDo->get('core.execute.transition')) - { $childBar->trash('articles.trash')->listCheck(true); } @@ -245,7 +225,7 @@ protected function addToolbar() } } - if ($this->state->get('filter.condition') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete')) + if ($this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete')) { $toolbar->delete('articles.delete') ->text('JTOOLBAR_EMPTY_TRASH') diff --git a/administrator/components/com_content/src/View/Featured/HtmlView.php b/administrator/components/com_content/src/View/Featured/HtmlView.php index b016c220a10b2..b2601d44c1b13 100644 --- a/administrator/components/com_content/src/View/Featured/HtmlView.php +++ b/administrator/components/com_content/src/View/Featured/HtmlView.php @@ -11,6 +11,7 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; @@ -64,6 +65,13 @@ class HtmlView extends BaseHtmlView */ public $activeFilters; + /** + * All transition, which can be executed of one if the items + * + * @var array + */ + protected $transitions = []; + /** * Display the view * @@ -81,6 +89,13 @@ public function display($tpl = null) $this->transitions = $this->get('Transitions'); $this->vote = PluginHelper::isEnabled('content', 'vote'); + 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'))) { @@ -96,51 +111,6 @@ public function display($tpl = null) $this->filterForm->removeField('language', 'filter'); } - $transitions = [ - 'publish' => [], - 'unpublish' => [], - 'archive' => [], - 'trash' => [] - ]; - - foreach ($this->transitions as $transition) - { - switch ($transition['stage_condition']) - { - case ContentComponent::CONDITION_PUBLISHED: - $transitions['publish'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - - case ContentComponent::CONDITION_UNPUBLISHED: - $transitions['unpublish'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - - case ContentComponent::CONDITION_ARCHIVED: - $transitions['archive'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - - case ContentComponent::CONDITION_TRASHED: - $transitions['trash'][$transition['workflow_id']][$transition['from_stage_id']][] = $transition; - break; - } - } - - $this->document->addScriptOptions('articles.transitions', $transitions); - - $articles = []; - - foreach ($this->items as $item) - { - $articles['article-' . (int) $item->id] = Text::sprintf('COM_CONTENT_STAGE_ARTICLE_TITLE', $this->escape($item->title), (int) $item->id); - } - - $this->document->addScriptOptions('articles.items', $articles); - - Text::script('COM_CONTENT_ERROR_CANNOT_PUBLISH'); - Text::script('COM_CONTENT_ERROR_CANNOT_UNPUBLISH'); - Text::script('COM_CONTENT_ERROR_CANNOT_TRASH'); - Text::script('COM_CONTENT_ERROR_CANNOT_ARCHIVE'); - return parent::display($tpl); } @@ -166,7 +136,7 @@ protected function addToolbar() $toolbar->addNew('article.add'); } - if ($canDo->get('core.edit.state') || $canDo->get('core.execute.transition')) + if ($canDo->get('core.edit.state') || count($this->transitions)) { $dropdown = $toolbar->dropdownButton('status-group') ->text('JTOOLBAR_CHANGE_STATUS') @@ -177,38 +147,49 @@ protected function addToolbar() $childBar = $dropdown->getChildToolbar(); - if ($canDo->get('core.execute.transition')) + if (count($this->transitions)) { - $childBar->publish('articles.publish')->listCheck(true); - - $childBar->unpublish('articles.unpublish')->listCheck(true); + $childBar->separatorButton('transition-headline') + ->text('COM_CONTENT_RUN_TRANSITIONS') + ->buttonClass('text-center py-2 h3'); + + $cmd = "Joomla.submitbutton('articles.runTransition');"; + $messages = "{error: [Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST')]}"; + $alert = 'Joomla.renderMessages(' . $messages . ')'; + $cmd = 'if (document.adminForm.boxchecked.value == 0) { ' . $alert . ' } else { ' . $cmd . ' }'; + + foreach ($this->transitions as $transition) + { + $childBar->standardButton('transition') + ->text($transition['text']) + ->buttonClass('transition-' . (int) $transition['value']) + ->icon('fas fa-project-diagram') + ->onclick('document.adminForm.transition_id.value=' . (int) $transition['value'] . ';' . $cmd); + } + + $childBar->separatorButton('transition-separator'); } if ($canDo->get('core.edit.state')) { + $childBar->publish('articles.publish')->listCheck(true); + + $childBar->unpublish('articles.unpublish')->listCheck(true); + $childBar->standardButton('unfeatured') ->text('JUNFEATURE') ->task('articles.unfeatured') ->listCheck(true); - } - if ($canDo->get('core.execute.transition')) - { $childBar->archive('articles.archive')->listCheck(true); - } - if ($canDo->get('core.edit.state')) - { $childBar->checkin('articles.checkin')->listCheck(true); - } - if ($canDo->get('core.execute.transition')) - { $childBar->trash('articles.trash')->listCheck(true); } } - if ($this->state->get('filter.condition') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete')) + if ($this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete')) { $toolbar->delete('articles.delete') ->text('JTOOLBAR_EMPTY_TRASH') diff --git a/administrator/components/com_content/tmpl/articles/default.php b/administrator/components/com_content/tmpl/articles/default.php index 421094d83bda3..4d545e71d1459 100644 --- a/administrator/components/com_content/tmpl/articles/default.php +++ b/administrator/components/com_content/tmpl/articles/default.php @@ -11,6 +11,8 @@ use Joomla\CMS\Button\FeaturedButton; use Joomla\CMS\Button\PublishedButton; +use Joomla\CMS\Button\TransitionButton; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Associations; @@ -21,6 +23,7 @@ use Joomla\CMS\Session\Session; use Joomla\Component\Content\Administrator\Extension\ContentComponent; use Joomla\Component\Content\Administrator\Helper\ContentHelper; +use Joomla\Utilities\ArrayHelper; HTMLHelper::_('behavior.multiselect'); @@ -54,6 +57,12 @@ HTMLHelper::_('draggablelist.draggable'); } +$workflow_enabled = ComponentHelper::getParams('com_content')->get('workflow_enabled'); +$workflow_state = false; +$workflow_featured = false; + +if ($workflow_enabled) : + $js = <<document->addScriptDeclaration($js); -$collection = new \stdClass; +HTMLHelper::_('script', 'com_workflow/admin-items-workflow-buttons.js', ['relative' => true, 'version' => 'auto']); -$collection->publish = []; -$collection->unpublish = []; -$collection->archive = []; -$collection->trash = []; + $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'); -$assoc = Associations::isEnabled(); +endif; -HTMLHelper::_('script', 'com_content/admin-articles-workflow-buttons.js', ['relative' => true, 'version' => 'auto']); +$assoc = Associations::isEnabled(); ?>
@@ -97,7 +104,7 @@ - +
+ + + @@ -169,36 +181,13 @@ $transitions = ContentHelper::filterTransitions($this->transitions, (int) $item->stage_id, (int) $item->workflow_id); - $publish = 0; - $unpublish = 0; - $archive = 0; - $trash = 0; - - foreach ($transitions as $transition) : - switch ($transition['stage_condition']) : - case ContentComponent::CONDITION_PUBLISHED: - ++$publish; - break; - case ContentComponent::CONDITION_UNPUBLISHED: - ++$unpublish; - break; - case ContentComponent::CONDITION_ARCHIVED: - ++$archive; - break; - case ContentComponent::CONDITION_TRASHED: - ++$trash; - break; - endswitch; - endforeach; + $transition_ids = ArrayHelper::getColumn($transitions, 'value'); + $transition_ids = ArrayHelper::toInteger($transition_ids); ?> - + + + -
, , @@ -111,6 +118,11 @@
+ +
id, false, 'cid', 'cb', $item->title); ?> @@ -222,6 +211,22 @@ +
+ $transitions, + 'title' => Text::_($item->stage_title), + 'tip_content' => Text::sprintf('JWORKFLOW', Text::_($item->workflow_title)) + ]; + + echo (new TransitionButton($options)) + ->render(0, $i); + ?> +
+
!$canChange ]; - echo (new FeaturedButton) - ->render((int) $item->featured, $i, $options, $item->featured_up, $item->featured_down); - ?> + if ($workflow_featured) : + $options['disabled'] = true; + endif; + + echo (new FeaturedButton) + ->render((int) $item->featured, $i, $options, $item->featured_up, $item->featured_down); + ?> -
-
- + 'articles.', + 'disabled' => $workflow_enabled || !$canChange + ]; - $options = [ - 'transitions' => $transitions, - 'stage' => Text::_($item->stage_title), - 'id' => (int) $item->id - ]; + if ($workflow_state) : + $options['disabled'] = true; + endif; - echo (new PublishedButton) - ->removeState(0) - ->removeState(1) - ->removeState(2) - ->removeState(-2) - ->addState(ContentComponent::CONDITION_PUBLISHED, '', 'publish', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JPUBLISHED')]) - ->addState(ContentComponent::CONDITION_UNPUBLISHED, '', 'unpublish', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JUNPUBLISHED')]) - ->addState(ContentComponent::CONDITION_ARCHIVED, '', 'archive', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JARCHIVED')]) - ->addState(ContentComponent::CONDITION_TRASHED, '', 'trash', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JTRASHED')]) - ->setLayout('joomla.button.transition-button') - ->render((int) $item->stage_condition, $i, $options, $item->publish_up, $item->publish_down); - ?> -
-
+ echo (new PublishedButton)->render((int) $item->state, $i, $options, $item->publish_up, $item->publish_down); + ?>
@@ -407,15 +404,10 @@ $this->loadTemplate('batch_body') ); ?> - Text::_('JTOOLBAR_CHANGE_STATUS'), - 'footer' => $this->loadTemplate('stage_footer'), - ), - $this->loadTemplate('stage_body') - ); ?> + + + + 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 cb2f379170592..9f88c07f16963 100644 --- a/administrator/components/com_content/tmpl/articles/default_batch_body.php +++ b/administrator/components/com_content/tmpl/articles/default_batch_body.php @@ -10,6 +10,9 @@ use Joomla\CMS\Factory; use Joomla\CMS\Layout\LayoutHelper; +use Joomla\CMS\Component\ComponentHelper; + +$params = ComponentHelper::getParams('com_content'); $published = $this->state->get('filter.published'); @@ -42,7 +45,7 @@
- authorise('core.admin', 'com_content')) : ?> + authorise('core.admin', 'com_content') && $params->get('workflow_enabled')) : ?>
'com_content']); ?> diff --git a/administrator/components/com_content/tmpl/articles/default_stage_body.php b/administrator/components/com_content/tmpl/articles/default_stage_body.php deleted file mode 100644 index 5c45de087c832..0000000000000 --- a/administrator/components/com_content/tmpl/articles/default_stage_body.php +++ /dev/null @@ -1,23 +0,0 @@ - - -
-
-
-

-
-
-
-
-
diff --git a/administrator/components/com_content/tmpl/articles/default_stage_footer.php b/administrator/components/com_content/tmpl/articles/default_stage_footer.php deleted file mode 100644 index c18ec8cfbfe67..0000000000000 --- a/administrator/components/com_content/tmpl/articles/default_stage_footer.php +++ /dev/null @@ -1,21 +0,0 @@ - 'auto', 'relative' => true]); -?> - - diff --git a/administrator/components/com_content/tmpl/featured/default.php b/administrator/components/com_content/tmpl/featured/default.php index 77fcb913eeb31..66805bade001f 100644 --- a/administrator/components/com_content/tmpl/featured/default.php +++ b/administrator/components/com_content/tmpl/featured/default.php @@ -11,6 +11,8 @@ use Joomla\CMS\Button\FeaturedButton; use Joomla\CMS\Button\PublishedButton; +use Joomla\CMS\Button\TransitionButton; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Associations; @@ -21,6 +23,7 @@ use Joomla\CMS\Session\Session; use Joomla\Component\Content\Administrator\Extension\ContentComponent; use Joomla\Component\Content\Administrator\Helper\ContentHelper; +use Joomla\Utilities\ArrayHelper; HTMLHelper::_('behavior.multiselect'); @@ -50,6 +53,10 @@ HTMLHelper::_('draggablelist.draggable'); } +$workflow_enabled = ComponentHelper::getParams('com_content')->get('workflow_enabled'); + +if ($workflow_enabled) : + $js = <<document->addScriptDeclaration($js); -$assoc = Associations::isEnabled(); +HTMLHelper::_('script', 'com_workflow/admin-items-workflow-buttons.js', ['relative' => true, 'version' => 'auto']); -HTMLHelper::_('script', 'com_content/admin-articles-workflow-buttons.js', ['relative' => true, 'version' => 'auto']); +endif; + +$assoc = Associations::isEnabled(); ?> @@ -87,7 +96,7 @@
- +
+ + + @@ -162,36 +176,12 @@ $transitions = ContentHelper::filterTransitions($this->transitions, (int) $item->stage_id, (int) $item->workflow_id); - $publish = 0; - $unpublish = 0; - $archive = 0; - $trash = 0; - - foreach ($transitions as $transition) : - switch ($transition['stage_condition']) : - case ContentComponent::CONDITION_PUBLISHED: - ++$publish; - break; - case ContentComponent::CONDITION_UNPUBLISHED: - ++$unpublish; - break; - case ContentComponent::CONDITION_ARCHIVED: - ++$archive; - break; - case ContentComponent::CONDITION_TRASHED: - ++$trash; - break; - endswitch; - endforeach; + $transition_ids = ArrayHelper::getColumn($transitions, 'value'); + $transition_ids = ArrayHelper::toInteger($transition_ids); ?> + + + - - - - @@ -116,7 +111,7 @@ diff --git a/administrator/components/com_workflow/tmpl/transition/edit.php b/administrator/components/com_workflow/tmpl/transition/edit.php index 215b26b435cd0..0682d7600ed52 100644 --- a/administrator/components/com_workflow/tmpl/transition/edit.php +++ b/administrator/components/com_workflow/tmpl/transition/edit.php @@ -17,6 +17,9 @@ HTMLHelper::_('behavior.formvalidator'); HTMLHelper::_('behavior.keepalive'); +$this->ignore_fieldsets = ['params', 'transition', 'permissions']; +$this->useCoreUI = true; + // In case of modal $isModal = $this->input->get('layout') === 'modal'; $layout = $isModal ? 'modal' : 'edit'; @@ -30,7 +33,7 @@ 'details')); ?> - +
@@ -51,6 +54,8 @@
+ +
diff --git a/administrator/components/com_workflow/tmpl/transitions/default.php b/administrator/components/com_workflow/tmpl/transitions/default.php index d8e14992aa3f7..fa5f26b053ec2 100644 --- a/administrator/components/com_workflow/tmpl/transitions/default.php +++ b/administrator/components/com_workflow/tmpl/transitions/default.php @@ -27,8 +27,6 @@ $saveOrder = ($listOrder == 't.ordering'); -$isCore = $this->workflow->core; - if ($saveOrder) { $saveOrderingUrl = 'index.php?option=com_workflow&task=transitions.saveOrderAjax&workflow_id=' . (int) $this->workflowID . '&extension=' . $this->escape($this->extension) . '&' . Session::getFormToken() . '=1'; @@ -68,16 +66,16 @@
- - - - - items as $i => $article) : ?> - items[$i]->stage_condition == ContentComponent::CONDITION_UNPUBLISHED) : ?> + items[$i]->state == ContentComponent::CONDITION_UNPUBLISHED) : ?> @@ -225,7 +225,7 @@ - stage_condition == ContentComponent::CONDITION_UNPUBLISHED) : ?> + state == ContentComponent::CONDITION_UNPUBLISHED) : ?> diff --git a/components/com_content/tmpl/form/edit.php b/components/com_content/tmpl/form/edit.php index ae2da60cbe4a1..3689b23b12d01 100644 --- a/components/com_content/tmpl/form/edit.php +++ b/components/com_content/tmpl/form/edit.php @@ -99,6 +99,9 @@ tab_name, 'publishing', Text::_('COM_CONTENT_PUBLISHING')); ?> + + form->renderField('transition'); ?> + form->renderField('state'); ?> form->renderField('catid'); ?> form->renderField('tags'); ?> form->renderField('note'); ?> @@ -108,9 +111,6 @@ get('show_publishing_options', 1) == 1) : ?> form->renderField('created_by_alias'); ?> - item->id > 0) : ?> - form->renderField('transition'); ?> - item->params->get('access-change')) : ?> form->renderField('featured'); ?> get('show_publishing_options', 1) == 1) : ?> diff --git a/composer.lock b/composer.lock index 9aebbc588d361..7f129ef1713a3 100644 --- a/composer.lock +++ b/composer.lock @@ -5759,12 +5759,12 @@ "source": { "type": "git", "url": "https://github.com/joomla-projects/joomla-browser.git", - "reference": "45a40262a139b599193f2aee0faefb29874d200a" + "reference": "0323d0d0741b0c9096bec5a015fc39444dd28cf5" }, "dist": { "type": "zip", - "url": "https://github.com/gitapi/repos/joomla-projects/joomla-browser/zipball/45a40262a139b599193f2aee0faefb29874d200a", - "reference": "45a40262a139b599193f2aee0faefb29874d200a", + "url": "https://github.com/gitapi/repos/joomla-projects/joomla-browser/zipball/0323d0d0741b0c9096bec5a015fc39444dd28cf5", + "reference": "0323d0d0741b0c9096bec5a015fc39444dd28cf5", "shasum": "" }, "require": { @@ -5802,7 +5802,7 @@ "acceptance testing", "joomla" ], - "time": "2020-04-12T10:39:07+00:00" + "time": "2020-05-01T19:53:33+00:00" }, { "name": "joomla/cms-coding-standards", diff --git a/installation/sql/mysql/base.sql b/installation/sql/mysql/base.sql index c1d69f4b5231f..28a2ca49b3207 100644 --- a/installation/sql/mysql/base.sql +++ b/installation/sql/mysql/base.sql @@ -75,15 +75,15 @@ INSERT INTO `#__assets` (`id`, `parent_id`, `lft`, `rgt`, `level`, `name`, `titl (53, 18, 90, 91, 2, 'com_modules.module.86', 'Joomla Version', '{}'), (54, 16, 54, 55, 2, 'com_menus.menu.1', 'Main Menu', '{}'), (55, 18, 92, 93, 2, 'com_modules.module.87', 'Sample Data', '{}'), -(56, 8, 20, 37, 2, 'com_content.workflow.1', 'COM_WORKFLOW_DEFAULT_WORKFLOW', '{}'), -(57, 56, 21, 22, 3, 'com_content.state.1', 'Unpublished', '{}'), -(58, 56, 23, 24, 3, 'com_content.state.2', 'Published', '{}'), -(59, 56, 25, 26, 3, 'com_content.state.3', 'Trashed', '{}'), -(60, 56, 27, 28, 3, 'com_content.state.4', 'Archived', '{}'), -(61, 56, 29, 30, 3, 'com_content.transition.1', 'Publish', '{}'), -(62, 56, 31, 32, 3, 'com_content.transition.2', 'Unpublish', '{}'), -(63, 56, 33, 34, 3, 'com_content.transition.3', 'Archive', '{}'), -(64, 56, 35, 36, 3, 'com_content.transition.4', 'Trash', '{}'), +(56, 8, 20, 37, 2, 'com_content.workflow.1', 'COM_WORKFLOW_BASIC_WORKFLOW', '{}'), +(57, 56, 21, 22, 3, 'com_content.state.1', 'COM_WORKFLOW_BASIC_STAGE', '{}'), +(58, 56, 23, 24, 3, 'com_content.transition.1', 'Publish', '{}'), +(59, 56, 25, 26, 3, 'com_content.transition.2', 'Unpublish', '{}'), +(60, 56, 27, 28, 3, 'com_content.transition.3', 'Archive', '{}'), +(61, 56, 29, 30, 3, 'com_content.transition.4', 'Trash', '{}'), +(62, 56, 31, 32, 3, 'com_content.transition.5', 'Feature', '{}'), +(63, 56, 33, 34, 3, 'com_content.transition.6', 'Unfeature', '{}'), +(64, 56, 35, 36, 3, 'com_content.transition.7', 'Publish & Feature', '{}'), (65, 1, 129, 130, 1, 'com_privacy', 'com_privacy', '{}'), (66, 1, 131, 132, 1, 'com_actionlogs', 'com_actionlogs', '{}'), (67, 18, 74, 75, 2, 'com_modules.module.88', 'Latest Actions', '{}'), @@ -357,7 +357,10 @@ INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, (0, 'plg_media-action_resize', 'plugin', 'resize', 'media-action', 0, 1, 1, 0, 1, '', '{}', 0, 0), (0, 'plg_media-action_rotate', 'plugin', 'rotate', 'media-action', 0, 1, 1, 0, 1, '', '{}', 0, 0), (0, 'plg_system_accessibility', 'plugin', 'accessibility', 'system', 0, 0, 1, 0, 1, '', '{}', 0, 0), -(0, 'plg_system_webauthn', 'plugin', 'webauthn', 'system', 0, 1, 1, 0, 1, '', '{}', 0, 0); +(0, 'plg_system_webauthn', 'plugin', 'webauthn', 'system', 0, 1, 1, 0, 1, '', '{}', 0, 0), +(0, 'plg_workflow_publishing', 'plugin', 'publishing', 'workflow', 0, 1, 1, 0, 1, '', '{}', 0, 0), +(0, 'plg_workflow_featuring', 'plugin', 'featuring', 'workflow', 0, 1, 1, 0, 1, '', '{}', 0, 0), +(0, 'plg_workflow_notification', 'plugin', 'notification', 'workflow', 0, 1, 1, 0, 1, '', '{}', 0, 0); -- Templates INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `ordering`, `state`) VALUES @@ -1043,7 +1046,6 @@ CREATE TABLE IF NOT EXISTS `#__workflows` ( `description` text NOT NULL, `extension` varchar(50) NOT NULL, `default` tinyint(1) NOT NULL DEFAULT 0, - `core` tinyint(1) NOT NULL DEFAULT 0, `ordering` int(11) NOT NULL DEFAULT 0, `created` datetime NOT NULL, `created_by` int(10) NOT NULL DEFAULT 0, @@ -1067,8 +1069,8 @@ CREATE TABLE IF NOT EXISTS `#__workflows` ( -- Dumping data for table `#__workflows` -- -INSERT INTO `#__workflows` (`id`, `asset_id`, `published`, `title`, `description`, `extension`, `default`, `core`, `ordering`, `created`, `created_by`, `modified`, `modified_by`) VALUES -(1, 56, 1, 'COM_WORKFLOW_DEFAULT_WORKFLOW', '', 'com_content', 1, 1, 1, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 42); +INSERT INTO `#__workflows` (`id`, `asset_id`, `published`, `title`, `description`, `extension`, `default`, `ordering`, `created`, `created_by`, `modified`, `modified_by`) VALUES +(1, 56, 1, 'COM_WORKFLOW_BASIC_WORKFLOW', '', 'com_content.article', 1, 1, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 42); -- -------------------------------------------------------- @@ -1101,7 +1103,6 @@ CREATE TABLE IF NOT EXISTS `#__workflow_stages` ( `published` tinyint(1) NOT NULL DEFAULT 0, `title` varchar(255) NOT NULL, `description` text NOT NULL, - `condition` int(10) DEFAULT 0, `default` tinyint(1) NOT NULL DEFAULT 0, `checked_out_time` datetime, `checked_out` int(10) unsigned, @@ -1117,11 +1118,8 @@ CREATE TABLE IF NOT EXISTS `#__workflow_stages` ( -- Dumping data for table `#__workflow_stages` -- -INSERT INTO `#__workflow_stages` (`id`, `asset_id`, `ordering`, `workflow_id`, `published`, `title`, `description`, `condition`, `default`) VALUES -(1, 57, 1, 1, 1, 'JUNPUBLISHED', '', 0, 1), -(2, 58, 2, 1, 1, 'JPUBLISHED', '', 1, 0), -(3, 59, 3, 1, 1, 'JTRASHED', '', -2, 0), -(4, 60, 4, 1, 1, 'JARCHIVED', '', 2, 0); +INSERT INTO `#__workflow_stages` (`id`, `asset_id`, `ordering`, `workflow_id`, `published`, `title`, `description`, `default`) VALUES +(1, 0, 1, 1, 1, 'COM_WORKFLOW_BASIC_STAGE', '', 1); -- -------------------------------------------------------- @@ -1139,6 +1137,7 @@ CREATE TABLE IF NOT EXISTS `#__workflow_transitions` ( `description` text NOT NULL, `from_stage_id` int(10) NOT NULL, `to_stage_id` int(10) NOT NULL, + `options` text NOT NULL, `checked_out_time` datetime, `checked_out` int(10) unsigned, PRIMARY KEY (`id`), @@ -1154,8 +1153,11 @@ CREATE TABLE IF NOT EXISTS `#__workflow_transitions` ( -- Dumping data for table `#__workflow_transitions` -- -INSERT INTO `#__workflow_transitions` (`id`, `asset_id`, `published`, `ordering`, `workflow_id`, `title`, `description`, `from_stage_id`, `to_stage_id`) VALUES -(1, 61, 1, 1, 1, 'Unpublish', '', -1, 1), -(2, 62, 1, 2, 1, 'Publish', '', -1, 2), -(3, 63, 1, 3, 1, 'Trash', '', -1, 3), -(4, 64, 1, 4, 1, 'Archive', '', -1, 4); +INSERT INTO `#__workflow_transitions` (`id`, `asset_id`, `published`, `ordering`, `workflow_id`, `title`, `description`, `from_stage_id`, `to_stage_id`, `options`) VALUES +(1, 58, 1, 1, 1, 'Unpublish', '', -1, 1, '{"publishing":"0"}'), +(2, 59, 1, 2, 1, 'Publish', '', -1, 1, '{"publishing":"1"}'), +(3, 60, 1, 3, 1, 'Trash', '', -1, 1, '{"publishing":"-2"}'), +(4, 61, 1, 4, 1, 'Archive', '', -1, 1, '{"publishing":"2"}'), +(5, 62, 1, 5, 1, 'Feature', '', -1, 1, '{"featuring":"1"}'), +(6, 63, 1, 6, 1, 'Unfeature', '', -1, 1, '{"featuring":"0"}'), +(7, 64, 1, 7, 1, 'Publish & Feature', '', -1, 1, '{"publishing":"1","featuring":"1"}'); diff --git a/installation/sql/postgresql/base.sql b/installation/sql/postgresql/base.sql index 623be320120f5..d565eb6b87d35 100644 --- a/installation/sql/postgresql/base.sql +++ b/installation/sql/postgresql/base.sql @@ -81,15 +81,15 @@ INSERT INTO "#__assets" ("id", "parent_id", "lft", "rgt", "level", "name", "titl (53, 18, 90, 91, 2, 'com_modules.module.86', 'Joomla Version', '{}'), (54, 16, 54, 55, 2, 'com_menus.menu.1', 'Main Menu', '{}'), (55, 18, 92, 93, 2, 'com_modules.module.87', 'Sample Data', '{}'), -(56, 8, 20, 37, 2, 'com_content.workflow.1', 'COM_WORKFLOW_DEFAULT_WORKFLOW', '{}'), -(57, 56, 21, 22, 3, 'com_content.state.1', 'Unpublished', '{}'), -(58, 56, 23, 24, 3, 'com_content.state.2', 'Published', '{}'), -(59, 56, 25, 26, 3, 'com_content.state.3', 'Trashed', '{}'), -(60, 56, 27, 28, 3, 'com_content.state.4', 'Archived', '{}'), -(61, 56, 29, 30, 3, 'com_content.transition.1', 'Publish', '{}'), -(62, 56, 31, 32, 3, 'com_content.transition.2', 'Unpublish', '{}'), -(63, 56, 33, 34, 3, 'com_content.transition.3', 'Archive', '{}'), -(64, 56, 35, 36, 3, 'com_content.transition.4', 'Trash', '{}'), +(56, 8, 20, 37, 2, 'com_content.workflow.1', 'COM_WORKFLOW_BASIC_WORKFLOW', '{}'), +(57, 56, 21, 22, 3, 'com_content.state.1', 'COM_WORKFLOW_BASIC_STAGE', '{}'), +(58, 56, 23, 24, 3, 'com_content.transition.1', 'Publish', '{}'), +(59, 56, 25, 26, 3, 'com_content.transition.2', 'Unpublish', '{}'), +(60, 56, 27, 28, 3, 'com_content.transition.3', 'Archive', '{}'), +(61, 56, 29, 30, 3, 'com_content.transition.4', 'Trash', '{}'), +(62, 56, 31, 32, 3, 'com_content.transition.5', 'Feature', '{}'), +(63, 56, 33, 34, 3, 'com_content.transition.6', 'Unfeature', '{}'), +(64, 56, 35, 36, 3, 'com_content.transition.7', 'Publish & Feature', '{}'), (65, 1, 129, 130, 1, 'com_privacy', 'com_privacy', '{}'), (66, 1, 131, 132, 1, 'com_actionlogs', 'com_actionlogs', '{}'), (67, 18, 74, 75, 2, 'com_modules.module.88', 'Latest Actions', '{}'), @@ -363,7 +363,10 @@ INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", (0, 'plg_media-action_resize', 'plugin', 'resize', 'media-action', 0, 1, 1, 0, 1, '', '{}', 0, 0), (0, 'plg_media-action_rotate', 'plugin', 'rotate', 'media-action', 0, 1, 1, 0, 1, '', '{}', 0, 0), (0, 'plg_system_accessibility', 'plugin', 'accessibility', 'system', 0, 0, 1, 0, 1, '', '{}', 0, 0), -(0, 'plg_system_webauthn', 'plugin', 'webauthn', 'system', 0, 1, 1, 0, 1, '', '{}', 0, 0); +(0, 'plg_system_webauthn', 'plugin', 'webauthn', 'system', 0, 1, 1, 0, 1, '', '{}', 0, 0), +(0, 'plg_workflow_publishing', 'plugin', 'publishing', 'workflow', 0, 1, 1, 0, 1, '', '{}', 0, 0), +(0, 'plg_workflow_featuring', 'plugin', 'featuring', 'workflow', 0, 1, 1, 0, 1, '', '{}', 0, 0), +(0, 'plg_workflow_notification', 'plugin', 'notification', 'workflow', 0, 1, 1, 0, 1, '', '{}', 0, 0); -- Templates INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "ordering", "state") VALUES @@ -1069,7 +1072,6 @@ CREATE TABLE IF NOT EXISTS "#__workflows" ( "description" text NOT NULL, "extension" varchar(50) NOT NULL, "default" smallint NOT NULL DEFAULT 0, - "core" smallint NOT NULL DEFAULT 0, "ordering" bigint NOT NULL DEFAULT 0, "created" timestamp without time zone NOT NULL, "created_by" bigint DEFAULT 0 NOT NULL, @@ -1090,8 +1092,8 @@ CREATE INDEX "#__workflows_idx_modified" ON "#__workflows" ("modified"); CREATE INDEX "#__workflows_idx_modified_by" ON "#__workflows" ("modified_by"); CREATE INDEX "#__workflows_idx_checked_out" ON "#__workflows" ("checked_out"); -INSERT INTO "#__workflows" ("id", "asset_id", "published", "title", "description", "extension", "default", "core", "ordering", "created", "created_by", "modified", "modified_by") VALUES -(1, 56, 1, 'COM_WORKFLOW_DEFAULT_WORKFLOW', '', 'com_content', 1, 1, 1, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 42); +INSERT INTO "#__workflows" ("id", "asset_id", "published", "title", "description", "extension", "default", "ordering", "created", "created_by", "modified", "modified_by") VALUES +(1, 56, 1, 'COM_WORKFLOW_BASIC_WORKFLOW', '', 'com_content.article', 1, 1, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 42); SELECT setval('#__workflows_id_seq', 2, false); @@ -1125,7 +1127,6 @@ CREATE TABLE IF NOT EXISTS "#__workflow_stages" ( "published" smallint NOT NULL DEFAULT 0, "title" varchar(255) NOT NULL, "description" text NOT NULL, - "condition" bigint DEFAULT 0 NOT NULL, "default" smallint NOT NULL DEFAULT 0, "checked_out_time" timestamp without time zone, "checked_out" integer, @@ -1141,13 +1142,10 @@ CREATE INDEX "#__workflow_stages_idx_checked_out" ON "#__workflow_stages" ("chec -- Dumping data for table `#__workflow_stages` -- -INSERT INTO "#__workflow_stages" ("id", "asset_id", "ordering", "workflow_id", "published", "title", "description", "condition", "default") VALUES -(1, 57, 1, 1, 1, 'JUNPUBLISHED', '', 0, 1), -(2, 58, 2, 1, 1, 'JPUBLISHED', '', 1, 0), -(3, 59, 3, 1, 1, 'JTRASHED', '', -2, 0), -(4, 60, 4, 1, 1, 'JARCHIVED', '', 2, 0); +INSERT INTO "#__workflow_stages" ("id", "asset_id", "ordering", "workflow_id", "published", "title", "description", "default") VALUES +(1, 57, 1, 1, 1, 'COM_WORKFLOW_BASIC_STAGE', '', 1); -SELECT setval('#__workflow_stages_id_seq', 5, false); +SELECT setval('#__workflow_stages_id_seq', 2, false); -- -- Table structure for table `#__workflow_transitions` @@ -1163,6 +1161,7 @@ CREATE TABLE IF NOT EXISTS "#__workflow_transitions" ( "description" text NOT NULL, "from_stage_id" bigint DEFAULT 0 NOT NULL, "to_stage_id" bigint DEFAULT 0 NOT NULL, + "options" text NOT NULL, "checked_out_time" timestamp without time zone, "checked_out" integer, PRIMARY KEY ("id") @@ -1174,10 +1173,13 @@ CREATE INDEX "#__workflow_transitions_idx_to_stage_id" ON "#__workflow_transitio CREATE INDEX "#__workflow_transitions_idx_workflow_id" ON "#__workflow_transitions" ("workflow_id"); CREATE INDEX "#__workflow_transitions_idx_checked_out" ON "#__workflow_transitions" ("checked_out"); -INSERT INTO "#__workflow_transitions" ("id", "asset_id", "published", "ordering", "workflow_id", "title", "description", "from_stage_id", "to_stage_id") VALUES -(1, 61, 1, 1, 1, 'Unpublish', '', -1, 1), -(2, 62, 1, 2, 1, 'Publish', '', -1, 2), -(3, 63, 1, 3, 1, 'Trash', '', -1, 3), -(4, 64, 1, 4, 1, 'Archive', '', -1, 4); +INSERT INTO "#__workflow_transitions" ("id", "asset_id", "published", "ordering", "workflow_id", "title", "description", "from_stage_id", "to_stage_id", "options") VALUES +(1, 58, 1, 1, 1, 'Unpublish', '', -1, 1, '{"publishing":"0"}'), +(2, 59, 1, 2, 1, 'Publish', '', -1, 1, '{"publishing":"1"}'), +(3, 60, 1, 3, 1, 'Trash', '', -1, 1, '{"publishing":"-2"}'), +(4, 61, 1, 4, 1, 'Archive', '', -1, 1, '{"publishing":"2"}'), +(5, 62, 1, 5, 1, 'Feature', '', -1, 1, '{"featuring":"1"}'), +(6, 63, 1, 6, 1, 'Unfeature', '', -1, 1, '{"featuring":"0"}'), +(7, 64, 1, 7, 1, 'Publish & Feature', '', -1, 1, '{"publishing":"1","featuring":"1"}'); -SELECT setval('#__workflow_transitions_id_seq', 5, false); +SELECT setval('#__workflow_transitions_id_seq', 7, false); diff --git a/layouts/joomla/button/transition-button.php b/layouts/joomla/button/transition-button.php index dc9d63c8948a5..dabad81bcdcb0 100644 --- a/layouts/joomla/button/transition-button.php +++ b/layouts/joomla/button/transition-button.php @@ -8,6 +8,7 @@ */ use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; HTMLHelper::_('bootstrap.popover'); @@ -23,50 +24,62 @@ $only_icon = empty($options['transitions']); $disabled = !empty($options['disabled']); $tip = !empty($options['tip']); -$id = (int) $options['id']; $tipTitle = $options['tip_title']; +$tipContent = $options['tip_content']; $checkboxName = $options['checkbox_name']; ?> - - - + + > + -
escape($options['stage']); ?>
- - - href="javascript://" +
+ + + - +
+ +
+
+ + + + +
+ + + escape($options['stage'])), + HTMLHelper::_('select.option', '', $this->escape($options['title'])), HTMLHelper::_('select.option', '-1', '--------', ['disable' => true]) ]; $transitions = array_merge($default, $options['transitions']); $attribs = [ - 'id' => 'transition-select_' . (int) $id, + 'id' => 'transition-select_' . (int) $row ?? '', 'list.attr' => [ 'class' => 'custom-select custom-select-sm w-auto', - 'onchange' => "Joomla.listItemTask('" . $checkboxName . $this->escape($row ?? '') . "', 'articles.runTransition')"] + 'onchange' => "this.form.transition_id.value=this.value;Joomla.listItemTask('" . $checkboxName . $this->escape($row ?? '') . "', 'articles.runTransition')"] ]; - echo HTMLHelper::_('select.genericlist', $transitions, 'transition_' . (int) $id, $attribs); + echo HTMLHelper::_('select.genericlist', $transitions, '', $attribs); ?>
diff --git a/layouts/joomla/edit/global.php b/layouts/joomla/edit/global.php index f2310e954a3c0..4c83001e8b942 100644 --- a/layouts/joomla/edit/global.php +++ b/layouts/joomla/edit/global.php @@ -33,8 +33,6 @@ array('published', 'state', 'enabled'), array('category', 'catid'), 'featured', - 'featured_up', - 'featured_down', 'sticky', 'access', 'language', diff --git a/layouts/joomla/edit/publishingdata.php b/layouts/joomla/edit/publishingdata.php index a21d03de73276..6dc2304850f14 100644 --- a/layouts/joomla/edit/publishingdata.php +++ b/layouts/joomla/edit/publishingdata.php @@ -17,6 +17,8 @@ $fields = $displayData->get('fields') ?: array( 'publish_up', 'publish_down', + 'featured_up', + 'featured_down', array('created', 'created_time'), array('created_by', 'created_user_id'), 'created_by_alias', diff --git a/layouts/joomla/toolbar/separator.php b/layouts/joomla/toolbar/separator.php index a170763d028b7..8de34b6f93f14 100644 --- a/layouts/joomla/toolbar/separator.php +++ b/layouts/joomla/toolbar/separator.php @@ -24,10 +24,10 @@ - - + diff --git a/libraries/src/Button/TransitionButton.php b/libraries/src/Button/TransitionButton.php new file mode 100644 index 0000000000000..b8279c0d3d45f --- /dev/null +++ b/libraries/src/Button/TransitionButton.php @@ -0,0 +1,67 @@ +unknownState['icon'] = 'shuffle'; + $this->unknownState['title'] = $options['title'] ?? Text::_('JLIB_HTML_UNKNOWN_STATE'); + $this->unknownState['tip_content'] = $options['tip_content'] ?? $this->unknownState['title']; + } + + /** + * Render action button by item value. + * + * @param integer|null $value Current value of this item. + * @param integer|null $row The row number of this item. + * @param array $options The options to override group options. + * + * @return string Rendered HTML. + * + * @since 4.0.0 + */ + public function render(?int $value = null, ?int $row = null, array $options = []): string + { + $default = $this->unknownState; + + $options['tip_title'] = $options['tip_title'] ?? ($options['title'] ?? $default['title']); + + return parent::render($value, $row, $options); + } +} diff --git a/libraries/src/Event/View/DisplayEvent.php b/libraries/src/Event/View/DisplayEvent.php new file mode 100644 index 0000000000000..325db2938d924 --- /dev/null +++ b/libraries/src/Event/View/DisplayEvent.php @@ -0,0 +1,71 @@ +name} is required but has not been provided"); + } + + if (!($arguments['subject'] instanceof ViewInterface)) + { + throw new BadMethodCallException("Argument 'subject' of event {$this->name} is not of type 'ViewInterface'"); + } + + if (!isset($arguments['extension'])) + { + throw new BadMethodCallException("Argument 'extension' of event {$this->name} is required but has not been provided"); + } + + if (!isset($arguments['extension']) || !is_string($arguments['extension'])) + { + throw new BadMethodCallException("Argument 'extension' of event {$this->name} is not of type 'string'"); + } + + if (strpos($arguments['extension'], '.') === false) + { + throw new BadMethodCallException("Argument 'extension' of event {$this->name} has wrong format. Valid format: 'component.section'"); + } + + if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments)) + { + $parts = explode('.', $arguments['extension']); + + $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0]; + $arguments['section'] = $arguments['section'] ?? $parts[1]; + } + + parent::__construct($name, $arguments); + } +} diff --git a/libraries/src/Event/Workflow/AbstractEvent.php b/libraries/src/Event/Workflow/AbstractEvent.php new file mode 100644 index 0000000000000..b0e92c06576e8 --- /dev/null +++ b/libraries/src/Event/Workflow/AbstractEvent.php @@ -0,0 +1,61 @@ +name} is required but has not been provided"); + } + + if (!\array_key_exists('extension', $arguments)) + { + throw new BadMethodCallException("Argument 'extension' of event {$this->name} is required but has not been provided"); + } + + if (strpos($arguments['extension'], '.') === false) + { + throw new BadMethodCallException("Argument 'extension' of event {$this->name} has wrong format. Valid format: 'component.section'"); + } + + if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments)) + { + $parts = explode('.', $arguments['extension']); + + $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0]; + $arguments['section'] = $arguments['section'] ?? $parts[1]; + } + + parent::__construct($name, $arguments); + } +} diff --git a/libraries/src/Event/Workflow/WorkflowFunctionalityUsedEvent.php b/libraries/src/Event/Workflow/WorkflowFunctionalityUsedEvent.php new file mode 100644 index 0000000000000..afb5938681b09 --- /dev/null +++ b/libraries/src/Event/Workflow/WorkflowFunctionalityUsedEvent.php @@ -0,0 +1,59 @@ +arguments['used'] = $value; + + if ($value === true) + { + $this->stopPropagation(); + } + } +} diff --git a/libraries/src/Event/Workflow/WorkflowTransitionEvent.php b/libraries/src/Event/Workflow/WorkflowTransitionEvent.php new file mode 100644 index 0000000000000..58c68150abd4f --- /dev/null +++ b/libraries/src/Event/Workflow/WorkflowTransitionEvent.php @@ -0,0 +1,61 @@ +arguments['stopTransition'] = $value; + + if ($value === true) + { + $this->stopPropagation(); + } + } + + +} diff --git a/libraries/src/Form/Field/TransitionField.php b/libraries/src/Form/Field/TransitionField.php index 5e0687864b6b2..4cfa8a93a0cb1 100644 --- a/libraries/src/Form/Field/TransitionField.php +++ b/libraries/src/Form/Field/TransitionField.php @@ -112,7 +112,6 @@ protected function getOptions() [ $db->quoteName('t.id', 'value'), $db->quoteName('t.title', 'text'), - $db->quoteName('s.condition'), ] ) ->from( @@ -126,19 +125,20 @@ protected function getOptions() ->where( [ $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id'), - $db->quoteName('t.to_stage_id') . ' != :stage1', $db->quoteName('s.workflow_id') . ' = ' . $db->quoteName('s2.workflow_id'), - $db->quoteName('s2.id') . ' = :stage2', + $db->quoteName('s.workflow_id') . ' = ' . $db->quoteName('t.workflow_id'), + $db->quoteName('s2.id') . ' = :stage1', $db->quoteName('t.published') . ' = 1', $db->quoteName('s.published') . ' = 1', ] ) ->bind(':stage1', $workflowStage, ParameterType::INTEGER) - ->bind(':stage2', $workflowStage, ParameterType::INTEGER) ->order($db->quoteName('t.ordering')); $items = $db->setQuery($query)->loadObjectList(); + Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR); + if (\count($items)) { $user = Factory::getUser(); @@ -153,17 +153,6 @@ function ($item) use ($user, $extension) // Sort by transition name $items = ArrayHelper::sortObjects($items, 'value', 1, true, true); - - Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR); - - $workflow = new Workflow(['extension' => $this->extension]); - - foreach ($items as $item) - { - $conditionName = $workflow->getConditionName((int) $item->condition); - - $item->text .= ' [' . Text::_($conditionName) . ']'; - } } // Get workflow stage title diff --git a/libraries/src/Form/Field/WorkflowComponentSectionsField.php b/libraries/src/Form/Field/WorkflowComponentSectionsField.php new file mode 100644 index 0000000000000..00c755cae805d --- /dev/null +++ b/libraries/src/Form/Field/WorkflowComponentSectionsField.php @@ -0,0 +1,69 @@ +value, 0, 4) !== 'com_') + { + continue; + } + + $component = $app->bootComponent($item->value); + + if (!($component instanceof WorkflowServiceInterface)) + { + continue; + } + + foreach ($component->getWorkflowContexts() as $extension => $text) + { + $options[] = HTMLHelper::_('select.option', $extension, Text::sprintf('JWORKFLOW_FIELD_COMPONENT_SECTIONS_TEXT', $item->text, $text)); + } + } + + return $options; + } +} diff --git a/libraries/src/Helper/ContentHelper.php b/libraries/src/Helper/ContentHelper.php index 8a11123651dec..566f1a265d408 100644 --- a/libraries/src/Helper/ContentHelper.php +++ b/libraries/src/Helper/ContentHelper.php @@ -66,8 +66,6 @@ public static function countRelations(&$items, $config) '2' => 'count_archived', ); - $usesWorkflows = (isset($config->uses_workflows) && $config->uses_workflows === true); - // Index category objects by their ID $records = array(); @@ -87,7 +85,7 @@ public static function countRelations(&$items, $config) // Table alias for related data table below will be 'c', and state / condition column is inside related data table $related_tbl = '#__' . $config->related_tbl; - $state_col = ($usesWorkflows ? 's.' : 'c.') . $config->state_col; + $state_col = 'c.' . $config->state_col; // Supported cases switch ($config->relation_type) @@ -117,24 +115,6 @@ public static function countRelations(&$items, $config) return $items; } - if ($usesWorkflows) - { - $query->from( - [ - $db->quoteName('#__workflow_stages', 's'), - $db->quoteName('#__workflow_associations', 'a'), - ] - ) - ->where( - [ - $db->quoteName('s.id') . ' = ' . $db->quoteName('a.stage_id'), - $db->quoteName('a.extension') . ' = :component', - $db->quoteName('a.item_id') . ' = ' . $db->quoteName('c.id'), - ] - ) - ->bind(':component', $config->workflows_component); - } - /** * Get relation counts for all category objects with single query * NOTE: 'state IN', allows counting specific states / conditions only, also prevents warnings with custom states / conditions, do not remove diff --git a/libraries/src/MVC/Controller/AdminController.php b/libraries/src/MVC/Controller/AdminController.php index 9fca6fa55f248..724841bdac40a 100644 --- a/libraries/src/MVC/Controller/AdminController.php +++ b/libraries/src/MVC/Controller/AdminController.php @@ -16,6 +16,7 @@ use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\BaseDatabaseModel; +use Joomla\CMS\MVC\Model\WorkflowModelInterface; use Joomla\CMS\Router\Route; use Joomla\Input\Input; use Joomla\Utilities\ArrayHelper; @@ -86,9 +87,6 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu $this->registerTask('orderup', 'reorder'); $this->registerTask('orderdown', 'reorder'); - // Transition - $this->registerTask('runTransition', 'runTransition'); - // Guess the option as com_NameOfController. if (empty($this->option)) { @@ -434,6 +432,9 @@ public function saveOrderAjax() */ public function runTransition() { + // Check for request forgeries + $this->checkToken(); + // Get the input $pks = $this->input->post->get('cid', array(), 'array'); @@ -442,13 +443,17 @@ public function runTransition() return false; } - $pk = (int) $pks[0]; - - $transitionId = $this->input->post->get('transition_' . $pk, -1, 'int'); + $transitionId = (int) $this->input->post->getInt('transition_id'); // Get the model $model = $this->getModel(); - $return = $model->runTransition($pk, $transitionId); + + if (!$model instanceof WorkflowModelInterface) + { + return false; + } + + $return = $model->executeTransition($pks, $transitionId); $redirect = Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false); diff --git a/libraries/src/MVC/Model/AdminModel.php b/libraries/src/MVC/Model/AdminModel.php index bbf08ab8b603b..69800571938a0 100644 --- a/libraries/src/MVC/Model/AdminModel.php +++ b/libraries/src/MVC/Model/AdminModel.php @@ -117,8 +117,7 @@ abstract class AdminModel extends FormModel protected $batch_commands = array( 'assetgroup_id' => 'batchAccess', 'language_id' => 'batchLanguage', - 'tag' => 'batchTag', - 'workflowstage_id' => 'batchWorkflowStage', + 'tag' => 'batchTag' ); /** @@ -1270,7 +1269,7 @@ public function save($data) } $key = $table->getKeyName(); - $pk = (!empty($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id'); + $pk = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id'); $isNew = true; // Include the plugins for the save events. diff --git a/libraries/src/MVC/Model/WorkflowBehaviorTrait.php b/libraries/src/MVC/Model/WorkflowBehaviorTrait.php new file mode 100644 index 0000000000000..f8cca5c09a6c5 --- /dev/null +++ b/libraries/src/MVC/Model/WorkflowBehaviorTrait.php @@ -0,0 +1,433 @@ +extension = array_shift($parts); + + if (count($parts)) + { + $this->section = array_shift($parts); + } + + $this->workflow = new Workflow(['extension' => $extension]); + + $params = ComponentHelper::getParams($this->extension); + + $this->workflowEnabled = $params->get('workflow_enabled'); + + $this->enableWorkflowBatch(); + } + + /** + * Add the workflow batch to the command list. Can be overwritten bei the child class + * + * @return void + * + * @since 4.0.0 + */ + protected function enableWorkflowBatch() + { + // Enable batch + if ($this->workflowEnabled && property_exists($this, 'batch_commands')) + { + $this->batch_commands['workflowstage_id'] = 'batchWorkflowStage'; + } + } + + /** + * Method to allow derived classes to preprocess the form. + * + * @param Form $form A Form object. + * @param mixed $data The data expected for the form. + * + * @return void + * + * @since 4.0.0 + * @see FormField + */ + public function workflowPreprocessForm(Form $form, $data) + { + $this->addTransitionField($form, $data); + + if (!$this->workflowEnabled) + { + return; + } + + // Import the workflow plugin group to allow form manipulation. + $this->importWorkflowPlugins(); + } + + /** + * Let plugins access stage change events + * + * @return void + * + * @since 4.0.0 + */ + public function workflowBeforeStageChange() + { + if (!$this->workflowEnabled) + { + return; + } + + $this->importWorkflowPlugins(); + } + + /** + * Preparation of workflow data/plugins + * + * @return void + * + * @since 4.0.0 + */ + public function workflowBeforeSave() + { + if (!$this->workflowEnabled) + { + return; + } + + $this->importWorkflowPlugins(); + } + + /** + * Executing of relevant workflow methods + * + * @return void + * + * @since 4.0.0 + */ + public function workflowAfterSave($data) + { + // Regardless if workflow is active or not, we have to set the default stage + // So we can work with the workflow, when the user activates it later + $id = $this->getState($this->getName() . '.id'); + $isNew = $this->getState($this->getName() . '.new'); + + // We save the first stage + if ($isNew) + { + // We have to add the paths, because it could be called outside of the extension context + $path = JPATH_BASE . '/components/' . $this->extension; + + $path = Path::check($path); + + Form::addFormPath($path . '/forms'); + Form::addFormPath($path . '/models/forms'); + Form::addFieldPath($path . '/models/fields'); + Form::addFormPath($path . '/model/form'); + Form::addFieldPath($path . '/model/field'); + + $form = $this->getForm(); + + $stage_id = $this->getStageForNewItem($form, $data); + + $this->workflow->createAssociation($id, $stage_id); + } + + if (!$this->workflowEnabled) + { + return; + } + + // Execute transition + if (!empty($data['transition'])) + { + $this->executeTransition([$id], $data['transition']); + } + } + + /** + * Batch change workflow stage or current. + * + * @param integer $value The workflow stage 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 4.0.0 + */ + public function batchWorkflowStage(int $value, array $pks, array $contexts) + { + $user = Factory::getApplication()->getIdentity(); + + $workflow = Factory::getApplication()->bootComponent('com_workflow'); + + if (!$user->authorise('core.admin', $this->option)) + { + $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EXECUTE_TRANSITION')); + } + + // Get workflow stage information + $stage = $workflow->getMVCFactory()->createTable('Stage', 'Administrator'); + + if (empty($value) || !$stage->load($value)) + { + Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_BATCH_WORKFLOW_STAGE_ROW_NOT_FOUND'), 'error'); + + return false; + } + + if (empty($pks)) + { + Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_BATCH_WORKFLOW_STAGE_ROW_NOT_FOUND'), 'error'); + + return false; + } + + // Update workflow associations + return $this->workflow->updateAssociations($pks, $value); + } + + /** + * Batch change workflow stage or current. + * + * @param integer $oldId The ID of the item copied from + * @param integer $newId The ID of the new item + * + * @return null + * + * @since 4.0.0 + */ + public function workflowCleanupBatchMove($oldId, $newId) + { + // Trigger workflow plugins only if enable (will be triggered from parent class) + if ($this->workflowEnabled) + { + $this->importWorkflowPlugins(); + } + + // We always need an association, so create one + $table = $this->getTable(); + + $table->load($newId); + + $catKey = $table->getColumnAlias('catid'); + + $stage_id = $this->workflow->getDefaultStageByCategory($table->$catKey); + + if (empty($stage_id)) + { + return; + } + + $this->workflow->createAssociation((int) $newId, (int) $stage_id); + } + + /** + * Runs transition for item. + * + * @param array $pks Id of items to execute the transition + * @param integer $transition_id Id of transition + * + * @return boolean + * + * @since 4.0.0 + */ + public function executeTransition(array $pks, int $transition_id) + { + $result = $this->workflow->executeTransition($pks, $transition_id); + + if (!$result) + { + $this->setError(Text::_('COM_CONTENT_ERROR_UPDATE_STAGE')); + + return false; + } + + return true; + } + + /** + * Import the Workflow plugins. + * + * @param Form $form A Form object. + * @param mixed $data The data expected for the form. + * + * @return void + */ + protected function importWorkflowPlugins() + { + PluginHelper::importPlugin('workflow'); + } + + /** + * Adds a transition field to the form. Can be overwritten by the child class if not needed + * + * @param Form $form A Form object. + * @param mixed $data The data expected for the form. + * + * @return void + * @since 4.0.0 + */ + protected function addTransitionField(Form $form, $data) + { + $extension = $this->extension . ($this->section ? '.' . $this->section : ''); + + $field = new \SimpleXMLElement(''); + + $field->addAttribute('name', 'transition'); + $field->addAttribute('type', $this->workflowEnabled ? 'transition' : 'hidden'); + $field->addAttribute('label', 'COM_CONTENT_TRANSITION'); + $field->addAttribute('extension', $extension); + + $form->setField($field); + + $table = $this->getTable(); + + $key = $table->getKeyName(); + + $id = isset($data->$key) ? $data->$key : $form->getValue($key); + + if ($id) + { + // Transition field + $assoc = $this->workflow->getAssociation($id); + + if (!empty($assoc->stage_id)) + { + $form->setFieldAttribute('transition', 'workflow_stage', (int) $assoc->stage_id); + } + } + else + { + $stage_id = $this->getStageForNewItem($form, $data); + + if (!empty($stage_id)) + { + $form->setFieldAttribute('transition', 'workflow_stage', (int) $stage_id); + } + } + } + + /** + * Try to load a workflow stage for newly created items + * which does not have a workflow assinged yet. If the category is not the + * carrier, overwrite it on your model and deliver your own carrier. + * + * @param Form $form A Form object. + * @param mixed $data The data expected for the form. + * + * @return boolean|integer An integer, holding the stage ID or false + * @since 4.0.0 + */ + protected function getStageForNewItem(Form $form, $data) + { + $table = $this->getTable(); + + $hasKey = $table->hasField('catid'); + + if (!$hasKey) + { + return false; + } + + $catKey = $table->getColumnAlias('catid'); + + $field = $form->getField($catKey); + + if (!$field) + { + return false; + } + + $catId = isset(((object) $data)->$catKey) ? ((object) $data)->$catKey : $form->getValue($catKey); + + // Try to get the category from the html code of the field + if (empty($catId)) + { + $catId = $field->getAttribute('default', null); + + // Choose the first category available + $xml = new \DOMDocument; + libxml_use_internal_errors(true); + $xml->loadHTML($field->__get('input')); + libxml_clear_errors(); + libxml_use_internal_errors(false); + $options = $xml->getElementsByTagName('option'); + + if (!$catId && $firstChoice = $options->item(0)) + { + $catId = $firstChoice->getAttribute('value'); + } + } + + if (empty($catId)) + { + return false; + } + + return $this->workflow->getDefaultStageByCategory($catId); + } +} diff --git a/libraries/src/MVC/Model/WorkflowModelInterface.php b/libraries/src/MVC/Model/WorkflowModelInterface.php new file mode 100644 index 0000000000000..cd03fa0a63090 --- /dev/null +++ b/libraries/src/MVC/Model/WorkflowModelInterface.php @@ -0,0 +1,138 @@ +option) + { + $component = $this->option; + } + else + { + $component = ApplicationHelper::getComponentName(); + } + + $context = $component . '.' . $this->getName(); + + $app->getDispatcher()->dispatch( + 'onBeforeDisplay', + AbstractEvent::create( + 'onBeforeDisplay', + [ + 'eventClass' => 'Joomla\CMS\Event\View\DisplayEvent', + 'subject' => $this, + 'extension' => $context + ] + ) + ); + $result = $this->loadTemplate($tpl); + $eventResult = $app->getDispatcher()->dispatch( + 'onAfterDisplay', + AbstractEvent::create( + 'onAfterDisplay', + [ + 'eventClass' => 'Joomla\CMS\Event\View\DisplayEvent', + 'subject' => $this, + 'extension' => $context, + 'source' => $result + ] + ) + ); + + $eventResult->getArgument('used', false); + echo $result; } diff --git a/libraries/src/MVC/View/ViewInterface.php b/libraries/src/MVC/View/ViewInterface.php index 3af625feb5ded..f955143306c25 100644 --- a/libraries/src/MVC/View/ViewInterface.php +++ b/libraries/src/MVC/View/ViewInterface.php @@ -28,4 +28,15 @@ interface ViewInterface * @since 4.0.0 */ public function display($tpl = null); + + /** + * Method to get the model object + * + * @param string $name The name of the model (optional) + * + * @return BaseDatabaseModel The model object + * + * @since 3.0 + */ + public function getModel($name = null); } diff --git a/libraries/src/Workflow/Workflow.php b/libraries/src/Workflow/Workflow.php index f82d2a9094809..1842ad393538a 100644 --- a/libraries/src/Workflow/Workflow.php +++ b/libraries/src/Workflow/Workflow.php @@ -10,9 +10,13 @@ \defined('JPATH_PLATFORM') or die; +use Joomla\CMS\Event\AbstractEvent; use Joomla\CMS\Extension\ComponentInterface; use Joomla\CMS\Factory; +use Joomla\CMS\Plugin\PluginHelper; +use Joomla\CMS\Table\Category; use Joomla\Database\ParameterType; +use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; /** @@ -145,6 +149,123 @@ protected function getComponent() return $this->component; } + /** + * Try to load a workflow default stage by category ID. + * + * @param integer $catId The category ID. + * + * @return boolean|integer An integer, holding the stage ID or false + * @since 4.0.0 + */ + public function getDefaultStageByCategory($catId = 0) + { + $db = Factory::getContainer()->get('DatabaseDriver'); + + // Let's check if a workflow ID is assigned to a category + $category = new Category($db); + + $categories = array_reverse($category->getPath($catId)); + + $workflow_id = 0; + + foreach ($categories as $cat) + { + $cat->params = new Registry($cat->params); + + $workflow_id = $cat->params->get('workflow_id'); + + if ($workflow_id == 'inherit') + { + $workflow_id = 0; + + continue; + } + elseif ($workflow_id == 'use_default') + { + $workflow_id = 0; + + break; + } + elseif ($workflow_id > 0) + { + break; + } + } + + // Check if the workflow exists + if ($workflow_id = (int) $workflow_id) + { + $query = $db->getQuery(true); + + $query->select( + [ + $db->quoteName('ws.id') + ] + ) + ->from( + [ + $db->quoteName('#__workflow_stages', 'ws'), + $db->quoteName('#__workflows', 'w'), + ] + ) + ->where( + [ + $db->quoteName('ws.workflow_id') . ' = ' . $db->quoteName('w.id'), + $db->quoteName('ws.default') . ' = 1', + $db->quoteName('w.published') . ' = 1', + $db->quoteName('ws.published') . ' = 1', + $db->quoteName('w.id') . ' = :workflowId', + $db->quoteName('w.extension') . ' = :extension', + ] + ) + ->bind(':workflowId', $workflow_id, ParameterType::INTEGER) + ->bind(':extension', $this->extension); + + $stage_id = (int) $db->setQuery($query)->loadResult(); + + if (!empty($stage_id)) + { + return $stage_id; + } + } + + // Use default workflow + $query = $db->getQuery(true); + + $query->select( + [ + $db->quoteName('ws.id') + ] + ) + ->from( + [ + $db->quoteName('#__workflow_stages', 'ws'), + $db->quoteName('#__workflows', 'w'), + ] + ) + ->where( + [ + $db->quoteName('ws.default') . ' = 1', + $db->quoteName('ws.workflow_id') . ' = ' . $db->quoteName('w.id'), + $db->quoteName('w.published') . ' = 1', + $db->quoteName('ws.published') . ' = 1', + $db->quoteName('w.default') . ' = 1', + $db->quoteName('w.extension') . ' = :extension' + ] + ) + ->bind(':extension', $this->extension); + + $stage_id = (int) $db->setQuery($query)->loadResult(); + + // Last check if we have a workflow ID + if (!empty($stage_id)) + { + return $stage_id; + } + + return false; + } + /** * Executes a transition to change the current state in the association table * @@ -171,85 +292,87 @@ public function executeTransition(array $pks, int $transition_id): bool $db->quoteName('t.id'), $db->quoteName('t.to_stage_id'), $db->quoteName('t.from_stage_id'), - $db->quoteName('s.condition'), + $db->quoteName('t.options'), + $db->quoteName('t.workflow_id'), ] ) ->from($db->quoteName('#__workflow_transitions', 't')) ->join('LEFT', $db->quoteName('#__workflow_stages', 's'), $db->quoteName('s.id') . ' = ' . $db->quoteName('t.to_stage_id')) ->where($db->quoteName('t.id') . ' = :id') + ->where($db->quoteName('t.published') . ' = 1') ->bind(':id', $transition_id, ParameterType::INTEGER); - if (!empty($this->options['published'])) + $transition = $db->setQuery($query)->loadObject(); + + if (empty($transition->id)) { - $query->where($db->quoteName('t.published') . ' = 1'); + return false; } - $transition = $db->setQuery($query)->loadObject(); + $transition->options = new Registry($transition->options); // Check if the items can execute this transition foreach ($pks as $pk) { $assoc = $this->getAssociation($pk); - if (!\in_array($transition->from_stage_id, [-1, $assoc->stage_id])) + // The transition has to be in the same workflow + if (!\in_array($transition->from_stage_id, [ + $assoc->stage_id, + -1 + ] + ) || $transition->workflow_id !== $assoc->workflow_id) { return false; } } - $component = $this->getComponent(); + $app = Factory::getApplication(); - if ($component instanceof WorkflowServiceInterface) + PluginHelper::importPlugin('workflow'); + + $eventResult = $app->getDispatcher()->dispatch( + 'onWorkflowBeforeTransition', + AbstractEvent::create( + 'onWorkflowBeforeTransition', + [ + 'eventClass' => 'Joomla\CMS\Event\Workflow\WorkflowTransitionEvent', + 'subject' => $this, + 'extension' => $this->extension, + 'pks' => $pks, + 'transition' => $transition, + 'stopTransition' => false, + ] + ) + ); + + if ($eventResult->getArgument('stopTransition')) { - $component->updateContentState($pks, (int) $transition->condition); + return false; } $success = $this->updateAssociations($pks, (int) $transition->to_stage_id); if ($success) { - $app = Factory::getApplication(); - $app->triggerEvent( + $app->getDispatcher()->dispatch( 'onWorkflowAfterTransition', - [ - 'pks' => $pks, - 'extension' => $this->extension, - 'user' => $app->getIdentity(), - 'transition' => $transition, - ] + AbstractEvent::create( + 'onWorkflowAfterTransition', + [ + 'eventClass' => 'Joomla\CMS\Event\Workflow\WorkflowTransitionEvent', + 'subject' => $this, + 'extension' => $this->extension, + 'pks' => $pks, + 'transition' => $transition + ] + ) ); } return $success; } - /** - * Gets the condition (i.e. state value) for a transition - * - * @param integer $transition_id The transition id to get the condition of - * - * @return integer|null Integer if transition exists. Otherwise null - */ - public function getConditionForTransition(int $transition_id): ?int - { - $db = Factory::getDbo(); - $query = $db->getQuery(true); - $query->select($db->quoteName('s.condition')) - ->from($db->quoteName('#__workflow_transitions', 't')) - ->join('LEFT', $db->quoteName('#__workflow_stages', 's'), $db->quoteName('s.id') . ' = ' . $db->quoteName('t.to_stage_id')) - ->where($db->quoteName('t.id') . ' = :transition_id') - ->bind(':transition_id', $transition_id, ParameterType::INTEGER); - $db->setQuery($query); - $condition = $db->loadResult(); - - if ($condition !== null) - { - $condition = (int) $condition; - } - - return $condition; - } - /** * Creates an association for the workflow_associations table * @@ -376,16 +499,21 @@ public function getAssociation(int $item_id): ?\stdClass $query->select( [ - $db->quoteName('item_id'), - $db->quoteName('stage_id'), + $db->quoteName('a.item_id'), + $db->quoteName('a.stage_id'), + $db->quoteName('s.workflow_id'), ] ) - ->from($db->quoteName('#__workflow_associations')) + ->from($db->quoteName('#__workflow_associations', 'a')) + ->innerJoin( + $db->quoteName('#__workflow_stages', 's'), + $db->quoteName('a.stage_id') . ' = ' . $db->quoteName('s.id') + ) ->where( [ - $db->quoteName('item_id') . ' = :id', - $db->quoteName('extension') . ' = :extension', - ] + $db->quoteName('item_id') . ' = :id', + $db->quoteName('extension') . ' = :extension', + ] ) ->bind(':id', $item_id, ParameterType::INTEGER) ->bind(':extension', $this->extension); diff --git a/libraries/src/Workflow/WorkflowPluginTrait.php b/libraries/src/Workflow/WorkflowPluginTrait.php new file mode 100644 index 0000000000000..2442042a14470 --- /dev/null +++ b/libraries/src/Workflow/WorkflowPluginTrait.php @@ -0,0 +1,154 @@ +workflow_id ?? $form->getValue('workflow_id')); + + $workflow = $this->getWorkflow($workflow_id); + + if (empty($workflow->id) || !$this->isSupported($workflow->extension)) + { + return false; + } + + // Load XML file from "parent" plugin + $path = dirname((new ReflectionClass(static::class))->getFileName()); + + if (file_exists($path . '/forms/action.xml')) + { + $form->loadFile($path . '/forms/action.xml'); + } + + return $workflow; + } + + /** + * Get the workflow for a given ID + * + * @param int|null $workflow_id ID of the workflow + * + * @return CMSObject|boolean Object on success, false on failure. + * + * @since __DEPLOY_VERSION__ + */ + protected function getWorkflow(int $workflow_id = null) + { + $workflow_id = !empty($workflow_id) ? $workflow_id : $this->app->input->getInt('workflow_id'); + + if (is_array($workflow_id)) + { + return false; + } + + return $this->app->bootComponent('com_workflow') + ->getMVCFactory() + ->createModel('Workflow', 'Administrator', ['ignore_request' => true]) + ->getItem($workflow_id); + } + + /** + * Check if the current plugin should execute workflow related activities + * + * @param string $context Context to check + * + * @return boolean + * + * @since 4.0.0 + */ + protected function isSupported($context) + { + return false; + } + + /** + * Check if the context is listed in the whitelist or in the blacklist and return the result + * + * @param string $context Context to check + * + * @return boolean + */ + protected function checkWhiteAndBlacklist($context) + { + $whitelist = \array_filter((array) $this->params->get('whitelist', [])); + $blacklist = \array_filter((array) $this->params->get('blacklist', [])); + + if (!empty($whitelist)) + { + foreach ($whitelist as $allowed) + { + if ($context === $allowed) + { + return true; + } + } + + return false; + } + + foreach ($blacklist as $forbidden) + { + if ($context === $forbidden) + { + return false; + } + } + + return true; + } + + /** + * Check if the context is listed in the whitelist or in the blacklist and return the result + * + * @param string $context Context to check + * @param string $functionality The funcationality + * + * @return boolean + */ + protected function checkExtensionSupport($context, $functionality) + { + $parts = explode('.', $context); + + $component = $this->app->bootComponent($parts[0]); + + if (!$component instanceof WorkflowServiceInterface + || !$component->isWorkflowActive($context) + || !$component->supportFunctionality($functionality, $context)) + { + return false; + } + + return true; + } + +} diff --git a/libraries/src/Workflow/WorkflowServiceInterface.php b/libraries/src/Workflow/WorkflowServiceInterface.php index 9f26b53951a99..5632fd6df7fac 100644 --- a/libraries/src/Workflow/WorkflowServiceInterface.php +++ b/libraries/src/Workflow/WorkflowServiceInterface.php @@ -18,28 +18,46 @@ interface WorkflowServiceInterface { /** - * Method to filter transitions by given id of state. + * Check if the functionality is supported by the context * - * @param integer[] $transitions Array of transitions to filter for - * @param integer $pk Id of the state on which the transitions are performed + * @param string $functionality The functionality + * @param string $context The context of the functionality * - * @return array + * @return boolean * * @since 4.0.0 */ - public function filterTransitions(array $transitions, int $pk): array; + public function supportFunctionality($functionality, $context): bool; /** - * Method to change state of multiple ids + * Returns the model name, based on the context * - * @param array $pks Array of IDs - * @param integer $condition Condition of the workflow state + * @param string $context The context of the workflow * - * @return boolean + * @return boolean + */ + public function getModelName($context): string; + + /** + * Check if the workflow is active * - * @since 4.0.0 + * @param string $context The context of the workflow + * + * @return boolean */ - public static function updateContentState(array $pks, int $condition): bool; + public function isWorkflowActive($context): bool; + + /** + * Method to filter transitions by given id of state. + * + * @param integer[] $transitions Array of transitions to filter for + * @param integer $pk Id of the state on which the transitions are performed + * + * @return array + * + * @since 4.0.0 + */ + public function filterTransitions(array $transitions, int $pk): array; /** * Returns an array of possible conditions for the component. @@ -62,4 +80,13 @@ public static function getConditions(string $extension): array; * @since 4.0.0 */ public function getWorkflowTableBySection(?string $section = null): string; + + /** + * Returns valid contexts. + * + * @return array + * + * @since 4.0.0 + */ + public function getWorkflowContexts(): array; } diff --git a/libraries/src/Workflow/WorkflowServiceTrait.php b/libraries/src/Workflow/WorkflowServiceTrait.php index d40c900d142cc..6637135e88eef 100644 --- a/libraries/src/Workflow/WorkflowServiceTrait.php +++ b/libraries/src/Workflow/WorkflowServiceTrait.php @@ -8,6 +8,14 @@ namespace Joomla\CMS\Workflow; +use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\AbstractEvent; +use Joomla\CMS\Factory; +use Joomla\CMS\MVC\Factory\MVCFactoryInterface; +use Joomla\CMS\MVC\Model\WorkflowModelInterface; +use Joomla\Event\DispatcherAwareInterface; +use function str_repeat; + \defined('JPATH_PLATFORM') or die; /** @@ -17,6 +25,118 @@ */ trait WorkflowServiceTrait { + /** + * Get a MVC factory + * + * @return MVCFactoryInterface + * + * @since __DEPLOY_VERSION__ + */ + abstract public function getMVCFactory(): MVCFactoryInterface; + + /** + * Check if the functionality is supported by the component + * The variable $supportFunctionality has the following structure + * [ + * 'core.featured' => [ + * 'com_content.article', + * ], + * 'core.state' => [ + * 'com_content.article', + * ], + * ] + * + * @param string $functionality The functionality + * @param string $context The context of the functionality + * + * @return boolean + */ + public function supportFunctionality($functionality, $context): bool + { + if (empty($this->supportedFunctionality[$functionality])) + { + return false; + } + + if (!is_array($this->supportedFunctionality[$functionality])) + { + return true; + } + + return in_array($context, $this->supportedFunctionality[$functionality], true); + } + + /** + * Check if the functionality is used by a plugin + * + * @param string $functionality The functionality + * @param string $extension The extension + * + * @return boolean + * @throws \Exception + * + * @since 4.0.0 + */ + public function isFunctionalityUsed($functionality, $extension): bool + { + static $used = []; + + $cacheKey = $extension . '.' . $functionality; + + if (isset($used[$cacheKey])) + { + return $used[$cacheKey]; + } + + // The container to get the services from + $app = Factory::getApplication(); + + if (!($app instanceof DispatcherAwareInterface)) + { + return false; + } + + $eventResult = $app->getDispatcher()->dispatch( + 'onWorkflowFunctionalityUsed', + AbstractEvent::create( + 'onWorkflowFunctionalityUsed', + [ + 'eventClass' => 'Joomla\CMS\Event\Workflow\WorkflowFunctionalityUsedEvent', + 'subject' => $this, + 'extension' => $extension, + 'functionality' => $functionality + ] + ) + ); + + $used[$cacheKey] = $eventResult->getArgument('used', false); + + return $used[$cacheKey]; + } + + /** + * Returns the model name, based on the context + * + * @param string $context The context of the workflow + * + * @return boolean + * + * @since 4.0.0 + */ + public function getModelName($context): string + { + $parts = explode('.', $context); + + if (count($parts) < 2) + { + return ''; + } + + array_shift($parts); + + return ucfirst(array_shift($parts)); + } + /** * Returns an array of possible conditions for the component. * @@ -30,4 +150,35 @@ public static function getConditions(string $extension): array { return \defined('self::CONDITION_NAMES') ? self::CONDITION_NAMES : Workflow::CONDITION_NAMES; } + + /** + * Check if the workflow is active + * + * @param string $context The context of the workflow + * + * @return boolean + */ + public function isWorkflowActive($context): bool + { + $parts = explode('.', $context); + $config = ComponentHelper::getParams($parts[0]); + + if (!$config->get('workflow_enabled')) + { + return false; + } + + $modelName = $this->getModelName($context); + + if (empty($modelName)) + { + return false; + } + + $component = $this->getMVCFactory(); + $appName = Factory::getApplication()->getName(); + $model = $component->createModel($modelName, $appName, ['ignore_request' => true]); + + return $model instanceof WorkflowModelInterface; + } } diff --git a/modules/mod_articles_archive/src/Helper/ArticlesArchiveHelper.php b/modules/mod_articles_archive/src/Helper/ArticlesArchiveHelper.php index f0671a1d561ed..f06e1cc4cc083 100644 --- a/modules/mod_articles_archive/src/Helper/ArticlesArchiveHelper.php +++ b/modules/mod_articles_archive/src/Helper/ArticlesArchiveHelper.php @@ -38,19 +38,15 @@ public static function getList(&$params) { $app = Factory::getApplication(); $db = Factory::getDbo(); - $condition = ContentComponent::CONDITION_ARCHIVED; $query = $db->getQuery(true); $query->select($query->month($db->quoteName('created')) . ' AS created_month') ->select('MIN(' . $db->quoteName('created') . ') AS created') ->select($query->year($db->quoteName('created')) . ' AS created_year') ->from($db->quoteName('#__content', 'c')) - ->innerJoin($db->quoteName('#__workflow_associations', 'wa'), $db->quoteName('wa.item_id') . ' = ' . $db->quoteName('c.id')) - ->innerJoin($db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('wa.stage_id') . ' = ' . $db->quoteName('ws.id')) - ->where($db->quoteName('ws.condition') . ' = :condition') + ->where($db->quoteName('c.state') . ' = ' . ContentComponent::CONDITION_ARCHIVED) ->group($query->year($db->quoteName('c.created')) . ', ' . $query->month($db->quoteName('c.created'))) - ->order($query->year($db->quoteName('c.created')) . ' DESC, ' . $query->month($db->quoteName('c.created')) . ' DESC') - ->bind(':condition', $condition, ParameterType::INTEGER); + ->order($query->year($db->quoteName('c.created')) . ' DESC, ' . $query->month($db->quoteName('c.created')) . ' DESC'); // Filter by language if ($app->getLanguageFilter()) diff --git a/modules/mod_related_items/src/Helper/RelatedItemsHelper.php b/modules/mod_related_items/src/Helper/RelatedItemsHelper.php index 6bdacc709dea5..53e707a3e6f91 100644 --- a/modules/mod_related_items/src/Helper/RelatedItemsHelper.php +++ b/modules/mod_related_items/src/Helper/RelatedItemsHelper.php @@ -41,7 +41,6 @@ public static function getList(&$params) $groups = Factory::getUser()->getAuthorisedViewLevels(); $maximum = (int) $params->get('maximum', 5); $factory = $app->bootComponent('com_content')->getMVCFactory(); - $condition = ContentComponent::CONDITION_PUBLISHED; // Get an instance of the generic articles model /** @var \Joomla\Component\Content\Site\Model\ArticlesModel $articles */ @@ -107,13 +106,10 @@ public static function getList(&$params) $query->clear() ->select($db->quoteName('a.id')) ->from($db->quoteName('#__content', 'a')) - ->join('LEFT', $db->quoteName('#__workflow_associations', 'wa'), $db->quoteName('wa.item_id') . ' = ' . $db->quoteName('a.id')) - ->join('LEFT', $db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('ws.id') . ' = ' . $db->quoteName('wa.stage_id')) ->where($db->quoteName('a.id') . ' != :id') - ->where($db->quoteName('ws.condition') . ' = :condition') + ->where($db->quoteName('a.state') . ' = ' . ContentComponent::CONDITION_PUBLISHED) ->whereIn($db->quoteName('a.access'), $groups) - ->bind(':id', $id, ParameterType::INTEGER) - ->bind(':condition', $condition, ParameterType::INTEGER); + ->bind(':id', $id, ParameterType::INTEGER); $binds = []; $wheres = []; diff --git a/modules/mod_stats/src/Helper/StatsHelper.php b/modules/mod_stats/src/Helper/StatsHelper.php index 79aea0eb0a569..a502f32b1a181 100644 --- a/modules/mod_stats/src/Helper/StatsHelper.php +++ b/modules/mod_stats/src/Helper/StatsHelper.php @@ -15,6 +15,7 @@ use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\Plugin\PluginHelper; +use Joomla\Component\Content\Administrator\Extension\ContentComponent; /** * Helper for mod_stats @@ -94,7 +95,7 @@ public static function &getList(&$params) $query->clear() ->select('COUNT(' . $db->quoteName('c.id') . ') AS count_items') ->from($db->quoteName('#__content', 'c')) - ->where($db->quoteName('c.state') . ' = 1'); + ->where($db->quoteName('c.state') . ' = ' . ContentComponent::CONDITION_PUBLISHED); $db->setQuery($query); try @@ -128,7 +129,7 @@ public static function &getList(&$params) $query->clear() ->select('SUM(' . $db->quoteName('hits') . ') AS count_hits') ->from($db->quoteName('#__content')) - ->where($db->quoteName('state') . ' = 1'); + ->where($db->quoteName('state') . ' = ' . ContentComponent::CONDITION_PUBLISHED); $db->setQuery($query); try diff --git a/plugins/content/joomla/joomla.php b/plugins/content/joomla/joomla.php index 688ab883ba3b9..3912473ccc777 100644 --- a/plugins/content/joomla/joomla.php +++ b/plugins/content/joomla/joomla.php @@ -611,6 +611,12 @@ public function onContentChangeState($context, $pks, $value) return true; } + // Check if this function is enabled. + if ($context != 'com_content.article') + { + return true; + } + $db = $this->db; $query = $db->getQuery(true) ->select($db->quoteName('core_content_id')) @@ -624,95 +630,6 @@ public function onContentChangeState($context, $pks, $value) $cctable = new CoreContent($db); $cctable->publish($ccIds, $value); - // Check if sending email on transition execution is enabled. - if (!$this->params->def('email_new_stage', 0) || $context !== 'com_content.article') - { - return true; - } - - $query = $db->getQuery(true) - ->select($db->quoteName('id')) - ->from($db->quoteName('#__users')) - ->where($db->quoteName('sendEmail') . ' = 1') - ->where($db->quoteName('block') . ' = 0'); - - $users = (array) $db->setQuery($query)->loadColumn(); - - if (empty($users)) - { - return true; - } - - $user = $this->app->getIdentity(); - - // Messaging for changed items - $default_language = ComponentHelper::getParams('com_languages')->get('administrator'); - $debug = $this->app->get('debug_lang'); - - $article = new ArticleTable($db); - - $workflow = new Workflow(['extension' => 'com_content']); - - foreach ($pks as $pk) - { - if (!$article->load($pk)) - { - continue; - } - - $assoc = $workflow->getAssociation($pk); - $stageId = (int) $assoc->stage_id; - - // Load new transitions - $query = $db->getQuery(true) - ->select($db->quoteName('t.id')) - ->from($db->quoteName('#__workflow_transitions', 't')) - ->from($db->quoteName('#__workflow_stages', 's')) - ->where($db->quoteName('t.from_stage_id') . ' = :stageid') - ->where($db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id')) - ->where($db->quoteName('t.published') . ' = 1') - ->where($db->quoteName('s.published') . ' = 1') - ->order($db->quoteName('t.ordering')) - ->bind(':stageid', $stageId, ParameterType::INTEGER); - - $transitions = $db->setQuery($query)->loadObjectList(); - - foreach ($users as $user_id) - { - if ($user_id != $user->id) - { - // Check if the user has available transitions - $items = array_filter( - $transitions, - function ($item) use ($user) - { - return $user->authorise('core.execute.transition', 'com_content.transition.' . $item->id); - } - ); - - if (!count($items)) - { - continue; - } - - // Load language for messaging - $receiver = User::getInstance($user_id); - $lang = Language::getInstance($receiver->getParam('admin_language', $default_language), $debug); - $lang->load('plg_content_joomla'); - - $message = array( - 'user_id_to' => $user_id, - 'subject' => $lang->_('PLG_CONTENT_JOOMLA_ON_STAGE_CHANGE_SUBJECT'), - 'message' => sprintf($lang->_('PLG_CONTENT_JOOMLA_ON_STAGE_CHANGE_MSG'), $user->name, $article->title), - ); - - $model_message = $this->app->bootComponent('com_messages') - ->getMVCFactory()->createModel('Message', 'Administrator'); - $model_message->save($message); - } - } - } - return true; } diff --git a/plugins/content/joomla/joomla.xml b/plugins/content/joomla/joomla.xml index 51572e9505904..8fbe01231966b 100644 --- a/plugins/content/joomla/joomla.xml +++ b/plugins/content/joomla/joomla.xml @@ -44,18 +44,6 @@ - - - - - diff --git a/plugins/content/pagenavigation/pagenavigation.php b/plugins/content/pagenavigation/pagenavigation.php index f2241da5c4a0f..50e07a6be1f65 100644 --- a/plugins/content/pagenavigation/pagenavigation.php +++ b/plugins/content/pagenavigation/pagenavigation.php @@ -14,6 +14,7 @@ use Joomla\CMS\Language\Text; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Plugin\PluginHelper; +use Joomla\Component\Content\Administrator\Extension\ContentComponent; use Joomla\Component\Content\Site\Helper\RouteHelper; use Joomla\Database\ParameterType; @@ -158,9 +159,7 @@ public function onContentBeforeDisplay($context, &$row, &$params, $page = 0) $query->select($db->quoteName(['a.id', 'a.title', 'a.catid', 'a.language'])) ->select([$case_when, $case_when1]) ->from($db->quoteName('#__content', 'a')) - ->join('LEFT', $db->quoteName('#__categories', 'cc'), $db->quoteName('cc.id') . ' = ' . $db->quoteName('a.catid')) - ->join('LEFT', $db->quoteName('#__workflow_associations', 'wa'), $db->quoteName('wa.item_id') . ' = ' . $db->quoteName('a.id')) - ->join('LEFT', $db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('wa.stage_id') . ' = ' . $db->quoteName('ws.id')); + ->join('LEFT', $db->quoteName('#__categories', 'cc'), $db->quoteName('cc.id') . ' = ' . $db->quoteName('a.catid')); if ($order_method === 'author' || $order_method === 'rauthor') { @@ -184,7 +183,6 @@ public function onContentBeforeDisplay($context, &$row, &$params, $page = 0) $query->where( [ - '(' . $db->quoteName('ws.condition') . ' = 1 OR ' . $db->quoteName('ws.condition') . ' = -2)', '(' . $db->quoteName('publish_up') . ' IS NULL OR ' . $db->quoteName('publish_up') . ' <= :nowDate1)', '(' . $db->quoteName('publish_down') . ' IS NULL OR ' . $db->quoteName('publish_down') . ' >= :nowDate2)', ] diff --git a/plugins/sampledata/blog/blog.php b/plugins/sampledata/blog/blog.php index 0e30926f551ea..75d70b4739f23 100644 --- a/plugins/sampledata/blog/blog.php +++ b/plugins/sampledata/blog/blog.php @@ -117,8 +117,6 @@ public function onAjaxSampledataApplyStep1() $language = Multilanguage::isEnabled() ? Factory::getLanguage()->getTag() : '*'; $langSuffix = ($language !== '*') ? ' (' . $language . ')' : ''; - $workflow_id = 1; - // Create "blog" category. $categoryModel = $this->app->bootComponent('com_categories') ->getMVCFactory()->createModel('Category', 'Administrator'); @@ -147,7 +145,7 @@ public function onAjaxSampledataApplyStep1() 'associations' => array(), 'description' => '', 'language' => $language, - 'params' => '{"workflow_id": "' . $workflow_id . '"}', + 'params' => '{}', ); try @@ -194,7 +192,7 @@ public function onAjaxSampledataApplyStep1() 'associations' => array(), 'description' => '', 'language' => $language, - 'params' => '{"workflow_id": "' . $workflow_id . '"}', + 'params' => '{}', ); try @@ -217,7 +215,6 @@ public function onAjaxSampledataApplyStep1() $catIds[] = $categoryModel->getItem()->id; // Create Articles. - $articleModel = new \Joomla\Component\Content\Administrator\Model\ArticleModel; $articles = array( array( 'catid' => $catIds[1], @@ -246,8 +243,14 @@ public function onAjaxSampledataApplyStep1() ), ); + $mvcFactory = $this->app->bootComponent('com_content')->getMVCFactory(); + + ComponentHelper::getParams('com_content')->set('workflow_enabled', 0); + foreach ($articles as $i => $article) { + $articleModel = $mvcFactory->createModel('Article', 'Administrator', ['ignore_request' => true]); + // Set values from language strings. $title = Text::_('PLG_SAMPLEDATA_BLOG_SAMPLEDATA_CONTENT_ARTICLE_' . $i . '_TITLE'); $alias = ApplicationHelper::stringURLSafe($title); @@ -270,6 +273,7 @@ public function onAjaxSampledataApplyStep1() $article['language'] = $language; $article['associations'] = array(); + $article['state'] = 1; $article['featured'] = 0; $article['images'] = ''; $article['metakey'] = ''; @@ -280,9 +284,6 @@ public function onAjaxSampledataApplyStep1() $article['access'] = $access; } - // Publish - $article['transition'] = 2; - if (!$articleModel->save($article)) { Factory::getLanguage()->load('com_content'); diff --git a/plugins/sampledata/multilang/multilang.php b/plugins/sampledata/multilang/multilang.php index e6337533af469..cbef355e6d30a 100644 --- a/plugins/sampledata/multilang/multilang.php +++ b/plugins/sampledata/multilang/multilang.php @@ -20,6 +20,7 @@ use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Session\Session; use Joomla\CMS\Table\Table; +use Joomla\CMS\Workflow\Workflow; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\ParameterType; @@ -375,6 +376,8 @@ public function onAjaxSampledataApplyStep6() $siteLanguages = $this->getInstalledlangsFrontend(); + ComponentHelper::getParams('com_content')->set('workflow_enabled', 0); + foreach ($siteLanguages as $siteLang) { if (!$tableCategory = $this->addCategory($siteLang)) @@ -1020,7 +1023,7 @@ public function addCategory($itemLanguage) 'description' => '', 'published' => 1, 'access' => 1, - 'params' => '{"target":"","image":"", "workflow_id":"1"}', + 'params' => '{"target":"","image":""}', 'metadesc' => '', 'metakey' => '', 'metadata' => '{"page_title":"","author":"","robots":""}', @@ -1155,15 +1158,16 @@ private function addArticle($itemLanguage, $categoryId) return false; } - $assoc = new stdClass; - - $assoc->item_id = $newId; - $assoc->stage_id = 2; - $assoc->extension = 'com_content'; + $workflow = new Workflow(['extension' => 'com_content.article']); try { - $db->insertObject('#__workflow_associations', $assoc); + $stage_id = $workflow->getDefaultStageByCategory($categoryId); + + if ($stage_id) + { + $workflow->createAssociation($newId, $stage_id); + } } catch (ExecutionFailureException $e) { diff --git a/plugins/sampledata/testing/testing.php b/plugins/sampledata/testing/testing.php index 527e1b9c8df20..e01f28dcf64b2 100644 --- a/plugins/sampledata/testing/testing.php +++ b/plugins/sampledata/testing/testing.php @@ -639,6 +639,8 @@ public function onAjaxSampledataApplyStep4() return $response; } + ComponentHelper::getParams('com_content')->set('workflow_enabled', 0); + $catIdsLevel1 = $this->app->getUserState('sampledata.testing.articles.catids1'); $catIdsLevel2 = $this->app->getUserState('sampledata.testing.articles.catids2'); $catIdsLevel3 = $this->app->getUserState('sampledata.testing.articles.catids3'); @@ -979,7 +981,7 @@ public function onAjaxSampledataApplyStep4() ), array( 'catid' => $catIdsLevel2[0], - 'transition' => 4, + 'state' => 2, 'ordering' => 0, ), array( @@ -4648,10 +4650,10 @@ private function addArticles(array $articles) $article['metadesc'] = ''; $article['xreference'] = ''; - // Set transition to published if not set. - if (!isset($article['transition'])) + // Set article to published if not set. + if (!isset($article['state'])) { - $article['transition'] = 2; + $article['state'] = 1; } // Set article to not featured if not set. diff --git a/plugins/workflow/featuring/featuring.php b/plugins/workflow/featuring/featuring.php new file mode 100644 index 0000000000000..88c9fd675b2ba --- /dev/null +++ b/plugins/workflow/featuring/featuring.php @@ -0,0 +1,481 @@ + 'onContentPrepareForm', + 'onAfterDisplay' => 'onAfterDisplay', + 'onWorkflowBeforeTransition' => 'onWorkflowBeforeTransition', + 'onWorkflowAfterTransition' => 'onWorkflowAfterTransition', + 'onContentBeforeChangeFeatured' => 'onContentBeforeChangeFeatured', + 'onContentBeforeSave' => 'onContentBeforeSave', + 'onWorkflowFunctionalityUsed' => 'onWorkflowFunctionalityUsed', + ]; + } + + /** + * The form event. + * + * @param EventInterface $event The event + * + * @since __DEPLOY_VERSION__ + */ + public function onContentPrepareForm(EventInterface $event) + { + $form = $event->getArgument('0'); + $data = $event->getArgument('1'); + + $context = $form->getName(); + + // Extend the transition form + if ($context === 'com_workflow.transition') + { + $this->enhanceWorkflowTransitionForm($form, $data); + + return; + } + + $this->enhanceItemForm($form, $data); + + return; + } + + /** + * Disable certain fields in the item form view, when we want to take over this function in the transition + * Check also for the workflow implementation and if the field exists + * + * @param Form $form The form + * @param stdClass $data The data + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + protected function enhanceItemForm(Form $form, $data) + { + $context = $form->getName(); + + if (!$this->isSupported($context)) + { + return true; + } + + $parts = explode('.', $context); + + $component = $this->app->bootComponent($parts[0]); + + $modelName = $component->getModelName($context); + + $table = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]) + ->getTable(); + + $fieldname = $table->getColumnAlias('featured'); + + $options = $form->getField($fieldname)->options; + + $value = isset($data->$fieldname) ? $data->$fieldname : $form->getValue($fieldname, null, 0); + + $text = '-'; + + $textclass = 'body'; + + switch ($value) + { + case 1: + $textclass = 'success'; + break; + + case 0: + case -2: + $textclass = 'danger'; + } + + if (!empty($options)) + { + foreach ($options as $option) + { + if ($option->value == $value) + { + $text = $option->text; + + break; + } + } + } + + $form->setFieldAttribute($fieldname, 'type', 'spacer'); + + $label = '' . htmlentities($text, ENT_COMPAT, 'UTF-8') . ''; + $form->setFieldAttribute( + $fieldname, + 'label', + Text::sprintf('PLG_WORKFLOW_FEATURING_FEATURED', $label) + ); + + return true; + } + + /** + * Manipulate the generic list view + * + * @param DisplayEvent $event + * + * @since 4.0.0 + */ + public function onAfterDisplay(DisplayEvent $event) + { + $app = Factory::getApplication(); + + if (!$app->isClient('administrator')) + { + return; + } + + $component = $event->getArgument('extensionName'); + $section = $event->getArgument('section'); + + // We need the single model context for checking for workflow + $singularsection = Inflector::singularize($section); + + if (!$this->isSupported($component . '.' . $singularsection)) + { + return true; + } + + // List of releated batch functions we need to hide + $states = [ + 'featured', + 'unfeatured' + ]; + + $js = " + document.addEventListener('DOMContentLoaded', function() + { + var dropdown = document.getElementById('toolbar-dropdown-status-group'); + + if (!dropdown) + { + reuturn; + } + + " . \json_encode($states) . ".forEach((action) => { + var button = document.getElementById('status-group-children-' + action); + + if (button) + { + button.classList.add('d-none'); + } + }); + + }); + "; + + $app->getDocument()->addScriptDeclaration($js); + + return true; + } + + /** + * Check if we can execute the transition + * + * @param WorkflowTransitionEvent $event + * + * @return boolean + * + * @since 4.0.0 + */ + public function onWorkflowBeforeTransition(WorkflowTransitionEvent $event) + { + $context = $event->getArgument('extension'); + $transition = $event->getArgument('transition'); + $pks = $event->getArgument('pks'); + + if (!$this->isSupported($context) || !is_numeric($transition->options->get('featuring'))) + { + return true; + } + + $value = $transition->options->get('featuring'); + + if (!is_numeric($value)) + { + return true; + } + + /** + * Here it becomes tricky. We would like to use the component models featured method, so we will + * Execute the normal "onContentBeforeChangeFeatured" plugins. But they could cancel the execution, + * So we have to precheck and cancel the whole transition stuff if not allowed. + */ + $this->app->set('plgWorkflowFeaturing.' . $context, $pks); + + $result = $this->app->triggerEvent('onContentBeforeChangeFeatured', [ + $context, + $pks, + $value + ] + ); + + // Release whitelist, the job is done + $this->app->set('plgWorkflowFeaturing.' . $context, []); + + if (\in_array(false, $result, true)) + { + return false; + } + + return true; + } + + /** + * Change Feature State of an item. Used to disable feature state change + * + * @param WorkflowTransitionEvent $event + * + * @return boolean + * + * @since 4.0.0 + */ + public function onWorkflowAfterTransition(WorkflowTransitionEvent $event) + { + $context = $event->getArgument('extension'); + $extensionName = $event->getArgument('extensionName'); + $transition = $event->getArgument('transition'); + $pks = $event->getArgument('pks'); + + if (!$this->isSupported($context)) + { + return; + } + + $component = $this->app->bootComponent($extensionName); + + $value = $transition->options->get('featuring'); + + if (!is_numeric($value)) + { + return; + } + + $options = [ + 'ignore_request' => true, + // We already have triggered onContentBeforeChangeFeatured, so use our own + 'event_before_change_featured' => 'onWorkflowBeforeChangeFeatured' + ]; + + $modelName = $component->getModelName($context); + + $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), $options); + + $model->featured($pks, $value); + } + + /** + * Change Feature State of an item. Used to disable Feature state change + * + * @param EventInterface $event + * + * @return boolean + * + * @throws Exception + * @since 4.0.0 + */ + public function onContentBeforeChangeFeatured(EventInterface $event) + { + $context = $event->getArgument('0'); + $pks = $event->getArgument('1'); + + if (!$this->isSupported($context)) + { + return true; + } + + // We have whitelisted the pks, so we're the one who triggered + // With onWorkflowBeforeTransition => free pass + if ($this->app->get('plgWorkflowFeaturing.' . $context) === $pks) + { + return true; + } + + throw new Exception(Text::_('PLG_WORKFLOW_FEATURING_CHANGE_STATE_NOT_ALLOWED')); + } + + /** + * The save event. + * + * @param EventInterface $event + * + * @return boolean + * + * @since 4.0.0 + */ + public function onContentBeforeSave(EventInterface $event) + { + $context = $event->getArgument('0'); + + // @var TableInterface + + $table = $event->getArgument('1'); + $isNew = $event->getArgument('2'); + $data = $event->getArgument('3'); + + if (!$this->isSupported($context)) + { + return true; + } + + $keyName = $table->getColumnAlias('featured'); + + // Check for the old value + $article = clone $table; + + $article->load($table->id); + + // We don't allow the change of the feature state when we use the workflow + // As we're setting the field to disabled, no value should be there at all + if (isset($data[$keyName])) + { + $this->app->enqueueMessage(Text::_('PLG_WORKFLOW_FEATURING_CHANGE_STATE_NOT_ALLOWED'), 'error'); + + return false; + } + + return true; + } + + /** + * Check if the current plugin should execute workflow related activities + * + * @param string $context + * + * @return boolean + * + * @since 4.0.0 + */ + protected function isSupported($context) + { + if (!$this->checkWhiteAndBlacklist($context) || !$this->checkExtensionSupport($context, $this->supportFunctionality)) + { + return false; + } + + $parts = explode('.', $context); + + // We need at least the extension + view for loading the table fields + if (count($parts) < 2) + { + return false; + } + + $component = $this->app->bootComponent($parts[0]); + + if (!$component instanceof WorkflowServiceInterface + || !$component->isWorkflowActive($context) + || !$component->supportFunctionality($this->supportFunctionality, $context)) + { + return false; + } + + $modelName = $component->getModelName($context); + + $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]); + + if (!$model instanceof DatabaseModelInterface || !method_exists($model, 'featured')) + { + return false; + } + + $table = $model->getTable(); + + if (!$table instanceof TableInterface || !$table->hasField('featured')) + { + return false; + } + + return true; + } + + /** + * If plugin supports the functionality we set the used variable + * + * @param WorkflowFunctionalityUsedEvent $event + * + * @since 4.0.0 + */ + public function onWorkflowFunctionalityUsed(WorkflowFunctionalityUsedEvent $event) + { + $functionality = $event->getArgument('functionality'); + + if ($functionality !== 'core.featured') + { + return; + } + + $event->setUsed(); + } +} diff --git a/plugins/workflow/featuring/featuring.xml b/plugins/workflow/featuring/featuring.xml new file mode 100644 index 0000000000000..ce6f529c61870 --- /dev/null +++ b/plugins/workflow/featuring/featuring.xml @@ -0,0 +1,42 @@ + + + plg_workflow_featuring + Joomla! Project + March 2020 + Copyright (C) 2005 - 2020 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + 4.0.0 + PLG_WORKFLOW_FEATURING_XML_DESCRIPTION + + featuring.php + + + plg_workflow_featuring.ini + plg_workflow_featuring.sys.ini + + + +
+ + +
+
+
+ +
diff --git a/plugins/workflow/featuring/forms/action.xml b/plugins/workflow/featuring/forms/action.xml new file mode 100644 index 0000000000000..9108b81c86bd0 --- /dev/null +++ b/plugins/workflow/featuring/forms/action.xml @@ -0,0 +1,18 @@ + + + +
+ + + + + +
+
+ diff --git a/plugins/workflow/notification/forms/action.xml b/plugins/workflow/notification/forms/action.xml new file mode 100644 index 0000000000000..cf6b8d127a653 --- /dev/null +++ b/plugins/workflow/notification/forms/action.xml @@ -0,0 +1,49 @@ + +
+ +
+ + + + + + + +
+
+ diff --git a/plugins/workflow/notification/notification.php b/plugins/workflow/notification/notification.php new file mode 100644 index 0000000000000..8a92c65ea292e --- /dev/null +++ b/plugins/workflow/notification/notification.php @@ -0,0 +1,342 @@ + 'onContentPrepareForm', + 'onWorkflowAfterTransition' => 'onWorkflowAfterTransition', + ]; + } + + /** + * The form event. + * + * @param Form $form The form + * @param stdClass $data The data + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function onContentPrepareForm(EventInterface $event) + { + $form = $event->getArgument('0'); + $data = $event->getArgument('1'); + + $context = $form->getName(); + + // Extend the transition form + if ($context === 'com_workflow.transition') + { + $this->enhanceWorkflowTransitionForm($form, $data); + } + + return true; + } + + /** + * Send a Notification to defined users a transion is performed + * + * @param string $context The context for the content passed to the plugin. + * @param array $pks A list of primary key ids of the content that has changed stage. + * @param object $data Object containing data about the transition + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function onWorkflowAfterTransition(WorkflowTransitionEvent $event) + { + $context = $event->getArgument('extension'); + $extensionName = $event->getArgument('extensionName'); + $transition = $event->getArgument('transition'); + $pks = $event->getArgument('pks'); + + if (!$this->isSupported($context)) + { + return; + } + + $component = $this->app->bootComponent($extensionName); + + // Check if send-mail is active + if (empty($transition->options['notification_send_mail'])) + { + return; + } + + // ID of the items whose state has changed. + $pks = ArrayHelper::toInteger($pks); + + if (empty($pks)) + { + return; + } + + // Get UserIds of Receivers + $userIds = $this->getUsersFromGroup($transition); + + // The active user + $user = $this->app->getIdentity(); + + // Prepare Language for messages + $defaultLanguage = ComponentHelper::getParams('com_languages')->get('administrator'); + $debug = $this->app->get('debug_lang'); + + $modelName = $component->getModelName($context); + $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]); + + // Don't send the notification to the active user + $key = array_search($user->id, $userIds); + + if (is_integer($key)) + { + unset($userIds[$key]); + } + + // Remove users with locked input box from the list of receivers + if (!empty($userIds)) + { + $userIds = $this->removeLocked($userIds); + } + + // If there are no receivers, stop here + if (empty($userIds)) + { + return; + } + + // Get the model for private messages + $model_message = $this->app->bootComponent('com_messages') + ->getMVCFactory()->createModel('Message', 'Administrator'); + + // Get the title of the stage + $model_stage = $this->app->bootComponent('com_workflow') + ->getMVCFactory()->createModel('Stage', 'Administrator'); + + $toStage = $model_stage->getItem($transition->to_stage_id)->title; + + $hasGetItem = method_exists($model, 'getItem'); + $container = Factory::getContainer(); + + foreach ($pks as $pk) + { + // Get the title of the item which has changed + $title = ''; + + if ($hasGetItem) + { + $title = $model->getItem($pk)->title; + } + + // Send Email to receivers + foreach ($userIds as $user_id) + { + $receiver = $container->get(UserFactoryInterface::class)->loadUserById($user_id); + + // Load language for messaging + $lang = $container->get(LanguageFactoryInterface::class)->createLanguage($user->getParam('admin_language', $defaultLanguage), $debug); + $lang->load('plg_workflow_notification'); + $messageText = sprintf($lang->_('PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_MSG'), $title, $user->name, $lang->_($toStage)); + + if (!empty($transition->options['notification_text'])) + { + $messageText .= '
' . htmlspecialchars($lang->_($transition->options['notification_text'])); + } + + $message = [ + 'id' => 0, + 'user_id_to' => $receiver->id, + 'subject' => sprintf($lang->_('PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_SUBJECT'), $modelName), + 'message' => $messageText, + ]; + + $model_message->save($message); + } + } + + $this->app->enqueueMessage(Text::_('PLG_WORKFLOW_NOTIFICATION_SENT'), 'message'); + + } + + /* + * Get user_ids of receivers + * + * @param object $data Object containing data about the transition + * + * @return array $userIds The receivers + * + * @since __DEPLOY_VERSION__ + */ + private function getUsersFromGroup($data): Array + { + $users = []; + + // Single userIds + if (!empty($data->options['notification_receivers'])) + { + $users = ArrayHelper::toInteger($data->options['notification_receivers']); + } + + // Usergroups + $groups = []; + + if (!empty($data->options['notification_groups'])) + { + $groups = ArrayHelper::toInteger($data->options['notification_groups']); + } + + $users2 = []; + + if (!empty($groups)) + { + // UserIds from usergroups + $model = Factory::getApplication()->bootComponent('com_users') + ->getMVCFactory()->createModel('Users', 'Administrator', ['ignore_request' => true]); + + $model->setState('list.select', 'id'); + $model->setState('filter.groups', $groups); + $model->setState('filter.state', 0); + $model->setState('filter.active', 1); + $model->setState('filter.sendEmail', 1); + + // Ids from usergroups + $groupUsers = $model->getItems(); + + $users2 = ArrayHelper::getColumn($groupUsers, 'id'); + } + + // Merge userIds from individual entries and userIDs from groups + return array_unique(array_merge($users, $users2)); + } + + + /** + * Check if the current plugin should execute workflow related activities + * + * @param string $context + * + * @return boolean + * + * @since 4.0.0 + */ + protected function isSupported($context) + { + if (!$this->checkWhiteAndBlacklist($context)) + { + return false; + } + + $parts = explode('.', $context); + + // We need at least the extension + view for loading the table fields + if (count($parts) < 2) + { + return false; + } + + $component = $this->app->bootComponent($parts[0]); + + if (!$component instanceof WorkflowServiceInterface + || !$component->isWorkflowActive($context)) + { + return false; + } + + return true; + } + + /* + * Remove receivers who have locked their message inputbox + * + * @param array $uerIds The userIds which must be checked + * + * @return array users with active message input box + * + * @since __DEPLOY_VERSION__ + */ + private function removeLocked(array $userIds): Array + { + if (empty($userIds)) + { + return []; + } + + // Check for locked inboxes would be better to have _cdf settings in the user_object or a filter in users model + $query = $this->db->getQuery(true); + + $query->select($this->db->quoteName('user_id')) + ->from($this->db->quoteName('#__messages_cfg')) + ->whereIn($this->db->quoteName('user_id'), $userIds) + ->where($this->db->quoteName('cfg_name') . ' = ' . $this->db->quote('locked')) + ->where($this->db->quoteName('cfg_value') . ' = 1'); + + $locked = $this->db->setQuery($query)->loadColumn(); + + return array_diff($userIds, $locked); + } +} diff --git a/plugins/workflow/notification/notification.xml b/plugins/workflow/notification/notification.xml new file mode 100644 index 0000000000000..6ff63442f3f2f --- /dev/null +++ b/plugins/workflow/notification/notification.xml @@ -0,0 +1,42 @@ + + + plg_workflow_notification + Joomla! Project + May 2020 + Copyright (C) 2005 - 2020 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + 4.0.0 + PLG_WORKFLOW_NOTIFICATION_XML_DESCRIPTION + + notification.php + + + plg_workflow_notification.ini + plg_workflow_notification.sys.ini + + + +
+ + +
+
+
+ +
diff --git a/plugins/workflow/publishing/forms/action.xml b/plugins/workflow/publishing/forms/action.xml new file mode 100644 index 0000000000000..6c1fd15f6ff3c --- /dev/null +++ b/plugins/workflow/publishing/forms/action.xml @@ -0,0 +1,19 @@ + +
+ +
+ + + +
+
+ diff --git a/plugins/workflow/publishing/publishing.php b/plugins/workflow/publishing/publishing.php new file mode 100644 index 0000000000000..6576eea577afe --- /dev/null +++ b/plugins/workflow/publishing/publishing.php @@ -0,0 +1,504 @@ + 'onContentPrepareForm', + 'onAfterDisplay' => 'onAfterDisplay', + 'onWorkflowBeforeTransition' => 'onWorkflowBeforeTransition', + 'onWorkflowAfterTransition' => 'onWorkflowAfterTransition', + 'onContentBeforeChangeState' => 'onContentBeforeChangeState', + 'onContentBeforeSave' => 'onContentBeforeSave', + 'onWorkflowFunctionalityUsed' => 'onWorkflowFunctionalityUsed', + ]; + } + + /** + * The form event. + * + * @param EventInterface $event The event + * + * @since __DEPLOY_VERSION__ + */ + public function onContentPrepareForm(EventInterface $event) + { + $form = $event->getArgument('0'); + $data = $event->getArgument('1'); + + $context = $form->getName(); + + // Extend the transition form + if ($context === 'com_workflow.transition') + { + $this->enhanceTransitionForm($form, $data); + + return; + } + + $this->enhanceItemForm($form, $data); + + return; + } + + /** + * Add different parameter options to the transition view, we need when executing the transition + * + * @param Form $form The form + * @param stdClass $data The data + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + protected function enhanceTransitionForm(Form $form, $data) + { + $workflow = $this->enhanceWorkflowTransitionForm($form, $data); + + if (!$workflow) + { + return true; + } + + $form->setFieldAttribute('publishing', 'extension', $workflow->extension, 'options'); + + return true; + } + + /** + * Disable certain fields in the item form view, when we want to take over this function in the transition + * Check also for the workflow implementation and if the field exists + * + * @param Form $form The form + * @param stdClass $data The data + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + protected function enhanceItemForm(Form $form, $data) + { + $context = $form->getName(); + + if (!$this->isSupported($context)) + { + return true; + } + + $parts = explode('.', $context); + + $component = $this->app->bootComponent($parts[0]); + + $modelName = $component->getModelName($context); + + $table = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]) + ->getTable(); + + $fieldname = $table->getColumnAlias('published'); + + $options = $form->getField($fieldname)->options; + + $value = isset($data->$fieldname) ? $data->$fieldname : $form->getValue($fieldname, null, 0); + + $text = '-'; + + $textclass = 'body'; + + switch ($value) + { + case 1: + $textclass = 'success'; + break; + + case 0: + case -2: + $textclass = 'danger'; + } + + if (!empty($options)) + { + foreach ($options as $option) + { + if ($option->value == $value) + { + $text = $option->text; + + break; + } + } + } + + $form->setFieldAttribute($fieldname, 'type', 'spacer'); + + $label = '' . htmlentities($text, ENT_COMPAT, 'UTF-8') . ''; + $form->setFieldAttribute($fieldname, 'label', Text::sprintf('PLG_WORKFLOW_PUBLISHING_PUBLISHED', $label)); + + return true; + } + + /** + * Manipulate the generic list view + * + * @param DisplayEvent $event + * + * @since 4.0.0 + */ + public function onAfterDisplay(DisplayEvent $event) + { + $app = Factory::getApplication(); + + if (!$app->isClient('administrator')) + { + return; + } + + $component = $event->getArgument('extensionName'); + $section = $event->getArgument('section'); + + // We need the single model context for checking for workflow + $singularsection = Inflector::singularize($section); + + if (!$this->isSupported($component . '.' . $singularsection)) + { + return true; + } + + // That's the hard coded list from the AdminController publish method => change, when it's make dynamic in the future + $states = [ + 'publish', + 'unpublish', + 'archive', + 'trash', + 'report' + ]; + + $js = " + document.addEventListener('DOMContentLoaded', function() + { + var dropdown = document.getElementById('toolbar-dropdown-status-group'); + + if (!dropdown) + { + reuturn; + } + + " . \json_encode($states) . ".forEach((action) => { + var button = document.getElementById('status-group-children-' + action); + + if (button) + { + button.classList.add('d-none'); + } + }); + + }); + "; + + $app->getDocument()->addScriptDeclaration($js); + + return true; + } + + /** + * Check if we can execute the transition + * + * @param WorkflowTransitionEvent $event + * + * @return boolean + * + * @since 4.0.0 + */ + public function onWorkflowBeforeTransition(WorkflowTransitionEvent $event) + { + $context = $event->getArgument('extension'); + $transition = $event->getArgument('transition'); + $pks = $event->getArgument('pks'); + + if (!$this->isSupported($context) || !is_numeric($transition->options->get('publishing'))) + { + return true; + } + + $value = $transition->options->get('publishing'); + + if (!is_numeric($value)) + { + return true; + } + + /** + * Here it becomes tricky. We would like to use the component models publish method, so we will + * Execute the normal "onContentBeforeChangeState" plugins. But they could cancel the execution, + * So we have to precheck and cancel the whole transition stuff if not allowed. + */ + $this->app->set('plgWorkflowPublishing.' . $context, $pks); + + $result = $this->app->triggerEvent('onContentBeforeChangeState', [ + $context, + $pks, + $value + ] + ); + + // Release whitelist, the job is done + $this->app->set('plgWorkflowPublishing.' . $context, []); + + if (\in_array(false, $result, true)) + { + return false; + } + + return true; + } + + /** + * Change State of an item. Used to disable state change + * + * @param WorkflowTransitionEvent $event + * + * @return boolean + * + * @since 4.0.0 + */ + public function onWorkflowAfterTransition(WorkflowTransitionEvent $event) + { + $context = $event->getArgument('extension'); + $extensionName = $event->getArgument('extensionName'); + $transition = $event->getArgument('transition'); + $pks = $event->getArgument('pks'); + + if (!$this->isSupported($context)) + { + return true; + } + + $component = $this->app->bootComponent($extensionName); + + $value = $transition->options->get('publishing'); + + if (!is_numeric($value)) + { + return; + } + + $options = [ + 'ignore_request' => true, + // We already have triggered onContentBeforeChangeState, so use our own + 'event_before_change_state' => 'onWorkflowBeforeChangeState' + ]; + + $modelName = $component->getModelName($context); + + $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), $options); + + $model->publish($pks, $value); + } + + /** + * Change State of an item. Used to disable state change + * + * @param EventInterface $event + * + * @return boolean + * + * @throws Exception + * @since 4.0.0 + */ + public function onContentBeforeChangeState(EventInterface $event) + { + $context = $event->getArgument('0'); + $pks = $event->getArgument('1'); + + if (!$this->isSupported($context)) + { + return true; + } + + // We have whitelisted the pks, so we're the one who triggered + // With onWorkflowBeforeTransition => free pass + if ($this->app->get('plgWorkflowPublishing.' . $context) === $pks) + { + return true; + } + + throw new Exception(Text::_('PLG_WORKFLOW_PUBLISHING_CHANGE_STATE_NOT_ALLOWED')); + } + + /** + * The save event. + * + * @param EventInterface $event + * + * @return boolean + * + * @since 4.0.0 + */ + public function onContentBeforeSave(EventInterface $event) + { + $context = $event->getArgument('0'); + + // @var TableInterface + + $table = $event->getArgument('1'); + $isNew = $event->getArgument('2'); + $data = $event->getArgument('3'); + + if (!$this->isSupported($context)) + { + return true; + } + + $keyName = $table->getColumnAlias('published'); + + // Check for the old value + $article = clone $table; + + $article->load($table->id); + + // We don't allow the change of the state when we use the workflow + // As we're setting the field to disabled, no value should be there at all + if (isset($data[$keyName])) + { + $this->app->enqueueMessage(Text::_('PLG_WORKFLOW_PUBLISHING_CHANGE_STATE_NOT_ALLOWED'), 'error'); + + return false; + } + + return true; + } + + /** + * Check if the current plugin should execute workflow related activities + * + * @param string $context + * + * @return boolean + * + * @since 4.0.0 + */ + protected function isSupported($context) + { + if (!$this->checkWhiteAndBlacklist($context) || !$this->checkExtensionSupport($context, $this->supportFunctionality)) + { + return false; + } + + $parts = explode('.', $context); + + // We need at least the extension + view for loading the table fields + if (count($parts) < 2) + { + return false; + } + + $component = $this->app->bootComponent($parts[0]); + + if (!$component instanceof WorkflowServiceInterface + || !$component->isWorkflowActive($context) + || !$component->supportFunctionality($this->supportFunctionality, $context)) + { + return false; + } + + $modelName = $component->getModelName($context); + + $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]); + + if (!$model instanceof DatabaseModelInterface || !method_exists($model, 'publish')) + { + return false; + } + + $table = $model->getTable(); + + if (!$table instanceof TableInterface || !$table->hasField('published')) + { + return false; + } + + return true; + } + + /** + * If plugin supports the functionality we set the used variable + * + * @param WorkflowFunctionalityUsedEvent $event + * + * @since 4.0.0 + */ + public function onWorkflowFunctionalityUsed(WorkflowFunctionalityUsedEvent $event) + { + $functionality = $event->getArgument('functionality'); + + if ($functionality !== 'core.state') + { + return; + } + + $event->setUsed(); + } +} diff --git a/plugins/workflow/publishing/publishing.xml b/plugins/workflow/publishing/publishing.xml new file mode 100644 index 0000000000000..648194d180361 --- /dev/null +++ b/plugins/workflow/publishing/publishing.xml @@ -0,0 +1,42 @@ + + + plg_workflow_publishing + Joomla! Project + March 2020 + Copyright (C) 2005 - 2020 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + 4.0.0 + PLG_WORKFLOW_PUBLISHING_XML_DESCRIPTION + + publishing.php + + + plg_workflow_publishing.ini + plg_workflow_publishing.sys.ini + + + +
+ + +
+
+
+ +
diff --git a/tests/Codeception/_support/Step/Acceptance/Administrator/Content.php b/tests/Codeception/_support/Step/Acceptance/Administrator/Content.php index cad35c9d28942..7620ac3ab84de 100644 --- a/tests/Codeception/_support/Step/Acceptance/Administrator/Content.php +++ b/tests/Codeception/_support/Step/Acceptance/Administrator/Content.php @@ -20,11 +20,17 @@ */ class Content extends Admin { + /** + * Flag if workflows are enabled by default + * + * @var bool + */ + private $workflowsEnabled = false; /** * Method to create an article. * - * @param Array articleDetails Array with Article Details like Title, Alias, Content etc + * @param array articleDetails Array with Article Details like Title, Alias, Content etc * * @return void * @@ -121,7 +127,16 @@ public function unPublishArticle($title) $I->checkAllResults(); $I->clickToolbarButton('Action'); $I->wait(2); - $I->clickToolbarButton('unpublish'); + + if ($this->workflowsEnabled) + { + $I->clickToolbarButton('transition', '1'); + } + else + { + $I->clickToolbarButton('unpublish'); + } + $I->filterByCondition($title, "Unpublished"); } @@ -144,7 +159,16 @@ public function publishArticle($title) $I->checkAllResults(); $I->clickToolbarButton('Action'); $I->wait(2); - $I->clickToolbarButton('publish'); + + if ($this->workflowsEnabled) + { + $I->clickToolbarButton('transition', '2'); + } + else + { + $I->clickToolbarButton('publish'); + } + $I->filterByCondition($title, "Published"); } @@ -168,7 +192,16 @@ public function trashArticle($title) $I->checkAllResults(); $I->clickToolbarButton('Action'); $I->wait(2); - $I->clickToolbarButton('trash'); + + if ($this->workflowsEnabled) + { + $I->clickToolbarButton('transition', '3'); + } + else + { + $I->clickToolbarButton('trash'); + } + $I->filterByCondition($title, "Trashed"); } @@ -210,7 +243,7 @@ public function filterByCondition($title, $condition) $I = $this; $I->click("//div[@class='js-stools-container-bar']//button[contains(text(), 'Filter')]"); $I->wait(2); - $I->selectOptionInChosenByIdUsingJs('filter_condition', $condition); + $I->selectOptionInChosenByIdUsingJs('filter_published', $condition); $I->see($title); } }
, , @@ -101,6 +110,11 @@
+ +
id); ?> @@ -216,6 +206,22 @@ +
+ $transitions, + 'title' => Text::_($item->stage_title), + 'tip_content' => Text::sprintf('JWORKFLOW', Text::_($item->workflow_title)) + ]; + + echo (new TransitionButton($options)) + ->render(0, $i); + ?> +
+
!$canChange ]; + if ($workflow_enabled) : + $options['disabled'] = true; + endif; + echo (new FeaturedButton) ->render((int) $item->featured, $i, $options, $item->featured_up, $item->featured_down); ?> -
-
- 'articles.', + 'disabled' => $workflow_enabled || !$canChange + ]; - $options = [ - 'transitions' => $transitions, - 'stage' => Text::_($item->stage_title), - 'id' => (int) $item->id - ]; - - echo (new PublishedButton) - ->removeState(0) - ->removeState(1) - ->removeState(2) - ->removeState(-2) - ->addState(ContentComponent::CONDITION_PUBLISHED, '', 'publish', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JPUBLISHED')]) - ->addState(ContentComponent::CONDITION_UNPUBLISHED, '', 'unpublish', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JUNPUBLISHED')]) - ->addState(ContentComponent::CONDITION_ARCHIVED, '', 'archive', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JARCHIVED')]) - ->addState(ContentComponent::CONDITION_TRASHED, '', 'trash', Text::_('COM_CONTENT_CHANGE_STAGE'), ['tip_title' => Text::_('JTRASHED')]) - ->setLayout('joomla.button.transition-button') - ->render((int) $item->stage_condition, $i, $options, $item->publish_up, $item->publish_down); - ?> -
-
+ echo (new PublishedButton)->render((int) $item->state, $i, $options, $item->publish_up, $item->publish_down); + ?>
@@ -399,6 +393,10 @@ + + + + diff --git a/administrator/components/com_menus/presets/alternate.xml b/administrator/components/com_menus/presets/alternate.xml index bbcfacb76098d..909ce19c642d7 100644 --- a/administrator/components/com_menus/presets/alternate.xml +++ b/administrator/components/com_menus/presets/alternate.xml @@ -305,7 +305,7 @@ title="COM_CONTENT_MENUS_WORKFLOW" type="component" element="com_workflow" - link="index.php?option=com_workflow&view=workflows&extension=com_content" + link="index.php?option=com_workflow&view=workflows&extension=com_content.article" /> - - - @@ -32,20 +24,17 @@ name="fullordering" type="list" label="JGLOBAL_SORT_BY" - statuses="*,0,1,2,-2" - onchange="this.form.submit();" default="s.ordering ASC" + onchange="this.form.submit();" validate="options" > - - + + - - diff --git a/administrator/components/com_workflow/forms/filter_transitions.xml b/administrator/components/com_workflow/forms/filter_transitions.xml index 4a839964ea748..0b2b2d39f3186 100644 --- a/administrator/components/com_workflow/forms/filter_transitions.xml +++ b/administrator/components/com_workflow/forms/filter_transitions.xml @@ -24,6 +24,7 @@ onchange="this.form.submit();" sql_select="id as value, title as from_stage" sql_from="#__workflow_stages" + translate="true" > @@ -34,6 +35,7 @@ onchange="this.form.submit();" sql_select="id as value, title as to_stage" sql_from="#__workflow_stages" + translate="true" > @@ -45,13 +47,12 @@ type="list" label="JGLOBAL_SORT_BY" default="t.ordering ASC" - statuses="*,0,1,2,-2" onchange="this.form.submit();" validate="options" > - - + + diff --git a/administrator/components/com_workflow/forms/filter_workflows.xml b/administrator/components/com_workflow/forms/filter_workflows.xml index cfd1063ba0274..e6c2915ce6efc 100644 --- a/administrator/components/com_workflow/forms/filter_workflows.xml +++ b/administrator/components/com_workflow/forms/filter_workflows.xml @@ -1,5 +1,13 @@ - + +
+ +
-
diff --git a/administrator/components/com_workflow/src/Controller/DisplayController.php b/administrator/components/com_workflow/src/Controller/DisplayController.php index 2841602e80aeb..c4c47cd284578 100644 --- a/administrator/components/com_workflow/src/Controller/DisplayController.php +++ b/administrator/components/com_workflow/src/Controller/DisplayController.php @@ -40,6 +40,14 @@ class DisplayController extends BaseController */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Constructor. * @@ -58,7 +66,16 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu // If extension is not set try to get it from input or throw an exception if (empty($this->extension)) { - $this->extension = $this->input->getCmd('extension'); + $extension = $this->input->getCmd('extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (empty($this->extension)) { diff --git a/administrator/components/com_workflow/src/Controller/StageController.php b/administrator/components/com_workflow/src/Controller/StageController.php index 4d38fafc7b6f6..69e7625fec329 100644 --- a/administrator/components/com_workflow/src/Controller/StageController.php +++ b/administrator/components/com_workflow/src/Controller/StageController.php @@ -40,6 +40,14 @@ class StageController extends FormController */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Constructor. * @@ -69,7 +77,16 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu // If extension is not set try to get it from input or throw an exception if (empty($this->extension)) { - $this->extension = $this->input->getCmd('extension'); + $extension = $this->input->getCmd('extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (empty($this->extension)) { @@ -89,18 +106,7 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu */ protected function allowAdd($data = array()) { - $user = Factory::getUser(); - - $model = $this->getModel('Workflow'); - - $workflow = $model->getItem($this->workflowId); - - if ($workflow->core) - { - return false; - } - - return $user->authorise('core.create', $this->extension); + return $this->app->getIdentity()->authorise('core.create', $this->extension); } /** @@ -116,20 +122,7 @@ protected function allowAdd($data = array()) protected function allowEdit($data = array(), $key = 'id') { $recordId = isset($data[$key]) ? (int) $data[$key] : 0; - $user = Factory::getUser(); - - $model = $this->getModel(); - - $item = $model->getItem($recordId); - - $model = $this->getModel('Workflow'); - - $workflow = $model->getItem($item->workflow_id); - - if ($workflow->core) - { - return false; - } + $user = $this->app->getIdentity(); // Check "edit" permission on record asset (explicit or inherited) if ($user->authorise('core.edit', $this->extension . '.stage.' . $recordId)) @@ -163,7 +156,7 @@ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id') { $append = parent::getRedirectToItemAppend($recordId); - $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension; + $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''); return $append; } @@ -178,7 +171,7 @@ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id') protected function getRedirectToListAppend() { $append = parent::getRedirectToListAppend(); - $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension; + $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''); return $append; } diff --git a/administrator/components/com_workflow/src/Controller/StagesController.php b/administrator/components/com_workflow/src/Controller/StagesController.php index 4d78855b3e3e5..dd2aab5c6e03f 100644 --- a/administrator/components/com_workflow/src/Controller/StagesController.php +++ b/administrator/components/com_workflow/src/Controller/StagesController.php @@ -42,6 +42,14 @@ class StagesController extends AdminController */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * The prefix to use with controller messages. * @@ -79,7 +87,16 @@ public function __construct(array $config = array(), MVCFactoryInterface $factor // If extension is not set try to get it from input or throw an exception if (empty($this->extension)) { - $this->extension = $this->input->getCmd('extension'); + $extension = $this->input->getCmd('extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (empty($this->extension)) { @@ -182,6 +199,6 @@ public function setDefault() */ protected function getRedirectToListAppend() { - return '&extension=' . $this->extension . '&workflow_id=' . $this->workflowId; + return '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '') . '&workflow_id=' . $this->workflowId; } } diff --git a/administrator/components/com_workflow/src/Controller/TransitionController.php b/administrator/components/com_workflow/src/Controller/TransitionController.php index 7fbc647c1ca68..215bb5e2dde69 100644 --- a/administrator/components/com_workflow/src/Controller/TransitionController.php +++ b/administrator/components/com_workflow/src/Controller/TransitionController.php @@ -39,6 +39,14 @@ class TransitionController extends FormController */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Constructor. * @@ -68,7 +76,16 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu // If extension is not set try to get it from input or throw an exception if (empty($this->extension)) { - $this->extension = $this->input->getCmd('extension'); + $extension = $this->input->getCmd('extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (empty($this->extension)) { @@ -88,18 +105,7 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu */ protected function allowAdd($data = array()) { - $user = $this->app->getIdentity(); - - $model = $this->getModel('Workflow'); - - $workflow = $model->getItem($this->workflowId); - - if ($workflow->core) - { - return false; - } - - return $user->authorise('core.create', $this->extension); + return $this->app->getIdentity()->authorise('core.create', $this->extension); } /** @@ -125,11 +131,6 @@ protected function allowEdit($data = array(), $key = 'id') $workflow = $model->getItem($item->workflow_id); - if ($workflow->core) - { - return false; - } - // Check "edit" permission on record asset (explicit or inherited) if ($user->authorise('core.edit', $this->extension . '.transition.' . $recordId)) { diff --git a/administrator/components/com_workflow/src/Controller/TransitionsController.php b/administrator/components/com_workflow/src/Controller/TransitionsController.php index 045e5f7dfd58a..7882376468aea 100644 --- a/administrator/components/com_workflow/src/Controller/TransitionsController.php +++ b/administrator/components/com_workflow/src/Controller/TransitionsController.php @@ -39,6 +39,14 @@ class TransitionsController extends AdminController */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * The prefix to use with controller messages. * @@ -76,7 +84,16 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu // If extension is not set try to get it from input or throw an exception if (empty($this->extension)) { - $this->extension = $this->input->getCmd('extension'); + $extension = $this->input->getCmd('extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (empty($this->extension)) { @@ -110,7 +127,11 @@ public function getModel($name = 'Transition', $prefix = 'Administrator', $confi */ protected function getRedirectToListAppend() { - return '&extension=' . $this->extension + $append = parent::getRedirectToListAppend(); + + $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '') . '&workflow_id=' . $this->workflowId; + + return $append; } } diff --git a/administrator/components/com_workflow/src/Controller/WorkflowController.php b/administrator/components/com_workflow/src/Controller/WorkflowController.php index 819ed6323586a..25e8b2677d958 100644 --- a/administrator/components/com_workflow/src/Controller/WorkflowController.php +++ b/administrator/components/com_workflow/src/Controller/WorkflowController.php @@ -32,6 +32,14 @@ class WorkflowController extends FormController */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Constructor. * @@ -50,7 +58,16 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu // If extension is not set try to get it from input or throw an exception if (empty($this->extension)) { - $this->extension = $this->input->getCmd('extension'); + $extension = $this->input->getCmd('extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (empty($this->extension)) { @@ -90,11 +107,6 @@ protected function allowEdit($data = array(), $key = 'id') $record = $this->getModel()->getItem($recordId); - if (!empty($record->id) && $record->core) - { - return false; - } - // Check "edit" permission on record asset (explicit or inherited) if ($user->authorise('core.edit', $this->extension . '.workflow.' . $recordId)) { @@ -123,7 +135,7 @@ protected function allowEdit($data = array(), $key = 'id') protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id') { $append = parent::getRedirectToItemAppend($recordId); - $append .= '&extension=' . $this->extension; + $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''); return $append; } @@ -138,7 +150,7 @@ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id') protected function getRedirectToListAppend() { $append = parent::getRedirectToListAppend(); - $append .= '&extension=' . $this->extension; + $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''); return $append; } diff --git a/administrator/components/com_workflow/src/Controller/WorkflowsController.php b/administrator/components/com_workflow/src/Controller/WorkflowsController.php index 552fe8643cf63..01c9f8dd988d6 100644 --- a/administrator/components/com_workflow/src/Controller/WorkflowsController.php +++ b/administrator/components/com_workflow/src/Controller/WorkflowsController.php @@ -34,6 +34,14 @@ class WorkflowsController extends AdminController */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Constructor. * @@ -52,7 +60,16 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu // If extension is not set try to get it from input or throw an exception if (empty($this->extension)) { - $this->extension = $this->input->getCmd('extension'); + $extension = $this->input->getCmd('extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (empty($this->extension)) { @@ -103,7 +120,7 @@ public function setDefault() $this->setRedirect( Route::_( 'index.php?option=' . $this->option . '&view=' . $this->view_list - . '&extension=' . $this->extension, false + . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''), false ) ); @@ -149,25 +166,7 @@ public function setDefault() $this->setRedirect( Route::_( 'index.php?option=' . $this->option . '&view=' . $this->view_list - . '&extension=' . $this->extension, false - ) - ); - } - - /** - * Deletes and returns correctly. - * - * @return void - * - * @since 4.0.0 - */ - public function delete() - { - parent::delete(); - $this->setRedirect( - Route::_( - 'index.php?option=' . $this->option . '&view=' . $this->view_list - . '&extension=' . $this->extension, false + . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''), false ) ); } @@ -181,6 +180,6 @@ public function delete() */ protected function getRedirectToListAppend() { - return '&extension=' . $this->extension; + return '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''); } } diff --git a/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php b/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php index b30e6ee24bbc5..792a1a263e96d 100644 --- a/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php +++ b/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php @@ -11,6 +11,7 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Access\Exception\NotAllowed; use Joomla\CMS\Dispatcher\ComponentDispatcher; /** diff --git a/administrator/components/com_workflow/src/Field/WorkflowcontextsField.php b/administrator/components/com_workflow/src/Field/WorkflowcontextsField.php new file mode 100644 index 0000000000000..c5af01c7622d1 --- /dev/null +++ b/administrator/components/com_workflow/src/Field/WorkflowcontextsField.php @@ -0,0 +1,70 @@ +getOptions()) < 2) + { + $this->layout = 'joomla.form.field.hidden'; + } + + return parent::getInput(); + } + + /** + * Method to get the field options. + * + * @return array The field option objects. + * + * @since 4.0.0 + */ + protected function getOptions() + { + $parts = explode('.', $this->value); + + $component = Factory::getApplication()->bootComponent($parts[0]); + + if ($component instanceof WorkflowServiceInterface) + { + return $component->getWorkflowContexts(); + } + + return []; + } +} diff --git a/administrator/components/com_workflow/src/Model/StageModel.php b/administrator/components/com_workflow/src/Model/StageModel.php index c2227fa184878..f9b802861f50c 100644 --- a/administrator/components/com_workflow/src/Model/StageModel.php +++ b/administrator/components/com_workflow/src/Model/StageModel.php @@ -121,7 +121,7 @@ protected function canDelete($record) $table->load($record->workflow_id); - if (empty($record->id) || $record->published != -2 || $table->core) + if (empty($record->id) || $record->published != -2) { return false; } @@ -165,15 +165,6 @@ protected function canEditState($record) $record->workflow_id = $workflowID; } - $table = $this->getTable('Workflow', 'Administrator'); - - $table->load($record->workflow_id); - - if ($table->core) - { - return false; - } - // Check for existing workflow. if (!empty($record->id)) { diff --git a/administrator/components/com_workflow/src/Model/StagesModel.php b/administrator/components/com_workflow/src/Model/StagesModel.php index 151ea6340ac26..817ded91d0ae3 100644 --- a/administrator/components/com_workflow/src/Model/StagesModel.php +++ b/administrator/components/com_workflow/src/Model/StagesModel.php @@ -37,7 +37,6 @@ public function __construct($config = array()) 'id', 's.id', 'title', 's.title', 'ordering','s.ordering', - 'condition','s.condition', 'published', 's.published' ); } @@ -134,7 +133,6 @@ public function getListQuery() 's.id', 's.title', 's.ordering', - 's.condition', 's.default', 's.published', 's.checked_out', @@ -153,21 +151,13 @@ public function getListQuery() $query->where($db->quoteName('s.workflow_id') . ' = ' . $workflowID); } - $condition = $this->getState('filter.condition'); - - // Filter by condition - if (is_numeric($condition)) - { - $query->where($db->quoteName('s.condition') . ' = ' . (int) $db->escape($condition)); - } - // Join over the users for the checked out user. $query->select($db->quoteName('uc.name', 'editor')) ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('s.checked_out')); $status = (string) $this->getState('filter.published'); - // Filter by condition + // Filter by publish state if (is_numeric($status)) { $query->where($db->quoteName('s.published') . ' = ' . (int) $status); diff --git a/administrator/components/com_workflow/src/Model/TransitionModel.php b/administrator/components/com_workflow/src/Model/TransitionModel.php index 3481eb6cae7cb..a6a48afcab21e 100644 --- a/administrator/components/com_workflow/src/Model/TransitionModel.php +++ b/administrator/components/com_workflow/src/Model/TransitionModel.php @@ -12,8 +12,10 @@ \defined('_JEXEC') or die; use Joomla\CMS\Factory; +use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\AdminModel; +use Joomla\Registry\Registry; use Joomla\String\StringHelper; /** @@ -54,11 +56,7 @@ public function populateState() */ protected function canDelete($record) { - $table = $this->getTable('Workflow', 'Administrator'); - - $table->load($record->workflow_id); - - if (empty($record->id) || $record->published != -2 || $table->core) + if (empty($record->id) || $record->published != -2) { return false; } @@ -91,15 +89,6 @@ protected function canEditState($record) $record->workflow_id = $workflowID; } - $table = $this->getTable('Workflow', 'Administrator'); - - $table->load($record->workflow_id); - - if ($table->core) - { - return false; - } - // Check for existing workflow. if (!empty($record->id)) { @@ -110,6 +99,28 @@ protected function canEditState($record) return $user->authorise('core.edit.state', $extension); } + /** + * Method to get a single record. + * + * @param integer $pk The id of the primary key. + * + * @return CMSObject|boolean Object on success, false on failure. + * + * @since 4.0.0 + */ + public function getItem($pk = null) + { + $item = parent::getItem($pk); + + if (property_exists($item, 'options')) + { + $registry = new Registry($item->options); + $item->options = $registry->toArray(); + } + + return $item; + } + /** * Method to save the form data. * @@ -132,35 +143,6 @@ public function save($data) $isNew = false; } - if ($data['to_stage_id'] == $data['from_stage_id']) - { - $this->setError(Text::_('COM_WORKFLOW_MSG_FROM_TO_STAGE')); - - return false; - } - - $db = $this->getDbo(); - $query = $db->getQuery(true) - ->select($db->quoteName('id')) - ->from($db->quoteName('#__workflow_transitions')) - ->where($db->quoteName('from_stage_id') . ' = ' . (int) $data['from_stage_id']) - ->where($db->quoteName('to_stage_id') . ' = ' . (int) $data['to_stage_id']); - - if (!$isNew) - { - $query->where($db->quoteName('id') . ' <> ' . (int) $data['id']); - } - - $db->setQuery($query); - $duplicate = $db->loadResult(); - - if (!empty($duplicate)) - { - $this->setError(Text::_("COM_WORKFLOW_TRANSITION_DUPLICATE")); - - return false; - } - $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); if (empty($data['workflow_id'])) @@ -221,6 +203,10 @@ protected function generateNewTitle($category_id, $alias, $title) */ public function getForm($data = array(), $loadData = true) { + $app = Factory::getApplication(); + + $context = $this->option . '.' . $this->name; + // Get the form. $form = $this->loadForm( 'com_workflow.transition', @@ -238,24 +224,31 @@ public function getForm($data = array(), $loadData = true) if ($loadData) { - $data = (object) $this->loadFormData(); + $data = (array) $this->loadFormData(); } - if (!$this->canEditState((object) $data)) + if (empty($data['workflow_id'])) { - // Disable fields for display. - $form->setFieldAttribute('published', 'disabled', 'true'); - - // Disable fields while saving. - // The controller has already verified this is a record you can edit. - $form->setFieldAttribute('published', 'filter', 'unset'); + $data['workflow_id'] = (int) $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); } - $app = Factory::getApplication(); + // Disable state when no permission to change + $disableFields = []; - $workflow_id = $app->input->getInt('workflow_id'); + if (!$this->canEditState((object) $data)) + { + $disableFields[] = 'published'; + } - $where = $this->getDbo()->quoteName('workflow_id') . ' = ' . $workflow_id . ' AND ' . $this->getDbo()->quoteName('published') . ' = 1'; + foreach ($disableFields as $field) + { + $form->setFieldAttribute($field, 'disabled', 'true'); + $form->setFieldAttribute($field, 'required', 'false'); + $form->setFieldAttribute($field, 'filter', 'unset'); + } + + $where = $this->getDbo()->quoteName('workflow_id') . ' = ' . (int) $data['workflow_id']; + $where .= ' AND ' . $this->getDbo()->quoteName('published') . ' = 1'; $form->setFieldAttribute('from_stage_id', 'sql_where', $where); $form->setFieldAttribute('to_stage_id', 'sql_where', $where); @@ -285,4 +278,37 @@ protected function loadFormData() return $data; } + + public function getWorkflow() + { + $app = Factory::getApplication(); + + $context = $this->option . '.' . $this->name; + + $workflow_id = (int) $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); + + $workflow = $this->getTable('Workflow'); + + $workflow->load($workflow_id); + + return (object) $workflow->getProperties(); + } + + /** + * Trigger the form preparation for the workflow group + * + * @param Form $form A Form object. + * @param mixed $data The data expected for the form. + * @param string $group The name of the plugin group to import (defaults to "content"). + * + * @return void + * + * @see FormField + * @since 4.0.0 + * @throws \Exception if there is an error in the form event. + */ + protected function preprocessForm(Form $form, $data, $group = 'workflow') + { + parent::preprocessForm($form, $data, $group); + } } diff --git a/administrator/components/com_workflow/src/Model/TransitionsModel.php b/administrator/components/com_workflow/src/Model/TransitionsModel.php index 5977afb4d2c2b..f7037188b2957 100644 --- a/administrator/components/com_workflow/src/Model/TransitionsModel.php +++ b/administrator/components/com_workflow/src/Model/TransitionsModel.php @@ -146,9 +146,7 @@ public function getListQuery() ); $select[] = $db->quoteName('f_stage.title', 'from_stage'); - $select[] = $db->quoteName('f_stage.condition', 'from_condition'); $select[] = $db->quoteName('t_stage.title', 'to_stage'); - $select[] = $db->quoteName('t_stage.condition', 'to_condition'); $joinTo = $db->quoteName('#__workflow_stages', 't_stage') . ' ON ' . $db->quoteName('t_stage.id') . ' = ' . $db->quoteName('t.to_stage_id'); @@ -172,7 +170,7 @@ public function getListQuery() $status = $this->getState('filter.published'); - // Filter by condition + // Filter by status if (is_numeric($status)) { $query->where($db->quoteName('t.published') . ' = ' . (int) $status); diff --git a/administrator/components/com_workflow/src/Model/WorkflowModel.php b/administrator/components/com_workflow/src/Model/WorkflowModel.php index 52f2db78e4215..5cf20b0ac9bc8 100644 --- a/administrator/components/com_workflow/src/Model/WorkflowModel.php +++ b/administrator/components/com_workflow/src/Model/WorkflowModel.php @@ -105,64 +105,21 @@ public function save($data) $result = parent::save($data); - // Create default stages/transitions + // Create default stage for new workflow if ($result && $input->getCmd('task') !== 'save2copy' && $this->getState($this->getName() . '.new')) { $workflow_id = (int) $this->getState($this->getName() . '.id'); - $stages = [ - [ - 'title' => 'JUNPUBLISHED', - 'condition' => Workflow::CONDITION_UNPUBLISHED, - 'default' => 1, - 'transition' => 'Unpublish' - ], - [ - 'title' => 'JPUBLISHED', - 'condition' => Workflow::CONDITION_PUBLISHED, - 'transition' => 'Publish' - ], - [ - 'title' => 'JTRASHED', - 'condition' => Workflow::CONDITION_TRASHED, - 'transition' => 'Trash' - ], - [ - 'title' => 'JARCHIVED', - 'condition' => Workflow::CONDITION_ARCHIVED, - 'transition' => 'Archive' - ] - ]; - $table = $this->getTable('Stage'); - $transition = $this->getTable('Transition'); - - foreach ($stages as $stage) - { - $table->reset(); - - $table->id = 0; - $table->title = $stage['title']; - $table->workflow_id = $workflow_id; - $table->condition = $stage['condition']; - $table->published = 1; - $table->default = (int) !empty($stage['default']); - $table->description = ''; - $table->store(); - - $transition->reset(); + $table->id = 0; + $table->title = 'COM_WORKFLOW_BASIC_STAGE'; + $table->description = ''; + $table->workflow_id = $workflow_id; + $table->published = 1; + $table->default = 1; - $transition->id = 0; - $transition->title = $stage['transition']; - $transition->description = ''; - $transition->workflow_id = $workflow_id; - $transition->published = 1; - $transition->from_stage_id = -1; - $transition->to_stage_id = (int) $table->id; - - $transition->store(); - } + $table->store(); } return $result; @@ -264,6 +221,10 @@ protected function preprocessForm(Form $form, $data, $group = 'content') { $extension = Factory::getApplication()->input->get('extension'); + $parts = explode('.', $extension); + + $extension = array_shift($parts); + // Set the access control rules field component value. $form->setFieldAttribute('rules', 'component', $extension); $form->setFieldAttribute('rules', 'section', 'workflow'); @@ -348,7 +309,7 @@ public function setDefault($pk, $value = 1) */ protected function canDelete($record) { - if (empty($record->id) || $record->published != -2 || $record->core) + if (empty($record->id) || $record->published != -2) { return false; } @@ -369,11 +330,6 @@ protected function canEditState($record) { $user = Factory::getUser(); - if (!empty($record->core)) - { - return false; - } - // Check for existing workflow. if (!empty($record->id)) { diff --git a/administrator/components/com_workflow/src/Model/WorkflowsModel.php b/administrator/components/com_workflow/src/Model/WorkflowsModel.php index d6b912e896693..a05bb2b15ab02 100644 --- a/administrator/components/com_workflow/src/Model/WorkflowsModel.php +++ b/administrator/components/com_workflow/src/Model/WorkflowsModel.php @@ -116,6 +116,28 @@ public function getItems() return $items; } + /** + * Get the filter form + * + * @param array $data data + * @param boolean $loadData load current data + * + * @return \JForm|false the JForm object or false + * + * @since 4.0.0 + */ + public function getFilterForm($data = array(), $loadData = true) + { + $form = parent::getFilterForm($data, $loadData); + + if ($form) + { + $form->setValue('extension', null, $this->getState('filter.extension')); + } + + return $form; + } + /** * Add the number of transitions and states to all workflow items * @@ -197,7 +219,6 @@ public function getListQuery() 'w.checked_out_time', 'w.ordering', 'w.default', - 'w.core', 'w.created_by', 'w.description', 'u.name' @@ -217,7 +238,7 @@ public function getListQuery() $status = (string) $this->getState('filter.published'); - // Filter by condition + // Filter by status if (is_numeric($status)) { $query->where($db->quoteName('w.published') . ' = ' . (int) $status); diff --git a/administrator/components/com_workflow/src/Table/StageTable.php b/administrator/components/com_workflow/src/Table/StageTable.php index 8b91cac144abe..31589d6180e0f 100644 --- a/administrator/components/com_workflow/src/Table/StageTable.php +++ b/administrator/components/com_workflow/src/Table/StageTable.php @@ -207,7 +207,11 @@ protected function _getAssetName() $workflow = new WorkflowTable($this->getDbo()); $workflow->load($this->workflow_id); - return $workflow->extension . '.stage.' . (int) $this->$k; + $parts = explode('.', $workflow->extension); + + $extension = array_shift($parts); + + return $extension . '.stage.' . (int) $this->$k; } /** @@ -235,9 +239,16 @@ protected function _getAssetTitle() protected function _getAssetParentId(Table $table = null, $id = null) { $asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo())); + $workflow = new WorkflowTable($this->getDbo()); $workflow->load($this->workflow_id); - $name = $workflow->extension . '.workflow.' . (int) $workflow->id; + + $parts = explode('.', $workflow->extension); + + $extension = array_shift($parts); + + $name = $extension . '.workflow.' . (int) $workflow->id; + $asset->loadByName($name); $assetId = $asset->id; diff --git a/administrator/components/com_workflow/src/Table/TransitionTable.php b/administrator/components/com_workflow/src/Table/TransitionTable.php index f2c9ad30f5a17..9da1e1a2bbca0 100644 --- a/administrator/components/com_workflow/src/Table/TransitionTable.php +++ b/administrator/components/com_workflow/src/Table/TransitionTable.php @@ -30,6 +30,16 @@ class TransitionTable extends Table */ protected $_supportNullValue = true; + /** + * An array of key names to be json encoded in the bind function + * + * @var array + * @since 4.0.0 + */ + protected $_jsonEncode = [ + 'options' + ]; + /** * Constructor * @@ -44,21 +54,6 @@ public function __construct(DatabaseDriver $db) $this->access = (int) Factory::getApplication()->get('access'); } - /** - * Overloaded store function - * - * @param boolean $updateNulls True to update fields even if they are null. - * - * @return mixed False on failure, positive integer on success. - * - * @see Table::store() - * @since 4.0.0 - */ - public function store($updateNulls = true) - { - return parent::store($updateNulls); - } - /** * Method to compute the default name of the asset. * The default name is in the form table_name.id @@ -74,7 +69,11 @@ protected function _getAssetName() $workflow = new WorkflowTable($this->getDbo()); $workflow->load($this->workflow_id); - return $workflow->extension . '.transition.' . (int) $this->$k; + $parts = explode('.', $workflow->extension); + + $extension = array_shift($parts); + + return $extension . '.transition.' . (int) $this->$k; } /** @@ -102,9 +101,16 @@ protected function _getAssetTitle() protected function _getAssetParentId(Table $table = null, $id = null) { $asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo())); + $workflow = new WorkflowTable($this->getDbo()); $workflow->load($this->workflow_id); - $name = $workflow->extension . '.workflow.' . (int) $workflow->id; + + $parts = explode('.', $workflow->extension); + + $extension = array_shift($parts); + + $name = $extension . '.workflow.' . (int) $workflow->id; + $asset->loadByName($name); $assetId = $asset->id; diff --git a/administrator/components/com_workflow/src/Table/WorkflowTable.php b/administrator/components/com_workflow/src/Table/WorkflowTable.php index 502d757dc3e5f..04797b014fc51 100644 --- a/administrator/components/com_workflow/src/Table/WorkflowTable.php +++ b/administrator/components/com_workflow/src/Table/WorkflowTable.php @@ -243,7 +243,11 @@ protected function _getAssetName() { $k = $this->_tbl_key; - return $this->extension . '.workflow.' . (int) $this->$k; + $parts = explode('.', $this->extension); + + $extension = array_shift($parts); + + return $extension . '.workflow.' . (int) $this->$k; } /** @@ -272,11 +276,15 @@ protected function _getAssetParentId(Table $table = null, $id = null) { $assetId = null; + $parts = explode('.', $this->extension); + + $extension = array_shift($parts); + // Build the query to get the asset id for the parent category. $query = $this->getDbo()->getQuery(true) ->select($this->getDbo()->quoteName('id')) ->from($this->getDbo()->quoteName('#__assets')) - ->where($this->getDbo()->quoteName('name') . ' = ' . $this->getDbo()->quote($this->extension)); + ->where($this->getDbo()->quoteName('name') . ' = ' . $this->getDbo()->quote($extension)); // Get the asset id from the database. $this->getDbo()->setQuery($query); diff --git a/administrator/components/com_workflow/src/View/Stage/HtmlView.php b/administrator/components/com_workflow/src/View/Stage/HtmlView.php index 93b783558a60c..7fe44313e013c 100644 --- a/administrator/components/com_workflow/src/View/Stage/HtmlView.php +++ b/administrator/components/com_workflow/src/View/Stage/HtmlView.php @@ -56,6 +56,14 @@ class HtmlView extends BaseHtmlView */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Display item view * @@ -77,7 +85,17 @@ public function display($tpl = null) $this->state = $this->get('State'); $this->form = $this->get('Form'); $this->item = $this->get('Item'); - $this->extension = $this->state->get('filter.extension'); + + $extension = $this->state->get('filter.extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } // Set the toolbar $this->addToolBar(); @@ -101,7 +119,7 @@ protected function addToolbar() $userId = $user->id; $isNew = empty($this->item->id); - $canDo = StageHelper::getActions($this->extension, 'state', $this->item->id); + $canDo = StageHelper::getActions($this->extension, 'stage', $this->item->id); ToolbarHelper::title(empty($this->item->id) ? Text::_('COM_WORKFLOW_STAGE_ADD') : Text::_('COM_WORKFLOW_STAGE_EDIT'), 'address'); diff --git a/administrator/components/com_workflow/src/View/Stages/HtmlView.php b/administrator/components/com_workflow/src/View/Stages/HtmlView.php index 2c8b121c04b34..9813e9f69207e 100644 --- a/administrator/components/com_workflow/src/View/Stages/HtmlView.php +++ b/administrator/components/com_workflow/src/View/Stages/HtmlView.php @@ -99,6 +99,14 @@ class HtmlView extends BaseHtmlView */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Display the view * @@ -124,17 +132,20 @@ public function display($tpl = null) $this->workflow = $this->get('Workflow'); $this->workflowID = $this->workflow->id; - $this->extension = $this->workflow->extension; + + $parts = explode('.', $this->workflow->extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } if (!empty($this->stages)) { $extension = Factory::getApplication()->input->getCmd('extension'); $workflow = new Workflow(['extension' => $extension]); - - foreach ($this->stages as $i => $item) - { - $item->condition = $workflow->getConditionName((int) $item->condition); - } } $this->addToolbar(); @@ -153,11 +164,12 @@ protected function addToolbar() { $canDo = ContentHelper::getActions($this->extension, 'workflow', $this->workflowID); + $user = Factory::getUser(); + $toolbar = Toolbar::getInstance('toolbar'); ToolbarHelper::title(Text::sprintf('COM_WORKFLOW_STAGES_LIST', Text::_($this->state->get('active_workflow', ''))), 'address contact'); - $isCore = $this->workflow->core; $arrow = Factory::getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left'; ToolbarHelper::link( @@ -166,48 +178,45 @@ protected function addToolbar() $arrow ); - if (!$isCore) + if ($canDo->get('core.create')) { - if ($canDo->get('core.create')) - { - $toolbar->addNew('stage.add'); - } + $toolbar->addNew('stage.add'); + } + + if ($canDo->get('core.edit.state') || $user->authorise('core.admin')) + { + $dropdown = $toolbar->dropdownButton('status-group') + ->text('JTOOLBAR_CHANGE_STATUS') + ->toggleSplit(false) + ->icon('fas fa-ellipsis-h') + ->buttonClass('btn btn-action') + ->listCheck(true); + + $childBar = $dropdown->getChildToolbar(); + + $childBar->publish('stages.publish', 'JTOOLBAR_ENABLE')->listCheck(true); + $childBar->unpublish('stages.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true); + $childBar->makeDefault('stages.setDefault', 'COM_WORKFLOW_TOOLBAR_DEFAULT'); - if ($canDo->get('core.edit.state') || $user->authorise('core.admin')) + if ($canDo->get('core.admin')) { - $dropdown = $toolbar->dropdownButton('status-group') - ->text('JTOOLBAR_CHANGE_STATUS') - ->toggleSplit(false) - ->icon('fas fa-ellipsis-h') - ->buttonClass('btn btn-action') - ->listCheck(true); - - $childBar = $dropdown->getChildToolbar(); - - $childBar->publish('stages.publish', 'JTOOLBAR_ENABLE')->listCheck(true); - $childBar->unpublish('stages.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true); - $childBar->makeDefault('stages.setDefault', 'COM_WORKFLOW_TOOLBAR_DEFAULT'); - - if ($canDo->get('core.admin')) - { - $childBar->checkin('stages.checkin')->listCheck(true); - } - - if ($this->state->get('filter.published') !== '-2') - { - $childBar->trash('stages.trash'); - } + $childBar->checkin('stages.checkin')->listCheck(true); } - if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete') && !$isCore) + if ($this->state->get('filter.published') !== '-2') { - $toolbar->delete('stages.delete') - ->text('JTOOLBAR_EMPTY_TRASH') - ->message('JGLOBAL_CONFIRM_DELETE') - ->listCheck(true); + $childBar->trash('stages.trash'); } } + if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete')) + { + $toolbar->delete('stages.delete') + ->text('JTOOLBAR_EMPTY_TRASH') + ->message('JGLOBAL_CONFIRM_DELETE') + ->listCheck(true); + } + $toolbar->help('JHELP_WORKFLOW_STAGES_LIST'); } } diff --git a/administrator/components/com_workflow/src/View/Transition/HtmlView.php b/administrator/components/com_workflow/src/View/Transition/HtmlView.php index aabab96354a4a..1614c167fffab 100644 --- a/administrator/components/com_workflow/src/View/Transition/HtmlView.php +++ b/administrator/components/com_workflow/src/View/Transition/HtmlView.php @@ -72,6 +72,22 @@ class HtmlView extends BaseHtmlView */ protected $workflowID; + /** + * The name of current extension + * + * @var string + * @since 4.0.0 + */ + protected $extension; + + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Display item view * @@ -96,7 +112,17 @@ public function display($tpl = null) $this->state = $this->get('State'); $this->form = $this->get('Form'); $this->item = $this->get('Item'); - $this->extension = $this->state->get('filter.extension'); + + $extension = $this->state->get('filter.extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } // Get the ID of workflow $this->workflowID = $this->input->getCmd("workflow_id"); @@ -129,10 +155,12 @@ protected function addToolbar() $toolbarButtons = []; + $canCreate = $canDo->get('core.create'); + if ($isNew) { // For new records, check the create permission. - if ($canDo->get('core.edit')) + if ($canCreate) { ToolbarHelper::apply('transition.apply'); $toolbarButtons = [['save', 'transition.save'], ['save2new', 'transition.save2new']]; @@ -151,20 +179,27 @@ protected function addToolbar() if ($itemEditable) { ToolbarHelper::apply('transition.apply'); - $toolbarButtons = [['save', 'transition.save']]; + $toolbarButtons[] = ['save', 'transition.save']; // We can save this record, but check the create permission to see if we can return to make a new one. - if ($canDo->get('core.create')) + if ($canCreate) { $toolbarButtons[] = ['save2new', 'transition.save2new']; $toolbarButtons[] = ['save2copy', 'transition.save2copy']; } } - ToolbarHelper::saveGroup( - $toolbarButtons, - 'btn-success' - ); + if (count($toolbarButtons) > 1) + { + ToolbarHelper::saveGroup( + $toolbarButtons, + 'btn-success' + ); + } + else + { + ToolbarHelper::save('transition.save'); + } } ToolbarHelper::cancel('transition.cancel'); diff --git a/administrator/components/com_workflow/src/View/Transitions/HtmlView.php b/administrator/components/com_workflow/src/View/Transitions/HtmlView.php index d0d70b94f300e..7fb4db0430e60 100644 --- a/administrator/components/com_workflow/src/View/Transitions/HtmlView.php +++ b/administrator/components/com_workflow/src/View/Transitions/HtmlView.php @@ -98,6 +98,14 @@ class HtmlView extends BaseHtmlView */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Display the view * @@ -123,7 +131,15 @@ public function display($tpl = null) $this->workflow = $this->get('Workflow'); $this->workflowID = $this->workflow->id; - $this->extension = $this->workflow->extension; + + $parts = explode('.', $this->workflow->extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } $this->addToolbar(); @@ -141,11 +157,12 @@ protected function addToolbar() { $canDo = ContentHelper::getActions($this->extension, 'workflow', $this->workflowID); + $user = Factory::getUser(); + $toolbar = Toolbar::getInstance('toolbar'); ToolbarHelper::title(Text::sprintf('COM_WORKFLOW_TRANSITIONS_LIST', Text::_($this->state->get('active_workflow'))), 'address contact'); - $isCore = $this->workflow->core; $arrow = Factory::getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left'; ToolbarHelper::link( @@ -154,47 +171,44 @@ protected function addToolbar() $arrow ); - if (!$isCore) + if ($canDo->get('core.create')) { - if ($canDo->get('core.create')) - { - $toolbar->addNew('transition.add'); - } + $toolbar->addNew('transition.add'); + } + + if ($canDo->get('core.edit.state') || $user->authorise('core.admin')) + { + $dropdown = $toolbar->dropdownButton('status-group') + ->text('JTOOLBAR_CHANGE_STATUS') + ->toggleSplit(false) + ->icon('fas fa-ellipsis-h') + ->buttonClass('btn btn-action') + ->listCheck(true); - if ($canDo->get('core.edit.state') || $user->authorise('core.admin')) + $childBar = $dropdown->getChildToolbar(); + + $childBar->publish('transitions.publish', 'JTOOLBAR_ENABLE'); + $childBar->unpublish('transitions.unpublish', 'JTOOLBAR_DISABLE'); + + if ($canDo->get('core.admin')) { - $dropdown = $toolbar->dropdownButton('status-group') - ->text('JTOOLBAR_CHANGE_STATUS') - ->toggleSplit(false) - ->icon('fas fa-ellipsis-h') - ->buttonClass('btn btn-action') - ->listCheck(true); - - $childBar = $dropdown->getChildToolbar(); - - $childBar->publish('transitions.publish', 'JTOOLBAR_ENABLE'); - $childBar->unpublish('transitions.unpublish', 'JTOOLBAR_DISABLE'); - - if ($canDo->get('core.admin')) - { - $childBar->checkin('transitions.checkin')->listCheck(true); - } - - if ($this->state->get('filter.published') !== '-2') - { - $childBar->trash('transitions.trash'); - } + $childBar->checkin('transitions.checkin')->listCheck(true); } - if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete')) + if ($this->state->get('filter.published') !== '-2') { - $toolbar->delete('transitions.delete') - ->text('JTOOLBAR_EMPTY_TRASH') - ->message('JGLOBAL_CONFIRM_DELETE') - ->listCheck(true); + $childBar->trash('transitions.trash'); } } + if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete')) + { + $toolbar->delete('transitions.delete') + ->text('JTOOLBAR_EMPTY_TRASH') + ->message('JGLOBAL_CONFIRM_DELETE') + ->listCheck(true); + } + $toolbar->help('JHELP_WORKFLOW_TRANSITIONS_LIST'); } } diff --git a/administrator/components/com_workflow/src/View/Workflow/HtmlView.php b/administrator/components/com_workflow/src/View/Workflow/HtmlView.php index 2829f1b85eecb..60df1924181f8 100644 --- a/administrator/components/com_workflow/src/View/Workflow/HtmlView.php +++ b/administrator/components/com_workflow/src/View/Workflow/HtmlView.php @@ -62,6 +62,14 @@ class HtmlView extends BaseHtmlView */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Display item view * @@ -77,7 +85,17 @@ public function display($tpl = null) $this->state = $this->get('State'); $this->form = $this->get('Form'); $this->item = $this->get('Item'); - $this->extension = $this->state->get('filter.extension'); + + $extension = $this->state->get('filter.extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } // Check for errors. if (count($errors = $this->get('Errors'))) @@ -132,7 +150,7 @@ protected function addToolbar() // Since it's an existing record, check the edit permission, or fall back to edit own if the owner. $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId); - if ($itemEditable && !$this->item->core) + if ($itemEditable) { ToolbarHelper::apply('workflow.apply'); $toolbarButtons = [['save', 'workflow.save']]; diff --git a/administrator/components/com_workflow/src/View/Workflows/HtmlView.php b/administrator/components/com_workflow/src/View/Workflows/HtmlView.php index 91c42fcac355e..6847a17b15c69 100644 --- a/administrator/components/com_workflow/src/View/Workflows/HtmlView.php +++ b/administrator/components/com_workflow/src/View/Workflows/HtmlView.php @@ -10,6 +10,7 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Factory; use Joomla\CMS\Helper\ContentHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\View\GenericDataException; @@ -80,6 +81,14 @@ class HtmlView extends BaseHtmlView */ protected $extension; + /** + * The section of the current extension + * + * @var string + * @since 4.0.0 + */ + protected $section; + /** * Display the view * @@ -103,7 +112,16 @@ public function display($tpl = null) throw new GenericDataException(implode("\n", $errors), 500); } - $this->extension = $this->state->get('filter.extension'); + $extension = $this->state->get('filter.extension'); + + $parts = explode('.', $extension); + + $this->extension = array_shift($parts); + + if (!empty($parts)) + { + $this->section = array_shift($parts); + } $this->addToolbar(); @@ -119,7 +137,9 @@ public function display($tpl = null) */ protected function addToolbar() { - $canDo = ContentHelper::getActions($this->extension); + $canDo = ContentHelper::getActions($this->extension, $this->section); + + $user = Factory::getApplication()->getIdentity(); // Get the toolbar object instance $toolbar = Toolbar::getInstance('toolbar'); diff --git a/administrator/components/com_workflow/tmpl/stage/edit.php b/administrator/components/com_workflow/tmpl/stage/edit.php index 220038f1a04d4..f8bc54f05fa7b 100644 --- a/administrator/components/com_workflow/tmpl/stage/edit.php +++ b/administrator/components/com_workflow/tmpl/stage/edit.php @@ -39,7 +39,6 @@
- form->renderField('condition'); ?> form->renderField('description'); ?>
diff --git a/administrator/components/com_workflow/tmpl/stages/default.php b/administrator/components/com_workflow/tmpl/stages/default.php index 7c706a4d2f1ee..74db6c9419393 100644 --- a/administrator/components/com_workflow/tmpl/stages/default.php +++ b/administrator/components/com_workflow/tmpl/stages/default.php @@ -27,8 +27,6 @@ $saveOrder = ($listOrder == 's.ordering'); -$isCore = $this->workflow->core; - if ($saveOrder) { $saveOrderingUrl = 'index.php?option=com_workflow&task=stages.saveOrderAjax&workflow_id=' . (int) $this->workflowID . '&extension=' . $this->escape($this->extension) . '&' . Session::getFormToken() . '=1'; @@ -68,18 +66,15 @@
- + + + + - - - published, $i, 'stages.', $canChange && !$isCore); ?> + published, $i, 'stages.', $canChange); ?> default, $i, 'stages.', $canChange); ?> @@ -125,7 +120,7 @@ checked_out) : ?> editor, $item->checked_out_time, 'stages.', $canCheckin); ?> - + escape(Text::_($item->title)); ?> @@ -135,21 +130,6 @@
escape(Text::_($item->description)); ?>
-
- condition == 'JARCHIVED'): - $icon = 'icon-archive'; - elseif ($item->condition == 'JTRASHED'): - $icon = 'icon-trash'; - elseif ($item->condition == 'JPUBLISHED'): - $icon = 'icon-publish'; - elseif ($item->condition == 'JUNPUBLISHED'): - $icon = 'icon-unpublish'; - endif; - ?> - - condition); ?> - id; ?> + + + @@ -89,9 +87,10 @@ transitions as $i => $item): $edit = Route::_('index.php?option=com_workflow&task=transition.edit&id=' . $item->id . '&workflow_id=' . (int) $this->workflowID . '&extension=' . $this->extension); - $canEdit = $user->authorise('core.edit', $this->extension . '.transition.' . $item->id) && !$isCore; - $canCheckin = $user->authorise('core.admin', 'com_workflow') || $item->checked_out == $userId || is_null($item->checked_out); - $canChange = $user->authorise('core.edit.state', $this->extension . '.transition.' . $item->id) && $canCheckin && !$isCore; ?> + $canEdit = $user->authorise('core.edit', $this->extension . '.transition.' . $item->id); + $canCheckin = $user->authorise('core.admin', 'com_workflow') || $item->checked_out == $user->id || is_null($item->checked_out); + $canChange = $user->authorise('core.edit.state', $this->extension . '.transition.' . $item->id) && $canCheckin; + ?>
id); ?> @@ -135,42 +134,10 @@ from_stage_id < 0): ?> - from_condition == Workflow::CONDITION_ARCHIVED): - $icon = 'icon-archive'; - $condition = Text::_('JARCHIVED'); - elseif ($item->from_condition == Workflow::CONDITION_TRASHED): - $icon = 'icon-trash'; - $condition = Text::_('JTRASHED'); - elseif ($item->from_condition == Workflow::CONDITION_PUBLISHED): - $icon = 'icon-publish'; - $condition = Text::_('JPUBLISHED'); - elseif ($item->from_condition == Workflow::CONDITION_UNPUBLISHED): - $icon = 'icon-unpublish'; - $condition = Text::_('JUNPUBLISHED'); - endif; ?> - - escape(Text::_($item->from_stage)); ?> - to_condition == Workflow::CONDITION_ARCHIVED): - $icon = 'icon-archive'; - $condition = Text::_('JARCHIVED'); - elseif ($item->to_condition == Workflow::CONDITION_TRASHED): - $icon = 'icon-trash'; - $condition = Text::_('JTRASHED'); - elseif ($item->to_condition == Workflow::CONDITION_PUBLISHED): - $icon = 'icon-publish'; - $condition = Text::_('JPUBLISHED'); - elseif ($item->to_condition == Workflow::CONDITION_UNPUBLISHED): - $icon = 'icon-unpublish'; - $condition = Text::_('JUNPUBLISHED'); - endif; ?> - - escape(Text::_($item->to_stage)); ?> diff --git a/administrator/components/com_workflow/tmpl/workflows/default.php b/administrator/components/com_workflow/tmpl/workflows/default.php index 6791eab751aca..0a579616fd227 100644 --- a/administrator/components/com_workflow/tmpl/workflows/default.php +++ b/administrator/components/com_workflow/tmpl/workflows/default.php @@ -53,7 +53,7 @@
$this)); + echo LayoutHelper::render('joomla.searchtools.default', array('view' => $this, 'options' => array('selectorFieldName' => 'extension'))); ?> workflows)) : ?>
@@ -75,10 +75,10 @@
+ + @@ -101,7 +101,6 @@ $transitions = Route::_('index.php?option=com_workflow&view=transitions&workflow_id=' . $item->id . '&extension=' . $extension); $edit = Route::_('index.php?option=com_workflow&task=workflow.edit&id=' . $item->id . '&extension=' . $extension); - $isCore = !empty($item->core); $canEdit = $user->authorise('core.edit', $extension . '.workflow.' . $item->id); $canCheckin = $user->authorise('core.admin', 'com_workflow') || $item->checked_out == $userId || is_null($item->checked_out); $canEditOwn = $user->authorise('core.edit.own', $extension . '.workflow.' . $item->id) && $item->created_by == $userId; @@ -131,13 +130,13 @@ - published, $i, 'workflows.', $canChange && !$isCore); ?> + published, $i, 'workflows.', $canChange); ?> checked_out) : ?> editor, $item->checked_out_time, 'workflows.', $canCheckin); ?> - + escape(Text::_($item->title)); ?> @@ -170,7 +169,6 @@ - diff --git a/administrator/language/en-GB/com_content.ini b/administrator/language/en-GB/com_content.ini index efba71e6a34c1..7489424f6a1e1 100644 --- a/administrator/language/en-GB/com_content.ini +++ b/administrator/language/en-GB/com_content.ini @@ -168,7 +168,7 @@ COM_CONTENT_SUBMENU_CATEGORIES="Categories" COM_CONTENT_SUBMENU_FEATURED="Featured Articles" COM_CONTENT_SUBMENU_WORKFLOWS="Workflows" COM_CONTENT_TIP_ASSOCIATION="Associated articles" -COM_CONTENT_TRANSITION="Status" +COM_CONTENT_TRANSITION="Transition" COM_CONTENT_TRASHED="Trashed" COM_CONTENT_UNFEATURED="Unfeatured Article" COM_CONTENT_UNPUBLISHED="Unpublished" diff --git a/administrator/language/en-GB/com_workflow.ini b/administrator/language/en-GB/com_workflow.ini index ebda98c477ad5..47c1ea6f62070 100644 --- a/administrator/language/en-GB/com_workflow.ini +++ b/administrator/language/en-GB/com_workflow.ini @@ -6,6 +6,7 @@ COM_WORKFLOW_ARE_YOU_SURE="Are you sure?" COM_WORKFLOW_AUTHOR="Author" COM_WORKFLOW_BASIC_TAB="Basic" +COM_WORKFLOW_BASIC_STAGE="Basic Stage" COM_WORKFLOW_CHOOSE_CONTEXT_LABEL="Context" COM_WORKFLOW_CONDITION="Condition of items in this stage: " COM_WORKFLOW_CONDITION_DESC="Defines item behaviour." @@ -22,10 +23,10 @@ COM_WORKFLOW_DATE_CREATED="Date Created" COM_WORKFLOW_DATE_MODIFIED="Date Modified" COM_WORKFLOW_DEFAULT="Default" COM_WORKFLOW_DEFAULT_ITEM="Default option is already set for a different item." -COM_WORKFLOW_DEFAULT_WORKFLOW="Joomla! Default" +COM_WORKFLOW_BASIC_WORKFLOW="Basic Workflow" COM_WORKFLOW_DESCRIPTION="Description" COM_WORKFLOW_DESC_TAB="Description" -COM_WORKFLOW_DISABLE_DEFAULT="Cannot change default stage of this item." +COM_WORKFLOW_DISABLE_DEFAULT="The default status cannot be changed." COM_WORKFLOW_EDIT="Edit" COM_WORKFLOW_EDIT_TAB="Edit" COM_WORKFLOW_ERROR_EXTENSION_NOT_SET="Extension not set." @@ -98,6 +99,7 @@ COM_WORKFLOW_TOO_MANY_STAGES="Only one stage can be set as the default." COM_WORKFLOW_TOO_MANY_WORKFLOWS="Only one workflow can be set as the default." COM_WORKFLOW_TO_STAGE="Target Stage" COM_WORKFLOW_TRANSITION="Transition" +COM_WORKFLOW_TRANSITION_ACTIONS_LABEL="Transition Actions" COM_WORKFLOW_TRANSITIONS="Transitions" COM_WORKFLOW_TRANSITIONS_LIST="Transitions List: %s" COM_WORKFLOW_TRANSITIONS_N_ITEMS_CHECKED_IN="%s transitions checked in." @@ -118,7 +120,7 @@ COM_WORKFLOW_TRANSITION_NOTE="Note" COM_WORKFLOW_TRANSITION_THE_SAME_STAGE="Current stage and target stage are the same." COM_WORKFLOW_TRASHED="Trashed" COM_WORKFLOW_UNPUBLISHED="Unpublished" -COM_WORKFLOW_UNPUBLISH_DEFAULT_ERROR="You can't unpublish the default workflow." +COM_WORKFLOW_UNPUBLISH_DEFAULT_ERROR="The default workflow cannot be disabled." COM_WORKFLOW_USER_GROUPS="User Group" COM_WORKFLOW_USER_GROUPS_DESC="Select user group." COM_WORKFLOW_USE_DEFAULT_WORKFLOW="Use default (%s)" diff --git a/administrator/language/en-GB/com_workflow.sys.ini b/administrator/language/en-GB/com_workflow.sys.ini index 53b54f1ecc162..5baa202207a65 100644 --- a/administrator/language/en-GB/com_workflow.sys.ini +++ b/administrator/language/en-GB/com_workflow.sys.ini @@ -4,7 +4,7 @@ ; Note : All ini files need to be saved as UTF-8 COM_WORKFLOW="Workflows" -COM_WORKFLOW_DEFAULT_WORKFLOW="Joomla! Default" +COM_WORKFLOW_BASIC_WORKFLOW="Basic Workflow" COM_WORKFLOW_MESSAGES_VIEW_DEFAULT_DESC="Customised workflow support for Joomla! site" COM_WORKFLOW_MESSAGES_VIEW_DEFAULT_TITLE="Workflows" COM_WORKFLOW_XML_DESCRIPTION="Customised workflow support for Joomla! site" diff --git a/administrator/language/en-GB/joomla.ini b/administrator/language/en-GB/joomla.ini index 15e68a5a206fd..6e345c18c637c 100644 --- a/administrator/language/en-GB/joomla.ini +++ b/administrator/language/en-GB/joomla.ini @@ -115,6 +115,10 @@ JREGISTER="Register" JORDERINGDISABLED="Please sort by order to enable reordering" JSAVE="Save & Close" JSELECT="Select" +JSTAGE="Stage" +JSTATUS="Status" +JSTAGE_ASC="Stage ascending" +JSTAGE_DESC="Stage descending" JSTATUS="Status" JSTATUS_ASC="Status ascending" JSTATUS_DESC="Status descending" @@ -649,7 +653,6 @@ JGLOBAL_WARNCOOKIES="Warning! Cookies must be enabled to access the Administrato JGLOBAL_WARNIE="Warning! Internet Explorer should not be used for proper operation of the Administrator Backend." JGLOBAL_WARNJAVASCRIPT="Warning! JavaScript must be enabled for proper operation of the Administrator Backend." JGLOBAL_WIDTH="Width" -JGLOBAL_WORKFLOWS_ENABLE_LABEL="Edit Workflows" JGRID_HEADING_ACCESS="Access" JGRID_HEADING_ACCESS_ASC="Access ascending" @@ -912,7 +915,6 @@ JOPTION_SELECT_AUTHORS="- Select Authors -" JOPTION_SELECT_AUTHOR_ALIAS="- Select Author Alias -" JOPTION_SELECT_AUTHOR_ALIASES="- Select Author Aliases -" JOPTION_SELECT_CATEGORY="- Select Category -" -JOPTION_SELECT_CONDITION="- Select Condition -" JOPTION_SELECT_EDITOR="- Select Editor -" JOPTION_SELECT_FEATURED="- Select Featured -" JOPTION_SELECT_IMAGE="- Select Image -" @@ -995,6 +997,17 @@ JWARNING_DELETE_MUST_SELECT="You must select at least one item to permanently de JWARNING_REMOVE_ROOT_USER="You are logged-in using the emergency Root User setting in configuration.php.
You should remove $root_user from the configuration.php as soon as you have restored control to your site to avoid future security breaches.
Select here to try to do it automatically." JWARNING_REMOVE_ROOT_USER_ADMIN="The emergency Root User setting is enabled for the user(id): %s.
You should remove $root_user from the configuration.php as soon as you have restored control to your site to avoid future security breaches.
Select here to try to do it automatically." +; Workflow +JWORKFLOW="Workflow: %s" +JWORKFLOW_TITLE="Workflow" +JWORKFLOW_ENABLED_LABEL="Enable Workflow" +JWORKFLOW_EXECUTE_TRANSITION="Select the transition to execute on this item." +JWORKFLOW_SHOW_TRANSITIONS_FOR_THIS_ITEM="Show the transition selection to execute a transition on this item." +JWORKFLOW_EXTENSION_WHITELIST_LABEL="Extension Whitelist" +JWORKFLOW_EXTENSION_WHITELIST_DESCRIPTION="Activate this plugin only for listed extensions. If used all other extensions are disabled." +JWORKFLOW_EXTENSION_BLACKLIST_LABEL="Extension Blacklist" +JWORKFLOW_EXTENSION_BLACKLIST_DESCRIPTION="Disable this plugin for listed extensions." +JWORKFLOW_FIELD_COMPONENT_SECTIONS_TEXT="%1$s: %2$s" ; Date format DATE_FORMAT_LC="l, d F Y" diff --git a/administrator/language/en-GB/plg_content_joomla.ini b/administrator/language/en-GB/plg_content_joomla.ini index 0cf5749dddb3f..c7f64edcf915a 100644 --- a/administrator/language/en-GB/plg_content_joomla.ini +++ b/administrator/language/en-GB/plg_content_joomla.ini @@ -8,8 +8,4 @@ PLG_CONTENT_JOOMLA_FIELD_CHECK_CATEGORIES_DESC="Check that categories are fully PLG_CONTENT_JOOMLA_FIELD_CHECK_CATEGORIES_LABEL="Check Category Deletion" PLG_CONTENT_JOOMLA_FIELD_EMAIL_NEW_FE_DESC="Email users if 'Send email' is on when there is a new article submitted via the Frontend." PLG_CONTENT_JOOMLA_FIELD_EMAIL_NEW_FE_LABEL="Email on New Site Article" -PLG_CONTENT_JOOMLA_FIELD_EMAIL_NEW_STAGE_DESC="Email users if 'Send email' is on when there is a status change of an article." -PLG_CONTENT_JOOMLA_FIELD_EMAIL_NEW_STAGE_LABEL="Email on transition execution" -PLG_CONTENT_JOOMLA_ON_STAGE_CHANGE_MSG="The status of an article has been changed by '%1$s' entitled '%2$s'." -PLG_CONTENT_JOOMLA_ON_STAGE_CHANGE_SUBJECT="Status of article has changed" PLG_CONTENT_JOOMLA_XML_DESCRIPTION="This plugin does category processing for core extensions; sends an email when new article is submitted in the Frontend or a transition is executed." diff --git a/administrator/language/en-GB/plg_workflow_featuring.ini b/administrator/language/en-GB/plg_workflow_featuring.ini new file mode 100644 index 0000000000000..21cb6e0e62527 --- /dev/null +++ b/administrator/language/en-GB/plg_workflow_featuring.ini @@ -0,0 +1,11 @@ +; Joomla! Project +; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_WORKFLOW_FEATURING="Workflow - Featuring" +PLG_WORKFLOW_FEATURING_CHANGE_STATE_NOT_ALLOWED="You're not allowed to change the featured state of this item. Please use a workflow transition." +PLG_WORKFLOW_FEATURING_FEATURED="Featured: %s" +PLG_WORKFLOW_FEATURING_TRANSITION_ACTIONS_FEATURING_DESC="Define the featured state an item should be set, when executing this transition" +PLG_WORKFLOW_FEATURING_TRANSITION_ACTIONS_FEATURING_LABEL="Featuring state" +PLG_WORKFLOW_FEATURING_XML_DESCRIPTION="Add featuring actions to the workflow transitions for your items" diff --git a/administrator/language/en-GB/plg_workflow_featuring.sys.ini b/administrator/language/en-GB/plg_workflow_featuring.sys.ini new file mode 100644 index 0000000000000..a80d79cb2fe80 --- /dev/null +++ b/administrator/language/en-GB/plg_workflow_featuring.sys.ini @@ -0,0 +1,7 @@ +; Joomla! Project +; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_WORKFLOW_FEATURING="Workflow - Featuring" +PLG_WORKFLOW_FEATURING_XML_DESCRIPTION="Add featuring options to the workflow transitions for your items" diff --git a/administrator/language/en-GB/plg_workflow_notification.ini b/administrator/language/en-GB/plg_workflow_notification.ini new file mode 100644 index 0000000000000..b73cc705d650c --- /dev/null +++ b/administrator/language/en-GB/plg_workflow_notification.ini @@ -0,0 +1,20 @@ +; Joomla! Project +; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +COM_WORKFLOW_NOTIFICATION_FIELDSET_LABEL="Notification" +PLG_WORKFLOW_NOTIFICATION="Workflow - Notification" +PLG_WORKFLOW_NOTIFICATION_ADDTEXT="The stage has changed" +PLG_WORKFLOW_NOTIFICATION_ADDTEXT_DESC="This text will be sent: Title [title], Changed by [user], New State: [state]. You can add own Text to this information. You can localise the Text by using a Language key and make language overrides." +PLG_WORKFLOW_NOTIFICATION_ADDTEXT_LABEL="Additional Message Text." +PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_SUBJECT="The status of an '%1$s' has been changed"; +PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_MSG="Title: '%1$s', Changed by '%2$s', New State: '%3$s'." +PLG_WORKFLOW_NOTIFICATION_RECEIVERS_LABEL="Users" +PLG_WORKFLOW_NOTIFICATION_RECEIVERS_SELECT="Select single Users who obtain a Notification" +PLG_WORKFLOW_NOTIFICATION_SENDMAIL_LABEL="Send Notification" +PLG_WORKFLOW_NOTIFICATION_SENT="Notifications have been sent" +PLG_WORKFLOW_NOTIFICATION_USERGROUP_DESC="The users in this usergroup get a notification if this transition has been performed" +PLG_WORKFLOW_NOTIFICATION_USERGROUP_LABEL="Usergroups" +PLG_WORKFLOW_NOTIFICATION_USERGROUP_SELECT="Select usergroups" +PLG_WORKFLOW_NOTIFICATION_XML_DESCRIPTION="Send Notification if a Transition has been performed in a Workflow" diff --git a/administrator/language/en-GB/plg_workflow_notification.sys.ini b/administrator/language/en-GB/plg_workflow_notification.sys.ini new file mode 100644 index 0000000000000..c585237338360 --- /dev/null +++ b/administrator/language/en-GB/plg_workflow_notification.sys.ini @@ -0,0 +1,7 @@ +; Joomla! Project +; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_WORKFLOW_NOTIFICATION="Workflow - Notification" +PLG_WORKFLOW_NOTIFICATION_XML_DESCRIPTION="Send Notification for Transitions in Publishing Workflow" \ No newline at end of file diff --git a/administrator/language/en-GB/plg_workflow_publishing.ini b/administrator/language/en-GB/plg_workflow_publishing.ini new file mode 100644 index 0000000000000..409a97882a65f --- /dev/null +++ b/administrator/language/en-GB/plg_workflow_publishing.ini @@ -0,0 +1,12 @@ +; Joomla! Project +; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_WORKFLOW_PUBLISHING="Workflow - Publishing" +PLG_WORKFLOW_PUBLISHING_CHANGE_STATE_NOT_ALLOWED="You're not allowed to change the publishing state of this item. Please use a workflow transition." +PLG_WORKFLOW_PUBLISHING_PUBLISHED="Status: %s" +PLG_WORKFLOW_PUBLISHING_TRANSITION_ACTIONS_PUBLISHING_DESC="Define the state an item should be set, when executing this transition" +PLG_WORKFLOW_PUBLISHING_TRANSITION_ACTIONS_PUBLISHING_DO_NO_CHANGE="- Do not change -" +PLG_WORKFLOW_PUBLISHING_TRANSITION_ACTIONS_PUBLISHING_LABEL="Publishing state" +PLG_WORKFLOW_PUBLISHING_XML_DESCRIPTION="Add publishing actions to the workflow transitions for your items" diff --git a/administrator/language/en-GB/plg_workflow_publishing.sys.ini b/administrator/language/en-GB/plg_workflow_publishing.sys.ini new file mode 100644 index 0000000000000..8f09ca95bbbf7 --- /dev/null +++ b/administrator/language/en-GB/plg_workflow_publishing.sys.ini @@ -0,0 +1,7 @@ +; Joomla! Project +; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_WORKFLOW_PUBLISHING="Workflow - Publishing" +PLG_WORKFLOW_PUBLISHING_XML_DESCRIPTION="Add publishing options to the workflow transitions for your items" diff --git a/administrator/modules/mod_menu/src/Menu/CssMenu.php b/administrator/modules/mod_menu/src/Menu/CssMenu.php index 6219a5f8300df..3b86b9ed11208 100644 --- a/administrator/modules/mod_menu/src/Menu/CssMenu.php +++ b/administrator/modules/mod_menu/src/Menu/CssMenu.php @@ -376,7 +376,9 @@ protected function preprocess($parent) if (isset($query['extension'])) { - $workflow = ComponentHelper::getParams($query['extension'])->get('workflows_enable', 1); + $parts = explode('.', $query['extension']); + + $workflow = ComponentHelper::getParams($parts[0])->get('workflow_enabled'); } if (!$workflow) diff --git a/administrator/modules/mod_submenu/src/Menu/Menu.php b/administrator/modules/mod_submenu/src/Menu/Menu.php index cb6cf462fffd9..a0af252bdcb86 100644 --- a/administrator/modules/mod_submenu/src/Menu/Menu.php +++ b/administrator/modules/mod_submenu/src/Menu/Menu.php @@ -129,7 +129,9 @@ public static function preprocess($parent) if (isset($query['extension'])) { - $workflow = ComponentHelper::getParams($query['extension'])->get('workflows_enable', 1); + $parts = explode('.', $query['extension']); + + $workflow = ComponentHelper::getParams($parts[0])->get('workflow_enabled'); } if (!$workflow) diff --git a/api/language/en-GB/joomla.ini b/api/language/en-GB/joomla.ini index d37b6df5223aa..0a3064752c85f 100644 --- a/api/language/en-GB/joomla.ini +++ b/api/language/en-GB/joomla.ini @@ -643,7 +643,7 @@ JGLOBAL_VOTES_DESC="Votes descending" JGLOBAL_WARNCOOKIES="Warning! Cookies must be enabled to access the Administrator Backend." JGLOBAL_WARNJAVASCRIPT="Warning! JavaScript must be enabled for proper operation of the Administrator Backend." JGLOBAL_WIDTH="Width" -JGLOBAL_WORKFLOWS_ENABLE_LABEL="Edit Workflows" +JGLOBAL_WORKFLOW_ENABLED_LABEL="Enable Workflows" JGRID_HEADING_ACCESS="Access" JGRID_HEADING_ACCESS_ASC="Access ascending" diff --git a/build/media_source/com_content/js/admin-articles-workflow-buttons.es6.js b/build/media_source/com_content/js/admin-articles-workflow-buttons.es6.js deleted file mode 100644 index 81ac73283f39a..0000000000000 --- a/build/media_source/com_content/js/admin-articles-workflow-buttons.es6.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - * @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. - * @license GNU General Public License version 2 or later; see LICENSE.txt - */ - -Joomla = window.Joomla || {}; - -/** - * Method that switches a given class to the following elements of the element provided - * - * @param {HTMLElement} element The reference element - * @param {string} className The class name to be toggled - */ -Joomla.toggleAllNextElements = (element, className) => { - const getNextSiblings = (el) => { - const siblings = []; - /* eslint-disable no-cond-assign,no-param-reassign */ - do { - siblings.push(el); - } while ((el = el.nextElementSibling) !== null); - /* eslint-enable no-cond-assign,no-param-reassign */ - return siblings; - }; - - const followingElements = getNextSiblings(element); - if (followingElements.length) { - followingElements.forEach((elem) => { - if (elem.classList.contains(className)) { - elem.classList.remove(className); - } else { - elem.classList.add(className); - } - }); - } -}; - -(() => { - 'use strict'; - - document.addEventListener('DOMContentLoaded', () => { - const dropDownBtn = document.getElementById('toolbar-dropdown-status-group'); - const publishBtn = dropDownBtn.getElementsByClassName('button-publish')[0]; - const unpublishBtn = dropDownBtn.getElementsByClassName('button-unpublish')[0]; - const archiveBtn = dropDownBtn.getElementsByClassName('button-archive')[0]; - const trashBtn = dropDownBtn.getElementsByClassName('button-trash')[0]; - const articleList = document.querySelector('#articleList'); - const modal = document.getElementById('stageModal'); - const modalcontent = document.getElementById('stageModal-content'); - const modalbutton = document.getElementById('stage-submit-button-id'); - const buttonDataSelector = 'data-submit-task'; - - let articleListRows = []; - let publishBool = false; - let unpublishBool = false; - let archiveBool = false; - let trashBool = false; - let countChecked = 0; - - if (articleList) { - articleListRows = [].slice.call(articleList.querySelectorAll('tbody tr')); - } - // TODO: remove jQuery dependency, when we have a new modal script - window.jQuery(modal).on('hide.bs.modal', () => { - modalcontent.innerHTML = ''; - }); - - function checkTransition(e, task) { - // Let's check for n:1 connections - const transitions = Joomla.getOptions('articles.transitions')[task]; - const availableTrans = {}; - let showModal = false; - - if (transitions === undefined) { - return; - } - - if (articleListRows.length) { - articleListRows.forEach((el) => { - const checkedBox = el.querySelectorAll('input[type=checkbox]')[0]; - - if (checkedBox.checked) { - const parentTr = checkedBox.closest('tr'); - const stage = parseInt(parentTr.getAttribute('data-stage_id'), 10); - const workflow = parseInt(parentTr.getAttribute('data-workflow_id'), 10); - - availableTrans[checkedBox.value] = []; - - if (transitions[workflow] === undefined) { - return; - } - - let k = 0; - - // Collect transitions - if (transitions[workflow][-1] !== undefined) { - for (let j = 0; j < transitions[workflow][-1].length; j += 1) { - if (transitions[workflow][-1][j].to_stage_id !== stage) { - availableTrans[checkedBox.value][k] = transitions[workflow][-1][j]; - - k += 1; - } - } - } - - if (transitions[workflow][stage] !== undefined) { - for (let j = 0; j < transitions[workflow][stage].length; j += 1) { - if (transitions[workflow][stage][j].to_stage_id !== stage) { - availableTrans[checkedBox.value][k] = transitions[workflow][stage][j]; - - k += 1; - } - } - } - - if (availableTrans[checkedBox.value].length > 1) { - showModal = true; - } else { - delete availableTrans[checkedBox.value]; - } - } - }); - } - - if (showModal) { - e.stopPropagation(); - - const articles = Joomla.getOptions('articles.items'); - let html = ''; - - modalbutton.setAttribute(buttonDataSelector, `articles.${task}`); - - Object.keys(availableTrans).forEach((id) => { - if (articles[`article-${id}`] !== undefined) { - html += '
'; - html += ``; - html += `'; - html += '
'; - html += ''; - } - }); - - modalcontent.innerHTML = html; - - // TODO: remove jQuery dependency, when we have a new modal script - window.jQuery(modal).modal(); - } - } - - publishBtn.parentElement.addEventListener('click', (e) => { - if (publishBtn.classList.contains('disabled')) { - e.stopImmediatePropagation(); - - Joomla.renderMessages({ error: [Joomla.JText._('COM_CONTENT_ERROR_CANNOT_PUBlISH')] }); - } else { - checkTransition(e, 'publish'); - } - }); - - unpublishBtn.parentElement.addEventListener('click', (e) => { - if (unpublishBtn.classList.contains('disabled')) { - e.stopImmediatePropagation(); - - Joomla.renderMessages({ error: [Joomla.JText._('COM_CONTENT_ERROR_CANNOT_UNPUBlISH')] }); - } else { - checkTransition(e, 'unpublish'); - } - }); - - archiveBtn.parentElement.addEventListener('click', (e) => { - if (archiveBtn.classList.contains('disabled')) { - e.stopImmediatePropagation(); - - Joomla.renderMessages({ error: [Joomla.JText._('COM_CONTENT_ERROR_CANNOT_ARCHIVE')] }); - } else { - checkTransition(e, 'archive'); - } - }); - - trashBtn.parentElement.addEventListener('click', (e) => { - if (trashBtn.classList.contains('disabled')) { - e.stopImmediatePropagation(); - - Joomla.renderMessages({ error: [Joomla.JText._('COM_CONTENT_ERROR_CANNOT_TRASH')] }); - } else { - checkTransition(e, 'trash'); - } - }); - - function setOrRemDisabled(btn, set) { - if (set) { - btn.classList.remove('disabled'); - } else { - btn.classList.add('disabled'); - } - } - - // disable or enable Buttons of transitions depending on the boolean variables - function disableButtons() { - setOrRemDisabled(publishBtn, publishBool); - setOrRemDisabled(unpublishBtn, unpublishBool); - setOrRemDisabled(archiveBtn, archiveBool); - setOrRemDisabled(trashBtn, trashBool); - } - - // check for common attributes for which the conditions for a transition are possible or not - // and save this information in a boolean variable. - function checkForAttributes(row) { - publishBool = row.getAttribute('data-condition-publish') > 0 && (countChecked === 0 || publishBool); - unpublishBool = row.getAttribute('data-condition-unpublish') > 0 && (countChecked === 0 || unpublishBool); - archiveBool = row.getAttribute('data-condition-archive') > 0 && (countChecked === 0 || archiveBool); - trashBool = row.getAttribute('data-condition-trash') > 0 && (countChecked === 0 || trashBool); - } - - // listen to click event to get selected rows - if (articleList) { - articleList.addEventListener('click', () => { - articleListRows.forEach((el) => { - const checkedBox = el.querySelectorAll('input[type=checkbox]')[0]; - - if (checkedBox.checked) { - const parentTr = checkedBox.closest('tr'); - checkForAttributes(parentTr); - countChecked += 1; - } - }); - disableButtons(); - countChecked = 0; - }); - } - }); -})(); diff --git a/build/media_source/com_workflow/js/admin-items-workflow-buttons.es6.js b/build/media_source/com_workflow/js/admin-items-workflow-buttons.es6.js new file mode 100644 index 0000000000000..b6434abc14cc8 --- /dev/null +++ b/build/media_source/com_workflow/js/admin-items-workflow-buttons.es6.js @@ -0,0 +1,113 @@ +/** + * @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +Joomla = window.Joomla || {}; + +/** + * Method that switches a given class to the following elements of the element provided + * + * @param {HTMLElement} element The reference element + * @param {string} className The class name to be toggled + */ +Joomla.toggleAllNextElements = (element, className) => { + const getNextSiblings = (el) => { + const siblings = []; + /* eslint-disable no-cond-assign,no-param-reassign */ + do { + siblings.push(el); + } while ((el = el.nextElementSibling) !== null); + /* eslint-enable no-cond-assign,no-param-reassign */ + return siblings; + }; + + const followingElements = getNextSiblings(element); + if (followingElements.length) { + followingElements.forEach((elem) => { + if (elem.classList.contains(className)) { + elem.classList.remove(className); + } else { + elem.classList.add(className); + } + }); + } +}; + +(() => { + 'use strict'; + + document.addEventListener('DOMContentLoaded', () => { + const dropDownBtn = document.getElementById('toolbar-dropdown-status-group'); + const transitions = [].slice.call(dropDownBtn.querySelectorAll('.button-transition')); + const headline = dropDownBtn.querySelector('.button-transition-headline'); + const separator = dropDownBtn.querySelector('.button-transition-separator'); + const itemList = document.querySelector('table.itemList'); + + let itemListRows = []; + let transitionIds = []; + + if (itemList) { + itemListRows = [].slice.call(itemList.querySelectorAll('tbody tr')); + } + + function enableTransitions() { + if (transitionIds.length) { + let availableTrans = transitionIds.shift(); + + while (transitionIds.length) { + const compareTrans = transitionIds.shift(); + + availableTrans = availableTrans.filter((id) => compareTrans.indexOf(id) !== -1); + } + + if (availableTrans.length) { + if (headline) { + headline.classList.remove('d-none'); + } + if (separator) { + separator.classList.remove('d-none'); + } + } + + availableTrans.forEach((trans) => { + const elem = dropDownBtn.querySelector(`.transition-${trans}`); + + if (elem) { + elem.parentNode.classList.remove('d-none'); + } + }); + } + } + + // check for common attributes for which the conditions for a transition are possible or not + // and save this information in a boolean variable. + function collectTransitions(row) { + transitionIds.push(row.getAttribute('data-transitions').split(',')); + } + + // listen to click event to get selected rows + if (itemList) { + itemList.addEventListener('click', () => { + transitions.forEach((trans) => { + trans.parentNode.classList.add('d-none'); + }); + if (headline) { + headline.classList.add('d-none'); + } + if (separator) { + separator.classList.add('d-none'); + } + transitionIds = []; + itemListRows.forEach((el) => { + const checkedBox = el.querySelector('input[type=checkbox]'); + if (checkedBox.checked) { + const parentTr = checkedBox.closest('tr'); + collectTransitions(parentTr); + } + }); + enableTransitions(); + }); + } + }); +})(); diff --git a/components/com_content/forms/article.xml b/components/com_content/forms/article.xml index 953761738838e..5925c669907f5 100644 --- a/components/com_content/forms/article.xml +++ b/components/com_content/forms/article.xml @@ -55,12 +55,17 @@ /> + name="state" + type="list" + label="JSTATUS" + class="custom-select-color-state" + size="1" + default="1" + > + + + + year($db->quoteName('c.created')); $query->select('DISTINCT ' . $years) - ->from( - [ - $db->quoteName('#__content', 'c'), - $db->quoteName('#__workflow_associations', 'wa'), - $db->quoteName('#__workflow_stages', 'ws'), - ] - ) - ->where( - [ - $db->quoteName('c.id') . ' = ' . $db->quoteName('wa.item_id'), - $db->quoteName('ws.id') . ' = ' . $db->quoteName('wa.stage_id'), - $db->quoteName('ws.condition') . ' = ' . ContentComponent::CONDITION_ARCHIVED, - ] - ) + ->from($db->quoteName('#__content', 'c')) + ->where($db->quoteName('c.state') . ' = ' . ContentComponent::CONDITION_ARCHIVED) ->extendWhere( 'AND', [ diff --git a/components/com_content/src/Model/ArticleModel.php b/components/com_content/src/Model/ArticleModel.php index 784891a857743..25093f1a12b99 100644 --- a/components/com_content/src/Model/ArticleModel.php +++ b/components/com_content/src/Model/ArticleModel.php @@ -138,7 +138,6 @@ public function getItem($pk = null) [ $db->quoteName('fp.featured_up'), $db->quoteName('fp.featured_down'), - $db->quoteName('ws.condition'), $db->quoteName('c.title', 'category_title'), $db->quoteName('c.alias', 'category_alias'), $db->quoteName('c.access', 'category_access'), @@ -156,16 +155,6 @@ public function getItem($pk = null) ] ) ->from($db->quoteName('#__content', 'a')) - ->join( - 'INNER', - $db->quoteName('#__workflow_associations', 'wa'), - $db->quoteName('a.id') . ' = ' . $db->quoteName('wa.item_id') - ) - ->join( - 'INNER', - $db->quoteName('#__workflow_stages', 'ws'), - $db->quoteName('wa.stage_id') . ' = ' . $db->quoteName('ws.id') - ) ->join( 'INNER', $db->quoteName('#__categories', 'c'), @@ -178,7 +167,6 @@ public function getItem($pk = null) ->where( [ $db->quoteName('a.id') . ' = :pk', - $db->quoteName('wa.extension') . ' = ' . $db->quote('com_content'), $db->quoteName('c.published') . ' > 0', ] ) @@ -222,7 +210,7 @@ public function getItem($pk = null) if (is_numeric($published)) { - $query->whereIn($db->quoteName('ws.condition'), [(int) $published, (int) $archived]); + $query->whereIn($db->quoteName('a.state'), [(int) $published, (int) $archived]); } $db->setQuery($query); @@ -235,7 +223,7 @@ public function getItem($pk = null) } // Check for published state if filter set. - if ((is_numeric($published) || is_numeric($archived)) && ($data->condition != $published && $data->condition != $archived)) + if ((is_numeric($published) || is_numeric($archived)) && ($data->state != $published && $data->state != $archived)) { throw new \Exception(Text::_('COM_CONTENT_ERROR_ARTICLE_NOT_FOUND'), 404); } diff --git a/components/com_content/src/Model/ArticlesModel.php b/components/com_content/src/Model/ArticlesModel.php index 7252ae479b55d..99593851a2386 100644 --- a/components/com_content/src/Model/ArticlesModel.php +++ b/components/com_content/src/Model/ArticlesModel.php @@ -52,7 +52,6 @@ public function __construct($config = array()) 'checked_out_time', 'a.checked_out_time', 'catid', 'a.catid', 'category_title', 'state', 'a.state', - 'stage_condition', 'ws.condition', 'access', 'a.access', 'access_level', 'created', 'a.created', 'created_by', 'a.created_by', @@ -243,13 +242,10 @@ protected function getListQuery() [ $db->quoteName('fp.featured_up'), $db->quoteName('fp.featured_down'), - $db->quoteName('wa.stage_id', 'stage_id'), - $db->quoteName('ws.title', 'state_title'), - $db->quoteName('ws.condition', 'stage_condition'), // Published/archived article in archived category is treated as archived article. If category is not published then force 0. - 'CASE WHEN ' . $db->quoteName('c.published') . ' = 2 AND ' . $db->quoteName('ws.condition') . ' > 0 THEN ' . $conditionArchived + 'CASE WHEN ' . $db->quoteName('c.published') . ' = 2 AND ' . $db->quoteName('a.state') . ' > 0 THEN ' . $conditionArchived . ' WHEN ' . $db->quoteName('c.published') . ' != 1 THEN ' . $conditionUnpublished - . ' ELSE ' . $db->quoteName('ws.condition') . ' END AS ' . $db->quoteName('state'), + . ' ELSE ' . $db->quoteName('a.state') . ' END AS ' . $db->quoteName('state'), $db->quoteName('c.title', 'category_title'), $db->quoteName('c.path', 'category_route'), $db->quoteName('c.access', 'category_access'), @@ -270,8 +266,6 @@ protected function getListQuery() ] ) ->from($db->quoteName('#__content', 'a')) - ->join('LEFT', $db->quoteName('#__workflow_associations', 'wa'), $db->quoteName('wa.item_id') . ' = ' . $db->quoteName('a.id')) - ->join('LEFT', $db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('ws.id') . ' = ' . $db->quoteName('wa.stage_id')) ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid')) ->join('LEFT', $db->quoteName('#__users', 'ua'), $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by')) ->join('LEFT', $db->quoteName('#__users', 'uam'), $db->quoteName('uam.id') . ' = ' . $db->quoteName('a.modified_by')) @@ -341,8 +335,8 @@ protected function getListQuery() * If category is archived then article has to be published or archived. * Or categogy is published then article has to be archived. */ - $query->where('((' . $db->quoteName('c.published') . ' = 2 AND ' . $db->quoteName('ws.condition') . ' > :conditionUnpublished)' - . ' OR (' . $db->quoteName('c.published') . ' = 1 AND ' . $db->quoteName('ws.condition') . ' = :conditionArchived))' + $query->where('((' . $db->quoteName('c.published') . ' = 2 AND ' . $db->quoteName('a.state') . ' > :conditionUnpublished)' + . ' OR (' . $db->quoteName('c.published') . ' = 1 AND ' . $db->quoteName('a.state') . ' = :conditionArchived))' ) ->bind(':conditionUnpublished', $conditionUnpublished, ParameterType::INTEGER) ->bind(':conditionArchived', $conditionArchived, ParameterType::INTEGER); @@ -352,14 +346,14 @@ protected function getListQuery() $condition = (int) $condition; // Category has to be published - $query->where($db->quoteName('c.published') . ' = 1 AND ' . $db->quoteName('ws.condition') . ' = :wsCondition') - ->bind(':wsCondition', $condition, ParameterType::INTEGER); + $query->where($db->quoteName('c.published') . ' = 1 AND ' . $db->quoteName('a.state') . ' = :condition') + ->bind(':condition', $condition, ParameterType::INTEGER); } elseif (is_array($condition)) { // Category has to be published $query->where( - $db->quoteName('c.published') . ' = 1 AND ' . $db->quoteName('ws.condition') + $db->quoteName('c.published') . ' = 1 AND ' . $db->quoteName('a.state') . ' IN (' . implode(',', $query->bindArray($condition)) . ')' ); } diff --git a/components/com_content/src/Model/CategoryModel.php b/components/com_content/src/Model/CategoryModel.php index 0f7945a2b3bac..28108621ecbaa 100644 --- a/components/com_content/src/Model/CategoryModel.php +++ b/components/com_content/src/Model/CategoryModel.php @@ -173,7 +173,7 @@ protected function populateState($ordering = null, $direction = null) } else { - $this->setState('filter.condition', array(0, 1)); + $this->setState('filter.condition', [0, 1]); } // Process show_noauth parameter diff --git a/components/com_content/src/Model/FeaturedModel.php b/components/com_content/src/Model/FeaturedModel.php index f81533535272b..2a39b46dc1a62 100644 --- a/components/com_content/src/Model/FeaturedModel.php +++ b/components/com_content/src/Model/FeaturedModel.php @@ -84,7 +84,7 @@ protected function populateState($ordering = null, $direction = null) } else { - $this->setState('filter.condition', array(ContentComponent::CONDITION_UNPUBLISHED, ContentComponent::CONDITION_PUBLISHED)); + $this->setState('filter.condition', [ContentComponent::CONDITION_UNPUBLISHED, ContentComponent::CONDITION_PUBLISHED]); } // Process show_noauth parameter diff --git a/components/com_content/tmpl/article/default.php b/components/com_content/tmpl/article/default.php index 16a89ea46a107..091b940ef68b8 100644 --- a/components/com_content/tmpl/article/default.php +++ b/components/com_content/tmpl/article/default.php @@ -52,7 +52,7 @@

escape($this->item->title); ?>

- item->condition == ContentComponent::CONDITION_UNPUBLISHED) : ?> + item->state == ContentComponent::CONDITION_UNPUBLISHED) : ?> item->publish_up) > strtotime(Factory::getDate())) : ?> diff --git a/components/com_content/tmpl/category/blog_item.php b/components/com_content/tmpl/category/blog_item.php index 684404121ca6d..d2c07719042aa 100644 --- a/components/com_content/tmpl/category/blog_item.php +++ b/components/com_content/tmpl/category/blog_item.php @@ -30,7 +30,7 @@ item); ?>
- item->stage_condition == ContentComponent::CONDITION_UNPUBLISHED || strtotime($this->item->publish_up) > strtotime(Factory::getDate()) + item->state == ContentComponent::CONDITION_UNPUBLISHED || strtotime($this->item->publish_up) > strtotime(Factory::getDate()) || (!is_null($this->item->publish_down) && strtotime($this->item->publish_down) < strtotime(Factory::getDate()))) : ?>
@@ -86,7 +86,7 @@ - item->stage_condition == ContentComponent::CONDITION_UNPUBLISHED || strtotime($this->item->publish_up) > strtotime(Factory::getDate()) + item->state == ContentComponent::CONDITION_UNPUBLISHED || strtotime($this->item->publish_up) > strtotime(Factory::getDate()) || (!is_null($this->item->publish_down) && strtotime($this->item->publish_down) < strtotime(Factory::getDate()))) : ?>
diff --git a/components/com_content/tmpl/category/default_articles.php b/components/com_content/tmpl/category/default_articles.php index 522fd600e0b11..829fdd0660b50 100644 --- a/components/com_content/tmpl/category/default_articles.php +++ b/components/com_content/tmpl/category/default_articles.php @@ -180,7 +180,7 @@