<?php

/**
 * @file classes/navigationMenu/NavigationMenuItemDAO.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class NavigationMenuItemDAO
 *
 * @ingroup navigationMenuItem
 *
 * @see NavigationMenuItem
 *
 * @brief Operations for retrieving and modifying NavigationMenuItem objects. NMI = NavigationMenuItem
 */

namespace PKP\navigationMenu;

use Illuminate\Support\Facades\DB;
use PKP\db\DAORegistry;
use PKP\db\DAOResultFactory;
use PKP\xml\PKPXMLParser;
use PKP\xml\XMLNode;

class NavigationMenuItemDAO extends \PKP\db\DAO
{
    /**
     * Retrieve a navigation menu item by ID.
     *
     * @param int $navigationMenuItemId
     *
     * @return ?NavigationMenuItem
     */
    public function getById($navigationMenuItemId)
    {
        $params = [(int) $navigationMenuItemId];
        $result = $this->retrieve(
            'SELECT	* FROM navigation_menu_items WHERE navigation_menu_item_id = ?',
            $params
        );

        $row = (array) $result->current();
        return $row ? $this->_fromRow($row) : null;
    }

    /**
     * Retrieve a navigation menu item by path.
     *
     * @param int $contextId Context Id
     * @param string $path
     *
     * @return ?NavigationMenuItem
     */
    public function getByPath($contextId, $path)
    {
        $result = $this->retrieve(
            'SELECT	* FROM navigation_menu_items WHERE path = ? and context_id = ? and type= ?',
            [$path, (int) $contextId, 'NMI_TYPE_CUSTOM']
        );

        $row = (array) $result->current();
        return $row ? $this->_fromRow($row) : null;
    }

    /**
     * Retrieve a navigation menu items by context Id.
     *
     * @param int $contextId Context Id
     *
     * @return DAOResultFactory<NavigationMenuItem>
     */
    public function getByContextId($contextId)
    {
        $result = $this->retrieve(
            'SELECT * FROM navigation_menu_items WHERE context_id = ?',
            [(int) $contextId]
        );

        return new DAOResultFactory($result, $this, '_fromRow');
    }

    /**
     * Retrieve items by menu id
     *
     * @param int $menuId
     *
     * @return DAOResultFactory<NavigationMenuItem>
     */
    public function getByMenuId($menuId)
    {
        $result = $this->retrieve(
            'SELECT nmi.*
				FROM navigation_menu_item_assignments as nmh
				LEFT JOIN navigation_menu_items as nmi ON (nmh.navigation_menu_item_id = nmi.navigation_menu_item_id)
				WHERE nmh.navigation_menu_id = ?
				ORDER BY nmh.seq',
            [(int) $menuId]
        );
        return new DAOResultFactory($result, $this, '_fromRow');
    }

    /**
     * Retrieve items by menuItemType and setting_name = titleLocaleKey
     *
     * @param int $contextId
     * @param string $menuItemType
     * @param string $menuItemTitleLocaleKey
     *
     * @return ?NavigationMenuItem
     */
    public function getByTypeAndTitleLocaleKey($contextId, $menuItemType, $menuItemTitleLocaleKey)
    {
        $result = $this->retrieve(
            'SELECT *
				FROM navigation_menu_items
				LEFT JOIN navigation_menu_item_settings ON (navigation_menu_items.navigation_menu_item_id = navigation_menu_item_settings.navigation_menu_item_id)
				WHERE navigation_menu_items.type = ?
				AND (navigation_menu_item_settings.setting_name = \'titleLocaleKey\' and navigation_menu_item_settings.setting_value = ?)
				AND navigation_menu_items.context_id = ?',
            [$menuItemType, $menuItemTitleLocaleKey, (int) $contextId]
        );
        $row = (array) $result->current();
        return $row ? $this->_fromRow($row) : null;
    }

    /**
     * Retrieve the menu items with the specified type.
     *
     * @param int $type NMI_TYPE_...
     * @param int $contextId
     *
     * @return DAOResultFactory<NavigationMenuItem> containing matching NavigationMenuItems
     */
    public function getByType($type, $contextId = null)
    {
        $params = [$type];
        if ($contextId !== null) {
            $params[] = $contextId;
        }
        $result = $this->retrieve(
            'SELECT	* FROM navigation_menu_items WHERE type = ?' .
            ($contextId !== null ? ' AND context_id = ?' : ''),
            $params
        );
        return new DAOResultFactory($result, $this, '_fromRow');
    }

    /**
     * Get the list of localized field names for this table
     *
     * @return array
     */
    public function getLocaleFieldNames()
    {
        return ['title', 'content', 'remoteUrl'];
    }

    /**
     * @copydoc DAO::getAdditionalFieldNames()
     */
    public function getAdditionalFieldNames()
    {
        return ['titleLocaleKey'];
    }

    /**
     * Get a new data object.
     *
     * @return NavigationMenuItem
     */
    public function newDataObject()
    {
        return new NavigationMenuItem();
    }

    /**
     * Internal function to return a NavigationMenuItem object from a row.
     *
     * @param array $row
     *
     * @return NavigationMenuItem
     */
    public function _fromRow($row, $dataObject = false)
    {
        $navigationMenuItem = $this->newDataObject();
        $navigationMenuItem->setId($row['navigation_menu_item_id']);
        $navigationMenuItem->setContextId($row['context_id']);
        $navigationMenuItem->setType($row['type']);
        $navigationMenuItem->setPath($row['path']);

        $this->getDataObjectSettings('navigation_menu_item_settings', 'navigation_menu_item_id', $row['navigation_menu_item_id'], $navigationMenuItem);

        return $navigationMenuItem;
    }

    /**
     * Update the settings for this object
     *
     * @param object $navigationMenuItem
     */
    public function updateLocaleFields($navigationMenuItem)
    {
        $this->updateDataObjectSettings('navigation_menu_item_settings', $navigationMenuItem, [
            'navigation_menu_item_id' => $navigationMenuItem->getId()
        ]);
    }

    /**
     * Insert a new NavigationMenuItem.
     *
     * @param NavigationMenuItem $navigationMenuItem
     *
     * @return int
     */
    public function insertObject($navigationMenuItem)
    {
        $this->update(
            'INSERT INTO navigation_menu_items
				(path, context_id, type)
				VALUES
				(?, ?, ?)',
            [
                $navigationMenuItem->getPath(),
                (int) $navigationMenuItem->getContextId(),
                $navigationMenuItem->getType(),
            ]
        );
        $navigationMenuItem->setId($this->getInsertId());
        $this->updateLocaleFields($navigationMenuItem);

        $this->unCacheRelatedNavigationMenus($navigationMenuItem->getId());

        return $navigationMenuItem->getId();
    }

    /**
     * Update an existing NavigationMenuItem.
     *
     * @param NavigationMenuItem $navigationMenuItem
     *
     * @return bool
     */
    public function updateObject($navigationMenuItem)
    {
        $returner = $this->update(
            'UPDATE navigation_menu_items
				SET
					path = ?,
					context_id = ?,
					type = ?
				WHERE navigation_menu_item_id = ?',
            [
                $navigationMenuItem->getPath(),
                (int) $navigationMenuItem->getContextId(),
                $navigationMenuItem->getType(),
                (int) $navigationMenuItem->getId(),
            ]
        );
        $this->updateLocaleFields($navigationMenuItem);

        $this->unCacheRelatedNavigationMenus($navigationMenuItem->getId());

        return $returner;
    }

    /**
     * Delete a NavigationMenuItem.
     *
     * @param NavigationMenuItem $navigationMenuItem
     */
    public function deleteObject($navigationMenuItem)
    {
        return $this->deleteById($navigationMenuItem->getId());
    }

    /**
     * Delete a NavigationMenuItem by navigationMenuItem ID.
     *
     * @param int $navigationMenuItemId
     */
    public function deleteById($navigationMenuItemId)
    {
        $this->unCacheRelatedNavigationMenus($navigationMenuItemId);

        $this->update('DELETE FROM navigation_menu_item_settings WHERE navigation_menu_item_id = ?', [(int) $navigationMenuItemId]);
        $this->update('DELETE FROM navigation_menu_items WHERE navigation_menu_item_id = ?', [(int) $navigationMenuItemId]);

        $navigationMenuItemAssignmentDao = DAORegistry::getDAO('NavigationMenuItemAssignmentDAO'); /** @var NavigationMenuItemAssignmentDAO $navigationMenuItemAssignmentDao */
        $navigationMenuItemAssignmentDao->deleteByMenuItemId($navigationMenuItemId);
    }

    /**
     * Delete NavigationMenuItems by contextId.
     *
     * @param int $contextId
     */
    public function deleteByContextId($contextId)
    {
        $navigationMenuItems = $this->getByContextId($contextId);

        while ($navigationMenuItem = $navigationMenuItems->next()) {
            $this->deleteObject($navigationMenuItem);
        }
    }

    /**
     * Load the XML file and move the settings to the DB
     *
     * @param int $contextId
     * @param string $filename
     *
     * @return bool true === success
     */
    public function installSettings($contextId, $filename)
    {
        $xmlParser = new PKPXMLParser();
        $tree = $xmlParser->parse($filename);

        if ($contextId == \PKP\core\PKPApplication::CONTEXT_ID_NONE) {
            $siteDao = DAORegistry::getDAO('SiteDAO'); /** @var \PKP\site\SiteDAO $siteDao */
            $site = $siteDao->getSite();
        }

        if (!$tree) {
            return false;
        }

        foreach ($tree->getChildren() as $setting) {
            $site = $setting->getAttribute('site');
            if ($contextId == \PKP\core\PKPApplication::CONTEXT_ID_NONE && !$site) {
                continue;
            }
            $this->installNodeSettings($contextId, $setting, null, null, 0, true);
        }

        return true;
    }

    /**
     * Load a XML node to DB
     *
     * @param int $contextId
     * @param XMLNode $node
     * @param int $navigationMenuId
     * @param int $navigationMenuItemParentId
     * @param int $seq
     * @param bool $checkChildren Optional
     *
     * @return bool true === success
     */
    public function installNodeSettings($contextId, $node, $navigationMenuId = null, $navigationMenuItemParentId = null, $seq = 0, $checkChildren = false)
    {
        $titleKey = $node->getAttribute('title');
        $path = $node->getAttribute('path');
        $type = $node->getAttribute('type');

        $navigationMenuItemExisting = $this->getByTypeAndTitleLocaleKey($contextId, $type, $titleKey);

        if (!isset($navigationMenuItemExisting)) {
            $navigationMenuItem = $this->newDataObject();
            $navigationMenuItem->setPath($path);
            $navigationMenuItem->setContextId($contextId);

            $navigationMenuItem->setType($type);

            $navigationMenuItemId = $this->insertObject($navigationMenuItem);

            // add the i18n keys to the settings table so that they
            // can be used when a new locale is added/reloaded
            $this->updateSetting($navigationMenuItemId, 'titleLocaleKey', $titleKey);
        } else {
            $navigationMenuItemId = $navigationMenuItemExisting->getId();

            $this->updateSetting($navigationMenuItemId, 'titleLocaleKey', $titleKey);
        }

        // insert into Assignments
        if ($navigationMenuId) {
            $navigationMenuItemAssignmentDao = DAORegistry::getDAO('NavigationMenuItemAssignmentDAO'); /** @var NavigationMenuItemAssignmentDAO $navigationMenuItemAssignmentDao */
            $assignmentExists = $navigationMenuItemAssignmentDao->getByNMIIdAndMenuIdAndParentId($navigationMenuItemId, $navigationMenuId, $navigationMenuItemParentId);

            if (!isset($assignmentExists)) {
                $navigationMenuItemAssignment = $navigationMenuItemAssignmentDao->newDataObject();

                $navigationMenuItemAssignment->setMenuItemId($navigationMenuItemId);
                $navigationMenuItemAssignment->setMenuId($navigationMenuId);

                if ($navigationMenuItemParentId) {
                    $navigationMenuItemAssignment->setParentId($navigationMenuItemParentId);
                }

                $navigationMenuItemAssignment->setSequence($seq);

                // Insert Assignment
                $navigationMenuItemAssignmentDao->insertObject($navigationMenuItemAssignment);
            }
        }

        if ($checkChildren) {
            $seqSec = 0;

            foreach ($node->getChildren() as $navigationMenuItemSecondLevelNode) {
                $this->installNodeSettings($contextId, $navigationMenuItemSecondLevelNode, $navigationMenuId, $navigationMenuItemId, $seqSec, false);
                $seqSec++;
            }
        }

        return true;
    }

    /**
     * Method for update navigationMenuItem setting
     *
     * @param int $navigationMenuItemId
     * @param string $name
     * @param string $type data type of the setting. If omitted, type will be guessed
     * @param bool $isLocalized
     */
    public function updateSetting($navigationMenuItemId, $name, $value, $type = null, $isLocalized = false)
    {
        $keyFields = ['setting_name', 'locale', 'navigation_menu_item_id'];

        if (!$isLocalized) {
            $value = $this->convertToDB($value, $type);
            DB::table('navigation_menu_item_settings')->updateOrInsert(
                ['navigation_menu_item_id' => (int) $navigationMenuItemId, 'setting_name' => $name, 'locale' => ''],
                ['setting_value' => trim($value, '##'), 'setting_type' => $type]
            );
        } else {
            if (is_array($value)) {
                foreach ($value as $locale => $localeValue) {
                    $this->update('DELETE FROM navigation_menu_item_settings WHERE navigation_menu_item_id = ? AND setting_name = ? AND locale = ?', [(int) $navigationMenuItemId, $name, $locale]);
                    if (empty($localeValue)) {
                        continue;
                    }
                    $type = null;
                    $this->update(
                        'INSERT INTO navigation_menu_item_settings
					(navigation_menu_item_id, setting_name, setting_value, setting_type, locale)
					VALUES (?, ?, ?, ?, ?)',
                        [$navigationMenuItemId, $name, trim($this->convertToDB($localeValue, $type), '##'), $type, $locale]
                    );
                }
            }
        }
    }

    /**
     * Retrieve a context setting value.
     *
     * @param string $name
     * @param string $locale optional
     */
    public function getSetting($navigationMenuItemId, $name, $locale = null)
    {
        $params = [(int) $navigationMenuItemId, $name];
        if ($locale) {
            $params[] = $locale;
        }
        $result = $this->retrieve(
            'SELECT	setting_name, setting_value, setting_type, locale
			FROM	navigation_menu_item_settings
			WHERE	navigation_menu_item_id = ? AND
				setting_name = ?' .
                ($locale ? ' AND locale = ?' : ''),
            $params
        );

        foreach ($result as $row) {
            $returner[$row->locale] = $this->convertFromDB($row->setting_value, $row->setting_type);
        }
        if (count($returner) == 1) {
            return array_shift($returner);
        }
        if (count($returner) == 0) {
            return false;
        }
        return $returner;
    }

    /**
     * Remove all settings associated with a locale
     *
     * @param string $locale
     */
    public function deleteSettingsByLocale($locale)
    {
        return $this->update('DELETE FROM navigation_menu_item_settings WHERE locale = ?', [$locale]);
    }

    /**
     * Uncache the related NMs to the NMI with $id
     */
    public function unCacheRelatedNavigationMenus($id)
    {
        /** @var NavigationMenuDAO */
        $navigationMenuDao = DAORegistry::getDAO('NavigationMenuDAO');
        /** @var NavigationMenuItemAssignmentDAO */
        $navigationMenuItemAssignmentDao = DAORegistry::getDAO('NavigationMenuItemAssignmentDAO');
        $assignments = $navigationMenuItemAssignmentDao->getByMenuItemId($id);
        if ($assignments) {
            $assignmentsArray = $assignments->toArray();
            foreach ($assignmentsArray as $assignment) {
                $cache = $navigationMenuDao->getCache($assignment->getMenuId());
                if ($cache) {
                    $cache->flush();
                }
            }
        }
    }

    /**
     * Port static page as a Custom NMI
     *
     * @param \APP\plugins\generic\staticPages\classes\StaticPage $staticPage
     *
     * @return int The id of the inserted NMI. Null if non is inserted
     */
    public function portStaticPage($staticPage)
    {
        $path = $staticPage->getPath();
        $contextId = $staticPage->getContextId();

        $existingNMIWithPath = $this->getByPath($contextId, $path);

        $retNavigationMenuItemId = null;

        if (!isset($existingNMIWithPath)) {
            $navigationMenuItem = $this->newDataObject();

            $navigationMenuItem->setPath($path);
            $navigationMenuItem->setContextId($contextId);
            $navigationMenuItem->setType(NavigationMenuItem::NMI_TYPE_CUSTOM);

            $navigationMenuItem->setTitle($staticPage->getTitle(null), null);
            $navigationMenuItem->setContent($staticPage->getContent(null), null);

            $retNavigationMenuItemId = $this->insertObject($navigationMenuItem);
        }

        return $retNavigationMenuItemId;
    }
}

if (!PKP_STRICT_MODE) {
    class_alias('\PKP\navigationMenu\NavigationMenuItemDAO', '\NavigationMenuItemDAO');
}
