/ src / configs / ConfigureTool.php
<?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
 *
 * Used to create and manipulate a profile and work directory from the
 * command-line for Yioop.
 *
 * @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\configs;

use seekquarry\yioop\library as L;
use seekquarry\yioop\library\CrawlConstants;
use seekquarry\yioop\controllers\AdminController;

if (php_sapi_name() != 'cli' ||
    defined("seekquarry\\yioop\\configs\\IS_OWN_WEB_SERVER")) {
    echo "BAD REQUEST"; exit();
}
/** Loads common utility functions*/
require_once  __DIR__."/../library/Utility.php";
/** Loads common constants for web crawling*/
require_once __DIR__."/../library/LocaleFunctions.php";
mb_internal_encoding("UTF-8");
mb_regex_encoding("UTF-8");
/**
 * shorthand for echo
 *
 * @param string $text string to send to the current output
 */
function e($text)
{
    echo $text;
}
$locale_tag = L\guessLocale();
$locale = null;
L\setLocaleObject($locale_tag);
/**
 * Provides a command-line interface way to configure a Yioop Instance.
 * Unlike the web interface this interface is English-only.
 */
class ConfigureTool
{
    /**
     * Used to hold an AdminController object used to manipulate the
     * Yioop configuration
     * @var object
     */
    public $admin;
    /**
     * Holds the main menu data for the configuration tool
     * @var array
     */
    public $menu = ["workDirectory" => "Create/Set Work Directory",
        "rootPassword" => "Change root password",
        "defaultLocale"=> "Set Default Locale",
        "upgradeLocales" => "Upgrade Locales",
        "debugDisplay"=> "Debug Display Set-up",
        "searchAccess"=> "Search Access Set-up",
        "searchPageElementLinks" => "Search Page Elements and Links",
        "nameServer" => "Name Server Set-up",
        "robotSetUp"=> "Crawl Robot Set-up",
        "quit" => "Exit program"
    ];
    /**
     * To change configuration parameters of Yioop, this program
     * invokes AdminController methods. These methods expect, data
     * passed to them in super globals set up as a result of an HTTP
     * request. This program fakes the settings of these variables.
     * To keep things simple this constructor initializes each of the
     * relevant super globals to be empty arrays.
     */
    function __construct()
    {
        $_REQUEST = [];
        $_POST = [];
        $_GET = [];
        $_SERVER = [];
        $_SESSION = [];
        $this->admin = new AdminController();
    }
    /**
     * This is the main loop where options of what the user can configure
     * are presented, a choice is requested, and so on...
     */
    function loop()
    {
        $done = false;
        $activities = array_keys($this->menu);
        $activities[] = "configureMenu";
        $state = "configureMenu";
        while($state != "quit") {
            if (in_array($state, $activities) ) {
                $state = $this->$state();
            }
        }
    }
    /**
     * This is used to draw the main configuration menu and ask for a
     * user selection
     * @return string name of the next menu state (e.g.
     *      "workDirectory", "quit") chosen by the user
     */
    function configureMenu()
    {
        $this->banner();
        $data = $this->callConfigure();
        e("Checking Yioop configuration...".
            "\n===============================\n");
        $check_status = str_replace("<br>", "\n", $data["SYSTEM_CHECK"]);
        e($check_status."\n===============================\n");
        $items = ["workDirectory" => "Create/Set Work Directory",
            "quit" => "Exit program"];
        if ($data["PROFILE"]) {
            $items = $this->menu;
        }
        return $this->drawChooseItems($items, "configureMenu");
    }
    /**
     * Used to create/change the location of this Yioop instances work
     * directory
     * @return string name of the next menu state to dispatch to after
     *      the work-directory step
     */
    function workDirectory()
    {
        $this->banner();
        $data = $this->callConfigure();
        $directory = (isset($data["WORK_DIRECTORY"]) &&
            $data["WORK_DIRECTORY"] != "") ? $data["WORK_DIRECTORY"]
            : "";
        /* The path an empty entry will use: the directory already in
           effect when there is one, otherwise the default location
           next to the Yioop folder. */
        $default_directory = ($directory != "") ? $directory :
            DEFAULT_WORK_DIRECTORY;
        $is_initialized = file_exists($default_directory .
            PROFILE_FILE_NAME);
        if ($directory != "") {
            e("CURRENT WORK DIRECTORY: $directory\n\n");
        } else {
            e("CURRENT WORK DIRECTORY: none set yet\n\n");
        }
        if (!$is_initialized) {
            e("This work directory has not been initialized yet ".
                "(no profile found at\n$default_directory).\n\n");
        }
        e("Enter a new value, or just press return to ".
            "create/reinitialize the\ncurrent work directory at ".
            "$default_directory:\n");
        if (!isset($_SERVER['REQUEST_URI'])) {
            $_SERVER['REQUEST_URI'] = "";
        }
        $this->prepareGlobals($data);
        $entered = trim(L\readInput());
        if ($entered == "") {
            $entered = $default_directory;
        }
        $_REQUEST["WORK_DIRECTORY"] = $entered;
        $_REQUEST["arg"] = "directory";
        $next_menu = $this->confirmChange("configure", "workDirectory");
        return $next_menu;
    }
    /**
     * Used to change the password of the root account of this Yioop Instance
     * @return string name of the next menu state to dispatch to after
     *      the root-password step
     */
    function rootPassword()
    {
        $this->banner();
        $data = $this->callConfigure();
        if ($data["PROFILE"] != 1) {
            $_REQUEST["MESSAGE"] = "Work directory needs to be set/created!";
            return "configureMenu";
        }
        e("Enter old password:");
        $_REQUEST["password"] = L\readPassword();
        e("Enter new password:");
        $_REQUEST["new_password"] = L\readPassword();
        e("Re-Enter new password:");
        $_REQUEST["retype_password"] = L\readPassword();
        $_SESSION['USER_ID'] = ROOT_ID;
        $_REQUEST['arg'] = "updateuser";
        $_REQUEST['edit_pass'] = "true";
        $next_menu = $this->confirmChange("manageAccount", "rootPassword");
        return $next_menu;
    }
    /**
     * Changes the default locale (language) used by Yioop when it cannot
     * determine that information from the user's browser
     * @return string name of the next menu state to dispatch to after
     *      the default-locale step
     */
    function defaultLocale()
    {
        $this->banner();
        $data = $this->callConfigure();
        if ($data["PROFILE"] != 1) {
            $_REQUEST["MESSAGE"] = "Work directory needs to be set/created!";
            return "configureMenu";
        }
        e("CURRENT LANGUAGE: ".$data["LANGUAGES"][
            $data["DEFAULT_LOCALE"]]."\n\n");
        $_SESSION = [];
        $items = $data["LANGUAGES"];
        $items["configureMenu"] = "Return to Main Menu";
        do {
            $choice = $this->drawChooseItems($items, "defaultLocale");
        } while( $choice == "defaultLocale");
        $this->prepareGlobals($data);
        if ($choice == "configureMenu") {
            $_REQUEST = [];
            $_SERVER = [];
            return "configureMenu";
        }
        $_REQUEST["DEFAULT_LOCALE"] = $choice;
        return "defaultLocale";
    }
    /**
     * Used to configure debugging information for this Yioop instance.
     * i.e., whether PHP notices, warnings, errors, should be displayed,
     * whether query statistics and info should be displayed, and whether
     * unit tests should be viewable from the web
     * @return string name of the next menu state to dispatch to after
     *      the debug-display step
     */
    function debugDisplay()
    {
        $this->banner();
        $data = $this->callConfigure();
        if ($data["PROFILE"] != 1) {
            $_REQUEST["MESSAGE"] = "Work directory needs to be set/created!";
            return "configureMenu";
        }
        e("CURRENT DEBUG SETTINGS\n======================\n");
        $dlevel = $data["DEBUG_LEVEL"];
        $setting = ($dlevel & ERROR_INFO) ? "On" : "Off";
        e("Error Info: [$setting]\n");
        $setting = ($dlevel & QUERY_INFO) ? "On" : "Off";
        e("Query Info: [$setting]\n");
        $setting = ($dlevel & TEST_INFO) ? "On" : "Off";
        e("Test Info: [$setting]\n");
        $items = ["ERROR_INFO" => "Toggle Error Info",
            "QUERY_INFO" => "Toggle Query Info",
            "TEST_INFO" => "Toggle Test Info",
            "configureMenu" => "Return to Main Menu"];
        do {
            $choice = $this->drawChooseItems($items, "debugDisplay");
        } while( $choice == "debugDisplay");
        $this->prepareGlobals($data);
        if ($choice == "configureMenu") {
            $_REQUEST = [];
            $_SERVER = [];
            return "configureMenu";
        }
        $flag = constant(NS_CONFIGS . $choice);
        $dlevel = ($dlevel & $flag) ? $dlevel - $flag : $dlevel + $flag;
        if ($dlevel & ERROR_INFO) {$_REQUEST["ERROR_INFO"] = "true";}
        if ($dlevel & QUERY_INFO) {$_REQUEST["QUERY_INFO"] = "true";}
        if ($dlevel & TEST_INFO) {$_REQUEST["TEST_INFO"] = "true";}
        return "debugDisplay";
    }
    /**
     * Configures which methods are allowed by this Yioop instance to access
     * search results, (via the web, via open rss search results, via the
     * API)
     * @return string name of the next menu state to dispatch to after
     *      the search-access step
     */
    function searchAccess()
    {
        $this->banner();
        $data = $this->callConfigure();
        if ($data["PROFILE"] != 1) {
            $_REQUEST["MESSAGE"] = "Work directory needs to be set/created!";
            return "configureMenu";
        }
        e("CURRENT SEARCH ACCESS SETTINGS\n==============================\n");
        $settings = ["WEB_ACCESS" => "Web",
            "RSS_ACCESS" => "RSS", "API_ACCESS" => "API"];
        $items = [];
        foreach ($settings as $setting => $setting_string) {
            $toggle = ($data[$setting]) ? "On" : "Off";
            e("$setting_string: [$toggle]\n");
            $items[$setting] = "Toggle $setting_string";
        }
        $items["configureMenu"] = "Return to Main Menu";
        do {
            $choice = $this->drawChooseItems($items, "searchAccess");
        } while( $choice == "searchAccess");
        $this->prepareGlobals($data);
        if ($choice == "configureMenu") {
            $_REQUEST = [];
            $_SERVER = [];
            return "configureMenu";
        }
        $_REQUEST[$choice] = ($data[$choice]) ? false : true;
        return "searchAccess";
    }
    /**
     * Configures which of the various links of the SERPS page such as
     * Cache, etc should be displayed. Also, configures whether the signin
     * links, etc should be displayed.
     * @return string name of the next menu state to dispatch to after
     *      the search-page-elements step
     */
    function searchPageElementLinks()
    {
        $this->banner();
        $data = $this->callConfigure();
        if ($data["PROFILE"] != 1) {
            $_REQUEST["MESSAGE"] = "Work directory needs to be set/created!";
            return "configureMenu";
        }
        e("CURRENT SEARCH PAGE ELEMENTS AND LINKS SETTINGS".
            "\n===================================================\n");
        $settings = ["WORD_SUGGEST" => "Word Suggest",
            "SUBSEARCH_LINK"  => "Subsearch Links",
            "SIGNIN_LINK" => "Sign-in Links",
            "MORE_RESULT" => "More Result Info",
            "CACHE_LINK" => "Cache Link",
            "SIMILAR_LINK" => "Similar Link", "IN_LINK" => "Inlinks",
            "IP_LINK"=> "IP Links"];
        $items = [];
        foreach ($settings as $setting => $setting_string) {
            $toggle = ($data[$setting]) ? "On" : "Off";
            e("$setting_string: [$toggle]\n");
            $items[$setting] = "Toggle $setting_string";
        }
        $items["configureMenu"] = "Return to Main Menu";
        do {
            $choice = $this->drawChooseItems($items, "searchPageElementLinks");
        } while( $choice == "searchPageElementLinks");
        $this->prepareGlobals($data);
        if ($choice == "configureMenu") {
            $_REQUEST = [];
            $_SERVER = [];
            return "configureMenu";
        }
        $_REQUEST[$choice] = ($data[$choice]) ? false : true;
        return "searchPageElementLinks";
    }
    /**
     * Configures settings relating to the location of the name server and
     * the salt used when communicating with it. Also, configures caching
     * mechanisms the name server should use when returning results.
     * @return string name of the next menu state to dispatch to after
     *      the name-server step
     */
    function nameServer()
    {
        $this->banner();
        $data = $this->callConfigure();
        if ($data["PROFILE"] != 1) {
            $_REQUEST["MESSAGE"] = "Work directory needs to be set/created!";
            return "configureMenu";
        }
        e("NAME SERVER SETTINGS\n====================\n");
        e("Server Key: [".$data["AUTH_KEY"]."]\n");
        e("Name Server URL: [".$data["NAME_SERVER"]."]\n");
        $settings = ["USE_FILECACHE" => "Use File Cache"];
        $items = ["serverKey" => "Edit Server Key",
            "nameServer" => "Edit Name Server Url"];
        foreach ($settings as $setting => $setting_string) {
            $toggle = ($data[$setting]) ? "On" : "Off";
            e("$setting_string: [$toggle]\n");
            $items[$setting] = "Toggle $setting_string";
        }
        $items["configureMenu"] = "Return to Main Menu";
        do {
            $choice = $this->drawChooseItems($items, "nameServerMenu");
        } while( $choice == "nameServerMenu");
        $this->prepareGlobals($data);
        switch ($choice) {
            case "configureMenu":
                $_REQUEST = [];
                $_SERVER = [];
                return "configureMenu";
                break;
            case "serverKey":
                e("Enter a new server key: ");
                $_REQUEST["AUTH_KEY"] = L\readInput();
                break;
            case "nameServer":
                e("Enter a new name server url: ");
                $_REQUEST["NAME_SERVER"] = L\readInput();
                break;
            default:
                $_REQUEST[$choice] = ($data[$choice]) ? false : true;
        }
        return "nameServer";
    }
    /**
     * Used to set up the name of this instance of the Yioop robot as well
     * as its description page.
     * @return string name of the next menu state to dispatch to after
     *      the robot-set-up step
     */
    function robotSetUp()
    {
        $this->banner();
        $data = $this->callConfigure();
        if ($data["PROFILE"] != 1) {
            $_REQUEST["MESSAGE"] = "Work directory needs to be set/created!";
            return "configureMenu";
        }
        e("CRAWL ROBOT SETTINGS\n====================\n");
        e("Crawl Robot Name: [".$data["USER_AGENT_SHORT"]."]\n");
        e("Robot Instance: [".$data["ROBOT_INSTANCE"]."]\n");
        e("\nRobot Description:\n=================\n".
            $data["ROBOT_DESCRIPTION"] . "\n=================\n");
        $items = ["robotName" => "Edit Robot Name",
            "robotInstance" => "Edit Robot Instance",
            "robotDescription" => "Edit Robot Description",
            "configureMenu" => "Return to Main Menu"];
        do {
            $choice = $this->drawChooseItems($items, "robotSetUp");
        } while( $choice == "robotSetUp");
        $this->prepareGlobals($data);
        switch ($choice) {
            case "configureMenu":
                $_REQUEST = [];
                $_SERVER = [];
                return "configureMenu";
                break;
            case "robotName":
                e("Enter a new robot name: ");
                $_REQUEST["USER_AGENT_SHORT"] = L\readInput();
                break;
            case "robotInstance":
                e("Enter a new robot instance value: ");
                $_REQUEST["ROBOT_INSTANCE"] = L\readInput();
                break;
            case "robotDescription":
                e("Enter a description of your web crawler robot.\n".
                  "Terminate input with a line with only '.' on it:\n");
                $_REQUEST["ROBOT_DESCRIPTION"] = L\readMessage();
                break;
        }
        return "robotSetUp";
    }
    /**
     * Used to select to confirm, cancel, or re-enter the last profile
     * change
     *
     * @param string $admin_method to call if confirmed
     * @param string $reenter_method , return value if reenter chosen
     * @return string menu name to do to next
     */
    function confirmChange($admin_method, $reenter_method)
    {
        $component_activities = AdminController::$component_activities;
        $items = ["confirm" => "Confirm Change",
            "reenter" => "Re-enter the information",
            "configureMenu" => "Return to the Configure Menu"];
        $first = true;
        do {
            $choice = $this->drawChooseItems($items, "confirmChange");
        } while( $choice == "confirmChange");
        switch ($choice) {
            case "confirm":
                $component = "system";
                foreach ($component_activities as $available_component =>
                    $activities) {
                    if (in_array($admin_method, $activities)) {
                        $component = $available_component;
                        break;
                    }
                }
                $data = $this->admin->component($component)->$admin_method();
                $_SERVER = [];
                $_SESSION = [];
                $_REQUEST = [];
                $_REQUEST["MESSAGE"] = $data["MESSAGE"];
                $next_menu = "configureMenu";
                break;
            case "reenter":
                $_SERVER = [];
                $_SESSION = [];
                $_REQUEST = [];
                $next_menu = $reenter_method;
                break;
            default:
                $_SERVER = [];
                $_SESSION = [];
                $_REQUEST = [];
                $next_menu = "configureMenu";
        }
        return $next_menu;
    }
    /**
     * Interactive menu step, and the action behind the upgrade-locales
     * command, that force-pushes the locale strings together with the
     * version-gated wiki and help pages and resource locale files from
     * the source tree into the work directory, regardless of the stored
     * resources-wiki version. This lets a developer see edited help
     * pages and resource files during ongoing work without bumping the
     * resources-wiki version on every change. The outcome is reported
     * through the usual menu message so both the menu and the command
     * surface the same line.
     *
     * @return string name of the next menu state, the main menu
     */
    function upgradeLocales()
    {
        require_once __DIR__ . "/../library/UpgradeFunctions.php";
        L\upgradeLocales(true);
        $_REQUEST["MESSAGE"] = "Locale strings and wiki resources " .
            "force-pushed from the source tree into the work directory.";
        return "configureMenu";
    }
    /**
     * Runs one configuration command from values given on the command
     * line, with no prompting. It sets up the request the way the
     * matching interactive step would, fills in the answer that step
     * would ask for from the supplied values, and saves the change.
     * The work-directory command is allowed to run on a fresh install;
     * every other command first needs a work directory to exist, and
     * prints a message and stops when one does not.
     *
     * @param string $command the command name the user typed
     * @param array $spec the command's specification (its activity and
     *      argument rules)
     * @param array $values the arguments the user gave after the
     *      command name
     */
    function runActivityFromArgs($command, $spec, $values)
    {
        $data = $this->callConfigure();
        if ($command != "work-dir" &&
            (!isset($data["PROFILE"]) || $data["PROFILE"] != 1)) {
            e("A work directory must be set up first. Run:\n");
            e("  php ConfigureTool.php work-dir <path>\n");
            return;
        }
        if ($command == "work-dir") {
            $directory = (isset($data["WORK_DIRECTORY"]) &&
                $data["WORK_DIRECTORY"] != "") ?
                $data["WORK_DIRECTORY"] : DEFAULT_WORK_DIRECTORY;
            $entered = (count($values) == 1 && trim($values[0]) != "") ?
                trim($values[0]) : $directory;
            $_REQUEST["WORK_DIRECTORY"] = $entered;
            $_REQUEST["arg"] = "directory";
            $this->commitActivity("configure", $data);
            return;
        }
        if ($command == "root-password") {
            $_REQUEST["password"] = $values[0];
            $_REQUEST["new_password"] = $values[1];
            $_REQUEST["retype_password"] = $values[1];
            $_SESSION['USER_ID'] = ROOT_ID;
            $_REQUEST['arg'] = "updateuser";
            $_REQUEST['edit_pass'] = "true";
            $this->commitActivity("manageAccount", $data);
            return;
        }
        if ($command == "locale") {
            $this->prepareGlobals($data);
            $_REQUEST["DEFAULT_LOCALE"] = $values[0];
            $this->commitActivity("configure", $data);
            return;
        }
        if ($command == "name-server") {
            $this->prepareGlobals($data);
            $_REQUEST["AUTH_KEY"] = $values[0];
            $_REQUEST["NAME_SERVER"] = $values[1];
            $this->commitActivity("configure", $data);
            return;
        }
        if ($command == "robot") {
            $this->prepareGlobals($data);
            $_REQUEST["USER_AGENT_SHORT"] = $values[0];
            $_REQUEST["ROBOT_INSTANCE"] = $values[1];
            $_REQUEST["ROBOT_DESCRIPTION"] = $values[2];
            $this->commitActivity("configure", $data);
            return;
        }
        if ($command == "upgrade-locales") {
            $this->upgradeLocales();
            e($_REQUEST["MESSAGE"] . "\n");
            return;
        }
        /* The remaining commands turn named settings on with +name or
           off with -name, leaving anything not listed unchanged. The
           name-to-request-key maps below match the labels each
           interactive step shows. */
        $toggle_settings = [
            "debug" => ["error" => "ERROR_INFO",
                "query" => "QUERY_INFO", "test" => "TEST_INFO"],
            "search-access" => ["web" => "WEB_ACCESS",
                "rss" => "RSS_ACCESS", "api" => "API_ACCESS"],
            "page-elements" => ["word-suggest" => "WORD_SUGGEST",
                "subsearch" => "SUBSEARCH_LINK",
                "signin" => "SIGNIN_LINK", "result-info" => "MORE_RESULT",
                "cache" => "CACHE_LINK", "similar" => "SIMILAR_LINK",
                "inlink" => "IN_LINK", "ip" => "IP_LINK"],
        ];
        $this->runToggleFromArgs($command, $toggle_settings[$command],
            $values, $data);
    }
    /**
     * Saves a setting change that turns named on/off options on with
     * +name or off with -name. The starting point is the current
     * setting, so anything the user does not name keeps its present
     * value. An unrecognized name prints the usage help and stops.
     *
     * @param string $command the command name the user typed
     * @param array $setting_names maps each accepted short name to the
     *      request key that stores its setting
     * @param array $values the +name / -name arguments the user gave
     * @param array $data the current profile state
     */
    function runToggleFromArgs($command, $setting_names, $values, $data)
    {
        $this->prepareGlobals($data);
        /* start every setting at its current value so unnamed ones do
           not change. */
        foreach ($setting_names as $short_name => $request_key) {
            if (!empty($data[$request_key])) {
                $_REQUEST[$request_key] = "true";
            } else {
                unset($_REQUEST[$request_key]);
            }
        }
        foreach ($values as $value) {
            $sign = substr($value, 0, 1);
            $short_name = substr($value, 1);
            if (($sign != "+" && $sign != "-") ||
                !isset($setting_names[$short_name])) {
                e("Bad option: $value\n\n");
                configureToolUsage();
                return;
            }
            $request_key = $setting_names[$short_name];
            if ($sign == "+") {
                $_REQUEST[$request_key] = "true";
            } else {
                unset($_REQUEST[$request_key]);
            }
        }
        $this->commitActivity("configure", $data);
    }
    /**
     * Saves the change set up in the request by calling the admin
     * activity that handles it, the same one the interactive Confirm
     * choice would call. Prints the result message the activity
     * returns.
     *
     * @param string $admin_method the admin activity to run, such as
     *      configure or manageAccount
     * @param array $data the current profile state, used to find which
     *      part of the admin controller owns the activity
     */
    function commitActivity($admin_method, $data)
    {
        $component_activities = AdminController::$component_activities;
        $component = "system";
        foreach ($component_activities as $available_component =>
            $activities) {
            if (in_array($admin_method, $activities)) {
                $component = $available_component;
                break;
            }
        }
        $result = $this->admin->component($component)->$admin_method();
        if (!empty($result["MESSAGE"])) {
            e($result["MESSAGE"] . "\n");
        }
    }
    /**
     * Draws a list of options to the screen and gets a choice
     * from this list from the user.
     *
     * @param array $items as associative array (return value => description)
     * @param string $currentView value to return if invalid choice made
     * @return string a choice from the user
     */
    function drawChooseItems($items, $currentView)
    {
        $choice_nums = [];
        $i = 1;
        e("\nAvailable Options:\n==================\n");
        foreach ($items as $name => $description) {
            e("($i) $description\n");
            $choice_nums[$i] = $name;
            $i++;
        }
        if (!empty($_REQUEST["MESSAGE"])) {
            e("\n+++ ".$_REQUEST["MESSAGE"]." +++\n");
            unset($_REQUEST["MESSAGE"]);
        }
        e("\nPlease choose an option:\n");
        $user_data = strtolower(trim(L\readInput()));
        if ($user_data >= 1 && $user_data < $i) {
            $_REQUEST["MESSAGE"] = "";
            return $choice_nums[$user_data];
        } else {
            $_REQUEST["MESSAGE"] = "Invalid choice. Please choose again.";
            return $currentView;
        }
    }
    /**
     * Used to call system components configure method. It detects if
     * a redirect happened by the fact that $data['PROFILE'] is not set.
     * If so it passes along the redirect message and re-calls configure()
     * @return array \$data array as returned by the Admin
     *      controller's configure activity (system check, current
     *      profile, locale fields)
     */
    function callConfigure()
    {
        $data = $this->admin->component("system")->configure();
        if (!isset($data["PROFILE"])) {
            $_REQUEST = [];
            $message = (isset($data['MESSAGE'])) ? $data['MESSAGE'] : "";
            $data = $this->admin->component("system")->configure();
            $data['MESSAGE'] = $message;
        }
        return $data;
    }
    /**
     * Prints the banner used by this configuration tool
     */
    function banner()
    {
        e(chr(27) . "[2J" . chr(27) . "[;H");
        e("\n\nYIOOP! CONFIGURATION TOOL\n");
        e("+++++++++++++++++++++++++\n\n");
    }
    /**
     * Sets-up the field values of the super globals used by AdminController
     * when changing a profile or managing passwords. These particular
     * values don't change with respect to what this tool does.
     *
     * @param array $data current profile state
     */
    function prepareGlobals($data)
    {
        $_SESSION = [];
        $_REQUEST = $this->copyProfileFields($data);
        $_REQUEST["arg"] = "profile";
        $_REQUEST['YIOOP_TOKEN'] = "";
        if (!isset($_SERVER['REQUEST_URI'])) {
            if (!empty($data['WEB_URI'])) {
                $_SERVER['REQUEST_URI'] = $data['WEB_URI'];
            } else {
                e("Enter web path for Yioop instance:\n");
                $_SERVER['REQUEST_URI'] = L\readInput();
            }
        }
    }
    /**
     * Used to copy the contents of $data which are profile fields to a
     * new array.
     *
     * @param array $data an array of profile and other fields
     * @return array a new array containing a copy of just the profile fields
     *     from the original array
     */
    function copyProfileFields($data)
    {
        $profile = [];
        foreach ($this->admin->model("profile")->profile_fields as $field) {
            if (isset($data[$field])) {
                $profile[$field] = $data[$field];
            }
        }
        return $profile;
    }
}
/**
 * The set of commands the tool understands from the command line. Each
 * entry maps a short command name to: the activity method it runs, the
 * list of arguments that command takes (for the help text and for
 * checking the count given), the smallest and largest number of
 * arguments allowed, and a one-line description. Toggle-style commands
 * take any number of +name / -name arguments to turn settings on or
 * off, so their range is open-ended.
 *
 * @return array the command specifications keyed by command name
 */
function configureToolCommands()
{
    return [
        "work-dir" => ["activity" => "workDirectory",
            "args" => "[path]", "min" => 0, "max" => 1,
            "help" => "Set or create the work directory (no path ".
                "means the default location)."],
        "root-password" => ["activity" => "rootPassword",
            "args" => "<old> <new>", "min" => 2, "max" => 2,
            "help" => "Change the root account password."],
        "locale" => ["activity" => "defaultLocale",
            "args" => "<tag>", "min" => 1, "max" => 1,
            "help" => "Set the default language, such as en-US."],
        "debug" => ["activity" => "debugDisplay",
            "args" => "[+/-error] [+/-query] [+/-test]",
            "min" => 1, "max" => 3,
            "help" => "Turn debug outputs on (+) or off (-)."],
        "search-access" => ["activity" => "searchAccess",
            "args" => "[+/-web] [+/-rss] [+/-api]",
            "min" => 1, "max" => 3,
            "help" => "Turn search-access methods on (+) or off (-)."],
        "page-elements" => ["activity" => "searchPageElementLinks",
            "args" => "[+/-word-suggest] [+/-subsearch] ".
                "[+/-signin] [+/-result-info]\n      ".
                "[+/-cache] [+/-similar] [+/-inlink] [+/-ip]",
            "min" => 1, "max" => 99,
            "help" => "Turn search-page elements and links on (+) ".
                "or off (-)."],
        "name-server" => ["activity" => "nameServer",
            "args" => "<key> <url>", "min" => 2, "max" => 2,
            "help" => "Set the server key and name-server URL."],
        "robot" => ["activity" => "robotSetUp",
            "args" => "<name> <instance> <description>",
            "min" => 3, "max" => 3,
            "help" => "Set the crawl robot name, instance, and ".
                "description."],
        "upgrade-locales" => ["activity" => "upgradeLocales",
            "args" => "", "min" => 0, "max" => 0,
            "help" => "Force-push locale strings and wiki resources ".
                "from the source tree (no version bump needed)."],
    ];
}
/**
 * Prints how to run the configuration tool from the command line: with
 * no arguments to see this help, with the word menu to use the
 * interactive menu, or with a command name and its arguments to run
 * that one step without prompting. Each command is listed with the
 * arguments it takes and a one-line description.
 */
function configureToolUsage()
{
    e("Yioop Configuration Tool\n");
    e("========================\n\n");
    e("Usage:\n");
    e("  php ConfigureTool.php\n");
    e("      show this help\n");
    e("  php ConfigureTool.php menu\n");
    e("      run the interactive menu\n");
    e("  php ConfigureTool.php <command> [arguments...]\n");
    e("      run one command without prompting\n\n");
    e("Commands:\n");
    foreach (configureToolCommands() as $name => $spec) {
        e("  $name " . $spec["args"] . "\n");
        e("      " . $spec["help"] . "\n");
    }
}
$configure_tool = new ConfigureTool();
if (empty($argv[1])) {
    configureToolUsage();
} else if ($argv[1] == "menu") {
    $configure_tool->loop();
} else {
    $commands = configureToolCommands();
    if (!isset($commands[$argv[1]])) {
        e("Unknown command: " . $argv[1] . "\n\n");
        configureToolUsage();
    } else {
        $spec = $commands[$argv[1]];
        $values = array_slice($argv, 2);
        if (count($values) < $spec["min"] ||
            count($values) > $spec["max"]) {
            e("Wrong number of arguments for " . $argv[1] . ".\n\n");
            configureToolUsage();
        } else {
            $configure_tool->runActivityFromArgs($argv[1], $spec,
                $values);
        }
    }
}
X