<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Backup user interface stages
 *
 * This file contains the classes required to manage the stages that make up the
 * backup user interface.
 * These will be primarily operated a {@link backup_ui} instance.
 *
 * @package   core_backup
 * @copyright 2010 Sam Hemelryk
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

/**
 * Abstract stage class
 *
 * This class should be extended by all backup stages (a requirement of many backup ui functions).
 * Each stage must then define two abstract methods
 *  - process : To process the stage
 *  - initialise_stage_form : To get a backup_moodleform instance for the stage
 *
 * @package   core_backup
 * @copyright 2010 Sam Hemelryk
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
abstract class backup_ui_stage extends base_ui_stage {

    /**
     * Constructor.
     *
     * @param backup_ui $ui
     * @param array $params
     */
    public function __construct(backup_ui $ui, ?array $params = null) {
        parent::__construct($ui, $params);
    }

    /**
     * The backup id from the backup controller
     * @return string
     */
    final public function get_backupid() {
        return $this->get_uniqueid();
    }
}

/**
 * Class representing the initial stage of a backup.
 *
 * In this stage the user is required to set the root level settings.
 *
 * @package   core_backup
 * @copyright 2010 Sam Hemelryk
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class backup_ui_stage_initial extends backup_ui_stage {

    /**
     * When set to true we skip all stages and jump to immediately processing the backup.
     * @var bool
     */
    protected $oneclickbackup = false;

    /**
     * Initial backup stage constructor
     * @param backup_ui $ui
     * @param array $params
     */
    public function __construct(backup_ui $ui, ?array $params = null) {
        $this->stage = backup_ui::STAGE_INITIAL;
        parent::__construct($ui, $params);
    }

    /**
     * Processes the initial backup stage
     * @param base_moodleform $m
     * @return int The number of changes
     */
    public function process(?base_moodleform $m = null) {

        $form = $this->initialise_stage_form();

        if ($form->is_cancelled()) {
            $this->ui->cancel_process();
        }

        $data = $form->get_data();
        if ($data && confirm_sesskey()) {
            if (isset($data->oneclickbackup)) {
                $this->oneclickbackup = true;
            }
            $tasks = $this->ui->get_tasks();
            $changes = 0;
            foreach ($tasks as &$task) {
                // We are only interesting in the backup root task for this stage.
                if ($task instanceof backup_root_task) {
                    // Get all settings into a var so we can iterate by reference.
                    $settings = $task->get_settings();
                    foreach ($settings as &$setting) {
                        $name = $setting->get_ui_name();
                        if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
                            $setting->set_value($data->$name);
                            $changes++;
                        } else if (!isset($data->$name) && $setting->get_value() &&
                                $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX &&
                                $setting->get_status() !== backup_setting::LOCKED_BY_HIERARCHY) {
                            $setting->set_value(0);
                            $changes++;
                        }
                    }
                }
            }
            // Return the number of changes the user made.
            return $changes;
        } else {
            return false;
        }
    }

    /**
     * Gets the next stage for the backup.
     *
     * We override this function to implement the one click backup.
     * When the user performs a one click backup we jump straight to the final stage.
     *
     * @return int
     */
    public function get_next_stage() {
        if ($this->oneclickbackup) {
            // Its a one click backup.
            // The default filename is backup.mbz, this normally gets set to something useful in the confirmation stage.
            // because we skipped that stage we must manually set this to a useful value.
            $tasks = $this->ui->get_tasks();
            foreach ($tasks as $task) {
                if ($task instanceof backup_root_task) {
                    // Find the filename setting.
                    $setting = $task->get_setting('filename');
                    if ($setting) {
                        // Use the helper objects to get a useful name.
                        $filename = backup_plan_dbops::get_default_backup_filename(
                            $this->ui->get_format(),
                            $this->ui->get_type(),
                            $this->ui->get_controller_id(),
                            $this->ui->get_setting_value('users'),
                            $this->ui->get_setting_value('anonymize'),
                            false,
                            (bool)$this->ui->get_setting_value('files')
                        );
                        $setting->set_value($filename);
                    }
                }
            }
            return backup_ui::STAGE_FINAL;
        }
        return parent::get_next_stage();
    }

    /**
     * Initialises the backup_moodleform instance for this stage
     *
     * @return backup_initial_form
     */
    protected function initialise_stage_form() {
        global $PAGE;
        if ($this->stageform === null) {
            $form = new backup_initial_form($this, $PAGE->url);
            // Store as a variable so we can iterate by reference.
            $tasks = $this->ui->get_tasks();
            // Iterate all tasks by reference.
            $add_settings = array();
            $dependencies = array();
            foreach ($tasks as &$task) {
                // For the initial stage we are only interested in the root settings.
                if ($task instanceof backup_root_task) {
                    if ($this->ui instanceof import_ui) {
                        $form->add_heading('rootsettings', get_string('importrootsettings', 'backup'));
                    } else {
                        $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
                    }
                    $settings = $task->get_settings();
                    // First add all settings except the filename setting.
                    foreach ($settings as &$setting) {
                        if ($setting->get_name() == 'filename') {
                            continue;
                        }
                        $add_settings[] = array($setting, $task);
                    }
                    // Then add all dependencies.
                    foreach ($settings as &$setting) {
                        if ($setting->get_name() == 'filename') {
                            continue;
                        }
                        $dependencies[] = $setting;
                    }
                }
            }
            // Add all settings at once.
            $form->add_settings($add_settings);
            // Add dependencies.
            foreach ($dependencies as $depsetting) {
                $form->add_dependencies($depsetting);
            }
            $this->stageform = $form;
        }
        // Return the form.
        return $this->stageform;
    }
}

/**
 * Schema stage of backup process
 *
 * During the schema stage the user is required to set the settings that relate
 * to the area that they are backing up as well as its children.
 *
 * @package   core_backup
 * @copyright 2010 Sam Hemelryk
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class backup_ui_stage_schema extends backup_ui_stage {

    /**
     * @var int Maximum number of settings to add to form at once
     */
    const MAX_SETTINGS_BATCH = 1000;

    /**
     * Schema stage constructor
     * @param backup_ui $ui
     * @param array $params
     */
    public function __construct(backup_ui $ui, ?array $params = null) {
        $this->stage = backup_ui::STAGE_SCHEMA;
        parent::__construct($ui, $params);
    }

    /**
     * Processes the schema stage
     *
     * @param base_moodleform $form
     * @return int The number of changes the user made
     */
    public function process(?base_moodleform $form = null) {
        $form = $this->initialise_stage_form();
        // Check it wasn't cancelled.
        if ($form->is_cancelled()) {
            $this->ui->cancel_process();
        }

        // Check it has been submit.
        $data = $form->get_data();
        if ($data && confirm_sesskey()) {
            // Get the tasks into a var so we can iterate by reference.
            $tasks = $this->ui->get_tasks();
            $changes = 0;
            // Iterate all tasks by reference.
            foreach ($tasks as &$task) {
                // We are only interested in schema settings.
                if (!($task instanceof backup_root_task)) {
                    // Store as a variable so we can iterate by reference.
                    $settings = $task->get_settings();
                    // Iterate by reference.
                    foreach ($settings as &$setting) {
                        $name = $setting->get_ui_name();
                        if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
                            $setting->set_value($data->$name);
                            $changes++;
                        } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
                            $setting->set_value(0);
                            $changes++;
                        }
                    }
                }
            }
            // Return the number of changes the user made.
            return $changes;
        } else {
            return false;
        }
    }

    /**
     * Creates the backup_schema_form instance for this stage
     *
     * @return backup_schema_form
     */
    protected function initialise_stage_form() {
        global $PAGE;
        if ($this->stageform === null) {
            $form = new backup_schema_form($this, $PAGE->url);
            $tasks = $this->ui->get_tasks();
            $content = '';
            $courseheading = false;
            $add_settings = array();
            $dependencies = array();

            // Track progress through each stage.
            $progress = $this->ui->get_controller()->get_progress();
            $progress->start_progress('Initialise stage form', 3);

            // Get settings for all tasks.
            $progress->start_progress('', count($tasks));
            $done = 1;
            foreach ($tasks as $task) {
                if (!($task instanceof backup_root_task)) {
                    if (!$courseheading) {
                        // If we haven't already display a course heading to group nicely.
                        $form->add_heading('coursesettings', get_string('includeactivities', 'backup'));
                        $courseheading = true;
                    }
                    // First add each setting.
                    foreach ($task->get_settings() as $setting) {
                        $add_settings[] = array($setting, $task);
                    }
                    // The add all the dependencies.
                    foreach ($task->get_settings() as $setting) {
                        $dependencies[] = $setting;
                    }
                } else if ($this->ui->enforce_changed_dependencies()) {
                    // Only show these settings if dependencies changed them.
                    // Add a root settings heading to group nicely.
                    $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
                    // Iterate all settings and add them to the form as a fixed
                    // setting. We only want schema settings to be editable.
                    foreach ($task->get_settings() as $setting) {
                        if ($setting->get_name() != 'filename') {
                            $form->add_fixed_setting($setting, $task);
                        }
                    }
                }
                // Update progress.
                $progress->progress($done++);
            }
            $progress->end_progress();

            // Add settings for tasks in batches of up to 1000. Adding settings
            // in larger batches improves performance, but if it takes too long,
            // we won't be able to update the progress bar so the backup might.
            // time out. 1000 is chosen to balance this.
            $numsettings = count($add_settings);
            $progress->start_progress('', ceil($numsettings / self::MAX_SETTINGS_BATCH));
            $start = 0;
            $done = 1;
            while ($start < $numsettings) {
                $length = min(self::MAX_SETTINGS_BATCH, $numsettings - $start);
                $form->add_settings(array_slice($add_settings, $start, $length));
                $start += $length;
                $progress->progress($done++);
            }
            $progress->end_progress();

            $progress->start_progress('', count($dependencies));
            $done = 1;
            foreach ($dependencies as $depsetting) {
                $form->add_dependencies($depsetting);
                $progress->progress($done++);
            }
            $progress->end_progress();

            // End overall progress through creating form.
            $progress->end_progress();
            $this->stageform = $form;
        }
        return $this->stageform;
    }
}

/**
 * Confirmation stage
 *
 * On this stage the user reviews the setting for the backup and can change the filename
 * of the file that will be generated.
 *
 * @package   core_backup
 * @copyright 2010 Sam Hemelryk
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class backup_ui_stage_confirmation extends backup_ui_stage {

    /**
     * Constructs the stage
     * @param backup_ui $ui
     * @param array $params
     */
    public function __construct($ui, ?array $params = null) {
        $this->stage = backup_ui::STAGE_CONFIRMATION;
        parent::__construct($ui, $params);
    }

    /**
     * Processes the confirmation stage
     *
     * @param base_moodleform $form
     * @return int The number of changes the user made
     */
    public function process(?base_moodleform $form = null) {
        $form = $this->initialise_stage_form();
        // Check it hasn't been cancelled.
        if ($form->is_cancelled()) {
            $this->ui->cancel_process();
        }

        $data = $form->get_data();
        if ($data && confirm_sesskey()) {
            // Collect into a variable so we can iterate by reference.
            $tasks = $this->ui->get_tasks();
            $changes = 0;
            // Iterate each task by reference.
            foreach ($tasks as &$task) {
                if ($task instanceof backup_root_task) {
                    // At this stage all we are interested in is the filename setting.
                    $setting = $task->get_setting('filename');
                    $name = $setting->get_ui_name();
                    if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
                        $setting->set_value($data->$name);
                        $changes++;
                    }
                }
            }
            // Return the number of changes the user made.
            return $changes;
        } else {
            return false;
        }
    }

    /**
     * Creates the backup_confirmation_form instance this stage requires
     *
     * @return backup_confirmation_form
     */
    protected function initialise_stage_form() {
        global $PAGE;
        if ($this->stageform === null) {
            // Get the form.
            $form = new backup_confirmation_form($this, $PAGE->url);
            $content = '';
            $courseheading = false;

            foreach ($this->ui->get_tasks() as $task) {
                if ($setting = $task->get_setting('filename')) {
                    $form->add_heading('filenamesetting', get_string('filename', 'backup'));
                    if ($setting->get_value() == 'backup.mbz') {
                        $format = $this->ui->get_format();
                        $type = $this->ui->get_type();
                        $id = $this->ui->get_controller_id();
                        $users = $this->ui->get_setting_value('users');
                        $anonymised = $this->ui->get_setting_value('anonymize');
                        $files = (bool)$this->ui->get_setting_value('files');
                        $filename = backup_plan_dbops::get_default_backup_filename(
                                $format,
                                $type,
                                $id,
                                $users,
                                $anonymised,
                                false,
                                $files);
                        $setting->set_value($filename);
                    }
                    $form->add_setting($setting, $task);
                    break;
                }
            }

            // Track progress through tasks.
            $progress = $this->ui->get_controller()->get_progress();
            $tasks = $this->ui->get_tasks();
            $progress->start_progress('initialise_stage_form', count($tasks));
            $done = 1;

            foreach ($tasks as $task) {
                if ($task instanceof backup_root_task) {
                    // If its a backup root add a root settings heading to group nicely.
                    if ($this->ui instanceof import_ui) {
                        $form->add_heading('rootsettings', get_string('importrootsettings', 'backup'));
                    } else {
                        $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
                    }
                } else if (!$courseheading) {
                    // We haven't already add a course heading.
                    $form->add_heading('coursesettings', get_string('includeditems', 'backup'));
                    $courseheading = true;
                }
                // Iterate all settings, doesnt need to happen by reference.
                foreach ($task->get_settings() as $setting) {
                    // For this stage only the filename setting should be editable.
                    if ($setting->get_name() != 'filename') {
                        $form->add_fixed_setting($setting, $task);
                    }
                }
                // Update progress.
                $progress->progress($done++);
            }
            $progress->end_progress();
            $this->stageform = $form;
        }
        return $this->stageform;
    }
}

/**
 * Final stage of backup
 *
 * This stage is special in that it is does not make use of a form. The reason for
 * this is the order of procession of backup at this stage.
 * The processesion is:
 * 1. The final stage will be intialise.
 * 2. The confirmation stage will be processed.
 * 3. The backup will be executed
 * 4. The complete stage will be loaded by execution
 * 5. The complete stage will be displayed
 *
 * This highlights that we neither need a form nor a display method for this stage
 * we simply need to process.
 *
 * @package   core_backup
 * @copyright 2010 Sam Hemelryk
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class backup_ui_stage_final extends backup_ui_stage {

    /**
     * Constructs the final stage
     * @param backup_ui $ui
     * @param array $params
     */
    public function __construct(backup_ui $ui, ?array $params = null) {
        $this->stage = backup_ui::STAGE_FINAL;
        parent::__construct($ui, $params);
    }

    /**
     * Processes the final stage.
     *
     * In this case it ALWAYS passes processing to the previous stage (confirmation)
     *
     * @param base_moodleform $form
     * @return bool
     */
    public function process(?base_moodleform $form = null) {
        return true;
    }

    /**
     * should NEVER be called... throws an exception
     */
    protected function initialise_stage_form() {
        throw new backup_ui_exception('backup_ui_must_execute_first');
    }

    /**
     * should NEVER be called... throws an exception
     *
     * @throws backup_ui_exception always
     * @param core_backup_renderer $renderer
     * @return void
     */
    public function display(core_backup_renderer $renderer) {
        throw new backup_ui_exception('backup_ui_must_execute_first');
    }
}

/**
 * The completed backup stage
 *
 * At this stage everything is done and the user will be redirected to view the
 * backup file in the file browser.
 *
 * @package   core_backup
 * @copyright 2010 Sam Hemelryk
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class backup_ui_stage_complete extends backup_ui_stage_final {

    /**
     * The results of the backup execution
     * @var array
     */
    protected $results;

    /**
     * Constructs the complete backup stage
     *
     * @param backup_ui $ui
     * @param array $params
     * @param array $results
     */
    public function __construct(backup_ui $ui, ?array $params = null, ?array $results = null) {
        $this->results = $results;
        parent::__construct($ui, $params);
        $this->stage = backup_ui::STAGE_COMPLETE;
    }

    /**
     * Displays the completed backup stage.
     *
     * Currently this just involves redirecting to the file browser with an
     * appropriate message.
     *
     * @param core_backup_renderer $renderer
     * @return string HTML code to echo
     */
    public function display(core_backup_renderer $renderer) {

        // Get the resulting stored_file record.
        $type = $this->get_ui()->get_controller()->get_type();
        $courseid = $this->get_ui()->get_controller()->get_courseid();
        switch ($type) {
            case 'activity':
                $cmid = $this->get_ui()->get_controller()->get_id();
                $cm = get_coursemodule_from_id(null, $cmid, $courseid);
                $modcontext = context_module::instance($cm->id);
                $restorerul = new moodle_url('/backup/restorefile.php', array('contextid' => $modcontext->id));
                break;
            case 'course':
            default:
                $coursecontext = context_course::instance($courseid);
                $restorerul = new moodle_url('/backup/restorefile.php', array('contextid' => $coursecontext->id));
        }

        $output = '';
        $output .= $renderer->box_start();
        if (!empty($this->results['include_file_references_to_external_content'])) {
            $output .= $renderer->notification(get_string('filereferencesincluded', 'backup'), 'notifyproblem');
        }
        if (!empty($this->results['missing_files_in_pool'])) {
            $output .= $renderer->notification(get_string('missingfilesinpool', 'backup'), 'notifyproblem');
        }
        $output .= $renderer->get_samesite_notification();
        $output .= $renderer->notification(get_string('executionsuccess', 'backup'), 'notifysuccess');
        $output .= $renderer->continue_button($restorerul, 'get');
        $output .= $renderer->box_end();

        return $output;
    }
}
