[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Workflow/ -> Workflow.php (source)

   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  }


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