Current File : /home/pacjaorg/public_html/nsa/libraries/src/Workflow/Workflow.php |
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Workflow;
\defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Event\AbstractEvent;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Category;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
/**
* Workflow Class.
*
* @since 4.0.0
*/
class Workflow
{
/**
* The booted component
*
* @var ComponentInterface
*/
protected $component = null;
/**
* Name of the extension the workflow belong to
*
* @var string
* @since 4.0.0
*/
protected $extension = null;
/**
* Application Object
*
* @var CMSApplication
* @since 4.0.0
*/
protected $app;
/**
* Database Driver
*
* @var DatabaseDriver
* @since 4.0.0
*/
protected $db;
/**
* Condition to names mapping
*
* @since 4.0.0
*/
const CONDITION_NAMES = [
self::CONDITION_PUBLISHED => 'JPUBLISHED',
self::CONDITION_UNPUBLISHED => 'JUNPUBLISHED',
self::CONDITION_TRASHED => 'JTRASHED',
self::CONDITION_ARCHIVED => 'JARCHIVED',
];
/**
* Every item with a state which has the condition PUBLISHED is visible/active on the page
*/
const CONDITION_PUBLISHED = 1;
/**
* Every item with a state which has the condition UNPUBLISHED is not visible/inactive on the page
*/
const CONDITION_UNPUBLISHED = 0;
/**
* Every item with a state which has the condition TRASHED is trashed
*/
const CONDITION_TRASHED = -2;
/**
* Every item with a state which has the condition ARCHIVED is archived
*/
const CONDITION_ARCHIVED = 2;
/**
* Class constructor
*
* @param string $extension The extension name
* @param ?CMSApplication $app Application Object
* @param ?DatabaseDriver $db Database Driver Object
*
* @since 4.0.0
*/
public function __construct(string $extension, ?CMSApplication $app = null, ?DatabaseDriver $db = null)
{
$this->extension = $extension;
// Initialise default objects if none have been provided
$this->app = $app ?: Factory::getApplication();
$this->db = $db ?: Factory::getDbo();
}
/**
* Returns the translated condition name, based on the given number
*
* @param integer $value The condition ID
*
* @return string
*
* @since 4.0.0
*/
public function getConditionName(int $value): string
{
$component = $this->getComponent();
if ($component instanceof WorkflowServiceInterface)
{
$conditions = $component->getConditions($this->extension);
}
else
{
$conditions = self::CONDITION_NAMES;
}
return ArrayHelper::getValue($conditions, $value, '', 'string');
}
/**
* Returns the booted component
*
* @return ComponentInterface
*
* @since 4.0.0
*/
protected function getComponent()
{
if (\is_null($this->component))
{
$parts = explode('.', $this->extension);
$this->component = $this->app->bootComponent($parts[0]);
}
return $this->component;
}
/**
* Try to load a workflow default stage by category ID.
*
* @param integer $catId The category ID.
*
* @return boolean|integer An integer, holding the stage ID or false
* @since 4.0.0
*/
public function getDefaultStageByCategory($catId = 0)
{
// Let's check if a workflow ID is assigned to a category
$category = new Category($this->db);
$categories = array_reverse($category->getPath($catId));
$workflow_id = 0;
foreach ($categories as $cat)
{
$cat->params = new Registry($cat->params);
$workflow_id = $cat->params->get('workflow_id');
if ($workflow_id == 'inherit')
{
$workflow_id = 0;
continue;
}
elseif ($workflow_id == 'use_default')
{
$workflow_id = 0;
break;
}
elseif ($workflow_id > 0)
{
break;
}
}
// Check if the workflow exists
if ($workflow_id = (int) $workflow_id)
{
$query = $this->db->getQuery(true);
$query->select(
[
$this->db->quoteName('ws.id')
]
)
->from(
[
$this->db->quoteName('#__workflow_stages', 'ws'),
$this->db->quoteName('#__workflows', 'w'),
]
)
->where(
[
$this->db->quoteName('ws.workflow_id') . ' = ' . $this->db->quoteName('w.id'),
$this->db->quoteName('ws.default') . ' = 1',
$this->db->quoteName('w.published') . ' = 1',
$this->db->quoteName('ws.published') . ' = 1',
$this->db->quoteName('w.id') . ' = :workflowId',
$this->db->quoteName('w.extension') . ' = :extension',
]
)
->bind(':workflowId', $workflow_id, ParameterType::INTEGER)
->bind(':extension', $this->extension);
$stage_id = (int) $this->db->setQuery($query)->loadResult();
if (!empty($stage_id))
{
return $stage_id;
}
}
// Use default workflow
$query = $this->db->getQuery(true);
$query->select(
[
$this->db->quoteName('ws.id')
]
)
->from(
[
$this->db->quoteName('#__workflow_stages', 'ws'),
$this->db->quoteName('#__workflows', 'w'),
]
)
->where(
[
$this->db->quoteName('ws.default') . ' = 1',
$this->db->quoteName('ws.workflow_id') . ' = ' . $this->db->quoteName('w.id'),
$this->db->quoteName('w.published') . ' = 1',
$this->db->quoteName('ws.published') . ' = 1',
$this->db->quoteName('w.default') . ' = 1',
$this->db->quoteName('w.extension') . ' = :extension'
]
)
->bind(':extension', $this->extension);
$stage_id = (int) $this->db->setQuery($query)->loadResult();
// Last check if we have a workflow ID
if (!empty($stage_id))
{
return $stage_id;
}
return false;
}
/**
* Check if a transition can be executed
*
* @param integer[] $pks The item IDs, which should use the transition
* @param integer $transitionId The transition which should be executed
*
* @return object | null
*/
public function getValidTransition(array $pks, int $transitionId)
{
$pks = ArrayHelper::toInteger($pks);
$pks = array_filter($pks);
if (!\count($pks))
{
return null;
}
$query = $this->db->getQuery(true);
$user = $this->app->getIdentity();
$query->select(
[
$this->db->quoteName('t.id'),
$this->db->quoteName('t.to_stage_id'),
$this->db->quoteName('t.from_stage_id'),
$this->db->quoteName('t.options'),
$this->db->quoteName('t.workflow_id'),
]
)
->from(
[
$this->db->quoteName('#__workflow_transitions', 't'),
]
)
->join('INNER', $this->db->quoteName('#__workflows', 'w'))
->join(
'LEFT',
$this->db->quoteName('#__workflow_stages', 's'),
$this->db->quoteName('s.id') . ' = ' . $this->db->quoteName('t.to_stage_id')
)
->where(
[
$this->db->quoteName('t.id') . ' = :id',
$this->db->quoteName('t.workflow_id') . ' = ' . $this->db->quoteName('w.id'),
$this->db->quoteName('t.published') . ' = 1',
$this->db->quoteName('w.extension') . ' = :extension'
]
)
->bind(':id', $transitionId, ParameterType::INTEGER)
->bind(':extension', $this->extension);
$transition = $this->db->setQuery($query)->loadObject();
$parts = explode('.', $this->extension);
$option = reset($parts);
if (!empty($transition->id) && $user->authorise('core.execute.transition', $option . '.transition.' . (int) $transition->id))
{
return $transition;
}
return null;
}
/**
* Executes a transition to change the current state in the association table
*
* @param integer[] $pks The item IDs, which should use the transition
* @param integer $transitionId The transition which should be executed
*
* @return boolean
*/
public function executeTransition(array $pks, int $transitionId): bool
{
$pks = ArrayHelper::toInteger($pks);
$pks = array_filter($pks);
if (!\count($pks))
{
return true;
}
$transition = $this->getValidTransition($pks, $transitionId);
if (is_null($transition))
{
return false;
}
$transition->options = new Registry($transition->options);
// Check if the items can execute this transition
foreach ($pks as $pk)
{
$assoc = $this->getAssociation($pk);
// The transition has to be in the same workflow
if (!\in_array($transition->from_stage_id, [
$assoc->stage_id,
-1
]
) || $transition->workflow_id !== $assoc->workflow_id)
{
return false;
}
}
PluginHelper::importPlugin('workflow');
$eventResult = $this->app->getDispatcher()->dispatch(
'onWorkflowBeforeTransition',
AbstractEvent::create(
'onWorkflowBeforeTransition',
[
'eventClass' => 'Joomla\CMS\Event\Workflow\WorkflowTransitionEvent',
'subject' => $this,
'extension' => $this->extension,
'pks' => $pks,
'transition' => $transition,
'stopTransition' => false,
]
)
);
if ($eventResult->getArgument('stopTransition'))
{
return false;
}
$success = $this->updateAssociations($pks, (int) $transition->to_stage_id);
if ($success)
{
$this->app->getDispatcher()->dispatch(
'onWorkflowAfterTransition',
AbstractEvent::create(
'onWorkflowAfterTransition',
[
'eventClass' => 'Joomla\CMS\Event\Workflow\WorkflowTransitionEvent',
'subject' => $this,
'extension' => $this->extension,
'pks' => $pks,
'transition' => $transition
]
)
);
}
return $success;
}
/**
* Creates an association for the workflow_associations table
*
* @param integer $pk ID of the item
* @param integer $state ID of state
*
* @return boolean
*
* @since 4.0.0
*/
public function createAssociation(int $pk, int $state): bool
{
try
{
$query = $this->db->getQuery(true);
$query->insert($this->db->quoteName('#__workflow_associations'))
->columns(
[
$this->db->quoteName('item_id'),
$this->db->quoteName('stage_id'),
$this->db->quoteName('extension'),
]
)
->values(':pk, :state, :extension')
->bind(':pk', $pk, ParameterType::INTEGER)
->bind(':state', $state, ParameterType::INTEGER)
->bind(':extension', $this->extension);
$this->db->setQuery($query)->execute();
}
catch (\Exception $e)
{
return false;
}
return true;
}
/**
* Update an existing association with a new state
*
* @param array $pks An Array of item IDs which should be changed
* @param integer $state The new state ID
*
* @return boolean
*
* @since 4.0.0
*/
public function updateAssociations(array $pks, int $state): bool
{
$pks = ArrayHelper::toInteger($pks);
try
{
$query = $this->db->getQuery(true);
$query->update($this->db->quoteName('#__workflow_associations'))
->set($this->db->quoteName('stage_id') . ' = :state')
->whereIn($this->db->quoteName('item_id'), $pks)
->where($this->db->quoteName('extension') . ' = :extension')
->bind(':state', $state, ParameterType::INTEGER)
->bind(':extension', $this->extension);
$this->db->setQuery($query)->execute();
}
catch (\Exception $e)
{
return false;
}
return true;
}
/**
* Removes associations from the workflow_associations table
*
* @param integer[] $pks ID of content
*
* @return boolean
*
* @since 4.0.0
*/
public function deleteAssociation(array $pks): bool
{
$pks = ArrayHelper::toInteger($pks);
try
{
$query = $this->db->getQuery(true);
$query
->delete($this->db->quoteName('#__workflow_associations'))
->whereIn($this->db->quoteName('item_id'), $pks)
->where($this->db->quoteName('extension') . ' = :extension')
->bind(':extension', $this->extension);
$this->db->setQuery($query)->execute();
}
catch (\Exception $e)
{
return false;
}
return true;
}
/**
* Loads an existing association item with state and item ID
*
* @param integer $itemId The item ID to load
*
* @return \stdClass|null
*
* @since 4.0.0
*/
public function getAssociation(int $itemId): ?\stdClass
{
$query = $this->db->getQuery(true);
$query->select(
[
$this->db->quoteName('a.item_id'),
$this->db->quoteName('a.stage_id'),
$this->db->quoteName('s.workflow_id'),
]
)
->from($this->db->quoteName('#__workflow_associations', 'a'))
->innerJoin(
$this->db->quoteName('#__workflow_stages', 's'),
$this->db->quoteName('a.stage_id') . ' = ' . $this->db->quoteName('s.id')
)
->where(
[
$this->db->quoteName('item_id') . ' = :id',
$this->db->quoteName('extension') . ' = :extension',
]
)
->bind(':id', $itemId, ParameterType::INTEGER)
->bind(':extension', $this->extension);
return $this->db->setQuery($query)->loadObject();
}
}