<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * This file contains global functions to check whether
 * upgrading the database or locales is needed as wells as auxiliary functions
 * to be used by the VersionFunctions.php code to actually carry out
 * upgrades between versions
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\library;

use seekquarry\atto as A;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\models as M;
use seekquarry\yioop\models\datasources as D;

/** For Yioop global defines */
require_once __DIR__."/../configs/Config.php";
/**
 * Checks to see if the locale data of Yioop! of a locale in the work dir is
 * older than the currently running Yioop!
 *
 * @param string $locale_tag locale to check directory of
 * @return mixed string "locale" when the on-disk configure.ini is older
 *      than the fallback or the locale version-marker disagrees; false
 *      when the locale is up-to-date; void when no profile is configured
 */
function upgradeLocalesCheck($locale_tag)
{
    if (!C\PROFILE) {
        return;
    }
    $dir_locale_tag = str_replace("-", "_", $locale_tag);
    $config_name = C\LOCALE_DIR . "/$dir_locale_tag/configure.ini";
    $fallback_config_name =
        C\FALLBACK_LOCALE_DIR . "/$dir_locale_tag/configure.ini";
    if (tl('view_locale_version') != C\RESOURCES_WIKI_VERSION ||
        filemtime($fallback_config_name) > filemtime($config_name)) {
        return "locale";
    }
    return false;
}
/**
 * If the locale data of Yioop! in the work directory is older than the
 * currently running Yioop! then this function is called to at least
 * try to copy the new strings into the old profile. The new strings are
 * always merged in; the version-gated resources folder and the wiki and
 * help pages are pushed out only when the stored resources-wiki version
 * differs from the running one, or when $force is true.
 *
 * @param bool $force when true, push the resources folder and the wiki
 *      and help pages even if the stored resources-wiki version already
 *      matches the running one. This lets a developer see edited
 *      resource files and help pages without bumping the resources-wiki
 *      version on every change.
 */
function upgradeLocales($force = false)
{
    if (!C\PROFILE) {
        return;
    }
    $locale = new M\LocaleModel();
    $locale->initialize(C\p('DEFAULT_LOCALE'));
    $force_folders = [];
    /*
        if we're upgrading version2 to 3 we want to make sure stemmer becomes
        tokenizer, version3 to 4 pushes out stopwordsRemover used for
        summarization. version 6 to 7 adds stemmers for french, english,
        german.
        version 7 to 8 adds stemmers for russian and spanish
    */
    if ($force ||
        empty($locale->configure['strings']["view_locale_version"]) ||
        $locale->configure['strings']["view_locale_version"]
        != C\RESOURCES_WIKI_VERSION) {
        $force_folders = ["resources"];
        upgradePublicHelpWiki($locale->db);
    }
    $locale->extractMergeLocales($force_folders);
}
/**
 * Used to force push the default Public and Wiki pages into the current
 * database
 * @param resource &$db datasource to use to upgrade
 */
function upgradePublicHelpWiki(&$db)
{
    /** For new wiki pages */
    require_once C\BASE_DIR . "/configs/PublicHelpPages.php";
    $group_model = new M\GroupModel(C\p('DB_NAME'), false);
    $wiki_model = new M\WikiModel(C\p('DB_NAME'), false);
    $wiki_model = new M\WikiModel(C\p('DB_NAME'), false);
    $group_model->db = $db;
    $default_locale = getLocaleTag();
    foreach ($public_pages as $locale_tag => $locale_pages) {
        setLocaleObject($locale_tag);
        foreach ($locale_pages as $page_name => $page_content) {
            $page_info = $wiki_model->getPageInfoByName(C\PUBLIC_GROUP_ID,
                $page_name, $locale_tag, "edit");
            if (empty($page_info) ||
                ($page_content != $page_info['PAGE'] &&
                (in_array($page_info['EDIT_COMMENT'], ['upgrade', 'create'])))||
                (!empty($page_info['PUBDATE']) &&
                $page_info['PUBDATE'] < 1555802108) ) {
                $wiki_model->setPageName(C\ROOT_ID, C\PUBLIC_GROUP_ID,
                    $page_name, $page_content, $locale_tag, "upgrade",
                    tl('social_component_page_created', $page_name),
                    tl('social_component_page_discuss_here'));
            }
        }
    }
    //Insert Default Public Help pages
    foreach ($help_pages as $locale_tag => $locale_pages) {
        setLocaleObject($locale_tag);
        foreach ($locale_pages as $page_name => $page_content) {
            $page_info = $wiki_model->getPageInfoByName(C\HELP_GROUP_ID,
                $page_name, $locale_tag, "edit");
            if (empty($page_info) || ($page_content != $page_info['PAGE'] &&
                (in_array($page_info['EDIT_COMMENT'], ['upgrade', 'create']) ||
                empty($page_info['PUB_DATE']) ||
                $page_info['PUB_DATE'] < 1593297907)) ) {
                $wiki_model->setPageName(C\ROOT_ID, C\HELP_GROUP_ID,
                    $page_name, $page_content, $locale_tag, "upgrade",
                    tl('social_component_page_created', $page_name),
                    tl('social_component_page_discuss_here'));
            }
        }
    }
    setLocaleObject($default_locale);
}
/**
 * Checks to see if the database data or work_dir folder of Yioop! is from an
 * older version of Yioop! than the currently running Yioop!
 *
 * @return bool true when the on-disk VERSION is older than the
 *      runtime DATABASE_VERSION (an upgrade is required), false when
 *      the database is up-to-date; falls through to subsequent code
 *      paths when the VERSION query fails on all retry attempts
 */
function upgradeDatabaseWorkDirectoryCheck()
{
    $model = new M\Model();
    $sql = "SELECT ID FROM VERSION";
    set_error_handler(null);
    for ($i = 0; $i < 3; $i++) {
        $result = @$model->db->execute($sql);
        if ($result !== false) {
            $row = $model->db->fetchArray($result);
            if ((isset($row['ID']) && $row['ID'] >= C\DATABASE_VERSION) ||
                (isset($row['id']) && $row['id'] >= C\DATABASE_VERSION)) {
                return false;
            } else {
                return true;
            }
        }
        sleep(3);
    }
    restore_error_handler();
    A\webExit();
}
/**
 * If the database data of Yioop is older than the version of the
 * currently running Yioop then this function is called to try
 * upgrade the database to the new version
 */
function upgradeDatabaseWorkDirectory()
{
    $model = new M\Model();
    $sql = "SELECT ID FROM VERSION";
    set_error_handler(null);
    $result = @$model->db->execute($sql);
    if ($result !== false) {
        $row = $model->db->fetchArray($result);
        if (!empty($row['ID'])) {
            $current_version = min($row['ID'], C\DATABASE_VERSION);
        } else if (!empty($row['id'])) {
            $current_version = min($row['id'], C\DATABASE_VERSION);
        } else {
            $current_version = 1;
        }
    } else {
        A\webExit(); // maybe someone else has locked DB, so bail
    }
    restore_error_handler();
    $result = null; //don't lock db if sqlite
    $versions = range(1, C\DATABASE_VERSION);
    $key = array_search($current_version, $versions);
    $versions = array_slice($versions, $key + 1);
    foreach ($versions as $version) {
        $upgrade_db = C\NS_LIB . "upgradeDatabaseVersion$version";
        if (function_exists($upgrade_db)) {
            $upgrade_db($model->db);
        }
    }
    updateVersionNumber($model->db, C\DATABASE_VERSION);
}

/**
 * Update the database version number to a new number
 * @param object $db datasource for Yioop database
 * @param int $number the new database number
 */
function updateVersionNumber(&$db, $number)
{
    $db->execute("DELETE FROM VERSION");
    $db->execute("INSERT INTO VERSION VALUES ($number)");
}
/**
 * Reads the Help articles from default db and returns the array of pages.
 *
 * @return array|bool list of [PAGE_NAME, PAGE] entries pulled from the
 *      packaged default-help database; false when the default database
 *      cannot be opened
 */
function getWikiHelpPages()
{
    $help_pages = [];
    $default_dbm = new D\Sqlite3Manager();
    $default_dbm->connect("", "", "", C\BASE_DIR . "/data/public_default.db");
    if (!$default_dbm) {
        return false;
    }
    $group_model = new M\GroupModel(C\p('DB_NAME'), true);
    $wiki_model = new M\WikiModel(C\p('DB_NAME'), true);
    $wiki_model = new M\WikiModel(C\p('DB_NAME'), true);
    $group_model->db = $default_dbm;
    $page_list = $wiki_model->getPageList(
        C\HELP_GROUP_ID, "en-US", '', 0, 200);
    foreach ($page_list[1] as $page) {
        if (isset($page['TITLE'])) {
            $page_info = $wiki_model->getPageInfoByName(
                C\HELP_GROUP_ID, $page['TITLE'], "en-US", "api");
            $page_content = str_replace("&amp;", "&", $page_info['PAGE']);
            $page_content = html_entity_decode($page_content, ENT_QUOTES,
                "UTF-8");
            $help_pages[$page['TITLE']] = $page_content;
        }
    }
    return $help_pages;
}
/**
 * Used to insert a new activity into the database at a given activity_id
 *
 * Inserting at an ID rather than at the end is useful since activities are
 * displayed in admin panel in order of increasing id.
 *
 * @param resource &$db database handle where Yioop database stored
 * @param string $string_id message identifier to give translations for
 *     for activity
 * @param string  $method_name admin_controller method to be called to perform
 *      this activity
 * @param int $activity_id the id location at which to create this activity
 *     activity at and below this location will be shifted down by 1.
 */
function addActivityAtId(&$db, $string_id, $method_name, $activity_id)
{
    $db->execute("UPDATE ACTIVITY SET ACTIVITY_ID = ACTIVITY_ID + 1 WHERE ".
        "ACTIVITY_ID >= ?", [$activity_id]);
    $sql = "SELECT * FROM ACTIVITY WHERE ACTIVITY_ID >= ?
        ORDER BY ACTIVITY_ID DESC";
    $result = $db->execute($sql, [$activity_id]);
    while ($row = $db->fetchArray($result)) {
        $db->execute("INSERT INTO ACTIVITY VALUES (?, ?, ?)",
            [($row['ACTIVITY_ID'] + 1), $row['TRANSLATION_ID'],
            $row['METHOD_NAME']]);
        $db->execute("DELETE FROM ACTIVITY WHERE ACTIVITY_ID = ?",
            [$row['ACTIVITY_ID']]);
    }
    if (!in_array($method_name, ["manageAdvertisements", "manageCredits"])) {
        $db->execute("UPDATE ROLE_ACTIVITY SET ACTIVITY_ID = ACTIVITY_ID + 1 ".
            "WHERE ACTIVITY_ID >= ?", [$activity_id]);
        //give root account permissions on the activity.
        $db->execute("INSERT INTO ROLE_ACTIVITY VALUES (1, ?, 'all')",
            [$activity_id]);
    }
    $sql = "SELECT COUNT(*) AS NUM FROM TRANSLATION";
    $result = $db->execute($sql);
    if (!$result || !($row = $db->fetchArray($result))) {
        echo "Upgrade activity error";
        exit();
    }
    //some search id start at 1000, so +1001 ensures we steer clear of them
    $translation_id = $row['NUM'] + 1001;
    $db->execute("INSERT INTO ACTIVITY VALUES (?, ?, ?)",
        [$activity_id, $translation_id, $method_name]);
    $db->execute("INSERT INTO TRANSLATION VALUES (?, ?)",
        [$translation_id, $string_id]);
}
/**
 * Adds or replaces a translation for a database message string for a given
 * IANA locale tag.
 *
 * @param resource &$db database handle where Yioop database stored
 * @param string $string_id message identifier to give translation for
 * @param string $locale_tag  the IANA language tag to update the strings of
 * @param string $translation the translation for $string_id in the language
 *     $locale_tag
 */
function updateTranslationForStringId(&$db, $string_id, $locale_tag,
    $translation)
{
    $sql = "SELECT LOCALE_ID FROM LOCALE ".
        "WHERE LOCALE_TAG = ? " . $db->limitOffset(1);
    $result = $db->execute($sql, [$locale_tag]);
    $row = $db->fetchArray($result);
    $locale_id = $row['LOCALE_ID'];
    $sql = "SELECT TRANSLATION_ID FROM TRANSLATION ".
        "WHERE IDENTIFIER_STRING = ? " . $db->limitOffset(1);
    $result = $db->execute($sql, [$string_id]);
    $row = $db->fetchArray($result);
    $translate_id = $row['TRANSLATION_ID'];
    $sql = "DELETE FROM TRANSLATION_LOCALE ".
        "WHERE TRANSLATION_ID =? AND ".
        "LOCALE_ID = ?";
    $result = $db->execute($sql, [$translate_id, $locale_id]);
    $sql = "INSERT INTO TRANSLATION_LOCALE VALUES (?, ?, ?)";
    $result = $db->execute($sql, [$translate_id, $locale_id, $translation]);
}
