<?php

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

namespace Joomla\Plugin\SampleData\MultiLanguage\Extension;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\Plugin\AjaxEvent;
use Joomla\CMS\Event\SampleData\GetOverviewEvent;
use Joomla\CMS\Extension\ExtensionHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Workflow\Workflow;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Database\ParameterType;
use Joomla\Event\SubscriberInterface;

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

/**
 * Sampledata - Multilang Plugin
 *
 * @since  4.0.0
 */
final class MultiLanguage extends CMSPlugin implements SubscriberInterface
{
    use DatabaseAwareTrait;

    /**
     * Affects constructor behavior. If true, language files will be loaded automatically.
     *
     * @var     boolean
     *
     * @since   4.0.0
     */
    protected $autoloadLanguage = true;

    /**
     * @var     string
     *
     * @since   4.0.0
     */
    protected $path = null;

    /**
     * @var    integer Id, author of all generated content.
     *
     * @since   4.0.0
     */
    protected $adminId;

    /**
     * Returns an array of events this subscriber will listen to.
     *
     * @return  array
     *
     * @since 5.3.0
     */
    public static function getSubscribedEvents(): array
    {
        return [
            'onSampledataGetOverview'    => 'onSampledataGetOverview',
            'onAjaxSampledataApplyStep1' => 'onAjaxSampledataApplyStep1',
            'onAjaxSampledataApplyStep2' => 'onAjaxSampledataApplyStep2',
            'onAjaxSampledataApplyStep3' => 'onAjaxSampledataApplyStep3',
            'onAjaxSampledataApplyStep4' => 'onAjaxSampledataApplyStep4',
            'onAjaxSampledataApplyStep5' => 'onAjaxSampledataApplyStep5',
            'onAjaxSampledataApplyStep6' => 'onAjaxSampledataApplyStep6',
            'onAjaxSampledataApplyStep7' => 'onAjaxSampledataApplyStep7',
            'onAjaxSampledataApplyStep8' => 'onAjaxSampledataApplyStep8',
        ];
    }

    /**
     * Get an overview of the proposed sampledata.
     *
     * @param  GetOverviewEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onSampledataGetOverview(GetOverviewEvent $event): void
    {
        if (!$this->getApplication()->getIdentity()->authorise('core.create', 'com_content')) {
            return;
        }

        $data              = new \stdClass();
        $data->name        = $this->_name;
        $data->title       = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_OVERVIEW_TITLE');
        $data->description = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_OVERVIEW_DESC');
        $data->icon        = 'wifi';
        $data->steps       = 8;

        $event->addResult($data);
    }

    /**
     * First step to enable the Language filter plugin.
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onAjaxSampledataApplyStep1(AjaxEvent $event): void
    {
        if (!Session::checkToken('get') || $this->getApplication()->getInput()->get('type') != $this->_name) {
            return;
        }

        if (!$this->getApplication()->getIdentity()->authorise('core.manage', 'com_installer')) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_LANGFILTER', 1, Text::_('JERROR_ALERTNOAUTHOR'));

            $event->addResult($response);
            return;
        }

        $languages = LanguageHelper::getContentLanguages([0, 1]);

        if (\count($languages) < 2) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_MISSING_LANGUAGE');

            $event->addResult($response);
            return;
        }

        if (!$this->enablePlugin('plg_system_languagefilter')) {
            $response            = [];
            $response['success'] = false;

            $lang = $this->getApplication()->getLanguage();
            $lang->load('plg_system_languagefilter', JPATH_ADMINISTRATOR);
            $message = $lang->_('PLG_SYSTEM_LANGUAGEFILTER');

            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_LANGFILTER', 2, $message);

            $event->addResult($response);
            return;
        }

        $response            = [];
        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP1_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Second step to add a language switcher module
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onAjaxSampledataApplyStep2(AjaxEvent $event): void
    {
        if (!Session::checkToken('get') || $this->getApplication()->getInput()->get('type') != $this->_name) {
            return;
        }

        if (!ComponentHelper::isEnabled('com_modules') || !$this->getApplication()->getIdentity()->authorise('core.create', 'com_modules')) {
            $response            = [];
            $response['success'] = true;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_STEP_SKIPPED', 2, 'com_modules');

            $event->addResult($response);
            return;
        }

        if (!$this->addModuleLanguageSwitcher()) {
            $response            = [];
            $response['success'] = false;

            $lang = $this->getApplication()->getLanguage();
            $lang->load('mod_languages', JPATH_SITE);
            $message = $lang->_('MOD_LANGUAGES');

            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_SWITCHER', 2, $message);

            $event->addResult($response);
            return;
        }

        $response            = [];
        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP2_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Third step to make sure all content languages are published
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onAjaxSampledataApplyStep3(AjaxEvent $event): void
    {
        if (!Session::checkToken('get') || $this->getApplication()->getInput()->get('type') != $this->_name) {
            return;
        }

        if (!$this->getApplication()->getIdentity()->authorise('core.edit', 'com_languages')) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_CONTENTLANGUAGES', 3);

            $event->addResult($response);
            return;
        }

        if (!ComponentHelper::isEnabled('com_languages')) {
            $response            = [];
            $response['success'] = true;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_STEP_SKIPPED', 3, 'com_languages');

            $event->addResult($response);
            return;
        }

        if (!$this->publishContentLanguages()) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_CONTENTLANGUAGES', 3);

            $event->addResult($response);
            return;
        }

        $response            = [];
        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP3_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Fourth step to create Menus and list all categories menu items
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onAjaxSampledataApplyStep4(AjaxEvent $event): void
    {
        if (!Session::checkToken('get') || $this->getApplication()->getInput()->get('type') != $this->_name) {
            return;
        }

        if (!ComponentHelper::isEnabled('com_menus') || !$this->getApplication()->getIdentity()->authorise('core.create', 'com_menus')) {
            $response            = [];
            $response['success'] = true;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_STEP_SKIPPED', 4, 'com_menus');

            $event->addResult($response);
            return;
        }

        $siteLanguages = $this->getInstalledlangsFrontend();

        foreach ($siteLanguages as $siteLang) {
            if (!$this->addMenuGroup($siteLang)) {
                $response            = [];
                $response['success'] = false;
                $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_MENUS', 4, $siteLang->language);

                $event->addResult($response);
                return;
            }

            if (!$tableMenuItem = $this->addAllCategoriesMenuItem($siteLang)) {
                $response            = [];
                $response['success'] = false;
                $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_ALLCATEGORIES', 4, $siteLang->language);

                $event->addResult($response);
                return;
            }

            $groupedAssociations['com_menus.item'][$siteLang->language] = $tableMenuItem->id;
        }

        if (!$this->addAssociations($groupedAssociations)) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_ASSOC_ALLCATEGORIES', 4);

            $event->addResult($response);
            return;
        }

        $response            = [];
        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP4_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Fifth step to add menu modules
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onAjaxSampledataApplyStep5(AjaxEvent $event): void
    {
        if (!Session::checkToken('get') || $this->getApplication()->getInput()->get('type') != $this->_name) {
            return;
        }

        if (!ComponentHelper::isEnabled('com_modules') || !$this->getApplication()->getIdentity()->authorise('core.create', 'com_modules')) {
            $response            = [];
            $response['success'] = true;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_STEP_SKIPPED', 5, 'com_modules');

            $event->addResult($response);
            return;
        }

        $siteLanguages = $this->getInstalledlangsFrontend();

        foreach ($siteLanguages as $siteLang) {
            if (!$this->addModuleMenu($siteLang)) {
                $response            = [];
                $response['success'] = false;
                $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_MENUMODULES', 5, $siteLang->language);

                $event->addResult($response);
                return;
            }
        }

        $response            = [];
        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP5_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Sixth step to add workflow, categories, articles and blog menu items
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onAjaxSampledataApplyStep6(AjaxEvent $event): void
    {
        if (!Session::checkToken('get') || $this->getApplication()->getInput()->get('type') != $this->_name) {
            return;
        }

        if (!ComponentHelper::isEnabled('com_content') || !$this->getApplication()->getIdentity()->authorise('core.create', 'com_content')) {
            $response            = [];
            $response['success'] = true;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_STEP_SKIPPED', 6, 'com_content');

            $event->addResult($response);
            return;
        }

        if (!ComponentHelper::isEnabled('com_categories') || !$this->getApplication()->getIdentity()->authorise('core.create', 'com_content.category')) {
            $response            = [];
            $response['success'] = true;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_STEP_SKIPPED', 6, 'com_categories');

            $event->addResult($response);
            return;
        }

        $siteLanguages = $this->getInstalledlangsFrontend();

        ComponentHelper::getParams('com_content')->set('workflow_enabled', 0);

        foreach ($siteLanguages as $siteLang) {
            if (!$tableCategory = $this->addCategory($siteLang)) {
                $response            = [];
                $response['success'] = false;
                $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_CATEGORY', 6, $siteLang->language);

                $event->addResult($response);
                return;
            }

            $groupedAssociations['com_categories.item'][$siteLang->language] = $tableCategory->id;

            if (!$tableArticle = $this->addArticle($siteLang, $tableCategory->id)) {
                $response            = [];
                $response['success'] = false;
                $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_ARTICLE', 6, $siteLang->language);

                $event->addResult($response);
                return;
            }

            $groupedAssociations['com_content.item'][$siteLang->language] = $tableArticle->id;

            if (!$tableMenuItem = $this->addBlogMenuItem($siteLang, $tableCategory->id)) {
                $response            = [];
                $response['success'] = false;
                $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_BLOG', 6, $siteLang->language);

                $event->addResult($response);
                return;
            }

            $groupedAssociations['com_menus.item'][$siteLang->language] = $tableMenuItem->id;
        }

        if (!$this->addAssociations($groupedAssociations)) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_ASSOC_VARIOUS', 6);

            $event->addResult($response);
            return;
        }

        $response            = [];
        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP6_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Seventh step to disable the mainmenu module whose home page is set to All languages.
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function onAjaxSampledataApplyStep7(AjaxEvent $event): void
    {
        if (!Session::checkToken('get') || $this->getApplication()->getInput()->get('type') != $this->_name) {
            return;
        }

        if (!$this->getApplication()->getIdentity()->authorise('core.edit.state', 'com_modules')) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_MAINMENU_MODULE', 7);

            $event->addResult($response);
            return;
        }

        if (!ComponentHelper::isEnabled('com_modules')) {
            $response            = [];
            $response['success'] = true;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_STEP_SKIPPED', 7, 'com_modules');

            $event->addResult($response);
            return;
        }

        if (!$this->disableModuleMainMenu()) {
            $response            = [];
            $response['success'] = false;
            $response['message'] = Text::sprintf('PLG_SAMPLEDATA_MULTILANG_ERROR_MAINMENU_MODULE', 7);

            $event->addResult($response);
            return;
        }

        $response            = [];
        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP7_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Final step to show completion of sampledata.
     *
     * @param AjaxEvent $event Event instance
     *
     * @return  void
     *
     * @since  4.0.0
     */
    public function onAjaxSampledataApplyStep8(AjaxEvent $event): void
    {
        if ($this->getApplication()->getInput()->get('type') !== $this->_name) {
            return;
        }

        $response['success'] = true;
        $response['message'] = $this->getApplication()->getLanguage()->_('PLG_SAMPLEDATA_MULTILANG_STEP8_SUCCESS');

        $event->addResult($response);
    }

    /**
     * Enable a Joomla plugin.
     *
     * @param   string  $pluginName  The name of plugin.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    private function enablePlugin($pluginName)
    {
        // Create a new db object.
        $db    = $this->getDatabase();
        $query = $db->getQuery(true);

        $query
            ->update($db->quoteName('#__extensions'))
            ->set($db->quoteName('enabled') . ' = 1')
            ->where($db->quoteName('name') . ' = :pluginname')
            ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
            ->bind(':pluginname', $pluginName);

        $db->setQuery($query);

        try {
            $db->execute();
        } catch (ExecutionFailureException) {
            return false;
        }

        // Store language filter plugin parameters.
        if ($pluginName == 'plg_system_languagefilter') {
            $params = '{'
                . '"detect_browser":"0",'
                . '"automatic_change":"1",'
                . '"item_associations":"1",'
                . '"remove_default_prefix":"0",'
                . '"lang_cookie":"0",'
                . '"alternate_meta":"1"'
                . '}';
            $query
                ->clear()
                ->update($db->quoteName('#__extensions'))
                ->set($db->quoteName('params') . ' = :params')
                ->where($db->quoteName('name') . ' = ' . $db->quote('plg_system_languagefilter'))
                ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
                ->bind(':params', $params);

            $db->setQuery($query);

            try {
                $db->execute();
            } catch (ExecutionFailureException) {
                return false;
            }
        }

        return true;
    }

    /**
     * Disable Default Main Menu Module.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    private function disableModuleMainMenu()
    {
        // Create a new db object.
        $db    = $this->getDatabase();
        $query = $db->getQuery(true);

        // Disable main menu module with Home set to ALL languages.
        $query
            ->update($db->quoteName('#__modules'))
            ->set($db->quoteName('published') . ' = 0')
            ->where(
                [
                    $db->quoteName('client_id') . ' = 0',
                    $db->quoteName('module') . ' = ' . $db->quote('mod_menu'),
                    $db->quoteName('language') . ' = ' . $db->quote('*'),
                    $db->quoteName('position') . ' = ' . $db->quote('sidebar-right'),
                ]
            );
        $db->setQuery($query);

        try {
            $db->execute();
        } catch (ExecutionFailureException) {
            return false;
        }

        return true;
    }

    /**
     * Enable the Language Switcher Module.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    private function addModuleLanguageSwitcher()
    {
        $tableModule = Table::getInstance('Module', 'Joomla\\CMS\\Table\\');

        $moduleData  = [
            'id'        => 0,
            'title'     => 'Language Switcher',
            'note'      => '',
            'content'   => '',
            'position'  => 'sidebar-right',
            'module'    => 'mod_languages',
            'access'    => 1,
            'showtitle' => 0,
            'params'    => '{"header_text":"","footer_text":"","dropdown":"0","image":"1","inline":"1","show_active":"1",'
                . '"full_name":"1","layout":"_:default","moduleclass_sfx":"","cache":"0","cache_time":"900","cachemode":"itemid",'
                . '"module_tag":"div","bootstrap_size":"0","header_tag":"h3","header_class":"","style":"0"}',
            'client_id' => 0,
            'language'  => '*',
            'published' => 1,
            'rules'     => [],
        ];

        // Bind the data.
        if (!$tableModule->bind($moduleData)) {
            return false;
        }

        // Check the data.
        if (!$tableModule->check()) {
            return false;
        }

        // Store the data.
        if (!$tableModule->store()) {
            return false;
        }

        return $this->addModuleInModuleMenu((int) $tableModule->id);
    }

    /**
     * Add Module Menu.
     *
     * @param   \stdClass  $itemLanguage  Language Object.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    private function addModuleMenu($itemLanguage)
    {
        $tableModule = Table::getInstance('Module', 'Joomla\\CMS\\Table\\');
        $title       = 'Main menu ' . $itemLanguage->language;

        $moduleData = [
            'id'        => 0,
            'title'     => $title,
            'note'      => '',
            'content'   => '',
            'position'  => 'sidebar-right',
            'module'    => 'mod_menu',
            'access'    => 1,
            'showtitle' => 1,
            'params'    => '{"menutype":"mainmenu-' . strtolower($itemLanguage->language)
                . '","startLevel":"0","endLevel":"0","showAllChildren":"0","tag_id":"","class_sfx":"","window_open":"",'
                . '"layout":"","moduleclass_sfx":"","cache":"1","cache_time":"900","cachemode":"itemid"}',
            'client_id' => 0,
            'language'  => $itemLanguage->language,
            'published' => 1,
            'rules'     => [],
        ];

        // Bind the data.
        if (!$tableModule->bind($moduleData)) {
            return false;
        }

        // Check the data.
        if (!$tableModule->check()) {
            return false;
        }

        // Store the data.
        if (!$tableModule->store()) {
            return false;
        }

        return $this->addModuleInModuleMenu((int) $tableModule->id);
    }

    /**
     * Add Menu Group.
     *
     * @param   \stdClass  $itemLanguage  Language Object.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    private function addMenuGroup($itemLanguage)
    {
        // Add Menu Group.
        $menuTable = $this->getApplication()->bootComponent('com_menus')->getMVCFactory()->createTable('MenuType', 'Administrator', ['dbo' => $this->getDatabase()]);

        $menuData = [
            'id'          => 0,
            'menutype'    => 'mainmenu-' . strtolower($itemLanguage->language),
            'title'       => 'Main Menu (' . $itemLanguage->language . ')',
            'description' => 'The main menu for the site in language ' . $itemLanguage->name,
        ];

        // Bind the data.
        if (!$menuTable->bind($menuData)) {
            return false;
        }

        // Check the data.
        if (!$menuTable->check()) {
            return false;
        }

        // Store the data.
        if (!$menuTable->store()) {
            return false;
        }

        return true;
    }

    /**
     * Add List All Categories Menu Item for new router.
     *
     * @param   \stdClass  $itemLanguage  Language Object.
     *
     * @return  Table|boolean Menu Item Object. False otherwise.
     *
     * @since   4.0.0
     */
    private function addAllCategoriesMenuItem($itemLanguage)
    {
        // Add Menu Item.
        $tableItem = $this->getApplication()->bootComponent('com_menus')->getMVCFactory()->createTable('Menu', 'Administrator', ['dbo' => $this->getDatabase()]);

        $newlanguage = new Language($itemLanguage->language, false);
        $newlanguage->load('joomla', JPATH_ADMINISTRATOR, $itemLanguage->language, true);
        $title = $newlanguage->_('JCATEGORIES');
        $alias = 'allcategories_' . $itemLanguage->language;

        $menuItem = [
            'title'        => $title,
            'alias'        => $alias,
            'menutype'     => 'mainmenu-' . strtolower($itemLanguage->language),
            'type'         => 'component',
            'link'         => 'index.php?option=com_content&view=categories&id=0',
            'component_id' => ExtensionHelper::getExtensionRecord('com_content', 'component')->extension_id,
            'published'    => 1,
            'parent_id'    => 1,
            'level'        => 1,
            'home'         => 0,
            'params'       => '{"show_base_description":"","categories_description":"","maxLevelcat":"",'
                . '"show_empty_categories_cat":"","show_subcat_desc_cat":"","show_cat_num_articles_cat":"",'
                . '"show_category_title":"","show_description":"","show_description_image":"","maxLevel":"",'
                . '"show_empty_categories":"","show_no_articles":"","show_subcat_desc":"","show_cat_num_articles":"",'
                . '"num_leading_articles":"","num_intro_articles":"","num_links":"",'
                . '"show_subcategory_content":"","orderby_pri":"","orderby_sec":"",'
                . '"order_date":"","show_pagination_limit":"","filter_field":"","show_headings":"",'
                . '"list_show_date":"","date_format":"","list_show_hits":"","list_show_author":"","display_num":"10",'
                . '"show_pagination":"","show_pagination_results":"","article_layout":"_:default","show_title":"",'
                . '"link_titles":"","show_intro":"","show_category":"","link_category":"","show_parent_category":"",'
                . '"link_parent_category":"","show_author":"","link_author":"","show_create_date":"",'
                . '"show_modify_date":"","show_publish_date":"","show_item_navigation":"","show_vote":"",'
                . '"show_readmore":"","show_readmore_title":"","show_hits":"","show_noauth":"","show_feed_link":"",'
                . '"feed_summary":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_image_css":"",'
                . '"menu_text":1,"menu_show":0,"page_title":"","show_page_heading":"","page_heading":"",'
                . '"pageclass_sfx":"","menu-meta_description":"","robots":""}',
            'language' => $itemLanguage->language,
        ];

        // Bind the data.
        if (!$tableItem->bind($menuItem)) {
            return false;
        }

        $tableItem->setLocation($menuItem['parent_id'], 'last-child');

        // Check the data.
        if (!$tableItem->check()) {
            return false;
        }

        // Store the data.
        if (!$tableItem->store()) {
            return false;
        }

        // Rebuild the tree path.
        if (!$tableItem->rebuildPath($tableItem->id)) {
            return false;
        }

        return $tableItem;
    }

    /**
     * Add Blog Menu Item.
     *
     * @param   \stdClass  $itemLanguage  Language Object.
     * @param   integer   $categoryId    The id of the category displayed by the blog.
     *
     * @return  Table|boolean Menu Item Object. False otherwise.
     *
     * @since   4.0.0
     */
    private function addBlogMenuItem($itemLanguage, $categoryId)
    {
        // Add Menu Item.
        $tableItem = $this->getApplication()->bootComponent('com_menus')->getMVCFactory()->createTable('Menu', 'Administrator', ['dbo' => $this->getDatabase()]);

        $newlanguage = new Language($itemLanguage->language, false);
        $newlanguage->load('com_languages', JPATH_ADMINISTRATOR, $itemLanguage->language, true);
        $title = $newlanguage->_('COM_LANGUAGES_HOMEPAGE');
        $alias = 'home_' . $itemLanguage->language;

        $menuItem = [
            'title'        => $title,
            'alias'        => $alias,
            'menutype'     => 'mainmenu-' . strtolower($itemLanguage->language),
            'type'         => 'component',
            'link'         => 'index.php?option=com_content&view=category&layout=blog&id=' . $categoryId,
            'component_id' => ExtensionHelper::getExtensionRecord('com_content', 'component')->extension_id,
            'published'    => 1,
            'parent_id'    => 1,
            'level'        => 1,
            'home'         => 1,
            'params'       => '{"layout_type":"blog","show_category_heading_title_text":"","show_category_title":"",'
                . '"show_description":"","show_description_image":"","maxLevel":"","show_empty_categories":"",'
                . '"show_no_articles":"","show_subcat_desc":"","show_cat_num_articles":"","show_cat_tags":"",'
                . '"blog_class_leading":"","blog_class":"","num_leading_articles":"1","num_intro_articles":"3",'
                . '"num_links":"0","show_subcategory_content":"","link_intro_image":"","orderby_pri":"",'
                . '"orderby_sec":"front","order_date":"","show_pagination":"2","show_pagination_results":"1",'
                . '"show_featured":"","article_layout":"_:default","show_title":"","link_titles":"","show_intro":"","info_block_position":"",'
                . '"info_block_show_title":"","show_category":"","link_category":"","show_parent_category":"",'
                . '"link_parent_category":"","show_associations":"","show_author":"","link_author":"",'
                . '"show_create_date":"","show_modify_date":"","show_publish_date":"","show_item_navigation":"",'
                . '"show_vote":"","show_readmore":"","show_readmore_title":"","show_hits":"","show_tags":"",'
                . '"show_noauth":"","show_feed_link":"1","feed_summary":"","menu-anchor_title":"","menu-anchor_css":"",'
                . '"menu_image":"","menu_image_css":"","menu_text":1,"menu_show":1,"page_title":"","show_page_heading":"1",'
                . '"page_heading":"","pageclass_sfx":"","menu-meta_description":"","robots":""}',
            'language' => $itemLanguage->language,
        ];

        // Bind the data.
        if (!$tableItem->bind($menuItem)) {
            return false;
        }

        $tableItem->setLocation($menuItem['parent_id'], 'last-child');

        // Check the data.
        if (!$tableItem->check()) {
            return false;
        }

        // Store the data.
        if (!$tableItem->store()) {
            return false;
        }

        // Rebuild the tree path.
        if (!$tableItem->rebuildPath($tableItem->id)) {
            return false;
        }

        return $tableItem;
    }

    /**
     * Create the language associations.
     *
     * @param   array  $groupedAssociations  Array of language associations for all items.
     *
     * @return  boolean  True on success.
     *
     * @since   4.0.0
     */
    private function addAssociations($groupedAssociations)
    {
        $db = $this->getDatabase();

        foreach ($groupedAssociations as $context => $associations) {
            $key   = md5(json_encode($associations));
            $query = $db->getQuery(true)
                ->insert($db->quoteName('#__associations'));

            foreach ($associations as $language => $id) {
                $query->values(
                    implode(
                        ',',
                        $query->bindArray(
                            [
                                $id,
                                $context,
                                $key,
                            ],
                            [
                                ParameterType::INTEGER,
                                ParameterType::STRING,
                                ParameterType::STRING,
                            ]
                        )
                    )
                );
            }

            $db->setQuery($query);

            try {
                $db->execute();
            } catch (\RuntimeException) {
                return false;
            }
        }

        return true;
    }

    /**
     * Add a Module in Module menus.
     *
     * @param   integer  $moduleId  The Id of module.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    private function addModuleInModuleMenu($moduleId)
    {
        // Create a new db object.
        $db       = $this->getDatabase();
        $query    = $db->getQuery(true);
        $moduleId = (int) $moduleId;

        // Add Module in Module menus.
        $query->insert($db->quoteName('#__modules_menu'))
            ->columns($db->quoteName(['moduleid', 'menuid']))
            ->values(':moduleId, 0')
            ->bind(':moduleId', $moduleId, ParameterType::INTEGER);

        $db->setQuery($query);

        try {
            $db->execute();
        } catch (\RuntimeException) {
            return false;
        }

        return true;
    }

    /**
     * Method to create a category for a specific language.
     *
     * @param   \stdClass  $itemLanguage  Language Object.
     *
     * @return  Table|boolean Category Object. False otherwise.
     *
     * @since   4.0.0
     */
    public function addCategory($itemLanguage)
    {
        $newlanguage = new Language($itemLanguage->language, false);
        $newlanguage->load('joomla', JPATH_ADMINISTRATOR, $itemLanguage->language, true);
        $title = $newlanguage->_('JCATEGORY');
        $alias = ApplicationHelper::stringURLSafe($title);

        $app = Factory::getApplication();

        // Set unicodeslugs if alias is empty
        if (trim(str_replace('-', '', $alias) == '')) {
            $unicode = $app->set('unicodeslugs', 1);
            $alias   = ApplicationHelper::stringURLSafe($title);
            $app->set('unicodeslugs', $unicode);
        }

        // Initialize a new category.
        $category = $this->getApplication()->bootComponent('com_categories')->getMVCFactory()->createTable('Category', 'Administrator', ['dbo' => $this->getDatabase()]);

        $data = [
            'extension'       => 'com_content',
            'title'           => $title . ' (' . strtolower($itemLanguage->language) . ')',
            'alias'           => $alias . ' (' . strtolower($itemLanguage->language) . ')',
            'description'     => '',
            'published'       => 1,
            'access'          => 1,
            'params'          => '{"target":"","image":""}',
            'metadesc'        => '',
            'metakey'         => '',
            'metadata'        => '{"page_title":"","author":"","robots":""}',
            'created_time'    => Factory::getDate()->toSql(),
            'created_user_id' => (int) $this->getAdminId(),
            'language'        => $itemLanguage->language,
            'rules'           => [],
            'parent_id'       => 1,
        ];

        // Set the location in the tree.
        $category->setLocation(1, 'last-child');

        // Bind the data to the table
        if (!$category->bind($data)) {
            return false;
        }

        // Check to make sure our data is valid.
        if (!$category->check()) {
            return false;
        }

        // Store the category.
        if (!$category->store(true)) {
            return false;
        }

        // Build the path for our category.
        $category->rebuildPath($category->id);

        return $category;
    }

    /**
     * Create an article in a specific language.
     *
     * @param   \stdClass  $itemLanguage  Language Object.
     * @param   integer   $categoryId    The id of the category where we want to add the article.
     *
     * @return  Table|boolean Article Object. False otherwise.
     *
     * @since   4.0.0
     */
    private function addArticle($itemLanguage, $categoryId)
    {
        $db = $this->getDatabase();

        $newlanguage = new Language($itemLanguage->language, false);
        $newlanguage->load('com_content.sys', JPATH_ADMINISTRATOR, $itemLanguage->language, true);
        $title       = $newlanguage->_('COM_CONTENT_CONTENT_TYPE_ARTICLE');
        $currentDate = Factory::getDate()->toSql();
        $alias       = ApplicationHelper::stringURLSafe($title);

        // Set unicodeslugs if alias is empty
        if (trim(str_replace('-', '', $alias) == '')) {
            $unicode = $this->getApplication()->set('unicodeslugs', 1);
            $alias   = ApplicationHelper::stringURLSafe($title);
            $this->getApplication()->set('unicodeslugs', $unicode);
        }

        // Initialize a new article.
        $article = $this->getApplication()->bootComponent('com_content')->getMVCFactory()->createTable('Article', 'Administrator', ['dbo' => $this->getDatabase()]);

        $data = [
            'title'     => $title . ' (' . strtolower($itemLanguage->language) . ')',
            'alias'     => $alias . ' (' . strtolower($itemLanguage->language) . ')',
            'introtext' => '<p>Lorem ipsum ad his scripta blandit partiendo, eum fastidii accumsan euripidis'
                . ' in, eum liber hendrerit an. Qui ut wisi vocibus suscipiantur, quo dicit'
                . ' ridens inciderint id. Quo mundi lobortis reformidans eu, legimus senserit'
                . 'definiebas an eos. Eu sit tincidunt incorrupte definitionem, vis mutat'
                . ' affert percipit cu, eirmod consectetuer signiferumque eu per. In usu latine'
                . 'equidem dolores. Quo no falli viris intellegam, ut fugit veritus placerat'
                . 'per. Ius id vidit volumus mandamus, vide veritus democritum te nec, ei eos'
                . 'debet libris consulatu.</p>',
            'fulltext'         => '',
            'images'           => json_encode([]),
            'urls'             => json_encode([]),
            'created'          => $currentDate,
            'created_by'       => (int) $this->getAdminId(),
            'created_by_alias' => 'Joomla',
            'publish_up'       => $currentDate,
            'publish_down'     => null,
            'version'          => 1,
            'catid'            => $categoryId,
            'metadata'         => '{"robots":"","author":"","rights":"","tags":null}',
            'metakey'          => '',
            'metadesc'         => '',
            'language'         => $itemLanguage->language,
            'state'            => 1,
            'featured'         => 1,
            'attribs'          => [],
            'rules'            => [],
        ];

        // Bind the data to the table
        if (!$article->bind($data)) {
            return false;
        }

        // Check to make sure our data is valid.
        if (!$article->check()) {
            return false;
        }

        // Now store the category.
        if (!$article->store(true)) {
            return false;
        }

        // Get the new item ID.
        $newId = $article->get('id');

        $query = $db->getQuery(true)
            ->insert($db->quoteName('#__content_frontpage'))
            ->values($newId . ', 0, NULL, NULL');

        $db->setQuery($query);

        try {
            $db->execute();
        } catch (ExecutionFailureException $e) {
            return false;
        }

        $workflow = new Workflow('com_content.article', $this->getApplication(), $db);

        try {
            $stage_id = $workflow->getDefaultStageByCategory($categoryId);

            if ($stage_id) {
                $workflow->createAssociation($newId, $stage_id);
            }
        } catch (ExecutionFailureException) {
            return false;
        }

        return $article;
    }

    /**
     * Publish the Installed Content Languages.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    private function publishContentLanguages()
    {
        // Publish the Content Languages.
        $tableLanguage = Table::getInstance('Language');

        $siteLanguages = $this->getInstalledlangs('site');

        // For each content language.
        foreach ($siteLanguages as $siteLang) {
            if ($tableLanguage->load(['lang_code' => $siteLang->language, 'published' => 0]) && !$tableLanguage->publish()) {
                $this->getApplication()->enqueueMessage(Text::sprintf('INSTL_DEFAULTLANGUAGE_COULD_NOT_CREATE_CONTENT_LANGUAGE', $siteLang->name), 'warning');
            }
        }

        return true;
    }

    /**
     * Get Languages item data for the Administrator.
     *
     * @return  array
     *
     * @since   4.0.0
     */
    public function getInstalledlangsAdministrator()
    {
        return $this->getInstalledlangs('administrator');
    }

    /**
     * Get Languages item data for the Frontend.
     *
     * @return  array  List of installed languages in the frontend application.
     *
     * @since   4.0.0
     */
    public function getInstalledlangsFrontend()
    {
        return $this->getInstalledlangs('site');
    }

    /**
     * Get Installed Languages.
     *
     * @param   string  $clientName  Name of the cms client.
     *
     * @return  array
     *
     * @since   4.0.0
     */
    protected function getInstalledlangs($clientName = 'administrator')
    {
        // Get information.
        $path     = $this->getPath();
        $client   = $this->getClient($clientName);
        $langlist = $this->getLanguageList($client->id);

        // Compute all the languages.
        $data = [];

        foreach ($langlist as $lang) {
            $file = $path . '/' . $lang . '/' . $lang . '.xml';

            if (!is_file($file)) {
                $file = $path . '/' . $lang . '/langmetadata.xml';
            }

            $info          = Installer::parseXMLInstallFile($file);
            $row           = new \stdClass();
            $row->language = $lang;

            if (!\is_array($info)) {
                continue;
            }

            foreach ($info as $key => $value) {
                $row->$key = $value;
            }

            // If current then set published.
            $params = ComponentHelper::getParams('com_languages');

            if ($params->get($client->name, 'en-GB') == $row->language) {
                $row->published = 1;
            } else {
                $row->published = 0;
            }

            $row->checked_out = null;
            $data[]           = $row;
        }

        usort($data, [$this, 'compareLanguages']);

        return $data;
    }

    /**
     * Get installed languages data.
     *
     * @param   integer  $clientId  The client ID to retrieve data for.
     *
     * @return  object  The language data.
     *
     * @since   4.0.0
     */
    protected function getLanguageList($clientId = 1)
    {
        // Create a new db object.
        $db    = $this->getDatabase();
        $query = $db->getQuery(true);

        // Select field element from the extensions table.
        $query->select($db->quoteName(['element', 'name']))
            ->from($db->quoteName('#__extensions'))
            ->where(
                [
                    $db->quoteName('type') . ' = ' . $db->quote('language'),
                    $db->quoteName('state') . ' = 0',
                    $db->quoteName('enabled') . ' = 1',
                    $db->quoteName('client_id') . ' = :clientid',
                ]
            )
            ->bind(':clientid', $clientId, ParameterType::INTEGER);

        $db->setQuery($query);

        return $db->loadColumn();
    }

    /**
     * Compare two languages in order to sort them.
     *
     * @param   object  $lang1  The first language.
     * @param   object  $lang2  The second language.
     *
     * @return  integer
     *
     * @since   4.0.0
     */
    protected function compareLanguages($lang1, $lang2)
    {
        return strcmp($lang1->name, $lang2->name);
    }

    /**
     * Get the languages folder path.
     *
     * @return  string  The path to the languages folders.
     *
     * @since   4.0.0
     */
    protected function getPath()
    {
        if ($this->path === null) {
            $client     = $this->getClient();
            $this->path = LanguageHelper::getLanguagePath($client->path);
        }

        return $this->path;
    }

    /**
     * Get the client object of Administrator or Frontend.
     *
     * @param   string  $client  Name of the client object.
     *
     * @return  object
     *
     * @since   4.0.0
     */
    protected function getClient($client = 'administrator')
    {
        return ApplicationHelper::getClientInfo($client, true);
    }

    /**
     * Retrieve the admin user id.
     *
     * @return  integer|boolean  One Administrator ID.
     *
     * @since   4.0.0
     */
    private function getAdminId()
    {
        if ($this->adminId) {
            // Return local cached admin ID.
            return $this->adminId;
        }

        $db    = $this->getDatabase();
        $query = $db->getQuery(true);

        // Select the admin user ID
        $query
            ->select($db->quoteName('u.id'))
            ->from($db->quoteName('#__users', 'u'))
            ->join(
                'LEFT',
                $db->quoteName('#__user_usergroup_map', 'map'),
                $db->quoteName('map.user_id') . ' = ' . $db->quoteName('u.id')
            )
            ->join(
                'LEFT',
                $db->quoteName('#__usergroups', 'g'),
                $db->quoteName('map.group_id') . ' = ' . $db->quoteName('g.id')
            )
            ->where(
                $db->quoteName('g.title') . ' = ' . $db->quote('Super Users')
            );

        $db->setQuery($query);
        $id = $db->loadResult();

        if (!$id || $id instanceof \Exception) {
            return false;
        }

        return $id;
    }
}
