<?php

/**
 * @package     Joomla.Administrator
 * @subpackage  com_installer
 *
 * @copyright   (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Component\Installer\Administrator\Model;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\DatabaseQuery;
use Joomla\Utilities\ArrayHelper;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Extension Manager Abstract Extension Model.
 *
 * @since  1.5
 */
class InstallerModel extends ListModel
{
    /**
     * Constructor.
     *
     * @param   array                 $config   An optional associative array of configuration settings.
     * @param   ?MVCFactoryInterface  $factory  The factory.
     *
     * @see     \Joomla\CMS\MVC\Model\ListModel
     * @since   1.6
     */
    public function __construct($config = [], ?MVCFactoryInterface $factory = null)
    {
        if (empty($config['filter_fields'])) {
            $config['filter_fields'] = [
                'name',
                'client_id',
                'client', 'client_translated',
                'enabled',
                'type', 'type_translated',
                'folder', 'folder_translated',
                'extension_id',
                'creationDate',
            ];
        }

        parent::__construct($config, $factory);
    }

    /**
     * Returns an object list
     *
     * @param   DatabaseQuery  $query       The query
     * @param   int            $limitstart  Offset
     * @param   int            $limit       The number of records
     *
     * @return  object[]
     */
    protected function _getList($query, $limitstart = 0, $limit = 0)
    {
        $listOrder = $this->getState('list.ordering', 'name');
        $listDirn  = $this->getState('list.direction', 'asc');

        // Replace slashes so preg_match will work
        $search = $this->getState('filter.search', '');
        $search = str_replace('/', ' ', $search);
        $db     = $this->getDatabase();

        // Define which fields have to be processed in a custom way because of translation.
        $customOrderFields = ['name', 'client_translated', 'type_translated', 'folder_translated', 'creationDate'];

        // Process searching, ordering and pagination for fields that need to be translated.
        if (\in_array($listOrder, $customOrderFields) || (!empty($search) && stripos($search, 'id:') !== 0)) {
            // Get results from database and translate them.
            $db->setQuery($query);
            $result = $db->loadObjectList();
            $this->translate($result);

            // Process searching.
            if (!empty($search) && stripos($search, 'id:') !== 0) {
                $escapedSearchString = $this->refineSearchStringToRegex($search, '/');

                // By default search only the extension name field.
                $searchFields = ['name'];

                // If in update sites view search also in the update site name field.
                if ($this instanceof UpdatesitesModel) {
                    $searchFields[] = 'update_site_name';
                }

                foreach ($result as $i => $item) {
                    // Check if search string exists in any of the fields to be searched.
                    $found = 0;

                    foreach ($searchFields as $key => $field) {
                        if (!$found && preg_match('/' . $escapedSearchString . '/i', $item->{$field})) {
                            $found = 1;
                        }
                    }

                    // If search string was not found in any of the fields searched remove it from results array.
                    if (!$found) {
                        unset($result[$i]);
                    }
                }
            }

            // Process ordering.
            // Sort array object by selected ordering and selected direction. Sort is case insensitive and using locale sorting.
            $result = ArrayHelper::sortObjects($result, $listOrder, strtolower($listDirn) == 'desc' ? -1 : 1, false, true);

            // Process pagination.
            $total                                      = \count($result);
            $this->cache[$this->getStoreId('getTotal')] = $total;

            if ($total <= $limitstart) {
                $limitstart = 0;
                $this->setState('list.limitstart', 0);
            }

            return \array_slice($result, $limitstart ?: 0, $limit ?: null);
        }

        // Process searching, ordering and pagination for regular database fields.
        $query->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn));
        $result = parent::_getList($query, $limitstart, $limit);
        $this->translate($result);

        return $result;
    }

    /**
     * Translate a list of objects
     *
     * @param   array  $items  The array of objects
     *
     * @return  void
     */
    protected function translate(&$items)
    {
        $lang = Factory::getLanguage();

        foreach ($items as &$item) {
            if (\strlen($item->manifest_cache) && $data = json_decode($item->manifest_cache)) {
                foreach ($data as $key => $value) {
                    if ($key == 'type') {
                        // Ignore the type field
                        continue;
                    }

                    $item->$key = $value;
                }
            }

            $item->author_info       = @$item->authorEmail . '<br>' . @$item->authorUrl;
            $item->client            = Text::_([0 => 'JSITE', 1 => 'JADMINISTRATOR', 3 => 'JAPI'][$item->client_id] ?? 'JSITE');
            $item->client_translated = $item->client;
            $item->type_translated   = Text::_('COM_INSTALLER_TYPE_' . strtoupper($item->type));
            $item->folder_translated = @$item->folder ? $item->folder : Text::_('COM_INSTALLER_TYPE_NONAPPLICABLE');

            $path = $item->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE;

            switch ($item->type) {
                case 'component':
                    $extension = $item->element;
                    $source    = JPATH_ADMINISTRATOR . '/components/' . $extension;
                    $lang->load("$extension.sys", JPATH_ADMINISTRATOR) || $lang->load("$extension.sys", $source);
                    break;
                case 'file':
                    $extension = 'files_' . $item->element;
                    $lang->load("$extension.sys", JPATH_SITE);
                    break;
                case 'library':
                    $parts     = explode('/', $item->element);
                    $vendor    = (isset($parts[1]) ? $parts[0] : null);
                    $extension = 'lib_' . ($vendor ? implode('_', $parts) : $item->element);

                    if (!$lang->load("$extension.sys", $path)) {
                        $source = $path . '/libraries/' . ($vendor ? $vendor . '/' . $parts[1] : $item->element);
                        $lang->load("$extension.sys", $source);
                    }
                    break;
                case 'module':
                    $extension = $item->element;
                    $source    = $path . '/modules/' . $extension;
                    $lang->load("$extension.sys", $path) || $lang->load("$extension.sys", $source);
                    break;
                case 'plugin':
                    $extension = 'plg_' . $item->folder . '_' . $item->element;
                    $source    = JPATH_PLUGINS . '/' . $item->folder . '/' . $item->element;
                    $lang->load("$extension.sys", JPATH_ADMINISTRATOR) || $lang->load("$extension.sys", $source);
                    break;
                case 'template':
                    $extension = 'tpl_' . $item->element;
                    $source    = $path . '/templates/' . $item->element;
                    $lang->load("$extension.sys", $path) || $lang->load("$extension.sys", $source);
                    break;
                case 'package':
                default:
                    $extension = $item->element;
                    $lang->load("$extension.sys", JPATH_SITE);
                    break;
            }

            // Translate the extension name if possible
            $item->name = Text::_($item->name);

            settype($item->description, 'string');

            if (!\in_array($item->type, ['language'])) {
                $item->description = Text::_($item->description);
            }
        }
    }
}
