[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\Workflow; 11 12 use Joomla\CMS\Application\CMSApplication; 13 use Joomla\CMS\Event\AbstractEvent; 14 use Joomla\CMS\Extension\ComponentInterface; 15 use Joomla\CMS\Factory; 16 use Joomla\CMS\Plugin\PluginHelper; 17 use Joomla\CMS\Table\Category; 18 use Joomla\Database\DatabaseDriver; 19 use Joomla\Database\ParameterType; 20 use Joomla\Registry\Registry; 21 use Joomla\Utilities\ArrayHelper; 22 23 // phpcs:disable PSR1.Files.SideEffects 24 \defined('JPATH_PLATFORM') or die; 25 // phpcs:enable PSR1.Files.SideEffects 26 27 /** 28 * Workflow Class. 29 * 30 * @since 4.0.0 31 */ 32 class Workflow 33 { 34 /** 35 * The booted component 36 * 37 * @var ComponentInterface 38 */ 39 protected $component = null; 40 41 /** 42 * Name of the extension the workflow belong to 43 * 44 * @var string 45 * @since 4.0.0 46 */ 47 protected $extension = null; 48 49 /** 50 * Application Object 51 * 52 * @var CMSApplication 53 * @since 4.0.0 54 */ 55 protected $app; 56 57 /** 58 * Database Driver 59 * 60 * @var DatabaseDriver 61 * @since 4.0.0 62 */ 63 protected $db; 64 65 /** 66 * Condition to names mapping 67 * 68 * @since 4.0.0 69 */ 70 public const CONDITION_NAMES = [ 71 self::CONDITION_PUBLISHED => 'JPUBLISHED', 72 self::CONDITION_UNPUBLISHED => 'JUNPUBLISHED', 73 self::CONDITION_TRASHED => 'JTRASHED', 74 self::CONDITION_ARCHIVED => 'JARCHIVED', 75 ]; 76 77 /** 78 * Every item with a state which has the condition PUBLISHED is visible/active on the page 79 */ 80 public const CONDITION_PUBLISHED = 1; 81 82 /** 83 * Every item with a state which has the condition UNPUBLISHED is not visible/inactive on the page 84 */ 85 public const CONDITION_UNPUBLISHED = 0; 86 87 /** 88 * Every item with a state which has the condition TRASHED is trashed 89 */ 90 public const CONDITION_TRASHED = -2; 91 92 /** 93 * Every item with a state which has the condition ARCHIVED is archived 94 */ 95 public const CONDITION_ARCHIVED = 2; 96 97 /** 98 * Class constructor 99 * 100 * @param string $extension The extension name 101 * @param ?CMSApplication $app Application Object 102 * @param ?DatabaseDriver $db Database Driver Object 103 * 104 * @since 4.0.0 105 */ 106 public function __construct(string $extension, ?CMSApplication $app = null, ?DatabaseDriver $db = null) 107 { 108 $this->extension = $extension; 109 110 // Initialise default objects if none have been provided 111 $this->app = $app ?: Factory::getApplication(); 112 $this->db = $db ?: Factory::getDbo(); 113 } 114 115 /** 116 * Returns the translated condition name, based on the given number 117 * 118 * @param integer $value The condition ID 119 * 120 * @return string 121 * 122 * @since 4.0.0 123 */ 124 public function getConditionName(int $value): string 125 { 126 $component = $this->getComponent(); 127 128 if ($component instanceof WorkflowServiceInterface) { 129 $conditions = $component->getConditions($this->extension); 130 } else { 131 $conditions = self::CONDITION_NAMES; 132 } 133 134 return ArrayHelper::getValue($conditions, $value, '', 'string'); 135 } 136 137 /** 138 * Returns the booted component 139 * 140 * @return ComponentInterface 141 * 142 * @since 4.0.0 143 */ 144 protected function getComponent() 145 { 146 if (\is_null($this->component)) { 147 $parts = explode('.', $this->extension); 148 149 $this->component = $this->app->bootComponent($parts[0]); 150 } 151 152 return $this->component; 153 } 154 155 /** 156 * Try to load a workflow default stage by category ID. 157 * 158 * @param integer $catId The category ID. 159 * 160 * @return boolean|integer An integer, holding the stage ID or false 161 * @since 4.0.0 162 */ 163 public function getDefaultStageByCategory($catId = 0) 164 { 165 // Let's check if a workflow ID is assigned to a category 166 $category = new Category($this->db); 167 168 $categories = array_reverse($category->getPath($catId)); 169 170 $workflow_id = 0; 171 172 foreach ($categories as $cat) { 173 $cat->params = new Registry($cat->params); 174 175 $workflow_id = $cat->params->get('workflow_id'); 176 177 if ($workflow_id == 'inherit') { 178 $workflow_id = 0; 179 } elseif ($workflow_id == 'use_default') { 180 $workflow_id = 0; 181 182 break; 183 } elseif ($workflow_id > 0) { 184 break; 185 } 186 } 187 188 // Check if the workflow exists 189 if ($workflow_id = (int) $workflow_id) { 190 $query = $this->db->getQuery(true); 191 192 $query->select( 193 [ 194 $this->db->quoteName('ws.id') 195 ] 196 ) 197 ->from( 198 [ 199 $this->db->quoteName('#__workflow_stages', 'ws'), 200 $this->db->quoteName('#__workflows', 'w'), 201 ] 202 ) 203 ->where( 204 [ 205 $this->db->quoteName('ws.workflow_id') . ' = ' . $this->db->quoteName('w.id'), 206 $this->db->quoteName('ws.default') . ' = 1', 207 $this->db->quoteName('w.published') . ' = 1', 208 $this->db->quoteName('ws.published') . ' = 1', 209 $this->db->quoteName('w.id') . ' = :workflowId', 210 $this->db->quoteName('w.extension') . ' = :extension', 211 ] 212 ) 213 ->bind(':workflowId', $workflow_id, ParameterType::INTEGER) 214 ->bind(':extension', $this->extension); 215 216 $stage_id = (int) $this->db->setQuery($query)->loadResult(); 217 218 if (!empty($stage_id)) { 219 return $stage_id; 220 } 221 } 222 223 // Use default workflow 224 $query = $this->db->getQuery(true); 225 226 $query->select( 227 [ 228 $this->db->quoteName('ws.id') 229 ] 230 ) 231 ->from( 232 [ 233 $this->db->quoteName('#__workflow_stages', 'ws'), 234 $this->db->quoteName('#__workflows', 'w'), 235 ] 236 ) 237 ->where( 238 [ 239 $this->db->quoteName('ws.default') . ' = 1', 240 $this->db->quoteName('ws.workflow_id') . ' = ' . $this->db->quoteName('w.id'), 241 $this->db->quoteName('w.published') . ' = 1', 242 $this->db->quoteName('ws.published') . ' = 1', 243 $this->db->quoteName('w.default') . ' = 1', 244 $this->db->quoteName('w.extension') . ' = :extension' 245 ] 246 ) 247 ->bind(':extension', $this->extension); 248 249 $stage_id = (int) $this->db->setQuery($query)->loadResult(); 250 251 // Last check if we have a workflow ID 252 if (!empty($stage_id)) { 253 return $stage_id; 254 } 255 256 return false; 257 } 258 259 /** 260 * Check if a transition can be executed 261 * 262 * @param integer[] $pks The item IDs, which should use the transition 263 * @param integer $transitionId The transition which should be executed 264 * 265 * @return object | null 266 */ 267 public function getValidTransition(array $pks, int $transitionId) 268 { 269 $pks = ArrayHelper::toInteger($pks); 270 $pks = array_filter($pks); 271 272 if (!\count($pks)) { 273 return null; 274 } 275 276 $query = $this->db->getQuery(true); 277 278 $user = $this->app->getIdentity(); 279 280 $query->select( 281 [ 282 $this->db->quoteName('t.id'), 283 $this->db->quoteName('t.to_stage_id'), 284 $this->db->quoteName('t.from_stage_id'), 285 $this->db->quoteName('t.options'), 286 $this->db->quoteName('t.workflow_id'), 287 ] 288 ) 289 ->from( 290 [ 291 $this->db->quoteName('#__workflow_transitions', 't'), 292 ] 293 ) 294 ->join('INNER', $this->db->quoteName('#__workflows', 'w')) 295 ->join( 296 'LEFT', 297 $this->db->quoteName('#__workflow_stages', 's'), 298 $this->db->quoteName('s.id') . ' = ' . $this->db->quoteName('t.to_stage_id') 299 ) 300 ->where( 301 [ 302 $this->db->quoteName('t.id') . ' = :id', 303 $this->db->quoteName('t.workflow_id') . ' = ' . $this->db->quoteName('w.id'), 304 $this->db->quoteName('t.published') . ' = 1', 305 $this->db->quoteName('w.extension') . ' = :extension' 306 ] 307 ) 308 ->bind(':id', $transitionId, ParameterType::INTEGER) 309 ->bind(':extension', $this->extension); 310 311 $transition = $this->db->setQuery($query)->loadObject(); 312 313 $parts = explode('.', $this->extension); 314 $option = reset($parts); 315 316 if (!empty($transition->id) && $user->authorise('core.execute.transition', $option . '.transition.' . (int) $transition->id)) { 317 return $transition; 318 } 319 320 return null; 321 } 322 323 /** 324 * Executes a transition to change the current state in the association table 325 * 326 * @param integer[] $pks The item IDs, which should use the transition 327 * @param integer $transitionId The transition which should be executed 328 * 329 * @return boolean 330 */ 331 public function executeTransition(array $pks, int $transitionId): bool 332 { 333 $pks = ArrayHelper::toInteger($pks); 334 $pks = array_filter($pks); 335 336 if (!\count($pks)) { 337 return true; 338 } 339 340 $transition = $this->getValidTransition($pks, $transitionId); 341 342 if (is_null($transition)) { 343 return false; 344 } 345 346 $transition->options = new Registry($transition->options); 347 348 // Check if the items can execute this transition 349 foreach ($pks as $pk) { 350 $assoc = $this->getAssociation($pk); 351 352 // The transition has to be in the same workflow 353 if ( 354 !\in_array($transition->from_stage_id, [ 355 $assoc->stage_id, 356 -1 357 ]) || $transition->workflow_id !== $assoc->workflow_id 358 ) { 359 return false; 360 } 361 } 362 363 PluginHelper::importPlugin('workflow'); 364 365 $eventResult = $this->app->getDispatcher()->dispatch( 366 'onWorkflowBeforeTransition', 367 AbstractEvent::create( 368 'onWorkflowBeforeTransition', 369 [ 370 'eventClass' => 'Joomla\CMS\Event\Workflow\WorkflowTransitionEvent', 371 'subject' => $this, 372 'extension' => $this->extension, 373 'pks' => $pks, 374 'transition' => $transition, 375 'stopTransition' => false, 376 ] 377 ) 378 ); 379 380 if ($eventResult->getArgument('stopTransition')) { 381 return false; 382 } 383 384 $success = $this->updateAssociations($pks, (int) $transition->to_stage_id); 385 386 if ($success) { 387 $this->app->getDispatcher()->dispatch( 388 'onWorkflowAfterTransition', 389 AbstractEvent::create( 390 'onWorkflowAfterTransition', 391 [ 392 'eventClass' => 'Joomla\CMS\Event\Workflow\WorkflowTransitionEvent', 393 'subject' => $this, 394 'extension' => $this->extension, 395 'pks' => $pks, 396 'transition' => $transition 397 ] 398 ) 399 ); 400 } 401 402 return $success; 403 } 404 405 /** 406 * Creates an association for the workflow_associations table 407 * 408 * @param integer $pk ID of the item 409 * @param integer $state ID of state 410 * 411 * @return boolean 412 * 413 * @since 4.0.0 414 */ 415 public function createAssociation(int $pk, int $state): bool 416 { 417 try { 418 $query = $this->db->getQuery(true); 419 420 $query->insert($this->db->quoteName('#__workflow_associations')) 421 ->columns( 422 [ 423 $this->db->quoteName('item_id'), 424 $this->db->quoteName('stage_id'), 425 $this->db->quoteName('extension'), 426 ] 427 ) 428 ->values(':pk, :state, :extension') 429 ->bind(':pk', $pk, ParameterType::INTEGER) 430 ->bind(':state', $state, ParameterType::INTEGER) 431 ->bind(':extension', $this->extension); 432 433 $this->db->setQuery($query)->execute(); 434 } catch (\Exception $e) { 435 return false; 436 } 437 438 return true; 439 } 440 441 /** 442 * Update an existing association with a new state 443 * 444 * @param array $pks An Array of item IDs which should be changed 445 * @param integer $state The new state ID 446 * 447 * @return boolean 448 * 449 * @since 4.0.0 450 */ 451 public function updateAssociations(array $pks, int $state): bool 452 { 453 $pks = ArrayHelper::toInteger($pks); 454 455 try { 456 $query = $this->db->getQuery(true); 457 458 $query->update($this->db->quoteName('#__workflow_associations')) 459 ->set($this->db->quoteName('stage_id') . ' = :state') 460 ->whereIn($this->db->quoteName('item_id'), $pks) 461 ->where($this->db->quoteName('extension') . ' = :extension') 462 ->bind(':state', $state, ParameterType::INTEGER) 463 ->bind(':extension', $this->extension); 464 465 $this->db->setQuery($query)->execute(); 466 } catch (\Exception $e) { 467 return false; 468 } 469 470 return true; 471 } 472 473 /** 474 * Removes associations from the workflow_associations table 475 * 476 * @param integer[] $pks ID of content 477 * 478 * @return boolean 479 * 480 * @since 4.0.0 481 */ 482 public function deleteAssociation(array $pks): bool 483 { 484 $pks = ArrayHelper::toInteger($pks); 485 486 try { 487 $query = $this->db->getQuery(true); 488 489 $query 490 ->delete($this->db->quoteName('#__workflow_associations')) 491 ->whereIn($this->db->quoteName('item_id'), $pks) 492 ->where($this->db->quoteName('extension') . ' = :extension') 493 ->bind(':extension', $this->extension); 494 495 $this->db->setQuery($query)->execute(); 496 } catch (\Exception $e) { 497 return false; 498 } 499 500 return true; 501 } 502 503 /** 504 * Loads an existing association item with state and item ID 505 * 506 * @param integer $itemId The item ID to load 507 * 508 * @return \stdClass|null 509 * 510 * @since 4.0.0 511 */ 512 public function getAssociation(int $itemId): ?\stdClass 513 { 514 $query = $this->db->getQuery(true); 515 516 $query->select( 517 [ 518 $this->db->quoteName('a.item_id'), 519 $this->db->quoteName('a.stage_id'), 520 $this->db->quoteName('s.workflow_id'), 521 ] 522 ) 523 ->from($this->db->quoteName('#__workflow_associations', 'a')) 524 ->innerJoin( 525 $this->db->quoteName('#__workflow_stages', 's'), 526 $this->db->quoteName('a.stage_id') . ' = ' . $this->db->quoteName('s.id') 527 ) 528 ->where( 529 [ 530 $this->db->quoteName('item_id') . ' = :id', 531 $this->db->quoteName('extension') . ' = :extension', 532 ] 533 ) 534 ->bind(':id', $itemId, ParameterType::INTEGER) 535 ->bind(':extension', $this->extension); 536 537 return $this->db->setQuery($query)->loadObject(); 538 } 539 }
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 |