primo commit
This commit is contained in:
		
							
								
								
									
										373
									
								
								administrator/components/com_workflow/src/Model/StageModel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								administrator/components/com_workflow/src/Model/StageModel.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,373 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Administrator | ||||
|  * @subpackage  com_workflow | ||||
|  * | ||||
|  * @copyright   (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  * @since       4.0.0 | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Component\Workflow\Administrator\Model; | ||||
|  | ||||
| use Joomla\CMS\Factory; | ||||
| use Joomla\CMS\Form\Form; | ||||
| use Joomla\CMS\Language\Text; | ||||
| use Joomla\CMS\Log\Log; | ||||
| use Joomla\CMS\MVC\Model\AdminModel; | ||||
| use Joomla\String\StringHelper; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Model class for stage | ||||
|  * | ||||
|  * @since  4.0.0 | ||||
|  */ | ||||
| class StageModel extends AdminModel | ||||
| { | ||||
|     /** | ||||
|      * Auto-populate the model state. | ||||
|      * | ||||
|      * Note. Calling getState in this method will result in recursion. | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function populateState() | ||||
|     { | ||||
|         parent::populateState(); | ||||
|  | ||||
|         $app       = Factory::getApplication(); | ||||
|         $context   = $this->option . '.' . $this->name; | ||||
|         $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         $this->setState('filter.extension', $extension); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to change the title | ||||
|      * | ||||
|      * @param   integer  $categoryId  The id of the category. | ||||
|      * @param   string   $alias       The alias. | ||||
|      * @param   string   $title       The title. | ||||
|      * | ||||
|      * @return  array  Contains the modified title and alias. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function generateNewTitle($categoryId, $alias, $title) | ||||
|     { | ||||
|         // Alter the title & alias | ||||
|         $table = $this->getTable(); | ||||
|  | ||||
|         while ($table->load(['title' => $title])) { | ||||
|             $title = StringHelper::increment($title); | ||||
|         } | ||||
|  | ||||
|         return [$title, $alias]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to save the form data. | ||||
|      * | ||||
|      * @param   array  $data  The form data. | ||||
|      * | ||||
|      * @return   boolean  True on success. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function save($data) | ||||
|     { | ||||
|         $table      = $this->getTable(); | ||||
|         $context    = $this->option . '.' . $this->name; | ||||
|         $app        = Factory::getApplication(); | ||||
|         $user       = $app->getIdentity(); | ||||
|         $input      = $app->getInput(); | ||||
|         $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); | ||||
|  | ||||
|         if (empty($data['workflow_id'])) { | ||||
|             $data['workflow_id'] = $workflowID; | ||||
|         } | ||||
|  | ||||
|         $workflow = $this->getTable('Workflow'); | ||||
|  | ||||
|         $workflow->load($data['workflow_id']); | ||||
|  | ||||
|         $parts = explode('.', $workflow->extension); | ||||
|  | ||||
|         if (isset($data['rules']) && !$user->authorise('core.admin', $parts[0])) { | ||||
|             unset($data['rules']); | ||||
|         } | ||||
|  | ||||
|         // Make sure we use the correct extension when editing an existing workflow | ||||
|         $key = $table->getKeyName(); | ||||
|         $pk  = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id'); | ||||
|  | ||||
|         if ($pk > 0) { | ||||
|             $table->load($pk); | ||||
|  | ||||
|             if ((int) $table->workflow_id) { | ||||
|                 $data['workflow_id'] = (int) $table->workflow_id; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($input->get('task') == 'save2copy') { | ||||
|             $origTable = clone $this->getTable(); | ||||
|  | ||||
|             // Alter the title for save as copy | ||||
|             if ($origTable->load(['title' => $data['title']])) { | ||||
|                 list($title)   = $this->generateNewTitle(0, '', $data['title']); | ||||
|                 $data['title'] = $title; | ||||
|             } | ||||
|  | ||||
|             $data['published'] = 0; | ||||
|             $data['default']   = 0; | ||||
|         } | ||||
|  | ||||
|         return parent::save($data); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to test whether a record can be deleted. | ||||
|      * | ||||
|      * @param   object  $record  A record object. | ||||
|      * | ||||
|      * @return  boolean  True if allowed to delete the record. Defaults to the permission for the component. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function canDelete($record) | ||||
|     { | ||||
|         $table = $this->getTable('Workflow', 'Administrator'); | ||||
|  | ||||
|         $table->load($record->workflow_id); | ||||
|  | ||||
|         if (empty($record->id) || $record->published != -2) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $app       = Factory::getApplication(); | ||||
|         $extension = $app->getUserStateFromRequest('com_workflow.stage.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         $parts = explode('.', $extension); | ||||
|  | ||||
|         $component = reset($parts); | ||||
|  | ||||
|         if (!$this->getCurrentUser()->authorise('core.delete', $component . '.state.' . (int) $record->id) || $record->default) { | ||||
|             $this->setError(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED')); | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to test whether a record can have its state changed. | ||||
|      * | ||||
|      * @param   object  $record  A record object. | ||||
|      * | ||||
|      * @return  boolean  True if allowed to change the state of the record. Defaults to the permission set in the component. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function canEditState($record) | ||||
|     { | ||||
|         $user      = $this->getCurrentUser(); | ||||
|         $app       = Factory::getApplication(); | ||||
|         $context   = $this->option . '.' . $this->name; | ||||
|         $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         if (!property_exists($record, 'workflow_id')) { | ||||
|             $workflowID          = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); | ||||
|             $record->workflow_id = $workflowID; | ||||
|         } | ||||
|  | ||||
|         // Check for existing workflow. | ||||
|         if (!empty($record->id)) { | ||||
|             return $user->authorise('core.edit.state', $extension . '.state.' . (int) $record->id); | ||||
|         } | ||||
|  | ||||
|         // Default to component settings if workflow isn't known. | ||||
|         return $user->authorise('core.edit.state', $extension); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Abstract method for getting the form from the model. | ||||
|      * | ||||
|      * @param   array    $data      Data for the form. | ||||
|      * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not. | ||||
|      * | ||||
|      * @return  Form|boolean A Form object on success, false on failure | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function getForm($data = [], $loadData = true) | ||||
|     { | ||||
|         // Get the form. | ||||
|         $form = $this->loadForm( | ||||
|             'com_workflow.state', | ||||
|             'stage', | ||||
|             [ | ||||
|                 'control'   => 'jform', | ||||
|                 'load_data' => $loadData, | ||||
|             ] | ||||
|         ); | ||||
|  | ||||
|         if (empty($form)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $id = $data['id'] ?? $form->getValue('id'); | ||||
|  | ||||
|         $item = $this->getItem($id); | ||||
|  | ||||
|         $canEditState = $this->canEditState((object) $item); | ||||
|  | ||||
|         // Modify the form based on access controls. | ||||
|         if (!$canEditState || !empty($item->default)) { | ||||
|             if (!$canEditState) { | ||||
|                 $form->setFieldAttribute('published', 'disabled', 'true'); | ||||
|                 $form->setFieldAttribute('published', 'required', 'false'); | ||||
|                 $form->setFieldAttribute('published', 'filter', 'unset'); | ||||
|             } | ||||
|  | ||||
|             $form->setFieldAttribute('default', 'disabled', 'true'); | ||||
|             $form->setFieldAttribute('default', 'required', 'false'); | ||||
|             $form->setFieldAttribute('default', 'filter', 'unset'); | ||||
|         } | ||||
|  | ||||
|         return $form; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get the data that should be injected in the form. | ||||
|      * | ||||
|      * @return mixed  The data for the form. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function loadFormData() | ||||
|     { | ||||
|         // Check the session for previously entered form data. | ||||
|         $data = Factory::getApplication()->getUserState( | ||||
|             'com_workflow.edit.state.data', | ||||
|             [] | ||||
|         ); | ||||
|  | ||||
|         if (empty($data)) { | ||||
|             $data = $this->getItem(); | ||||
|         } | ||||
|  | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to change the home state of one or more items. | ||||
|      * | ||||
|      * @param   array    $pk     A list of the primary keys to change. | ||||
|      * @param   integer  $value  The value of the home state. | ||||
|      * | ||||
|      * @return  boolean  True on success. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function setDefault($pk, $value = 1) | ||||
|     { | ||||
|         $table = $this->getTable(); | ||||
|  | ||||
|         if ($table->load($pk)) { | ||||
|             if (!$table->published) { | ||||
|                 $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED')); | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (empty($table->id) || !$this->canEditState($table)) { | ||||
|             Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror'); | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if ($value) { | ||||
|             // Verify that the home page for this language is unique per client id | ||||
|             if ($table->load(['default' => '1', 'workflow_id' => $table->workflow_id])) { | ||||
|                 $table->default = 0; | ||||
|                 $table->store(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($table->load($pk)) { | ||||
|             $table->default = $value; | ||||
|             $table->store(); | ||||
|         } | ||||
|  | ||||
|         // Clean the cache | ||||
|         $this->cleanCache(); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to change the published state of one or more records. | ||||
|      * | ||||
|      * @param   array    &$pks   A list of the primary keys to change. | ||||
|      * @param   integer  $value  The value of the published state. | ||||
|      * | ||||
|      * @return  boolean  True on success. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function publish(&$pks, $value = 1) | ||||
|     { | ||||
|         $table     = $this->getTable(); | ||||
|         $pks       = (array) $pks; | ||||
|         $app       = Factory::getApplication(); | ||||
|         $extension = $app->getUserStateFromRequest('com_workflow.state.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         // Default item existence checks. | ||||
|         if ($value != 1) { | ||||
|             foreach ($pks as $i => $pk) { | ||||
|                 if ($table->load($pk) && $table->default) { | ||||
|                     // Prune items that you can't change. | ||||
|                     $app->enqueueMessage(Text::_('COM_WORKFLOW_MSG_DISABLE_DEFAULT'), 'error'); | ||||
|  | ||||
|                     unset($pks[$i]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return parent::publish($pks, $value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to preprocess the form. | ||||
|      * | ||||
|      * @param   Form    $form  A Form object. | ||||
|      * @param   mixed   $data  The data expected for the form. | ||||
|      * @param   string  $group The name of the plugin group to import (defaults to "content"). | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function preprocessForm(Form $form, $data, $group = 'content') | ||||
|     { | ||||
|         $extension = Factory::getApplication()->getInput()->get('extension'); | ||||
|  | ||||
|         $parts = explode('.', $extension); | ||||
|  | ||||
|         $extension = array_shift($parts); | ||||
|  | ||||
|         // Set the access control rules field component value. | ||||
|         $form->setFieldAttribute('rules', 'component', $extension); | ||||
|  | ||||
|         parent::preprocessForm($form, $data, $group); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										201
									
								
								administrator/components/com_workflow/src/Model/StagesModel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								administrator/components/com_workflow/src/Model/StagesModel.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Administrator | ||||
|  * @subpackage  com_workflow | ||||
|  * | ||||
|  * @copyright   (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  * @since       4.0.0 | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Component\Workflow\Administrator\Model; | ||||
|  | ||||
| use Joomla\CMS\Factory; | ||||
| use Joomla\CMS\MVC\Model\ListModel; | ||||
| use Joomla\Database\ParameterType; | ||||
| use Joomla\Database\QueryInterface; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Model class for stages | ||||
|  * | ||||
|  * @since  4.0.0 | ||||
|  */ | ||||
| class StagesModel extends ListModel | ||||
| { | ||||
|     /** | ||||
|      * Constructor. | ||||
|      * | ||||
|      * @param   array  $config  An optional associative array of configuration settings. | ||||
|      * | ||||
|      * @see     JController | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function __construct($config = []) | ||||
|     { | ||||
|         if (empty($config['filter_fields'])) { | ||||
|             $config['filter_fields'] = [ | ||||
|                 'id', 's.id', | ||||
|                 'title', 's.title', | ||||
|                 'ordering','s.ordering', | ||||
|                 'published', 's.published', | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         parent::__construct($config); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to auto-populate the model state. | ||||
|      * | ||||
|      * This method should only be called once per instantiation and is designed | ||||
|      * to be called on the first call to the getState() method unless the model | ||||
|      * configuration flag to ignore the request is set. | ||||
|      * | ||||
|      * Note. Calling getState in this method will result in recursion. | ||||
|      * | ||||
|      * @param   string  $ordering   An optional ordering field. | ||||
|      * @param   string  $direction  An optional direction (asc|desc). | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function populateState($ordering = 's.ordering', $direction = 'ASC') | ||||
|     { | ||||
|         $app = Factory::getApplication(); | ||||
|  | ||||
|         $workflowID = $app->getUserStateFromRequest($this->context . '.filter.workflow_id', 'workflow_id', 1, 'int'); | ||||
|         $extension  = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         if ($workflowID) { | ||||
|             $table = $this->getTable('Workflow', 'Administrator'); | ||||
|  | ||||
|             if ($table->load($workflowID)) { | ||||
|                 $this->setState('active_workflow', $table->title); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->setState('filter.workflow_id', $workflowID); | ||||
|         $this->setState('filter.extension', $extension); | ||||
|  | ||||
|         parent::populateState($ordering, $direction); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A protected method to get a set of ordering conditions. | ||||
|      * | ||||
|      * @param   object  $table  A record object. | ||||
|      * | ||||
|      * @return  array  An array of conditions to add to ordering queries. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function getReorderConditions($table) | ||||
|     { | ||||
|         return [ | ||||
|             $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $table->workflow_id, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get a table object, load it if necessary. | ||||
|      * | ||||
|      * @param   string  $type    The table name. Optional. | ||||
|      * @param   string  $prefix  The class prefix. Optional. | ||||
|      * @param   array   $config  Configuration array for model. Optional. | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Table\Table  A Table object | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getTable($type = 'Stage', $prefix = 'Administrator', $config = []) | ||||
|     { | ||||
|         return parent::getTable($type, $prefix, $config); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get the data that should be injected in the form. | ||||
|      * | ||||
|      * @return  QueryInterface  The query to database. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getListQuery() | ||||
|     { | ||||
|         $db    = $this->getDatabase(); | ||||
|         $query = $db->getQuery(true); | ||||
|  | ||||
|         $query | ||||
|             ->select( | ||||
|                 [ | ||||
|                     $db->quoteName('s.id'), | ||||
|                     $db->quoteName('s.title'), | ||||
|                     $db->quoteName('s.ordering'), | ||||
|                     $db->quoteName('s.default'), | ||||
|                     $db->quoteName('s.published'), | ||||
|                     $db->quoteName('s.checked_out'), | ||||
|                     $db->quoteName('s.checked_out_time'), | ||||
|                     $db->quoteName('s.description'), | ||||
|                     $db->quoteName('uc.name', 'editor'), | ||||
|                 ] | ||||
|             ) | ||||
|             ->from($db->quoteName('#__workflow_stages', 's')) | ||||
|             ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('s.checked_out')); | ||||
|  | ||||
|         // Filter by extension | ||||
|         if ($workflowID = (int) $this->getState('filter.workflow_id')) { | ||||
|             $query->where($db->quoteName('s.workflow_id') . ' = :id') | ||||
|                 ->bind(':id', $workflowID, ParameterType::INTEGER); | ||||
|         } | ||||
|  | ||||
|         $status = (string) $this->getState('filter.published'); | ||||
|  | ||||
|         // Filter by publish state | ||||
|         if (is_numeric($status)) { | ||||
|             $status = (int) $status; | ||||
|             $query->where($db->quoteName('s.published') . ' = :status') | ||||
|                 ->bind(':status', $status, ParameterType::INTEGER); | ||||
|         } elseif ($status === '') { | ||||
|             $query->where($db->quoteName('s.published') . ' IN (0, 1)'); | ||||
|         } | ||||
|  | ||||
|         // Filter by search in title | ||||
|         $search = $this->getState('filter.search'); | ||||
|  | ||||
|         if (!empty($search)) { | ||||
|             $search = '%' . str_replace(' ', '%', trim($search)) . '%'; | ||||
|             $query->where('(' . $db->quoteName('s.title') . ' LIKE :search1 OR ' . $db->quoteName('s.description') . ' LIKE :search2)') | ||||
|                 ->bind([':search1', ':search2'], $search); | ||||
|         } | ||||
|  | ||||
|         // Add the list ordering clause. | ||||
|         $query->order($db->escape($this->getState('list.ordering', 's.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); | ||||
|  | ||||
|         return $query; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a workflow object | ||||
|      * | ||||
|      * @return  object  The workflow | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getWorkflow() | ||||
|     { | ||||
|         $table = $this->getTable('Workflow', 'Administrator'); | ||||
|  | ||||
|         $workflowId = (int) $this->getState('filter.workflow_id'); | ||||
|  | ||||
|         if ($workflowId > 0) { | ||||
|             $table->load($workflowId); | ||||
|         } | ||||
|  | ||||
|         return (object) $table->getProperties(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,336 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Administrator | ||||
|  * @subpackage  com_workflow | ||||
|  * | ||||
|  * @copyright   (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  * @since       4.0.0 | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Component\Workflow\Administrator\Model; | ||||
|  | ||||
| use Joomla\CMS\Factory; | ||||
| use Joomla\CMS\Form\Form; | ||||
| use Joomla\CMS\MVC\Model\AdminModel; | ||||
| use Joomla\CMS\Plugin\PluginHelper; | ||||
| use Joomla\Registry\Registry; | ||||
| use Joomla\String\StringHelper; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Model class for transition | ||||
|  * | ||||
|  * @since  4.0.0 | ||||
|  */ | ||||
| class TransitionModel extends AdminModel | ||||
| { | ||||
|     /** | ||||
|      * Auto-populate the model state. | ||||
|      * | ||||
|      * Note. Calling getState in this method will result in recursion. | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function populateState() | ||||
|     { | ||||
|         parent::populateState(); | ||||
|  | ||||
|         $app       = Factory::getApplication(); | ||||
|         $context   = $this->option . '.' . $this->name; | ||||
|         $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         $this->setState('filter.extension', $extension); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to test whether a record can be deleted. | ||||
|      * | ||||
|      * @param   object  $record  A record object. | ||||
|      * | ||||
|      * @return  boolean  True if allowed to delete the record. Defaults to the permission for the component. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function canDelete($record) | ||||
|     { | ||||
|         if (empty($record->id) || $record->published != -2) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $app       = Factory::getApplication(); | ||||
|         $extension = $app->getUserStateFromRequest('com_workflow.transition.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         return $this->getCurrentUser()->authorise('core.delete', $extension . '.transition.' . (int) $record->id); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to test whether a record can have its state changed. | ||||
|      * | ||||
|      * @param   object  $record  A record object. | ||||
|      * | ||||
|      * @return  boolean  True if allowed to change the state of the record. Defaults to the permission set in the component. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function canEditState($record) | ||||
|     { | ||||
|         $user      = $this->getCurrentUser(); | ||||
|         $app       = Factory::getApplication(); | ||||
|         $context   = $this->option . '.' . $this->name; | ||||
|         $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         if (!property_exists($record, 'workflow_id')) { | ||||
|             $workflowID          = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); | ||||
|             $record->workflow_id = $workflowID; | ||||
|         } | ||||
|  | ||||
|         // Check for existing workflow. | ||||
|         if (!empty($record->id)) { | ||||
|             return $user->authorise('core.edit.state', $extension . '.transition.' . (int) $record->id); | ||||
|         } | ||||
|  | ||||
|         // Default to component settings if workflow isn't known. | ||||
|         return $user->authorise('core.edit.state', $extension); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get a single record. | ||||
|      * | ||||
|      * @param   integer  $pk  The id of the primary key. | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Object\CMSObject|boolean  Object on success, false on failure. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function getItem($pk = null) | ||||
|     { | ||||
|         $item = parent::getItem($pk); | ||||
|  | ||||
|         if (property_exists($item, 'options')) { | ||||
|             $registry      = new Registry($item->options); | ||||
|             $item->options = $registry->toArray(); | ||||
|         } | ||||
|  | ||||
|         return $item; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to save the form data. | ||||
|      * | ||||
|      * @param   array  $data  The form data. | ||||
|      * | ||||
|      * @return   boolean  True on success. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function save($data) | ||||
|     { | ||||
|         $table      = $this->getTable(); | ||||
|         $context    = $this->option . '.' . $this->name; | ||||
|         $app        = Factory::getApplication(); | ||||
|         $user       = $app->getIdentity(); | ||||
|         $input      = $app->getInput(); | ||||
|  | ||||
|         $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); | ||||
|  | ||||
|         if (empty($data['workflow_id'])) { | ||||
|             $data['workflow_id'] = $workflowID; | ||||
|         } | ||||
|  | ||||
|         $workflow = $this->getTable('Workflow'); | ||||
|  | ||||
|         $workflow->load($data['workflow_id']); | ||||
|  | ||||
|         $parts = explode('.', $workflow->extension); | ||||
|  | ||||
|         if (isset($data['rules']) && !$user->authorise('core.admin', $parts[0])) { | ||||
|             unset($data['rules']); | ||||
|         } | ||||
|  | ||||
|         // Make sure we use the correct workflow_id when editing an existing transition | ||||
|         $key = $table->getKeyName(); | ||||
|         $pk  = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id'); | ||||
|  | ||||
|         if ($pk > 0) { | ||||
|             $table->load($pk); | ||||
|  | ||||
|             if ((int) $table->workflow_id) { | ||||
|                 $data['workflow_id'] = (int) $table->workflow_id; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($input->get('task') == 'save2copy') { | ||||
|             $origTable = clone $this->getTable(); | ||||
|  | ||||
|             // Alter the title for save as copy | ||||
|             if ($origTable->load(['title' => $data['title']])) { | ||||
|                 list($title)   = $this->generateNewTitle(0, '', $data['title']); | ||||
|                 $data['title'] = $title; | ||||
|             } | ||||
|  | ||||
|             $data['published'] = 0; | ||||
|         } | ||||
|  | ||||
|         return parent::save($data); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to change the title | ||||
|      * | ||||
|      * @param   integer  $categoryId  The id of the category. | ||||
|      * @param   string   $alias       The alias. | ||||
|      * @param   string   $title       The title. | ||||
|      * | ||||
|      * @return  array  Contains the modified title and alias. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function generateNewTitle($categoryId, $alias, $title) | ||||
|     { | ||||
|         // Alter the title & alias | ||||
|         $table = $this->getTable(); | ||||
|  | ||||
|         while ($table->load(['title' => $title])) { | ||||
|             $title = StringHelper::increment($title); | ||||
|         } | ||||
|  | ||||
|         return [$title, $alias]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Abstract method for getting the form from the model. | ||||
|      * | ||||
|      * @param   array    $data      Data for the form. | ||||
|      * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not. | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Form\Form|boolean  A Form object on success, false on failure | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function getForm($data = [], $loadData = true) | ||||
|     { | ||||
|         // Get the form. | ||||
|         $form = $this->loadForm( | ||||
|             'com_workflow.transition', | ||||
|             'transition', | ||||
|             [ | ||||
|                 'control'   => 'jform', | ||||
|                 'load_data' => $loadData, | ||||
|             ] | ||||
|         ); | ||||
|  | ||||
|         if (empty($form)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $id = $data['id'] ?? $form->getValue('id'); | ||||
|  | ||||
|         $item = $this->getItem($id); | ||||
|  | ||||
|         $canEditState = $this->canEditState((object) $item); | ||||
|  | ||||
|         // Modify the form based on access controls. | ||||
|         if (!$canEditState) { | ||||
|             $form->setFieldAttribute('published', 'disabled', 'true'); | ||||
|             $form->setFieldAttribute('published', 'required', 'false'); | ||||
|             $form->setFieldAttribute('published', 'filter', 'unset'); | ||||
|         } | ||||
|  | ||||
|         if (!empty($item->workflow_id)) { | ||||
|             $data['workflow_id'] = (int) $item->workflow_id; | ||||
|         } | ||||
|  | ||||
|         if (empty($data['workflow_id'])) { | ||||
|             $context = $this->option . '.' . $this->name; | ||||
|  | ||||
|             $data['workflow_id'] = (int) Factory::getApplication()->getUserStateFromRequest( | ||||
|                 $context . '.filter.workflow_id', | ||||
|                 'workflow_id', | ||||
|                 0, | ||||
|                 'int' | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         $where = $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $data['workflow_id']; | ||||
|         $where .= ' AND ' . $this->getDatabase()->quoteName('published') . ' = 1'; | ||||
|  | ||||
|         $form->setFieldAttribute('from_stage_id', 'sql_where', $where); | ||||
|         $form->setFieldAttribute('to_stage_id', 'sql_where', $where); | ||||
|  | ||||
|         return $form; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get the data that should be injected in the form. | ||||
|      * | ||||
|      * @return mixed  The data for the form. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function loadFormData() | ||||
|     { | ||||
|         // Check the session for previously entered form data. | ||||
|         $data = Factory::getApplication()->getUserState( | ||||
|             'com_workflow.edit.transition.data', | ||||
|             [] | ||||
|         ); | ||||
|  | ||||
|         if (empty($data)) { | ||||
|             $data = $this->getItem(); | ||||
|         } | ||||
|  | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|     public function getWorkflow() | ||||
|     { | ||||
|         $app = Factory::getApplication(); | ||||
|  | ||||
|         $context = $this->option . '.' . $this->name; | ||||
|  | ||||
|         $workflow_id = (int) $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int'); | ||||
|  | ||||
|         $workflow = $this->getTable('Workflow'); | ||||
|  | ||||
|         $workflow->load($workflow_id); | ||||
|  | ||||
|         return (object) $workflow->getProperties(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Trigger the form preparation for the workflow group | ||||
|      * | ||||
|      * @param   Form    $form   A Form object. | ||||
|      * @param   mixed   $data   The data expected for the form. | ||||
|      * @param   string  $group  The name of the plugin group to import (defaults to "content"). | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @see     FormField | ||||
|      * @since   4.0.0 | ||||
|      * @throws  \Exception if there is an error in the form event. | ||||
|      */ | ||||
|     protected function preprocessForm(Form $form, $data, $group = 'content') | ||||
|     { | ||||
|         $extension = Factory::getApplication()->getInput()->get('extension'); | ||||
|  | ||||
|         $parts = explode('.', $extension); | ||||
|  | ||||
|         $extension = array_shift($parts); | ||||
|  | ||||
|         // Set the access control rules field component value. | ||||
|         $form->setFieldAttribute('rules', 'component', $extension); | ||||
|  | ||||
|         // Import the appropriate plugin group. | ||||
|         PluginHelper::importPlugin('workflow'); | ||||
|  | ||||
|         parent::preprocessForm($form, $data, $group); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,248 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Administrator | ||||
|  * @subpackage  com_workflow | ||||
|  * | ||||
|  * @copyright   (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  * @since       4.0.0 | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Component\Workflow\Administrator\Model; | ||||
|  | ||||
| use Joomla\CMS\Factory; | ||||
| use Joomla\CMS\MVC\Model\ListModel; | ||||
| use Joomla\Database\ParameterType; | ||||
| use Joomla\Database\QueryInterface; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Model class for transitions | ||||
|  * | ||||
|  * @since  4.0.0 | ||||
|  */ | ||||
| class TransitionsModel extends ListModel | ||||
| { | ||||
|     /** | ||||
|      * Constructor. | ||||
|      * | ||||
|      * @param   array  $config  An optional associative array of configuration settings. | ||||
|      * | ||||
|      * @see     JController | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function __construct($config = []) | ||||
|     { | ||||
|         if (empty($config['filter_fields'])) { | ||||
|             $config['filter_fields'] = [ | ||||
|                 'id', 't.id', | ||||
|                 'published', 't.published', | ||||
|                 'ordering', 't.ordering', | ||||
|                 'title', 't.title', | ||||
|                 'from_stage', 't.from_stage_id', | ||||
|                 'to_stage', 't.to_stage_id', | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         parent::__construct($config); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to auto-populate the model state. | ||||
|      * | ||||
|      * This method should only be called once per instantiation and is designed | ||||
|      * to be called on the first call to the getState() method unless the model | ||||
|      * configuration flag to ignore the request is set. | ||||
|      * | ||||
|      * Note. Calling getState in this method will result in recursion. | ||||
|      * | ||||
|      * @param   string  $ordering   An optional ordering field. | ||||
|      * @param   string  $direction  An optional direction (asc|desc). | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function populateState($ordering = 't.ordering', $direction = 'ASC') | ||||
|     { | ||||
|         $app        = Factory::getApplication(); | ||||
|         $workflowID = $app->getUserStateFromRequest($this->context . '.filter.workflow_id', 'workflow_id', 1, 'int'); | ||||
|         $extension  = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         if ($workflowID) { | ||||
|             $table = $this->getTable('Workflow', 'Administrator'); | ||||
|  | ||||
|             if ($table->load($workflowID)) { | ||||
|                 $this->setState('active_workflow', $table->title); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->setState('filter.workflow_id', $workflowID); | ||||
|         $this->setState('filter.extension', $extension); | ||||
|  | ||||
|         parent::populateState($ordering, $direction); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get a table object, load it if necessary. | ||||
|      * | ||||
|      * @param   string  $type    The table name. Optional. | ||||
|      * @param   string  $prefix  The class prefix. Optional. | ||||
|      * @param   array   $config  Configuration array for model. Optional. | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Table\Table  A Table object | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getTable($type = 'Transition', $prefix = 'Administrator', $config = []) | ||||
|     { | ||||
|         return parent::getTable($type, $prefix, $config); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A protected method to get a set of ordering conditions. | ||||
|      * | ||||
|      * @param   object  $table  A record object. | ||||
|      * | ||||
|      * @return  array  An array of conditions to add to ordering queries. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function getReorderConditions($table) | ||||
|     { | ||||
|         return [ | ||||
|             $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $table->workflow_id, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get the data that should be injected in the form. | ||||
|      * | ||||
|      * @return  QueryInterface  The query to database. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getListQuery() | ||||
|     { | ||||
|         $db    = $this->getDatabase(); | ||||
|         $query = $db->getQuery(true); | ||||
|  | ||||
|         $query | ||||
|             ->select( | ||||
|                 [ | ||||
|                     $db->quoteName('t.id'), | ||||
|                     $db->quoteName('t.title'), | ||||
|                     $db->quoteName('t.from_stage_id'), | ||||
|                     $db->quoteName('t.to_stage_id'), | ||||
|                     $db->quoteName('t.published'), | ||||
|                     $db->quoteName('t.checked_out'), | ||||
|                     $db->quoteName('t.checked_out_time'), | ||||
|                     $db->quoteName('t.ordering'), | ||||
|                     $db->quoteName('t.description'), | ||||
|                     $db->quoteName('f_stage.title', 'from_stage'), | ||||
|                     $db->quoteName('t_stage.title', 'to_stage'), | ||||
|                     $db->quoteName('uc.name', 'editor'), | ||||
|                 ] | ||||
|             ) | ||||
|             ->from($db->quoteName('#__workflow_transitions', 't')) | ||||
|             ->join('LEFT', $db->quoteName('#__workflow_stages', 'f_stage'), $db->quoteName('f_stage.id') . ' = ' . $db->quoteName('t.from_stage_id')) | ||||
|             ->join('LEFT', $db->quoteName('#__workflow_stages', 't_stage'), $db->quoteName('t_stage.id') . ' = ' . $db->quoteName('t.to_stage_id')) | ||||
|             ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('t.checked_out')); | ||||
|  | ||||
|         // Filter by extension | ||||
|         if ($workflowID = (int) $this->getState('filter.workflow_id')) { | ||||
|             $query->where($db->quoteName('t.workflow_id') . ' = :id') | ||||
|                 ->bind(':id', $workflowID, ParameterType::INTEGER); | ||||
|         } | ||||
|  | ||||
|         $status = (string) $this->getState('filter.published'); | ||||
|  | ||||
|         // Filter by status | ||||
|         if (is_numeric($status)) { | ||||
|             $status = (int) $status; | ||||
|             $query->where($db->quoteName('t.published') . ' = :status') | ||||
|                 ->bind(':status', $status, ParameterType::INTEGER); | ||||
|         } elseif ($status === '') { | ||||
|             $query->where($db->quoteName('t.published') . ' IN (0, 1)'); | ||||
|         } | ||||
|  | ||||
|         // Filter by column from_stage_id | ||||
|         if ($fromStage = (int) $this->getState('filter.from_stage')) { | ||||
|             $query->where($db->quoteName('from_stage_id') . ' = :fromStage') | ||||
|                 ->bind(':fromStage', $fromStage, ParameterType::INTEGER); | ||||
|         } | ||||
|  | ||||
|         // Filter by column to_stage_id | ||||
|         if ($toStage = (int) $this->getState('filter.to_stage')) { | ||||
|             $query->where($db->quoteName('to_stage_id') . ' = :toStage') | ||||
|                 ->bind(':toStage', $toStage, ParameterType::INTEGER); | ||||
|         } | ||||
|  | ||||
|         // Filter by search in title | ||||
|         $search = $this->getState('filter.search'); | ||||
|  | ||||
|         if (!empty($search)) { | ||||
|             $search = '%' . str_replace(' ', '%', trim($search)) . '%'; | ||||
|             $query->where('(' . $db->quoteName('t.title') . ' LIKE :search1 OR ' . $db->quoteName('t.description') . ' LIKE :search2)') | ||||
|                 ->bind([':search1', ':search2'], $search); | ||||
|         } | ||||
|  | ||||
|         // Add the list ordering clause. | ||||
|         $orderCol   = $this->state->get('list.ordering', 't.id'); | ||||
|         $orderDirn  = strtoupper($this->state->get('list.direction', 'ASC')); | ||||
|  | ||||
|         $query->order($db->escape($orderCol) . ' ' . ($orderDirn === 'DESC' ? 'DESC' : 'ASC')); | ||||
|  | ||||
|         return $query; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the filter form | ||||
|      * | ||||
|      * @param   array    $data      data | ||||
|      * @param   boolean  $loadData  load current data | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Form\Form|boolean The Form object or false on error | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function getFilterForm($data = [], $loadData = true) | ||||
|     { | ||||
|         $form = parent::getFilterForm($data, $loadData); | ||||
|  | ||||
|         $id = (int) $this->getState('filter.workflow_id'); | ||||
|  | ||||
|         if ($form) { | ||||
|             $where = $this->getDatabase()->quoteName('workflow_id') . ' = ' . $id . ' AND ' . $this->getDatabase()->quoteName('published') . ' = 1'; | ||||
|  | ||||
|             $form->setFieldAttribute('from_stage', 'sql_where', $where, 'filter'); | ||||
|             $form->setFieldAttribute('to_stage', 'sql_where', $where, 'filter'); | ||||
|         } | ||||
|  | ||||
|         return $form; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a workflow object | ||||
|      * | ||||
|      * @return  object  The workflow | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getWorkflow() | ||||
|     { | ||||
|         $table = $this->getTable('Workflow', 'Administrator'); | ||||
|  | ||||
|         $workflowId = (int) $this->getState('filter.workflow_id'); | ||||
|  | ||||
|         if ($workflowId > 0) { | ||||
|             $table->load($workflowId); | ||||
|         } | ||||
|  | ||||
|         return (object) $table->getProperties(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,398 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Administrator | ||||
|  * @subpackage  com_workflow | ||||
|  * | ||||
|  * @copyright   (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  * @since       4.0.0 | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Component\Workflow\Administrator\Model; | ||||
|  | ||||
| use Joomla\CMS\Factory; | ||||
| use Joomla\CMS\Form\Form; | ||||
| use Joomla\CMS\Language\Text; | ||||
| use Joomla\CMS\Log\Log; | ||||
| use Joomla\CMS\MVC\Model\AdminModel; | ||||
| use Joomla\String\StringHelper; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Model class for workflow | ||||
|  * | ||||
|  * @since  4.0.0 | ||||
|  */ | ||||
| class WorkflowModel extends AdminModel | ||||
| { | ||||
|     /** | ||||
|      * Auto-populate the model state. | ||||
|      * | ||||
|      * Note. Calling getState in this method will result in recursion. | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function populateState() | ||||
|     { | ||||
|         parent::populateState(); | ||||
|  | ||||
|         $app       = Factory::getApplication(); | ||||
|         $context   = $this->option . '.' . $this->name; | ||||
|         $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         $this->setState('filter.extension', $extension); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to change the title | ||||
|      * | ||||
|      * @param   integer  $categoryId  The id of the category. | ||||
|      * @param   string   $alias       The alias. | ||||
|      * @param   string   $title       The title. | ||||
|      * | ||||
|      * @return  array  Contains the modified title and alias. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function generateNewTitle($categoryId, $alias, $title) | ||||
|     { | ||||
|         // Alter the title & alias | ||||
|         $table = $this->getTable(); | ||||
|  | ||||
|         while ($table->load(['title' => $title])) { | ||||
|             $title = StringHelper::increment($title); | ||||
|         } | ||||
|  | ||||
|         return [$title, $alias]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to save the form data. | ||||
|      * | ||||
|      * @param   array  $data  The form data. | ||||
|      * | ||||
|      * @return  boolean True on success. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function save($data) | ||||
|     { | ||||
|         $table             = $this->getTable(); | ||||
|         $app               = Factory::getApplication(); | ||||
|         $user              = $app->getIdentity(); | ||||
|         $input             = $app->getInput(); | ||||
|         $context           = $this->option . '.' . $this->name; | ||||
|         $extension         = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|         $data['extension'] = !empty($data['extension']) ? $data['extension'] : $extension; | ||||
|  | ||||
|         // Make sure we use the correct extension when editing an existing workflow | ||||
|         $key = $table->getKeyName(); | ||||
|         $pk  = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id'); | ||||
|  | ||||
|         if ($pk > 0) { | ||||
|             $table->load($pk); | ||||
|  | ||||
|             $data['extension'] = $table->extension; | ||||
|         } | ||||
|  | ||||
|         if (isset($data['rules']) && !$user->authorise('core.admin', $data['extension'])) { | ||||
|             unset($data['rules']); | ||||
|         } | ||||
|  | ||||
|         if ($input->get('task') == 'save2copy') { | ||||
|             $origTable = clone $this->getTable(); | ||||
|  | ||||
|             // Alter the title for save as copy | ||||
|             if ($origTable->load(['title' => $data['title']])) { | ||||
|                 list($title)   = $this->generateNewTitle(0, '', $data['title']); | ||||
|                 $data['title'] = $title; | ||||
|             } | ||||
|  | ||||
|             // Unpublish new copy | ||||
|             $data['published'] = 0; | ||||
|             $data['default']   = 0; | ||||
|         } | ||||
|  | ||||
|         $result = parent::save($data); | ||||
|  | ||||
|         // Create default stage for new workflow | ||||
|         if ($result && $input->getCmd('task') !== 'save2copy' && $this->getState($this->getName() . '.new')) { | ||||
|             $workflow_id = (int) $this->getState($this->getName() . '.id'); | ||||
|  | ||||
|             $table = $this->getTable('Stage'); | ||||
|  | ||||
|             $table->id          = 0; | ||||
|             $table->title       = 'COM_WORKFLOW_BASIC_STAGE'; | ||||
|             $table->description = ''; | ||||
|             $table->workflow_id = $workflow_id; | ||||
|             $table->published   = 1; | ||||
|             $table->default     = 1; | ||||
|  | ||||
|             $table->store(); | ||||
|         } | ||||
|  | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Abstract method for getting the form from the model. | ||||
|      * | ||||
|      * @param   array    $data      Data for the form. | ||||
|      * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not. | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function getForm($data = [], $loadData = true) | ||||
|     { | ||||
|         // Get the form. | ||||
|         $form = $this->loadForm( | ||||
|             'com_workflow.workflow', | ||||
|             'workflow', | ||||
|             [ | ||||
|                 'control'   => 'jform', | ||||
|                 'load_data' => $loadData, | ||||
|             ] | ||||
|         ); | ||||
|  | ||||
|         if (empty($form)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $id = $data['id'] ?? $form->getValue('id'); | ||||
|  | ||||
|         $item = $this->getItem($id); | ||||
|  | ||||
|         $canEditState = $this->canEditState((object) $item); | ||||
|  | ||||
|         // Modify the form based on access controls. | ||||
|         if (!$canEditState || !empty($item->default)) { | ||||
|             if (!$canEditState) { | ||||
|                 $form->setFieldAttribute('published', 'disabled', 'true'); | ||||
|                 $form->setFieldAttribute('published', 'required', 'false'); | ||||
|                 $form->setFieldAttribute('published', 'filter', 'unset'); | ||||
|             } | ||||
|  | ||||
|             $form->setFieldAttribute('default', 'disabled', 'true'); | ||||
|             $form->setFieldAttribute('default', 'required', 'false'); | ||||
|             $form->setFieldAttribute('default', 'filter', 'unset'); | ||||
|         } | ||||
|  | ||||
|         $form->setFieldAttribute('created', 'default', Factory::getDate()->format('Y-m-d H:i:s')); | ||||
|         $form->setFieldAttribute('modified', 'default', Factory::getDate()->format('Y-m-d H:i:s')); | ||||
|  | ||||
|         return $form; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get the data that should be injected in the form. | ||||
|      * | ||||
|      * @return mixed  The data for the form. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function loadFormData() | ||||
|     { | ||||
|         // Check the session for previously entered form data. | ||||
|         $data = Factory::getApplication()->getUserState( | ||||
|             'com_workflow.edit.workflow.data', | ||||
|             [] | ||||
|         ); | ||||
|  | ||||
|         if (empty($data)) { | ||||
|             $data = $this->getItem(); | ||||
|         } | ||||
|  | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to preprocess the form. | ||||
|      * | ||||
|      * @param   Form    $form   Form object. | ||||
|      * @param   mixed   $data   The data expected for the form. | ||||
|      * @param   string  $group  The name of the plugin group to import (defaults to "content"). | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function preprocessForm(Form $form, $data, $group = 'content') | ||||
|     { | ||||
|         $extension = Factory::getApplication()->getInput()->get('extension'); | ||||
|  | ||||
|         $parts = explode('.', $extension); | ||||
|  | ||||
|         $extension = array_shift($parts); | ||||
|  | ||||
|         // Set the access control rules field component value. | ||||
|         $form->setFieldAttribute('rules', 'component', $extension); | ||||
|  | ||||
|         parent::preprocessForm($form, $data, $group); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A protected method to get a set of ordering conditions. | ||||
|      * | ||||
|      * @param   object  $table  A record object. | ||||
|      * | ||||
|      * @return  array  An array of conditions to add to ordering queries. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function getReorderConditions($table) | ||||
|     { | ||||
|         $db = $this->getDatabase(); | ||||
|  | ||||
|         return [ | ||||
|             $db->quoteName('extension') . ' = ' . $db->quote($table->extension), | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to change the default state of one item. | ||||
|      * | ||||
|      * @param   array    $pk     A list of the primary keys to change. | ||||
|      * @param   integer  $value  The value of the home state. | ||||
|      * | ||||
|      * @return  boolean  True on success. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function setDefault($pk, $value = 1) | ||||
|     { | ||||
|         $table = $this->getTable(); | ||||
|  | ||||
|         if ($table->load($pk)) { | ||||
|             if ($table->published !== 1) { | ||||
|                 $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED')); | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (empty($table->id) || !$this->canEditState($table)) { | ||||
|             Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror'); | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $date = Factory::getDate()->toSql(); | ||||
|  | ||||
|         if ($value) { | ||||
|             // Unset other default item | ||||
|             if ( | ||||
|                 $table->load( | ||||
|                     [ | ||||
|                     'default'   => '1', | ||||
|                     'extension' => $table->extension, | ||||
|                     ] | ||||
|                 ) | ||||
|             ) { | ||||
|                 $table->default  = 0; | ||||
|                 $table->modified = $date; | ||||
|                 $table->store(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($table->load($pk)) { | ||||
|             $table->modified = $date; | ||||
|             $table->default  = $value; | ||||
|             $table->store(); | ||||
|         } | ||||
|  | ||||
|         // Clean the cache | ||||
|         $this->cleanCache(); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to test whether a record can be deleted. | ||||
|      * | ||||
|      * @param   object  $record  A record object. | ||||
|      * | ||||
|      * @return  boolean  True if allowed to delete the record. Defaults to the permission for the component. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function canDelete($record) | ||||
|     { | ||||
|         if (empty($record->id) || $record->published != -2) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return $this->getCurrentUser()->authorise('core.delete', $record->extension . '.workflow.' . (int) $record->id); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to test whether a record can have its state changed. | ||||
|      * | ||||
|      * @param   object  $record  A record object. | ||||
|      * | ||||
|      * @return  boolean  True if allowed to change the state of the record. Defaults to the permission set in the component. | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     protected function canEditState($record) | ||||
|     { | ||||
|         $user = $this->getCurrentUser(); | ||||
|  | ||||
|         // Check for existing workflow. | ||||
|         if (!empty($record->id)) { | ||||
|             return $user->authorise('core.edit.state', $record->extension . '.workflow.' . (int) $record->id); | ||||
|         } | ||||
|  | ||||
|         // Default to component settings if workflow isn't known. | ||||
|         return $user->authorise('core.edit.state', $record->extension); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to change the published state of one or more records. | ||||
|      * | ||||
|      * @param   array    &$pks   A list of the primary keys to change. | ||||
|      * @param   integer  $value  The value of the published state. | ||||
|      * | ||||
|      * @return  boolean  True on success. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function publish(&$pks, $value = 1) | ||||
|     { | ||||
|         $table = $this->getTable(); | ||||
|         $pks   = (array) $pks; | ||||
|  | ||||
|         $date = Factory::getDate()->toSql(); | ||||
|  | ||||
|         // Default workflow item check. | ||||
|         foreach ($pks as $i => $pk) { | ||||
|             if ($table->load($pk) && $value != 1 && $table->default) { | ||||
|                 // Prune items that you can't change. | ||||
|                 Factory::getApplication()->enqueueMessage(Text::_('COM_WORKFLOW_UNPUBLISH_DEFAULT_ERROR'), 'error'); | ||||
|                 unset($pks[$i]); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Clean the cache. | ||||
|         $this->cleanCache(); | ||||
|  | ||||
|         // Ensure that previous checks don't empty the array. | ||||
|         if (empty($pks)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         $table->load($pk); | ||||
|         $table->modified = $date; | ||||
|         $table->store(); | ||||
|  | ||||
|         return parent::publish($pks, $value); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,274 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Administrator | ||||
|  * @subpackage  com_workflow | ||||
|  * | ||||
|  * @copyright   (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  * @since       4.0.0 | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Component\Workflow\Administrator\Model; | ||||
|  | ||||
| use Joomla\CMS\Factory; | ||||
| use Joomla\CMS\MVC\Model\ListModel; | ||||
| use Joomla\Database\ParameterType; | ||||
| use Joomla\Database\QueryInterface; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Model class for workflows | ||||
|  * | ||||
|  * @since  4.0.0 | ||||
|  */ | ||||
| class WorkflowsModel extends ListModel | ||||
| { | ||||
|     /** | ||||
|      * Constructor. | ||||
|      * | ||||
|      * @param   array  $config  An optional associative array of configuration settings. | ||||
|      * | ||||
|      * @see     JController | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function __construct($config = []) | ||||
|     { | ||||
|         if (empty($config['filter_fields'])) { | ||||
|             $config['filter_fields'] = [ | ||||
|                 'id', 'w.id', | ||||
|                 'title', 'w.title', | ||||
|                 'published', 'w.published', | ||||
|                 'created_by', 'w.created_by', | ||||
|                 'created', 'w.created', | ||||
|                 'ordering', 'w.ordering', | ||||
|                 'modified', 'w.modified', | ||||
|                 'description', 'w.description', | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         parent::__construct($config); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to auto-populate the model state. | ||||
|      * | ||||
|      * This method should only be called once per instantiation and is designed | ||||
|      * to be called on the first call to the getState() method unless the model | ||||
|      * configuration flag to ignore the request is set. | ||||
|      * | ||||
|      * Note. Calling getState in this method will result in recursion. | ||||
|      * | ||||
|      * @param   string  $ordering   An optional ordering field. | ||||
|      * @param   string  $direction  An optional direction (asc|desc). | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function populateState($ordering = 'w.ordering', $direction = 'asc') | ||||
|     { | ||||
|         $app       = Factory::getApplication(); | ||||
|         $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd'); | ||||
|  | ||||
|         $this->setState('filter.extension', $extension); | ||||
|         $parts = explode('.', $extension); | ||||
|  | ||||
|         // Extract the component name | ||||
|         $this->setState('filter.component', $parts[0]); | ||||
|  | ||||
|         // Extract the optional section name | ||||
|         $this->setState('filter.section', (\count($parts) > 1) ? $parts[1] : null); | ||||
|  | ||||
|         parent::populateState($ordering, $direction); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get a table object, load it if necessary. | ||||
|      * | ||||
|      * @param   string  $type    The table name. Optional. | ||||
|      * @param   string  $prefix  The class prefix. Optional. | ||||
|      * @param   array   $config  Configuration array for model. Optional. | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Table\Table  A Table object | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getTable($type = 'Workflow', $prefix = 'Administrator', $config = []) | ||||
|     { | ||||
|         return parent::getTable($type, $prefix, $config); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get an array of data items. | ||||
|      * | ||||
|      * @return  mixed  An array of data items on success, false on failure. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getItems() | ||||
|     { | ||||
|         $items = parent::getItems(); | ||||
|  | ||||
|         if ($items) { | ||||
|             $this->countItems($items); | ||||
|         } | ||||
|  | ||||
|         return $items; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the filter form | ||||
|      * | ||||
|      * @param   array    $data      data | ||||
|      * @param   boolean  $loadData  load current data | ||||
|      * | ||||
|      * @return  \Joomla\CMS\Form\Form|bool the Form object or false | ||||
|      * | ||||
|      * @since   4.0.0 | ||||
|      */ | ||||
|     public function getFilterForm($data = [], $loadData = true) | ||||
|     { | ||||
|         $form = parent::getFilterForm($data, $loadData); | ||||
|  | ||||
|         if ($form) { | ||||
|             $form->setValue('extension', null, $this->getState('filter.extension')); | ||||
|         } | ||||
|  | ||||
|         return $form; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Add the number of transitions and states to all workflow items | ||||
|      * | ||||
|      * @param   array  $items  The workflow items | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     protected function countItems($items) | ||||
|     { | ||||
|         $db = $this->getDatabase(); | ||||
|  | ||||
|         $ids = [0]; | ||||
|  | ||||
|         foreach ($items as $item) { | ||||
|             $ids[] = (int) $item->id; | ||||
|  | ||||
|             $item->count_states      = 0; | ||||
|             $item->count_transitions = 0; | ||||
|         } | ||||
|  | ||||
|         $query = $db->getQuery(true); | ||||
|  | ||||
|         $query->select( | ||||
|             [ | ||||
|                 $db->quoteName('workflow_id'), | ||||
|                 'COUNT(*) AS ' . $db->quoteName('count'), | ||||
|             ] | ||||
|         ) | ||||
|             ->from($db->quoteName('#__workflow_stages')) | ||||
|             ->whereIn($db->quoteName('workflow_id'), $ids) | ||||
|             ->where($db->quoteName('published') . ' >= 0') | ||||
|             ->group($db->quoteName('workflow_id')); | ||||
|  | ||||
|         $status = $db->setQuery($query)->loadObjectList('workflow_id'); | ||||
|  | ||||
|         $query = $db->getQuery(true); | ||||
|  | ||||
|         $query->select( | ||||
|             [ | ||||
|                 $db->quoteName('workflow_id'), | ||||
|                 'COUNT(*) AS ' . $db->quoteName('count'), | ||||
|             ] | ||||
|         ) | ||||
|             ->from($db->quoteName('#__workflow_transitions')) | ||||
|             ->whereIn($db->quoteName('workflow_id'), $ids) | ||||
|             ->where($db->quoteName('published') . ' >= 0') | ||||
|             ->group($db->quoteName('workflow_id')); | ||||
|  | ||||
|         $transitions = $db->setQuery($query)->loadObjectList('workflow_id'); | ||||
|  | ||||
|         foreach ($items as $item) { | ||||
|             if (isset($status[$item->id])) { | ||||
|                 $item->count_states = (int) $status[$item->id]->count; | ||||
|             } | ||||
|  | ||||
|             if (isset($transitions[$item->id])) { | ||||
|                 $item->count_transitions = (int) $transitions[$item->id]->count; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get the data that should be injected in the form. | ||||
|      * | ||||
|      * @return  QueryInterface  The query to database. | ||||
|      * | ||||
|      * @since  4.0.0 | ||||
|      */ | ||||
|     public function getListQuery() | ||||
|     { | ||||
|         $db    = $this->getDatabase(); | ||||
|         $query = $db->getQuery(true); | ||||
|  | ||||
|         $query->select( | ||||
|             [ | ||||
|                 $db->quoteName('w.id'), | ||||
|                 $db->quoteName('w.title'), | ||||
|                 $db->quoteName('w.created'), | ||||
|                 $db->quoteName('w.modified'), | ||||
|                 $db->quoteName('w.published'), | ||||
|                 $db->quoteName('w.checked_out'), | ||||
|                 $db->quoteName('w.checked_out_time'), | ||||
|                 $db->quoteName('w.ordering'), | ||||
|                 $db->quoteName('w.default'), | ||||
|                 $db->quoteName('w.created_by'), | ||||
|                 $db->quoteName('w.description'), | ||||
|                 $db->quoteName('u.name'), | ||||
|                 $db->quoteName('uc.name', 'editor'), | ||||
|             ] | ||||
|         ) | ||||
|             ->from($db->quoteName('#__workflows', 'w')) | ||||
|             ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('w.created_by')) | ||||
|             ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('w.checked_out')); | ||||
|  | ||||
|         // Filter by extension | ||||
|         if ($extension = $this->getState('filter.extension')) { | ||||
|             $query->where($db->quoteName('extension') . ' = :extension') | ||||
|                 ->bind(':extension', $extension); | ||||
|         } | ||||
|  | ||||
|         $status = (string) $this->getState('filter.published'); | ||||
|  | ||||
|         // Filter by status | ||||
|         if (is_numeric($status)) { | ||||
|             $status = (int) $status; | ||||
|             $query->where($db->quoteName('w.published') . ' = :published') | ||||
|                 ->bind(':published', $status, ParameterType::INTEGER); | ||||
|         } elseif ($status === '') { | ||||
|             $query->where($db->quoteName('w.published') . ' IN (0, 1)'); | ||||
|         } | ||||
|  | ||||
|         // Filter by search in title | ||||
|         $search = $this->getState('filter.search'); | ||||
|  | ||||
|         if (!empty($search)) { | ||||
|             $search = '%' . str_replace(' ', '%', trim($search)) . '%'; | ||||
|             $query->where('(' . $db->quoteName('w.title') . ' LIKE :search1 OR ' . $db->quoteName('w.description') . ' LIKE :search2)') | ||||
|                 ->bind([':search1', ':search2'], $search); | ||||
|         } | ||||
|  | ||||
|         // Add the list ordering clause. | ||||
|         $orderCol  = $this->state->get('list.ordering', 'w.ordering'); | ||||
|         $orderDirn = strtoupper($this->state->get('list.direction', 'ASC')); | ||||
|  | ||||
|         $query->order($db->escape($orderCol) . ' ' . ($orderDirn === 'DESC' ? 'DESC' : 'ASC')); | ||||
|  | ||||
|         return $query; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user