<?php
/**
 * @package         Advanced Module Manager
 * @version         9.8.0PRO
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            https://regularlabs.com
 * @copyright       Copyright © 2023 Regular Labs All Rights Reserved
 * @license         GNU General Public License version 2 or later
 */

/**
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Form\Form as JForm;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\MVC\Controller\FormController as JControllerForm;
use Joomla\CMS\MVC\Model\BaseDatabaseModel as JModel;
use Joomla\CMS\Response\JsonResponse as JResponseJson;
use Joomla\CMS\Router\Route as JRoute;
use Joomla\CMS\Session\Session as JSession;
use Joomla\CMS\Uri\Uri as JUri;

/**
 * Module controller class.
 */
class AdvancedModulesControllerModule extends JControllerForm
{
    /**
     * Override parent add method.
     *
     * @return  mixed  True if the record can be added, a JError object if not.
     */
    public function add()
    {
        $app = JFactory::getApplication();

        // Get the result of the parent method. If an error, just return it.
        $result = parent::add();

        if ($result instanceof Exception)
        {
            return $result;
        }

        // Look for the Extension ID.
        $extensionId = $app->input->get('eid', 0, 'int');

        if (empty($extensionId))
        {
            $redirectUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . '&layout=edit';

            $this->setRedirect(JRoute::_($redirectUrl, false));

            throw new Exception(JText::_('COM_MODULES_ERROR_INVALID_EXTENSION'), 500);
        }

        $app->setUserState('com_advancedmodules.add.module.extension_id', $extensionId);
        $app->setUserState('com_advancedmodules.add.module.params', null);

        // Parameters could be coming in for a new item, so let's set them.
        $params = $app->input->get('params', [], 'array');
        $app->setUserState('com_advancedmodules.add.module.params', $params);

        return true;
    }

    /**
     * Method to run batch operations.
     *
     * @param string $model The model
     *
     * @return  boolean  True on success.
     */
    public function batch($model = null)
    {
        $this->checkToken();

        // Set the model
        $model = $this->getModel('Module', '', []);

        // Preset the redirect
        $redirectUrl = 'index.php?option=com_advancedmodules&view=modules' . $this->getRedirectToListAppend();

        $this->setRedirect(JRoute::_($redirectUrl, false));

        return parent::batch($model);
    }

    /**
     * Override parent cancel method to reset the add module state.
     *
     * @param string $key The name of the primary key of the URL variable.
     *
     * @return  boolean  True if access level checks pass, false otherwise.
     */
    public function cancel($key = null)
    {
        $app = JFactory::getApplication();

        $result = parent::cancel();

        $app->setUserState('com_advancedmodules.add.module.extension_id', null);
        $app->setUserState('com_advancedmodules.add.module.params', null);

        if ( ! JFactory::getApplication()->isClient('administrator'))
        {
            $returnUri = $this->input->post->get('return', null, 'base64');
            $returnUri = ! empty($returnUri) ? base64_decode(urldecode($returnUri)) : JUri::base();
            $this->setRedirect($returnUri);
            $this->redirect();
        }

        return $result;
    }

    /**
     * Method to get the other modules in the same position
     *
     * @since   3.6.3
     */
    public function orderPosition()
    {
        $app = JFactory::getApplication();

        // Send json mime type.
        $app->mimeType = 'application/json';
        $app->setHeader('Content-Type', $app->mimeType . '; charset=' . $app->charSet);
        $app->sendHeaders();

        // Check if user token is valid.
        if ( ! JSession::checkToken('get'))
        {
            $app->enqueueMessage(JText::_('JINVALID_TOKEN_NOTICE'), 'error');
            echo new JResponseJson;
            $app->close();
        }

        $jinput   = $app->input;
        $clientId = $jinput->getValue('client_id');
        $position = $jinput->getValue('position');

        $db    = JFactory::getDbo();
        $query = $db->getQuery(true)
            ->select('position, ordering, title')
            ->from('#__modules')
            ->where('client_id = ' . (int) $clientId . ' AND position = ' . $db->q($position))
            ->order('ordering');

        $db->setQuery($query);

        try
        {
            $orders = $db->loadObjectList();
        }
        catch (RuntimeException $e)
        {
            throw new Exception($e->getMessage(), 500);
        }

        $html    = [];
        $orders2 = [];
        $n       = count($orders);

        if ($n > 0)
        {
            for ($i = 0; $i < $n; $i++)
            {
                if ( ! isset($orders2[$orders[$i]->position]))
                {
                    $orders2[$orders[$i]->position] = 0;
                }

                $orders2[$orders[$i]->position]++;
                $ord   = $orders2[$orders[$i]->position];
                $title = JText::sprintf('COM_MODULES_OPTION_ORDER_POSITION', $ord, htmlspecialchars($orders[$i]->title, ENT_QUOTES, 'UTF-8'));

                $html[] = $orders[$i]->position . ',' . $ord . ',' . $title;
            }
        }
        else
        {
            $html[] = $position . ',' . 1 . ',' . JText::_('JNONE');
        }

        echo new JResponseJson($html);
        $app->close();
    }

    /**
     * Method to save a record.
     *
     * @param string $key    The name of the primary key of the URL variable.
     * @param string $urlVar The name of the URL variable if different from the primary key
     *
     * @return  boolean  True if successful, false otherwise.
     */
    public function save($key = null, $urlVar = null)
    {
        $this->checkToken();

        if (JFactory::getDocument()->getType() == 'json')
        {
            $model      = $this->getModel();
            $data       = $this->input->post->get('jform', [], 'array');
            $item       = $model->getItem($this->input->get('id'));
            $properties = $item->getProperties();

            // Replace changed properties
            $data = array_replace_recursive($properties, $data);

            if ( ! empty($data['assigned']))
            {
                $data['assigned'] = array_map('abs', $data['assigned']);
            }

            // Add new data to input before process by parent save()
            $this->input->post->set('jform', $data);

            // Add path of forms directory
            JForm::addFormPath(JPATH_ADMINISTRATOR . '/components/com_advancedmodules/models/forms');
        }

        if ( ! JFactory::getApplication()->isClient('administrator'))
        {
            $this->saveFrontend($key, $urlVar);
        }

        return parent::save($key, $urlVar);
    }

    public function saveFrontend($key = null, $urlVar = null)
    {
        // Check for request forgeries.
        JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));

        $app     = JFactory::getApplication();
        $lang    = JFactory::getLanguage();
        $model   = $this->getModel();
        $table   = $model->getTable();
        $data    = $this->input->post->get('jform', [], 'array');
        $checkin = property_exists($table, 'checked_out');
        $context = 'com_advancedmodules.edit.' . $this->context;
        $task    = $this->getTask();

        $returnUri = $this->input->post->get('current', null, 'base64');
        if (empty($returnUri))
        {
            $returnUri = $this->input->post->get('return', null, 'base64');
        }
        $returnUri = ! empty($returnUri) ? base64_decode(urldecode($returnUri)) : JUri::base();

        // Determine the name of the primary key for the data.
        if (empty($key))
        {
            $key = $table->getKeyName();
        }

        // To avoid data collisions the urlVar may be different from the primary key.
        if (empty($urlVar))
        {
            $urlVar = $key;
        }

        $recordId = $this->input->getInt($urlVar);

        // Populate the row id from the session.
        $data[$key] = $recordId;

        // Access check.
        if ( ! $this->allowSave($data, $key))
        {
            $this->setError(JText::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
            $this->setMessage($this->getError(), 'error');

            $this->setRedirect($returnUri);

            return false;
        }

        // Validate the posted data.
        // Sometimes the form needs some posted data, such as for plugins and modules.
        $form = $model->getForm($data, false);

        if ( ! $form)
        {
            $app->enqueueMessage($model->getError(), 'error');

            return false;
        }

        // Test whether the data is valid.
        $validData = $model->validate($form, $data);

        // Check for validation errors.
        if ($validData === false)
        {
            // Get the validation messages.
            $errors = $model->getErrors();

            // Push up to three validation messages out to the user.
            for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
            {
                if ($errors[$i] instanceof Exception)
                {
                    $app->enqueueMessage($errors[$i]->getMessage(), 'warning');
                    continue;
                }

                $app->enqueueMessage($errors[$i], 'warning');
            }

            // Save the data in the session.
            $app->setUserState($context . '.data', $data);

            // Redirect back to the edit screen.
            $this->setRedirect($returnUri);

            return false;
        }

        if ( ! isset($validData['tags']))
        {
            $validData['tags'] = null;
        }

        // Redirect back to the edit screen.
        $this->setRedirect($returnUri);

        // Attempt to save the data.
        if ( ! $model->save($validData))
        {
            // Save the data in the session.
            $app->setUserState($context . '.data', $validData);

            // Redirect back to the edit screen.
            $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
            $this->setMessage($this->getError(), 'error');

            return false;
        }

        // Save succeeded, so check-in the record.
        if ($checkin && $model->checkin($validData[$key]) === false)
        {
            // Save the data in the session.
            $app->setUserState($context . '.data', $validData);

            // Check-in failed, so go back to the record and display a notice.
            $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
            $this->setMessage($this->getError(), 'error');

            return false;
        }

        $this->setMessage(
            JText::_(
                ($lang->hasKey($this->text_prefix . ($recordId == 0 && $app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS')
                    ? $this->text_prefix
                    : 'JLIB_APPLICATION') . ($recordId == 0 && $app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS'
            )
        );

        // Redirect the user and adjust session state based on the chosen task.
        switch ($task)
        {
            case 'apply':
                $app->setUserState($context . '.data', null);
                break;

            default:
                // Clear the record id and data from the session.
                $this->releaseEditId($context, $recordId);
                $app->setUserState($context . '.data', null);

                $returnUri = $this->input->post->get('return', null, 'base64');
                $returnUri = ! empty($returnUri) ? base64_decode(urldecode($returnUri)) : JUri::base();

                // Redirect to the previous url
                $this->setRedirect($returnUri);
                break;
        }

        $this->redirect();

        return true;
    }

    /**
     * Override parent allowAdd method.
     *
     * @param array $data An array of input data.
     *
     * @return  boolean
     */
    protected function allowAdd($data = [])
    {
        $user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();

        return ($user->authorise('core.create', 'com_modules') || count($user->getAuthorisedCategories('com_modules', 'core.create')));
    }

    /**
     * Method override to check if you can edit an existing record.
     *
     * @param array  $data An array of input data.
     * @param string $key  The name of the key for the primary key.
     *
     * @return  boolean
     */
    protected function allowEdit($data = [], $key = 'id')
    {
        // Initialise variables.
        $recordId = (int) ($data[$key] ?? 0);
        $user     = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();

        // Zero record (id:0), return component edit permission by calling parent controller method
        if ( ! $recordId)
        {
            return parent::allowEdit($data, $key);
        }

        // Check edit on the record asset (explicit or inherited)
        if ($user->authorise('core.edit', 'com_modules.module.' . $recordId))
        {
            return true;
        }

        return false;
    }

    /**
     * Override parent allowSave method.
     *
     * @param array  $data An array of input data.
     * @param string $key  The name of the key for the primary key.
     *
     * @return  boolean
     */
    protected function allowSave($data, $key = 'id')
    {
        // Use custom position if selected
        if (isset($data['custom_position']))
        {
            if (empty($data['position']))
            {
                $data['position'] = $data['custom_position'];
            }

            unset($data['custom_position']);
        }

        return parent::allowSave($data, $key);
    }

    /**
     * Function that allows child controller access to model data after the data has been saved.
     *
     * @param JModel $model     The data model object.
     * @param array  $validData The validated data.
     *
     * @return  void
     */
    protected function postSaveHook(JModel $model, $validData = [])
    {
        $app  = JFactory::getApplication();
        $task = $this->getTask();

        switch ($task)
        {
            case 'save2new':
                $app->setUserState('com_advancedmodules.add.module.extension_id', $model->getState('module.extension_id'));
                break;

            default:
                $app->setUserState('com_advancedmodules.add.module.extension_id', null);
                break;
        }

        $app->setUserState('com_advancedmodules.add.module.params', null);
    }
}
