<?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/>.

/**
 * Class for loading/storing data requests from the DB.
 *
 * @package    tool_dataprivacy
 * @copyright  2018 Jun Pataleta
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace tool_dataprivacy;

defined('MOODLE_INTERNAL') || die();

use lang_string;
use core\persistent;

/**
 * Class for loading/storing data requests from the DB.
 *
 * @copyright  2018 Jun Pataleta
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class data_request extends persistent {

    /** The table name this persistent object maps to. */
    const TABLE = 'tool_dataprivacy_request';

    /** Data request created manually. */
    const DATAREQUEST_CREATION_MANUAL = 0;

    /** Data request created automatically. */
    const DATAREQUEST_CREATION_AUTO = 1;

    /**
     * Return the definition of the properties of this model.
     *
     * @return array
     */
    protected static function define_properties() {
        return [
            'type' => [
                'choices' => [
                    api::DATAREQUEST_TYPE_EXPORT,
                    api::DATAREQUEST_TYPE_DELETE,
                    api::DATAREQUEST_TYPE_OTHERS,
                ],
                'type' => PARAM_INT
            ],
            'comments' => [
                'type' => PARAM_TEXT,
                'message' => new lang_string('errorinvalidrequestcomments', 'tool_dataprivacy'),
                'default' => ''
            ],
            'commentsformat' => [
                'choices' => [
                    FORMAT_HTML,
                    FORMAT_MOODLE,
                    FORMAT_PLAIN,
                    FORMAT_MARKDOWN
                ],
                'type' => PARAM_INT,
                'default' => FORMAT_PLAIN
            ],
            'userid' => [
                'default' => function() {
                    global $USER;
                    return $USER->id;
                },
                'type' => PARAM_INT
            ],
            'requestedby' => [
                'default' => 0,
                'type' => PARAM_INT
            ],
            'status' => [
                'default' => api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
                'choices' => [
                    api::DATAREQUEST_STATUS_PENDING,
                    api::DATAREQUEST_STATUS_PREPROCESSING,
                    api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
                    api::DATAREQUEST_STATUS_APPROVED,
                    api::DATAREQUEST_STATUS_PROCESSING,
                    api::DATAREQUEST_STATUS_COMPLETE,
                    api::DATAREQUEST_STATUS_CANCELLED,
                    api::DATAREQUEST_STATUS_REJECTED,
                    api::DATAREQUEST_STATUS_DOWNLOAD_READY,
                    api::DATAREQUEST_STATUS_EXPIRED,
                    api::DATAREQUEST_STATUS_DELETED,
                ],
                'type' => PARAM_INT
            ],
            'dpo' => [
                'default' => 0,
                'type' => PARAM_INT,
                'null' => NULL_ALLOWED
            ],
            'dpocomment' => [
                'default' => '',
                'type' => PARAM_TEXT,
                'null' => NULL_ALLOWED
            ],
            'dpocommentformat' => [
                'choices' => [
                    FORMAT_HTML,
                    FORMAT_MOODLE,
                    FORMAT_PLAIN,
                    FORMAT_MARKDOWN
                ],
                'type' => PARAM_INT,
                'default' => FORMAT_PLAIN
            ],
            'systemapproved' => [
                'default' => false,
                'type' => PARAM_BOOL,
            ],
            'creationmethod' => [
                'default' => self::DATAREQUEST_CREATION_MANUAL,
                'choices' => [
                    self::DATAREQUEST_CREATION_MANUAL,
                    self::DATAREQUEST_CREATION_AUTO
                ],
                'type' => PARAM_INT
            ],
        ];
    }

    /**
     * Determines whether a completed data export request has expired.
     * The response will be valid regardless of the expiry scheduled task having run.
     *
     * @param data_request $request the data request object whose expiry will be checked.
     * @return bool true if the request has expired.
     */
    public static function is_expired(data_request $request) {
        $result = false;

        // Only export requests expire.
        if ($request->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
            switch ($request->get('status')) {
                // Expired requests are obviously expired.
                case api::DATAREQUEST_STATUS_EXPIRED:
                    $result = true;
                    break;
                // Complete requests are expired if the expiry time is a positive value, and has elapsed.
                case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
                    $expiryseconds = (int) get_config('tool_dataprivacy', 'privacyrequestexpiry');
                    if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
                        $result = true;
                    }
                    break;
            }
        }

        return $result;
    }

    /**
     * Fetch completed data requests which are due to expire.
     *
     * @param int $userid Optional user ID to filter by.
     *
     * @return array Details of completed requests which are due to expire.
     */
    public static function get_expired_requests($userid = 0) {
        global $DB;

        // Complete requests are expired if the expiry time is a positive value, and has elapsed.
        $expiryseconds = (int) get_config('tool_dataprivacy', 'privacyrequestexpiry');
        if ($expiryseconds <= 0) {
            return [];
        }

        $expirytime = strtotime("-{$expiryseconds} second");
        $table = self::TABLE;
        $sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
        $params = array(
            'export_type' => api::DATAREQUEST_TYPE_EXPORT,
            'completestatus' => api::DATAREQUEST_STATUS_DOWNLOAD_READY,
            'expirytime' => $expirytime,
        );
        $sort = 'id';
        $fields = 'id, userid';

        // Filter by user ID if specified.
        if ($userid > 0) {
            $sqlwhere .= ' AND (userid = :userid OR requestedby = :requestedby)';
            $params['userid'] = $userid;
            $params['requestedby'] = $userid;
        }

        return $DB->get_records_select_menu($table, $sqlwhere, $params, $sort, $fields, 0, 2000);
    }

    /**
     * Expire a given set of data requests.
     * Update request status and delete the files.
     *
     * @param array $expiredrequests [requestid => userid]
     *
     * @return void
     */
    public static function expire($expiredrequests) {
        global $DB;

        $ids = array_keys($expiredrequests);

        if (count($ids) > 0) {
            list($insql, $inparams) = $DB->get_in_or_equal($ids);
            $initialparams = array(api::DATAREQUEST_STATUS_EXPIRED, time());
            $params = array_merge($initialparams, $inparams);

            $update = "UPDATE {" . self::TABLE . "}
                          SET status = ?, timemodified = ?
                        WHERE id $insql";

            if ($DB->execute($update, $params)) {
                $fs = get_file_storage();

                foreach ($expiredrequests as $id => $userid) {
                    $usercontext = \context_user::instance($userid);
                    $fs->delete_area_files($usercontext->id, 'tool_dataprivacy', 'export', $id);
                }
            }
        }
    }

    /**
     * Whether this request is in a state appropriate for reset/resubmission.
     *
     * Note: This does not check whether any other completed requests exist for this user.
     *
     * @return  bool
     */
    public function is_resettable(): bool {
        if (api::DATAREQUEST_TYPE_OTHERS == $this->get('type')) {
            // It is not possible to reset 'other' reqeusts.
            return false;
        }

        $resettable = [
            api::DATAREQUEST_STATUS_APPROVED => true,
            api::DATAREQUEST_STATUS_REJECTED => true,
        ];

        return isset($resettable[$this->get('status')]);
    }

    /**
     * Whether this request is 'active'.
     *
     * @return  bool
     */
    public function is_active(): bool {
        $active = [
            api::DATAREQUEST_STATUS_APPROVED => true,
        ];

        return isset($active[$this->get('status')]);
    }

    /**
     * Reject this request and resubmit it as a fresh request.
     *
     * Note: This does not check whether any other completed requests exist for this user.
     *
     * @return  self
     */
    public function resubmit_request(): data_request {
        if ($this->is_active()) {
            $this->set('status', api::DATAREQUEST_STATUS_REJECTED)->save();
        }

        if (!$this->is_resettable()) {
            throw new \moodle_exception('cannotreset', 'tool_dataprivacy');
        }

        $currentdata = $this->to_record();
        unset($currentdata->id);

        // Clone the original request, but do not notify.
        $clone = api::create_data_request(
                $this->get('userid'),
                $this->get('type'),
                $this->get('comments'),
                $this->get('creationmethod'),
                false
            );
        $clone->set('comments', $this->get('comments'));
        $clone->set('dpo', $this->get('dpo'));
        $clone->set('requestedby', $this->get('requestedby'));
        $clone->save();

        return $clone;
    }
}
