[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |