<?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  = $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 = $this->getTable();

            // Alter the title for save as copy
            if ($origTable->load(['title' => $data['title']])) {
                [$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);
    }
}
