[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/plugins/workflow/publishing/ -> publishing.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Plugin
   5   * @subpackage  Workflow.Publishing
   6   *
   7   * @copyright   (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
   8   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   9  
  10   * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
  11   */
  12  
  13  use Joomla\CMS\Application\CMSApplicationInterface;
  14  use Joomla\CMS\Event\Table\BeforeStoreEvent;
  15  use Joomla\CMS\Event\View\DisplayEvent;
  16  use Joomla\CMS\Event\Workflow\WorkflowFunctionalityUsedEvent;
  17  use Joomla\CMS\Event\Workflow\WorkflowTransitionEvent;
  18  use Joomla\CMS\Factory;
  19  use Joomla\CMS\Form\Form;
  20  use Joomla\CMS\Language\Text;
  21  use Joomla\CMS\MVC\Model\DatabaseModelInterface;
  22  use Joomla\CMS\Plugin\CMSPlugin;
  23  use Joomla\CMS\Table\ContentHistory;
  24  use Joomla\CMS\Table\TableInterface;
  25  use Joomla\CMS\Workflow\WorkflowPluginTrait;
  26  use Joomla\CMS\Workflow\WorkflowServiceInterface;
  27  use Joomla\Event\EventInterface;
  28  use Joomla\Event\SubscriberInterface;
  29  use Joomla\Registry\Registry;
  30  use Joomla\String\Inflector;
  31  
  32  // phpcs:disable PSR1.Files.SideEffects
  33  \defined('_JEXEC') or die;
  34  // phpcs:enable PSR1.Files.SideEffects
  35  
  36  /**
  37   * Workflow Publishing Plugin
  38   *
  39   * @since  4.0.0
  40   */
  41  class PlgWorkflowPublishing extends CMSPlugin implements SubscriberInterface
  42  {
  43      use WorkflowPluginTrait;
  44  
  45      /**
  46       * Load the language file on instantiation.
  47       *
  48       * @var    boolean
  49       * @since  4.0.0
  50       */
  51      protected $autoloadLanguage = true;
  52  
  53      /**
  54       * Loads the CMS Application for direct access
  55       *
  56       * @var   CMSApplicationInterface
  57       * @since 4.0.0
  58       */
  59      protected $app;
  60  
  61      /**
  62       * The name of the supported name to check against
  63       *
  64       * @var   string
  65       * @since 4.0.0
  66       */
  67      protected $supportFunctionality = 'core.state';
  68  
  69      /**
  70       * Returns an array of events this subscriber will listen to.
  71       *
  72       * @return  array
  73       *
  74       * @since   4.0.0
  75       */
  76      public static function getSubscribedEvents(): array
  77      {
  78          return [
  79              'onAfterDisplay'                  => 'onAfterDisplay',
  80              'onContentBeforeChangeState'      => 'onContentBeforeChangeState',
  81              'onContentBeforeSave'             => 'onContentBeforeSave',
  82              'onContentPrepareForm'            => 'onContentPrepareForm',
  83              'onContentVersioningPrepareTable' => 'onContentVersioningPrepareTable',
  84              'onTableBeforeStore'              => 'onTableBeforeStore',
  85              'onWorkflowAfterTransition'       => 'onWorkflowAfterTransition',
  86              'onWorkflowBeforeTransition'      => 'onWorkflowBeforeTransition',
  87              'onWorkflowFunctionalityUsed'     => 'onWorkflowFunctionalityUsed',
  88          ];
  89      }
  90  
  91      /**
  92       * The form event.
  93       *
  94       * @param   EventInterface  $event  The event
  95       *
  96       * @since   4.0.0
  97       */
  98      public function onContentPrepareForm(EventInterface $event)
  99      {
 100          $form = $event->getArgument('0');
 101          $data = $event->getArgument('1');
 102  
 103          $context = $form->getName();
 104  
 105          // Extend the transition form
 106          if ($context === 'com_workflow.transition') {
 107              $this->enhanceTransitionForm($form, $data);
 108  
 109              return;
 110          }
 111  
 112          $this->enhanceItemForm($form, $data);
 113      }
 114  
 115      /**
 116       * Add different parameter options to the transition view, we need when executing the transition
 117       *
 118       * @param   Form      $form The form
 119       * @param   stdClass  $data The data
 120       *
 121       * @return  boolean
 122       *
 123       * @since   4.0.0
 124       */
 125      protected function enhanceTransitionForm(Form $form, $data)
 126      {
 127          $workflow = $this->enhanceWorkflowTransitionForm($form, $data);
 128  
 129          if (!$workflow) {
 130              return true;
 131          }
 132  
 133          $form->setFieldAttribute('publishing', 'extension', $workflow->extension, 'options');
 134  
 135          return true;
 136      }
 137  
 138      /**
 139       * Disable certain fields in the item  form view, when we want to take over this function in the transition
 140       * Check also for the workflow implementation and if the field exists
 141       *
 142       * @param   Form      $form  The form
 143       * @param   stdClass  $data  The data
 144       *
 145       * @return  boolean
 146       *
 147       * @since   4.0.0
 148       */
 149      protected function enhanceItemForm(Form $form, $data)
 150      {
 151          $context = $form->getName();
 152  
 153          if (!$this->isSupported($context)) {
 154              return true;
 155          }
 156  
 157          $parts = explode('.', $context);
 158  
 159          $component = $this->app->bootComponent($parts[0]);
 160  
 161          $modelName = $component->getModelName($context);
 162  
 163          $table = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true])
 164              ->getTable();
 165  
 166          $fieldname = $table->getColumnAlias('published');
 167  
 168          $options = $form->getField($fieldname)->options;
 169  
 170          $value = $data->$fieldname ?? $form->getValue($fieldname, null, 0);
 171  
 172          $text = '-';
 173  
 174          $textclass = 'body';
 175  
 176          switch ($value) {
 177              case 1:
 178                  $textclass = 'success';
 179                  break;
 180  
 181              case 0:
 182              case -2:
 183                  $textclass = 'danger';
 184          }
 185  
 186          if (!empty($options)) {
 187              foreach ($options as $option) {
 188                  if ($option->value == $value) {
 189                      $text = $option->text;
 190  
 191                      break;
 192                  }
 193              }
 194          }
 195  
 196          $form->setFieldAttribute($fieldname, 'type', 'spacer');
 197  
 198          $label = '<span class="text-' . $textclass . '">' . htmlentities($text, ENT_COMPAT, 'UTF-8') . '</span>';
 199          $form->setFieldAttribute($fieldname, 'label', Text::sprintf('PLG_WORKFLOW_PUBLISHING_PUBLISHED', $label));
 200  
 201          return true;
 202      }
 203  
 204      /**
 205       * Manipulate the generic list view
 206       *
 207       * @param   DisplayEvent    $event
 208       *
 209       * @since   4.0.0
 210       */
 211      public function onAfterDisplay(DisplayEvent $event)
 212      {
 213          $app = Factory::getApplication();
 214  
 215          if (!$app->isClient('administrator')) {
 216              return;
 217          }
 218  
 219          $component = $event->getArgument('extensionName');
 220          $section   = $event->getArgument('section');
 221  
 222          // We need the single model context for checking for workflow
 223          $singularsection = Inflector::singularize($section);
 224  
 225          if (!$this->isSupported($component . '.' . $singularsection)) {
 226              return true;
 227          }
 228  
 229          // That's the hard coded list from the AdminController publish method => change, when it's make dynamic in the future
 230          $states = [
 231              'publish',
 232              'unpublish',
 233              'archive',
 234              'trash',
 235              'report',
 236          ];
 237  
 238          $js = "
 239              document.addEventListener('DOMContentLoaded', function()
 240              {
 241                  var dropdown = document.getElementById('toolbar-status-group');
 242  
 243                  if (!dropdown)
 244                  {
 245                      return;
 246                  }
 247  
 248                  " . json_encode($states) . ".forEach((action) => {
 249                      var button = document.getElementById('status-group-children-' + action);
 250  
 251                      if (button)
 252                      {
 253                          button.classList.add('d-none');
 254                      }
 255                  });
 256  
 257              });
 258          ";
 259  
 260          $app->getDocument()->addScriptDeclaration($js);
 261  
 262          return true;
 263      }
 264  
 265      /**
 266       * Check if we can execute the transition
 267       *
 268       * @param   WorkflowTransitionEvent  $event
 269       *
 270       * @return boolean
 271       *
 272       * @since   4.0.0
 273       */
 274      public function onWorkflowBeforeTransition(WorkflowTransitionEvent $event)
 275      {
 276          $context    = $event->getArgument('extension');
 277          $transition = $event->getArgument('transition');
 278          $pks        = $event->getArgument('pks');
 279  
 280          if (!$this->isSupported($context) || !is_numeric($transition->options->get('publishing'))) {
 281              return true;
 282          }
 283  
 284          $value = $transition->options->get('publishing');
 285  
 286          if (!is_numeric($value)) {
 287              return true;
 288          }
 289  
 290          /**
 291           * Here it becomes tricky. We would like to use the component models publish method, so we will
 292           * Execute the normal "onContentBeforeChangeState" plugins. But they could cancel the execution,
 293           * So we have to precheck and cancel the whole transition stuff if not allowed.
 294           */
 295          $this->app->set('plgWorkflowPublishing.' . $context, $pks);
 296  
 297          $result = $this->app->triggerEvent('onContentBeforeChangeState', [
 298              $context,
 299              $pks,
 300              $value,
 301              ]);
 302  
 303          // Release allowed pks, the job is done
 304          $this->app->set('plgWorkflowPublishing.' . $context, []);
 305  
 306          if (in_array(false, $result, true)) {
 307              $event->setStopTransition();
 308  
 309              return false;
 310          }
 311  
 312          return true;
 313      }
 314  
 315      /**
 316       * Change State of an item. Used to disable state change
 317       *
 318       * @param   WorkflowTransitionEvent  $event
 319       *
 320       * @return boolean
 321       *
 322       * @since   4.0.0
 323       */
 324      public function onWorkflowAfterTransition(WorkflowTransitionEvent $event)
 325      {
 326          $context       = $event->getArgument('extension');
 327          $extensionName = $event->getArgument('extensionName');
 328          $transition    = $event->getArgument('transition');
 329          $pks           = $event->getArgument('pks');
 330  
 331          if (!$this->isSupported($context)) {
 332              return true;
 333          }
 334  
 335          $component = $this->app->bootComponent($extensionName);
 336  
 337          $value = $transition->options->get('publishing');
 338  
 339          if (!is_numeric($value)) {
 340              return;
 341          }
 342  
 343          $options = [
 344              'ignore_request'            => true,
 345              // We already have triggered onContentBeforeChangeState, so use our own
 346              'event_before_change_state' => 'onWorkflowBeforeChangeState',
 347          ];
 348  
 349          $modelName = $component->getModelName($context);
 350  
 351          $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), $options);
 352  
 353          $model->publish($pks, $value);
 354      }
 355  
 356      /**
 357       * Change State of an item. Used to disable state change
 358       *
 359       * @param   EventInterface  $event
 360       *
 361       * @return boolean
 362       *
 363       * @throws Exception
 364       * @since   4.0.0
 365       */
 366      public function onContentBeforeChangeState(EventInterface $event)
 367      {
 368          $context = $event->getArgument('0');
 369          $pks     = $event->getArgument('1');
 370  
 371          if (!$this->isSupported($context)) {
 372              return true;
 373          }
 374  
 375          // We have allowed the pks, so we're the one who triggered
 376          // With onWorkflowBeforeTransition => free pass
 377          if ($this->app->get('plgWorkflowPublishing.' . $context) === $pks) {
 378              return true;
 379          }
 380  
 381          throw new Exception(Text::_('PLG_WORKFLOW_PUBLISHING_CHANGE_STATE_NOT_ALLOWED'));
 382      }
 383  
 384      /**
 385       * The save event.
 386       *
 387       * @param   EventInterface  $event
 388       *
 389       * @return  boolean
 390       *
 391       * @since   4.0.0
 392       */
 393      public function onContentBeforeSave(EventInterface $event)
 394      {
 395          $context = $event->getArgument('0');
 396  
 397          /** @var TableInterface $table */
 398          $table = $event->getArgument('1');
 399          $isNew = $event->getArgument('2');
 400          $data  = $event->getArgument('3');
 401  
 402          if (!$this->isSupported($context)) {
 403              return true;
 404          }
 405  
 406          $keyName = $table->getColumnAlias('published');
 407  
 408          // Check for the old value
 409          $article = clone $table;
 410  
 411          $article->load($table->id);
 412  
 413          /**
 414           * We don't allow the change of the state when we use the workflow
 415           * As we're setting the field to disabled, no value should be there at all
 416           */
 417          if (isset($data[$keyName])) {
 418              $this->app->enqueueMessage(Text::_('PLG_WORKFLOW_PUBLISHING_CHANGE_STATE_NOT_ALLOWED'), 'error');
 419  
 420              return false;
 421          }
 422  
 423          return true;
 424      }
 425  
 426      /**
 427       * We remove the publishing field from the versioning
 428       *
 429       * @param   EventInterface  $event
 430       *
 431       * @return  boolean
 432       *
 433       * @since   4.0.0
 434       */
 435      public function onContentVersioningPrepareTable(EventInterface $event)
 436      {
 437          $subject = $event->getArgument('subject');
 438          $context = $event->getArgument('extension');
 439  
 440          if (!$this->isSupported($context)) {
 441              return true;
 442          }
 443  
 444          $parts = explode('.', $context);
 445  
 446          $component = $this->app->bootComponent($parts[0]);
 447  
 448          $modelName = $component->getModelName($context);
 449  
 450          $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]);
 451  
 452          $table = $model->getTable();
 453  
 454          $subject->ignoreChanges[] = $table->getColumnAlias('published');
 455      }
 456  
 457      /**
 458       * Pre-processor for $table->store($updateNulls)
 459       *
 460       * @param   BeforeStoreEvent  $event  The event to handle
 461       *
 462       * @return  void
 463       *
 464       * @since   4.0.0
 465       */
 466      public function onTableBeforeStore(BeforeStoreEvent $event)
 467      {
 468          $subject = $event->getArgument('subject');
 469  
 470          if (!($subject instanceof ContentHistory)) {
 471              return;
 472          }
 473  
 474          $parts = explode('.', $subject->item_id);
 475  
 476          $typeAlias = $parts[0] . (isset($parts[1]) ? '.' . $parts[1] : '');
 477  
 478          if (!$this->isSupported($typeAlias)) {
 479              return;
 480          }
 481  
 482          $component = $this->app->bootComponent($parts[0]);
 483  
 484          $modelName = $component->getModelName($typeAlias);
 485  
 486          $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]);
 487  
 488          $table = $model->getTable();
 489  
 490          $field = $table->getColumnAlias('published');
 491  
 492          $versionData = new Registry($subject->version_data);
 493  
 494          $versionData->remove($field);
 495  
 496          $subject->version_data = $versionData->toString();
 497      }
 498  
 499      /**
 500       * Check if the current plugin should execute workflow related activities
 501       *
 502       * @param   string  $context
 503       *
 504       * @return boolean
 505       *
 506       * @since   4.0.0
 507       */
 508      protected function isSupported($context)
 509      {
 510          if (!$this->checkAllowedAndForbiddenlist($context) || !$this->checkExtensionSupport($context, $this->supportFunctionality)) {
 511              return false;
 512          }
 513  
 514          $parts = explode('.', $context);
 515  
 516          // We need at least the extension + view for loading the table fields
 517          if (count($parts) < 2) {
 518              return false;
 519          }
 520  
 521          $component = $this->app->bootComponent($parts[0]);
 522  
 523          if (
 524              !$component instanceof WorkflowServiceInterface
 525              || !$component->isWorkflowActive($context)
 526              || !$component->supportFunctionality($this->supportFunctionality, $context)
 527          ) {
 528              return false;
 529          }
 530  
 531          $modelName = $component->getModelName($context);
 532  
 533          $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]);
 534  
 535          if (!$model instanceof DatabaseModelInterface || !method_exists($model, 'publish')) {
 536              return false;
 537          }
 538  
 539          $table = $model->getTable();
 540  
 541          if (!$table instanceof TableInterface || !$table->hasField('published')) {
 542              return false;
 543          }
 544  
 545          return true;
 546      }
 547  
 548      /**
 549       * If plugin supports the functionality we set the used variable
 550       *
 551       * @param   WorkflowFunctionalityUsedEvent  $event
 552       *
 553       * @since 4.0.0
 554       */
 555      public function onWorkflowFunctionalityUsed(WorkflowFunctionalityUsedEvent $event)
 556      {
 557          $functionality = $event->getArgument('functionality');
 558  
 559          if ($functionality !== 'core.state') {
 560              return;
 561          }
 562  
 563          $event->setUsed();
 564      }
 565  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer