primo commit
This commit is contained in:
		
							
								
								
									
										551
									
								
								plugins/workflow/publishing/src/Extension/Publishing.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										551
									
								
								plugins/workflow/publishing/src/Extension/Publishing.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,551 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Plugin | ||||
|  * @subpackage  Workflow.publishing | ||||
|  * | ||||
|  * @copyright   (C) 2020 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Plugin\Workflow\Publishing\Extension; | ||||
|  | ||||
| use Joomla\CMS\Event\Model; | ||||
| use Joomla\CMS\Event\Table\BeforeStoreEvent; | ||||
| use Joomla\CMS\Event\View\DisplayEvent; | ||||
| use Joomla\CMS\Event\Workflow\WorkflowFunctionalityUsedEvent; | ||||
| use Joomla\CMS\Event\Workflow\WorkflowTransitionEvent; | ||||
| use Joomla\CMS\Form\Form; | ||||
| use Joomla\CMS\Language\Text; | ||||
| use Joomla\CMS\MVC\Model\DatabaseModelInterface; | ||||
| use Joomla\CMS\Plugin\CMSPlugin; | ||||
| use Joomla\CMS\Table\ContentHistory; | ||||
| use Joomla\CMS\Table\TableInterface; | ||||
| use Joomla\CMS\Workflow\WorkflowPluginTrait; | ||||
| use Joomla\CMS\Workflow\WorkflowServiceInterface; | ||||
| use Joomla\Event\EventInterface; | ||||
| use Joomla\Event\SubscriberInterface; | ||||
| use Joomla\Registry\Registry; | ||||
| use Joomla\String\Inflector; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Workflow Publishing Plugin | ||||
|  * | ||||
|  * @since  4.0.0 | ||||
|  */ | ||||
| final class Publishing extends CMSPlugin implements SubscriberInterface | ||||
| { | ||||
|     use WorkflowPluginTrait; | ||||
|  | ||||
|     /** | ||||
|      * Load the language file on instantiation. | ||||
|      * | ||||
|      * @var    boolean | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected $autoloadLanguage = true; | ||||
|  | ||||
|     /** | ||||
|      * The name of the supported name to check against | ||||
|      * | ||||
|      * @var   string | ||||
|      * @since 4.0.0 | ||||
|      */ | ||||
|     protected $supportFunctionality = 'core.state'; | ||||
|  | ||||
|     /** | ||||
|      * Returns an array of events this subscriber will listen to. | ||||
|      * | ||||
|      * @return  array | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public static function getSubscribedEvents(): array | ||||
|     { | ||||
|         return [ | ||||
|             'onAfterDisplay'                  => 'onAfterDisplay', | ||||
|             'onContentBeforeChangeState'      => 'onContentBeforeChangeState', | ||||
|             'onContentBeforeSave'             => 'onContentBeforeSave', | ||||
|             'onContentPrepareForm'            => 'onContentPrepareForm', | ||||
|             'onContentVersioningPrepareTable' => 'onContentVersioningPrepareTable', | ||||
|             'onTableBeforeStore'              => 'onTableBeforeStore', | ||||
|             'onWorkflowAfterTransition'       => 'onWorkflowAfterTransition', | ||||
|             'onWorkflowBeforeTransition'      => 'onWorkflowBeforeTransition', | ||||
|             'onWorkflowFunctionalityUsed'     => 'onWorkflowFunctionalityUsed', | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * The form event. | ||||
|      * | ||||
|      * @param   Model\PrepareFormEvent  $event  The event | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function onContentPrepareForm(Model\PrepareFormEvent $event) | ||||
|     { | ||||
|         $form    = $event->getForm(); | ||||
|         $data    = $event->getData(); | ||||
|         $context = $form->getName(); | ||||
|  | ||||
|         // Extend the transition form | ||||
|         if ($context === 'com_workflow.transition') { | ||||
|             $this->enhanceTransitionForm($form, $data); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $this->enhanceItemForm($form, $data); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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   4.0.0 | ||||
|      */ | ||||
|     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   4.0.0 | ||||
|      */ | ||||
|     protected function enhanceItemForm(Form $form, $data) | ||||
|     { | ||||
|         $context = $form->getName(); | ||||
|  | ||||
|         if (!$this->isSupported($context)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         $parts = explode('.', $context); | ||||
|  | ||||
|         $component = $this->getApplication()->bootComponent($parts[0]); | ||||
|  | ||||
|         $modelName = $component->getModelName($context); | ||||
|  | ||||
|         $table = $component->getMVCFactory()->createModel($modelName, $this->getApplication()->getName(), ['ignore_request' => true]) | ||||
|             ->getTable(); | ||||
|  | ||||
|         $fieldname = $table->getColumnAlias('published'); | ||||
|  | ||||
|         $options = $form->getField($fieldname)->options; | ||||
|  | ||||
|         $value = $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 = '<span class="text-' . $textclass . '">' . htmlentities($text, ENT_COMPAT, 'UTF-8') . '</span>'; | ||||
|         $form->setFieldAttribute($fieldname, 'label', Text::sprintf('PLG_WORKFLOW_PUBLISHING_PUBLISHED', $label)); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Manipulate the generic list view | ||||
|      * | ||||
|      * @param   DisplayEvent  $event | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function onAfterDisplay(DisplayEvent $event) | ||||
|     { | ||||
|         if (!$this->getApplication()->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; | ||||
|         } | ||||
|  | ||||
|         // 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-status-group'); | ||||
|  | ||||
| 				if (!dropdown) | ||||
| 				{ | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				" . json_encode($states) . ".forEach((action) => { | ||||
| 					var button = document.getElementById('status-group-children-' + action); | ||||
|  | ||||
| 					if (button) | ||||
| 					{ | ||||
| 						button.classList.add('d-none'); | ||||
| 					} | ||||
| 				}); | ||||
|  | ||||
| 			}); | ||||
| 		"; | ||||
|  | ||||
|         $this->getApplication()->getDocument()->addScriptDeclaration($js); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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->getApplication()->set('plgWorkflowPublishing.' . $context, $pks); | ||||
|  | ||||
|         $result = $this->getApplication()->triggerEvent('onContentBeforeChangeState', [ | ||||
|             $context, | ||||
|             $pks, | ||||
|             $value, | ||||
|             ]); | ||||
|  | ||||
|         // Release allowed pks, the job is done | ||||
|         $this->getApplication()->set('plgWorkflowPublishing.' . $context, []); | ||||
|  | ||||
|         if (\in_array(false, $result, true)) { | ||||
|             $event->setStopTransition(); | ||||
|  | ||||
|             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->getApplication()->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->getApplication()->getName(), $options); | ||||
|  | ||||
|         $model->publish($pks, $value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Change State of an item. Used to disable state change | ||||
|      * | ||||
|      * @param   Model\BeforeChangeStateEvent  $event | ||||
|      * | ||||
|      * @return boolean | ||||
|      * | ||||
|      * @throws \Exception | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function onContentBeforeChangeState(Model\BeforeChangeStateEvent $event) | ||||
|     { | ||||
|         $context = $event->getContext(); | ||||
|         $pks     = $event->getPks(); | ||||
|  | ||||
|         if (!$this->isSupported($context)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // We have allowed the pks, so we're the one who triggered | ||||
|         // With onWorkflowBeforeTransition => free pass | ||||
|         if ($this->getApplication()->get('plgWorkflowPublishing.' . $context) === $pks) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         throw new \Exception($this->getApplication()->getLanguage()->_('PLG_WORKFLOW_PUBLISHING_CHANGE_STATE_NOT_ALLOWED')); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * The save event. | ||||
|      * | ||||
|      * @param   Model\BeforeSaveEvent  $event | ||||
|      * | ||||
|      * @return  boolean | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function onContentBeforeSave(Model\BeforeSaveEvent $event) | ||||
|     { | ||||
|         $context = $event->getContext(); | ||||
|  | ||||
|         if (!$this->isSupported($context)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         /** @var TableInterface $table */ | ||||
|         $table   = $event->getItem(); | ||||
|         $data    = $event->getData(); | ||||
|         $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->getApplication()->enqueueMessage($this->getApplication()->getLanguage()->_('PLG_WORKFLOW_PUBLISHING_CHANGE_STATE_NOT_ALLOWED'), 'error'); | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * We remove the publishing field from the versioning | ||||
|      * | ||||
|      * @param   EventInterface  $event | ||||
|      * | ||||
|      * @return  boolean | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function onContentVersioningPrepareTable(EventInterface $event) | ||||
|     { | ||||
|         $subject = $event->getArgument('subject'); | ||||
|         $context = $event->getArgument('extension'); | ||||
|  | ||||
|         if (!$this->isSupported($context)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         $parts = explode('.', $context); | ||||
|  | ||||
|         $component = $this->getApplication()->bootComponent($parts[0]); | ||||
|  | ||||
|         $modelName = $component->getModelName($context); | ||||
|  | ||||
|         $model = $component->getMVCFactory()->createModel($modelName, $this->getApplication()->getName(), ['ignore_request' => true]); | ||||
|  | ||||
|         $table = $model->getTable(); | ||||
|  | ||||
|         $subject->ignoreChanges[] = $table->getColumnAlias('published'); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Pre-processor for $table->store($updateNulls) | ||||
|      * | ||||
|      * @param   BeforeStoreEvent  $event  The event to handle | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function onTableBeforeStore(BeforeStoreEvent $event) | ||||
|     { | ||||
|         $subject = $event->getArgument('subject'); | ||||
|  | ||||
|         if (!($subject instanceof ContentHistory)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $parts = explode('.', $subject->item_id); | ||||
|  | ||||
|         $typeAlias = $parts[0] . (isset($parts[1]) ? '.' . $parts[1] : ''); | ||||
|  | ||||
|         if (!$this->isSupported($typeAlias)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $component = $this->getApplication()->bootComponent($parts[0]); | ||||
|  | ||||
|         $modelName = $component->getModelName($typeAlias); | ||||
|  | ||||
|         $model = $component->getMVCFactory()->createModel($modelName, $this->getApplication()->getName(), ['ignore_request' => true]); | ||||
|  | ||||
|         $table = $model->getTable(); | ||||
|  | ||||
|         $field = $table->getColumnAlias('published'); | ||||
|  | ||||
|         $versionData = new Registry($subject->version_data); | ||||
|  | ||||
|         $versionData->remove($field); | ||||
|  | ||||
|         $subject->version_data = $versionData->toString(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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->checkAllowedAndForbiddenlist($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->getApplication()->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->getApplication()->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(); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user