diff --git a/README.md b/README.md index 4761814..33f9748 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ This is a Moodle plugin which makes the student experience of planned outages ni The main idea is that instead of an outage being a very booleon on/off situation, this plugin creates the concept of graduated outages where at predefined times before an outage and after, different levels of warning and access can be provided to students and testers letting them know what is about to happen and why. +![Screenshot as of 2016-09-06](docs/2016-09-06_screenshot.png?raw=true) + Why it is an auth plugin? ------------------------- diff --git a/auth.php b/auth.php index b8838f9..bdb7e14 100644 --- a/auth.php +++ b/auth.php @@ -24,17 +24,14 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. -} +defined('MOODLE_INTERNAL') || die(); -require_once($CFG->libdir . '/authlib.php'); +require_once($CFG->libdir.'/authlib.php'); /** * Class auth_plugin_outage */ -class auth_plugin_outage extends auth_plugin_base -{ +class auth_plugin_outage extends auth_plugin_base { /** * Constructor. */ @@ -43,10 +40,19 @@ class auth_plugin_outage extends auth_plugin_base } /** - * Do not authenticate users. + * @param string $username Not used in this plugin. + * @param string $password Not used in this plugin. * @return bool False + * @SuppressWarnings("unused") */ public function user_login($username, $password) { return false; } + + /** + * Login page hook. + */ + public function loginpage_hook() { + \auth_outage\local\outagelib::inject(); + } } diff --git a/checkfinished.php b/checkfinished.php new file mode 100644 index 0000000..3700146 --- /dev/null +++ b/checkfinished.php @@ -0,0 +1,32 @@ +. + +/** + * Called async from warning bar to check if the outage has finished. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\dml\outagedb; + +require_once(__DIR__.'/../../config.php'); + +$active = outagedb::get_active(); + +echo $active ? 'ongoing' : 'finished'; diff --git a/classes/calendar/calendar.php b/classes/calendar/calendar.php new file mode 100644 index 0000000..37eac35 --- /dev/null +++ b/classes/calendar/calendar.php @@ -0,0 +1,118 @@ +. + +namespace auth_outage\calendar; + +use auth_outage\local\outage; +use calendar_event; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot.'/calendar/lib.php'); + +/** + * Manages outages in the calendar. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class calendar { + /** + * Private constructor, use static methods instead. + */ + private function __construct() { + } + + /** + * Create an event on the calendar for this outage. + * @param outage $outage Outage to be added to the calendar. + */ + public static function calendar_create(outage $outage) { + calendar_event::create(self::calendar_data($outage)); + } + + /** + * Updates an event on the calendar based on this outage. + * @param outage $outage Outage to be updated in the calendar. + */ + public static function calendar_update(outage $outage) { + $event = self::calendar_load($outage->id); + + if (is_null($event)) { + debugging('Cannot update calendar entry for outage #'.$outage->id.', event not found. Creating it...'); + self::calendar_create($outage); + } else { + $event->update(self::calendar_data($outage)); + } + } + + /** + * Removes an event from the calendar related to this outage. + * @param int $outageid Id of outage to be deleted from the calendar. + */ + public static function calendar_delete($outageid) { + $event = self::calendar_load($outageid); + + // If not found (was not created before) ignore it. + if (is_null($event)) { + debugging('Cannot delete calendar entry for outage #'.$outageid.', event not found. Ignoring it...'); + } else { + $event->delete(); + } + } + + /** + * Generates an array with the calendar event data based on an outage object. + * @param outage $outage Outage to use as reference for the calendar event. + * @return mixed[] Calendar event data. + */ + private static function calendar_data(outage $outage) { + return [ + 'name' => $outage->get_title(), + 'description' => $outage->get_description(), + 'courseid' => 1, + 'groupid' => 0, + 'userid' => 0, + 'modulename' => '', + 'instance' => $outage->id, + 'eventtype' => 'auth_outage', + 'timestart' => $outage->starttime, + 'visible' => true, + 'timeduration' => $outage->get_duration_planned(), + ]; + } + + /** + * Finds the calendar event for an specific outage. + * @param int $outageid The outage id to find in the calendar. + * @return calendar_event|null The calendar event or null if not found. + */ + private static function calendar_load($outageid) { + global $DB; + + $event = $DB->get_record_select( + 'event', + "(eventtype = 'auth_outage' AND instance = :outageid)", + ['outageid' => $outageid], + 'id', + IGNORE_MISSING + ); + + return ($event === false) ? null : calendar_event::load($event->id); + } +} diff --git a/classes/dml/outagedb.php b/classes/dml/outagedb.php new file mode 100644 index 0000000..85b754d --- /dev/null +++ b/classes/dml/outagedb.php @@ -0,0 +1,346 @@ +. + +namespace auth_outage\dml; + +use auth_outage\calendar\calendar; +use auth_outage\event\outage_created; +use auth_outage\event\outage_deleted; +use auth_outage\event\outage_updated; +use auth_outage\local\outage; +use auth_outage\local\outagelib; +use coding_exception; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot.'/calendar/lib.php'); + +/** + * The DB Context to manipulate Outages. + * It will also commit changes to the calendar as you change outages. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class outagedb { + /** + * Private constructor, use static methods instead. + */ + private function __construct() { + } + + /** + * Gets all outage entries. + */ + public static function get_all() { + global $DB; + + $outages = []; + + $rs = $DB->get_recordset('auth_outage', null, 'starttime,stoptime,title'); + foreach ($rs as $r) { + $outages[] = new outage($r); + } + $rs->close(); + + return $outages; + } + + /** + * @param $id int Outage id to get. + * @return outage|null Returns the outage or null if not found. + * @throws coding_exception + */ + public static function get_by_id($id) { + global $DB; + + if (!is_int($id) || ($id <= 0)) { + throw new coding_exception('$id must be an positive int.', $id); + } + + $outage = $DB->get_record('auth_outage', ['id' => $id]); + if ($outage === false) { + return null; + } + + return new outage($outage); + } + + /** + * Saves an outage to the database. + * + * @param outage $outage Outage to save. + * @return int Outage ID. + */ + public static function save(outage $outage) { + global $DB, $USER; + + // Do not change the original object. + $outage = clone $outage; + + // Update control fields. + $outage->modifiedby = $USER->id; + $outage->lastmodified = time(); + + if ($outage->id === null) { + // If new outage, set its creator. + $outage->createdby = $USER->id; + // Then create it, log it and adjust its id. + $outage->id = $DB->insert_record('auth_outage', $outage, true); + outage_created::create( + ['objectid' => $outage->id, 'other' => (array)$outage] + )->trigger(); + // Create calendar entry. + calendar::calendar_create($outage); + } else { + // Remove the createdby field so it does not get updated. + unset($outage->createdby); + $DB->update_record('auth_outage', $outage); + // Log it. + outage_updated::create( + ['objectid' => $outage->id, 'other' => (array)$outage] + )->trigger(); + // Update calendar entry. + calendar::calendar_update($outage); + } + + // Trigger outages modified events. + outagelib::outages_modified(); + + // All done, return the id. + return $outage->id; + } + + /** + * Deletes an outage from the database. + * + * @param int $id Outage ID to delete + * @throws coding_exception + */ + public static function delete($id) { + global $DB; + + if (!is_int($id) || ($id <= 0)) { + throw new coding_exception('$id must be an int.', $id); + } + + // Log it. + $previous = $DB->get_record('auth_outage', ['id' => $id], '*', MUST_EXIST); + $event = outage_deleted::create(['objectid' => $id, 'other' => (array)$previous]); + $event->add_record_snapshot('auth_outage', $previous); + $event->trigger(); + + // Delete it and remove from calendar. + $DB->delete_records('auth_outage', ['id' => $id]); + calendar::calendar_delete($id); + + // Trigger events. + outagelib::outages_modified(); + } + + /** + * Gets the most important active outage, considering importance as: + * - Ongoing outages more important than outages in warning period. + * - Outages that start earlier are more important. + * - Outages that stop later are more important. + * @param int|null $time Timestamp considered to check for outages, null for current date/time. + * @return outage|null The outage or null if no active outages were found. + * @throws coding_exception + */ + public static function get_active($time = null) { + global $DB; + + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be null or a positive int.', $time); + } + + $select = ':datetime2 <= stoptime AND (finished IS NULL OR :datetime3 <= finished)'; // End condition. + $select = "(warntime <= :datetime1 AND (${select}))"; // Full select part. + $data = $DB->get_records_select( + 'auth_outage', + $select, + ['datetime1' => $time, 'datetime2' => $time, 'datetime3' => $time], + 'starttime ASC, stoptime DESC, title ASC', + '*', + 0, + 1 + ); + + // Not using $DB->get_record_select instead because there is no 'limit' parameter. + // Allowing multiple records still raises an internal error. + return (count($data) == 0) ? null : new outage(array_shift($data)); + } + + /** + * Gets all outages that have not ended yet. + * @param int|null $time Timestamp considered to check for outages, null for current date/time. + * @return outage[] An array of outages or an empty array if no unded outages were found. + * @throws coding_exception + */ + public static function get_all_unended($time = null) { + global $DB; + + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be null or a positive int.'); + } + + $outages = []; + + $rs = $DB->get_recordset_select( + 'auth_outage', + ':datetime1 < stoptime AND (finished IS NULL OR :datetime2 < finished)', + ['datetime1' => $time, 'datetime2' => $time], + 'starttime ASC, stoptime DESC, title ASC', + '*'); + foreach ($rs as $r) { + $outages[] = new outage($r); + } + $rs->close(); + + return $outages; + } + + /** + * Gets all ended outages. + * @param int|null $time Timestamp considered to check for outages, null for current date/time. + * @return outage[] An array of outages or an empty array if no ended outages found. + * @throws coding_exception + */ + public static function get_all_ended($time = null) { + global $DB; + + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be null or a positive int.', $time); + } + + $outages = []; + + $rs = $DB->get_recordset_select( + 'auth_outage', + ':datetime1 >= stoptime OR (finished IS NOT NULL AND :datetime2 >= finished)', + ['datetime1' => $time, 'datetime2' => $time], + 'stoptime DESC, starttime DESC, title ASC', + '*'); + foreach ($rs as $r) { + $outages[] = new outage($r); + } + $rs->close(); + + return $outages; + } + + /** + * Marks an outage as finished. + * @param int $id Outage id. + * @param int|null $time Timestamp to consider as finished date or null for current time. + * @throws coding_exception + */ + public static function finish($id, $time = null) { + if (is_null($time)) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be null or a positive int.', $time); + } + + $outage = self::get_by_id($id); + if (is_null($outage)) { + debugging('Cannot finish outage #'.$id.': outage not found.'); + return; + } + + if (!$outage->is_ongoing($time)) { + debugging('Cannot finish outage #'.$id.': outage not ongoing.'); + return; + } + + $outage->finished = $time; + self::save($outage); + } + + /** + * Gets the next outage which has not started yet. + * @param null $time Timestamp reference for current time. + * @return outage|null The outage or null if not found. + * @throws coding_exception + */ + public static function get_next_starting($time = null) { + global $DB; + + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be null or a positive int.', $time); + } + + $data = $DB->get_records_select( + 'auth_outage', + ':datetime <= starttime', + ['datetime' => $time], + 'starttime ASC', + '*', + 0, + 1 + ); + + // Not using $DB->get_record_select instead because there is no 'limit' parameter. + // Allowing multiple records still raises an internal error. + return (count($data) == 0) ? null : new outage(array_shift($data)); + } + + /** + * Gets the next outage which has not started yet and has the autostart flag set to true. + * @param null $time Timestamp reference for current time. + * @return outage|null The outage or null if not found. + * @throws coding_exception + */ + public static function get_next_autostarting($time = null) { + global $DB; + + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be null or a positive int.', $time); + } + + $data = $DB->get_records_select( + 'auth_outage', + '(:datetime <= starttime) AND (autostart = 1)', + ['datetime' => $time], + 'starttime ASC', + '*', + 0, + 1 + ); + + // Not using $DB->get_record_select instead because there is no 'limit' parameter. + // Allowing multiple records still raises an internal error. + return (count($data) == 0) ? null : new outage(array_shift($data)); + } +} diff --git a/classes/event/outage_created.php b/classes/event/outage_created.php index 28ef9d1..d83113c 100644 --- a/classes/event/outage_created.php +++ b/classes/event/outage_created.php @@ -16,6 +16,9 @@ namespace auth_outage\event; +use core\event\base; +use moodle_url; + defined('MOODLE_INTERNAL') || die(); /** @@ -23,10 +26,28 @@ defined('MOODLE_INTERNAL') || die(); * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class outage_created extends \core\event\base { +class outage_created extends base { + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "The user with the id '{$this->userid}' created a new outage title '{$this->other['title']}' ". + " with id '{$this->other['id']}'."; + } + + /** + * Returns relevant URL, override in subclasses. + * @return moodle_url + */ + public function get_url() { + return new moodle_url('/auth/outage/list.php#auth_outage_id_'.$this->other['id']); + } + /** * Init method. */ @@ -36,13 +57,4 @@ class outage_created extends \core\event\base { $this->data['edulevel'] = self::LEVEL_OTHER; $this->context = \context_system::instance(); } - - public function get_description() { - return "The user with the id '{$this->userid}' created a new outage title '{$this->other['title']}' " - . " with id '{$this->other['id']}'."; - } - - public function get_url() { - return new \moodle_url('/auth/outage/list.php#auth_outage_id_' . $this->other['id']); - } } diff --git a/classes/event/outage_deleted.php b/classes/event/outage_deleted.php index f73a516..5d3f273 100644 --- a/classes/event/outage_deleted.php +++ b/classes/event/outage_deleted.php @@ -16,6 +16,9 @@ namespace auth_outage\event; +use core\event\base; +use moodle_url; + defined('MOODLE_INTERNAL') || die(); /** @@ -23,10 +26,28 @@ defined('MOODLE_INTERNAL') || die(); * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class outage_deleted extends \core\event\base { +class outage_deleted extends base { + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "The user with the id '{$this->userid}' deleted the outage titled '{$this->other['title']}' ". + "with id '{$this->other['id']}'."; + } + + /** + * Returns relevant URL, override in subclasses. + * @return moodle_url + */ + public function get_url() { + return new moodle_url('/auth/outage/list.php#auth_outage_id_'.$this->other['id']); + } + /** * Init method. */ @@ -36,13 +57,4 @@ class outage_deleted extends \core\event\base { $this->data['edulevel'] = self::LEVEL_OTHER; $this->context = \context_system::instance(); } - - public function get_description() { - return "The user with the id '{$this->userid}' deleted the outage titled '{$this->other['title']}' " - . "with id '{$this->other['id']}'."; - } - - public function get_url() { - return new \moodle_url('/auth/outage/list.php#auth_outage_id_' . $this->other['id']); - } } diff --git a/classes/event/outage_updated.php b/classes/event/outage_updated.php index 59a846e..ed176fa 100644 --- a/classes/event/outage_updated.php +++ b/classes/event/outage_updated.php @@ -16,6 +16,9 @@ namespace auth_outage\event; +use core\event\base; +use moodle_url; + defined('MOODLE_INTERNAL') || die(); /** @@ -23,10 +26,28 @@ defined('MOODLE_INTERNAL') || die(); * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class outage_updated extends \core\event\base { +class outage_updated extends base { + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + return "The user with the id '{$this->userid}' updated the outage title '{$this->other['title']}' ". + "with id '{$this->other['id']}'."; + } + + /** + * Returns relevant URL, override in subclasses. + * @return moodle_url + */ + public function get_url() { + return new moodle_url('/auth/outage/list.php'); + } + /** * Init method. */ @@ -36,14 +57,4 @@ class outage_updated extends \core\event\base { $this->data['edulevel'] = self::LEVEL_OTHER; $this->context = \context_system::instance(); } - - - public function get_description() { - return "The user with the id '{$this->userid}' updated the outage title '{$this->other['title']}' " - . "with id '{$this->other['id']}'."; - } - - public function get_url() { - return new \moodle_url('/auth/outage/list.php'); - } } diff --git a/classes/forms/outage/delete.php b/classes/form/outage/delete.php similarity index 64% rename from classes/forms/outage/delete.php rename to classes/form/outage/delete.php index 26139fa..b7aea50 100644 --- a/classes/forms/outage/delete.php +++ b/classes/form/outage/delete.php @@ -14,20 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace auth_outage\forms\outage; +namespace auth_outage\form\outage; -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. -} +defined('MOODLE_INTERNAL') || die(); -require_once($CFG->libdir . '/formslib.php'); +require_once($CFG->libdir.'/formslib.php'); + +defined('MOODLE_INTERNAL') || die(); /** * Outage delete confirmation form. * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class delete extends \moodleform { @@ -41,20 +41,6 @@ class delete extends \moodleform { $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); - $this->add_action_buttons(true, get_string('remove')); + $this->add_action_buttons(true, get_string('delete')); } - - /** - * Validate the parts of the request form for this module - * - * @param array $data An array of form data - * @param array $files An array of form files - * @return array of error messages - */ - public function validation($data, $files) { - $errors = parent::validation($data, $files); - - return $errors; - } - -} \ No newline at end of file +} diff --git a/classes/form/outage/edit.php b/classes/form/outage/edit.php new file mode 100644 index 0000000..df00b48 --- /dev/null +++ b/classes/form/outage/edit.php @@ -0,0 +1,146 @@ +. + +namespace auth_outage\form\outage; + +use auth_outage\local\outage; +use coding_exception; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/formslib.php'); + +/** + * Outage form. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class edit extends \moodleform { + const TITLE_MAX_CHARS = 100; + + /** + * {@inheritDoc} + * @see moodleform::definition() + */ + public function definition() { + $mform = $this->_form; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $mform->addElement('duration', 'warningduration', get_string('warningduration', 'auth_outage')); + $mform->addHelpButton('warningduration', 'warningduration', 'auth_outage'); + + $mform->addElement('date_time_selector', 'starttime', get_string('starttime', 'auth_outage')); + $mform->addHelpButton('starttime', 'starttime', 'auth_outage'); + + $mform->addElement('duration', 'outageduration', get_string('outageduration', 'auth_outage')); + $mform->addHelpButton('outageduration', 'outageduration', 'auth_outage'); + + $mform->addElement( + 'text', + 'title', + get_string('title', 'auth_outage'), + 'maxlength="'.self::TITLE_MAX_CHARS.'" size="60"' + ); + $mform->setType('title', PARAM_TEXT); + $mform->addHelpButton('title', 'title', 'auth_outage'); + + $mform->addElement('editor', 'description', get_string('description', 'auth_outage')); + $mform->addHelpButton('description', 'description', 'auth_outage'); + + $mform->addElement('static', 'usagehints', '', get_string('textplaceholdershint', 'auth_outage')); + + $this->add_action_buttons(); + } + + /** + * Validate the parts of the request form for this module + * + * @param mixed[] $data An array of form data + * @param string[] $files An array of form files + * @return string[] of error messages + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + + if ($data['outageduration'] <= 0) { + $errors['outageduration'] = get_string('outagedurationerrorinvalid', 'auth_outage'); + } + if ($data['warningduration'] <= 0) { + $errors['warningduration'] = get_string('warningdurationerrorinvalid', 'auth_outage'); + } + + $titlelen = strlen(trim($data['title'])); + if ($titlelen == 0) { + $errors['title'] = get_string('titleerrorinvalid', 'auth_outage'); + } + if ($titlelen > self::TITLE_MAX_CHARS) { + $errors['title'] = get_string('titleerrortoolong', 'auth_outage', self::TITLE_MAX_CHARS); + } + + return $errors; + } + + /** + * Return submitted data if properly submitted or returns NULL if validation fails. + * @return outage submitted data; NULL if not valid or not submitted or cancelled + */ + public function get_data() { + // Fetch data and check if description is the correct format. + $data = parent::get_data(); + if (is_null($data)) { + return null; + } + if ($data->description['format'] != '1') { + debugging('Not implemented for format '.$data->description['format'], DEBUG_DEVELOPER); + return null; + } + // Return an outage. + return new outage([ + 'id' => ($data->id === 0) ? null : $data->id, + 'starttime' => $data->starttime, + 'stoptime' => $data->starttime + $data->outageduration, + 'warntime' => $data->starttime - $data->warningduration, + 'title' => $data->title, + 'description' => $data->description['text'], + ]); + } + + /** + * Load in existing outage as form defaults. + * @param outage $outage outage object with default values + * @throws coding_exception + */ + public function set_data($outage) { + // Cannot change method signature, check type. + if ($outage instanceof outage) { + $this->_form->setDefaults([ + 'id' => $outage->id, + 'starttime' => $outage->starttime, + 'outageduration' => $outage->get_duration_planned(), + 'warningduration' => $outage->get_warning_duration(), + 'title' => $outage->title, + 'description' => ['text' => $outage->description, 'format' => '1'], + ]); + } else { + throw new coding_exception('$outage must be an outage object.', $outage); + } + } +} diff --git a/classes/form/outage/finish.php b/classes/form/outage/finish.php new file mode 100644 index 0000000..76d2c4d --- /dev/null +++ b/classes/form/outage/finish.php @@ -0,0 +1,45 @@ +. + +namespace auth_outage\form\outage; + +use moodleform; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/formslib.php'); + +/** + * Outage finish confirmation form. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class finish extends moodleform { + /** + * Defines the form elements. + */ + public function definition() { + $mform = $this->_form; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $this->add_action_buttons(true, get_string('finish', 'auth_outage')); + } +} diff --git a/classes/forms/outage/edit.php b/classes/forms/outage/edit.php deleted file mode 100644 index 3419b48..0000000 --- a/classes/forms/outage/edit.php +++ /dev/null @@ -1,96 +0,0 @@ -. - -namespace auth_outage\forms\outage; - -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. -} - -require_once($CFG->libdir . '/formslib.php'); - -/** - * Outage form. - * - * @package auth_outage - * @author Daniel Thee Roperto - * @copyright Catalyst IT - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class edit extends \moodleform { - const TITLE_MAX_CHARS = 100; - - /** - * {@inheritDoc} - * @see moodleform::definition() - */ - public function definition() { - $mform = $this->_form; - - $mform->addElement('hidden', 'id'); - $mform->setType('id', PARAM_INT); - - $mform->addElement('date_time_selector', 'starttime', get_string('starttime', 'auth_outage')); - - $mform->addElement('date_time_selector', 'stoptime', get_string('stoptime', 'auth_outage')); - - $mform->addElement('duration', 'warningduration', get_string('warningduration', 'auth_outage')); - - $mform->addElement( - 'text', - 'title', - get_string('title', 'auth_outage'), - 'maxlength="'.self::TITLE_MAX_CHARS.'"' - ); - $mform->setType('title', PARAM_TEXT); - - $mform->addElement('editor', 'description', get_string('description', 'auth_outage')); - - $this->add_action_buttons(); - } - - /** - * Validate the parts of the request form for this module - * - * @param array $data An array of form data - * @param array $files An array of form files - * @return array of error messages - */ - public function validation($data, $files) { - $errors = parent::validation($data, $files); - - if ($data['starttime'] <= time()) { - $errors['starttime'] = get_string('starttimeerrornotinfuture', 'auth_outage'); - } - if ($data['stoptime'] <= $data['starttime']) { - $errors['stoptime'] = get_string('stoptimeerrornotafterstart', 'auth_outage'); - } - if ($data['warningduration'] <= 0) { - $errors['warningduration'] = get_string('warningdurationerrorinvalid', 'auth_outage'); - } - - $titlelen = strlen(trim($data['title'])); - if ($titlelen == 0) { - $errors['title'] = get_string('titleerrorinvalid', 'auth_outage'); - } - if ($titlelen > self::TITLE_MAX_CHARS) { - $errors['title'] = get_string('titleerrortoolong', 'auth_outage', self::TITLE_MAX_CHARS); - } - - return $errors; - } - -} \ No newline at end of file diff --git a/classes/local/cli/cli_exception.php b/classes/local/cli/cli_exception.php new file mode 100644 index 0000000..750e98a --- /dev/null +++ b/classes/local/cli/cli_exception.php @@ -0,0 +1,76 @@ +. + +namespace auth_outage\local\cli; + +use Exception; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Exception executing CLI. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cli_exception extends Exception { + /** + * Undefined error. + */ + const ERROR_UNDEFINED = 1; + + /** + * Unknow parameter. + */ + const ERROR_PARAMETER_UNKNOWN = 2; + + /** + * Invalid parameter usage. + */ + const ERROR_PARAMETER_INVALID = 3; + + /** + * Missing required parameter. + */ + const ERROR_PARAMETER_MISSING = 4; + + /** + * The informed outage cannot be used for that purpose. + */ + const ERROR_OUTAGE_INVALID = 5; + + /** + * The informed outage was not found. + */ + const ERROR_OUTAGE_NOT_FOUND = 6; + + /** + * The outage has changed before the completion of the command. + */ + const ERROR_OUTAGE_CHANGED = 7; + + /** + * cliexception constructor. + * @param string $message An explanation of the exception. + * @param int $code Exit code to be used. + * @param Exception|null $previous Another exception as reference. + */ + public function __construct($message, $code = 1, Exception $previous = null) { + parent::__construct('*ERROR* '.$message, $code, $previous = null); + } +} diff --git a/classes/local/cli/clibase.php b/classes/local/cli/clibase.php new file mode 100644 index 0000000..6d3cae5 --- /dev/null +++ b/classes/local/cli/clibase.php @@ -0,0 +1,136 @@ +. + +namespace auth_outage\local\cli; + +use coding_exception; +use core\session\manager; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage CLI base class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class clibase { + /** + * @var mixed[] Options passed as parameters to the CLI. + */ + protected $options; + + /** + * @var int The reference time to use when creating an outage. + */ + protected $time; + + /** + * clibase constructor. + * @param mixed[]|null $options The parameters to use or null to read from the command line. + * @throws cli_exception + */ + public function __construct(array $options = null) { + global $CFG; + require_once($CFG->libdir.'/clilib.php'); + + $this->become_admin_user(); + + if (is_null($options)) { + // Using Moodle CLI API to read the parameters. + list($options, $unrecognized) = cli_get_params($this->generate_options(), $this->generate_shortcuts()); + if ($unrecognized) { + $unrecognized = implode("\n ", $unrecognized); + throw new cli_exception(get_string('cliunknowoption', 'admin', $unrecognized), + cli_exception::ERROR_PARAMETER_UNKNOWN); + } + } else { + // If not using Moodle CLI API to read parameters, ensure all keys exist. + $default = $this->generate_options(); + foreach ($options as $k => $v) { + if (!array_key_exists($k, $default)) { + throw new cli_exception(get_string('cliunknowoption', 'admin', $k), cli_exception::ERROR_PARAMETER_UNKNOWN); + } + $default[$k] = $v; + } + $options = $default; + } + + $this->options = $options; + $this->time = time(); + } + + /** + * Sets the reference time for creating outages. + * @param int $time Timestamp for the reference time. + * @throws coding_exception + */ + public function set_referencetime($time) { + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be a positive int.', $time); + } + $this->time = $time; + } + + /** + * Generates all options (parameters) available for the CLI command. + * @return mixed[] Options. + */ + public abstract function generate_options(); + + /** + * Generate all short forms for the available options. + * @return string[] Short form options. + */ + public abstract function generate_shortcuts(); + + /** + * Executes the CLI script. + */ + public abstract function execute(); + + /** + * Change session to admin user. + */ + protected function become_admin_user() { + global $DB; + $user = $DB->get_record('user', ['id' => 2]); + unset($user->description); + unset($user->access); + unset($user->preference); + manager::init_empty_session(); + manager::set_user($user); + } + + /** + * Outputs a help message. + * @param string $cliname Name of CLI used in the language file. + */ + protected function show_help($cliname) { + $options = $this->generate_options(); + $shorts = array_flip($this->generate_shortcuts()); + + printf("%s\n\n", get_string('cli'.$cliname.'help', 'auth_outage')); + foreach (array_keys($options) as $long) { + $text = get_string('cli'.$cliname.'param'.$long, 'auth_outage'); + $short = isset($shorts[$long]) ? ('-'.$shorts[$long].',') : ''; + $long = '--'.$long; + printf(" %-4s %-20s %s\n", $short, $long, $text); + } + } +} diff --git a/classes/local/cli/create.php b/classes/local/cli/create.php new file mode 100644 index 0000000..075dc18 --- /dev/null +++ b/classes/local/cli/create.php @@ -0,0 +1,289 @@ +. + +namespace auth_outage\local\cli; + +use auth_outage\dml\outagedb; +use auth_outage\local\outage; +use coding_exception; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage CLI to create outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class create extends clibase { + /** + * @var mixed[] Defaults to use if given option is null. + */ + private $defaults; + + /** + * Generates all options (parameters) available for the CLI command. + * @return mixed[] Options. + */ + public function generate_options() { + // Do not provide some defaults, if cloning an outage we need to know which parameters were provided. + return [ + 'help' => false, + 'clone' => null, + 'autostart' => null, + 'warn' => null, + 'start' => null, + 'duration' => null, + 'title' => null, + 'description' => null, + 'onlyid' => false, + 'block' => false, + ]; + } + + /** + * Generate all short forms for the available options. + * @return string[] Short form options. + */ + public function generate_shortcuts() { + return [ + 'a' => 'autostart', + 'b' => 'block', + 'c' => 'clone', + 'd' => 'duration', + 'e' => 'description', + 'h' => 'help', + 's' => 'start', + 't' => 'title', + 'w' => 'warn', + ]; + } + + /** + * Sets the default values for options. + * @param mixed[] $defaults Defaults. + * @throws coding_exception + */ + public function set_defaults(array $defaults) { + $missing = $this->generate_options(); + + // Check if any extra parameter was given. + foreach (array_keys($defaults) as $key) { + if (!array_key_exists($key, $missing)) { + throw new coding_exception('$default['.$key.'] is not valid.'); + } + unset($missing[$key]); + } + + // Check if any required parameter is missing. + foreach (array_keys($missing) as $k => $v) { + if (is_null($v)) { + throw new coding_exception('$default[] missing: '.$k); + } + } + + $this->defaults = $defaults; + } + + /** + * Executes the CLI. + */ + public function execute() { + // Help always overrides any other parameter. + if ($this->options['help']) { + $this->show_help('create'); + return; + } + + // If not help mode, 'start' is required and cannot use default. + if (is_null($this->options['start'])) { + throw new cli_exception(get_string('clierrormissingparamaters', 'auth_outage'), + cli_exception::ERROR_PARAMETER_MISSING); + } + + // If cloning, set defaults to outage being cloned. + if (!is_null($this->options['clone'])) { + $this->clone_defaults(); + } + + // Merge provided parameters with defaults then create outage. + $options = $this->merge_options(); + $id = $this->create_outage($options); + + if ($options['block']) { + $block = new waitforit(['outageid' => $id]); + $block->execute(); + } + } + + /** + * Merges provided options with defaults. + * @return mixed[] Parameters to use. + * @throws cli_exception + */ + private function merge_options() { + $options = $this->options; + + // Merge with defaults. + if (!is_null($this->defaults)) { + foreach ($options as $k => $v) { + if (is_null($v) && array_key_exists($k, $this->defaults)) { + $options[$k] = $this->defaults[$k]; + } + } + } + + return $this->merge_options_check_parameters($options); + } + + /** + * Creates an outages based on the provided options. + * @param mixed[] $options Options used to create the outage. + * @return int Id of the new outage. + */ + private function create_outage(array $options) { + // We need to become an admin to avoid permission problems. + $this->become_admin_user(); + + // Create the outage. + $start = $this->time + $options['start']; + $outage = new outage([ + 'autostart' => $options['autostart'], + 'warntime' => $start - $options['warn'], + 'starttime' => $start, + 'stoptime' => $start + $options['duration'], + 'title' => $options['title'], + 'description' => $options['description'], + ]); + $id = outagedb::save($outage); + + // All done! + if ($options['onlyid']) { + printf("%d\n", $id); + } else { + printf("%s\n", get_string('clioutagecreated', 'auth_outage', ['id' => $id])); + } + + return $id; + } + + private function clone_defaults() { + $id = $this->options['clone']; + if (!is_number($id) || ($id <= 0)) { + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'clone']), + cli_exception::ERROR_PARAMETER_INVALID); + } + + $outage = outagedb::get_by_id((int)$id); + $this->set_defaults([ + 'autostart' => $outage->autostart, + 'warn' => $outage->get_warning_duration(), + 'duration' => $outage->get_duration_planned(), + 'title' => $outage->title, + 'description' => $outage->description, + ]); + } + + /** + * Check parameters converting their type as needed. + * @param mixed $options Input options. + * @return mixed Output options. + * @throws cli_exception + */ + private function merge_options_check_parameters(array $options) { + foreach (['start', 'warn', 'duration'] as $param) { + $options[$param] = $this->merge_options_check_parameters_int_nonnegative($options[$param], $param); + } + + foreach (['title', 'description'] as $param) { + $options[$param] = $this->merge_options_check_parameters_string_nonempty($options[$param], $param); + } + + foreach (['autostart'] as $param) { + $options[$param] = $this->merge_options_check_parameters_bool($options[$param], $param); + } + + return $options; + } + + /** + * Ensures the given option is or can be converted to a non-negative int. + * @param mixed $option The parameter to check. + * @param string $param Name of that parameter. + * @return int The converted parameter. + * @throws cli_exception + */ + private function merge_options_check_parameters_int_nonnegative($option, $param) { + if (!is_number($option)) { + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]), + cli_exception::ERROR_PARAMETER_INVALID); + } + $option = (int)$option; + if ($option < 0) { + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]), + cli_exception::ERROR_PARAMETER_INVALID); + } + return $option; + } + + /** + * Ensures the given option is or can be converted to a non-empty string. + * @param mixed $option The parameter to check. + * @param string $param Name of that parameter. + * @return string The converted parameter. + * @throws cli_exception + */ + + private function merge_options_check_parameters_string_nonempty($option, $param) { + if (!is_string($option)) { + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]), + cli_exception::ERROR_PARAMETER_INVALID); + } + $option = trim($option); + if (strlen($option) == 0) { + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]), + cli_exception::ERROR_PARAMETER_INVALID); + } + return $option; + } + + /** + * Ensures the given option is or can be converted to a bool. + * @param mixed $option The parameter to check. + * @param string $param Name of that parameter. + * @return bool The converted parameter. + * @throws cli_exception + */ + private function merge_options_check_parameters_bool($option, $param) { + if (is_bool($option)) { + return $option; + } + + if (is_string($option)) { + $option = strtoupper($option); + if (in_array($option, ['0', 'FALSE', 'NO', 'N'])) { + return false; + } + if (in_array($option, ['1', 'TRUE', 'YES', 'Y'])) { + return true; + } + } + + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param])); + } +} diff --git a/classes/local/cli/finish.php b/classes/local/cli/finish.php new file mode 100644 index 0000000..d3675d9 --- /dev/null +++ b/classes/local/cli/finish.php @@ -0,0 +1,108 @@ +. + +namespace auth_outage\local\cli; + +use auth_outage\dml\outagedb; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage CLI to finish an outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class finish extends clibase { + /** + * Generates all options (parameters) available for the CLI command. + * @return mixed[] Options. + */ + public function generate_options() { + // Do not provide some defaults, if cloning an outage we need to know which parameters were provided. + $options = [ + 'help' => false, + 'outageid' => null, + 'active' => false, + ]; + return $options; + } + + /** + * Generate all short forms for the available options. + * @return string[] Short form options. + */ + public function generate_shortcuts() { + return [ + 'h' => 'help', + 'id' => 'outageid', + 'a' => 'active', + ]; + } + + /** + * Executes the CLI. + */ + public function execute() { + // Help always overrides any other parameter. + if ($this->options['help']) { + $this->show_help('finish'); + return; + } + + // Requires outageid or active but not both at the same time. + $byid = !is_null($this->options['outageid']); + $byactive = $this->options['active']; + if ($byid == $byactive) { + throw new cli_exception(get_string('cliwaitforiterroridxoractive', 'auth_outage'), + cli_exception::ERROR_PARAMETER_MISSING); + } + + $outage = $this->get_outage(); + if (!$outage->is_ongoing()) { + throw new cli_exception(get_string('clifinishnotongoing', 'auth_outage'), cli_exception::ERROR_OUTAGE_INVALID); + } + + outagedb::finish($outage->id, $this->time); + } + + /** + * Gets the outage to finish. + * @return outage|null The outage to wait for. + * @throws cli_exception + */ + private function get_outage() { + if ($this->options['active']) { + $outage = outagedb::get_active(); + } else { + $id = $this->options['outageid']; + if (!is_number($id) || ($id <= 0)) { + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'outageid']), + cli_exception::ERROR_PARAMETER_INVALID); + } + $outage = outagedb::get_by_id((int)$id); + } + + if (is_null($outage)) { + throw new cli_exception(get_string('clierroroutagenotfound', 'auth_outage'), cli_exception::ERROR_OUTAGE_NOT_FOUND); + } + + return $outage; + } +} diff --git a/classes/local/cli/waitforit.php b/classes/local/cli/waitforit.php new file mode 100644 index 0000000..8f2cc2a --- /dev/null +++ b/classes/local/cli/waitforit.php @@ -0,0 +1,186 @@ +. + +namespace auth_outage\local\cli; + +use auth_outage\dml\outagedb; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage CLI to wait for an outage to start. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class waitforit extends clibase { + /** + * Default value if --sleep no provided. + */ + const DEFAULT_SLEEP_SECONDS = 300; + + /** + * @var callable Alternative callback for sleeping thread, must return the new reference timestamp. + */ + private $sleepcallback = null; + + /** + * Generates all options (parameters) available for the CLI command. + * @return mixed[] Options. + */ + public function generate_options() { + // Do not provide some defaults, if cloning an outage we need to know which parameters were provided. + $options = [ + 'help' => false, + 'outageid' => null, + 'active' => false, + 'verbose' => false, + 'sleep' => self::DEFAULT_SLEEP_SECONDS, + ]; + return $options; + } + + /** + * Generate all short forms for the available options. + * @return string[] Short form options. + */ + public function generate_shortcuts() { + return [ + 'h' => 'help', + 'id' => 'outageid', + 'a' => 'active', + 'v' => 'verbose', + 's' => 'sleep', + ]; + } + + /** + * Sets a callback to be used instead of the sleep method. + * @param callable $callback Callback function. + */ + public function set_sleepcallback(callable $callback) { + $this->sleepcallback = $callback; + } + + /** + * Executes the CLI. + */ + public function execute() { + // Help always overrides any other parameter. + if ($this->options['help']) { + $this->show_help('waitforit'); + return; + } + + // Requires outageid or active but not both at the same time. + $byid = !is_null($this->options['outageid']); + $byactive = $this->options['active']; + if ($byid == $byactive) { + throw new cli_exception(get_string('cliwaitforiterroridxoractive', 'auth_outage'), + cli_exception::ERROR_PARAMETER_INVALID); + } + + $this->verbose('Verbose mode activated.'); + + $outage = $this->get_outage(); + + while ($sleep = $this->wait_for_outage_to_start($outage)) { + if (is_null($this->sleepcallback)) { + $this->verbose('Sleeping for '.$sleep.' second(s).'); + sleep($sleep); + $this->time = time(); + } else { + $this->verbose('Calling callback to sleep '.$sleep.' second(s).'); + $callback = $this->sleepcallback; + $this->time = $callback($sleep); + } + } + } + + /** + * Shows a message if in verbose mode. + * @param string $message Message. + */ + private function verbose($message) { + if (!$this->options['verbose']) { + return; + } + + $time = strftime('%F %T %Z'); + printf("[%s] %s\n", $time, $message); + } + + /** + * Gets the outage to wait for. + * @return outage|null The outage to wait for. + * @throws cli_exception + */ + private function get_outage() { + if ($this->options['active']) { + $this->verbose('Querying database for active outage...'); + $outage = outagedb::get_active(); + } else { + $id = $this->options['outageid']; + if (!is_number($id) || ($id <= 0)) { + throw new cli_exception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'outageid']), + cli_exception::ERROR_PARAMETER_INVALID); + } + $this->verbose('Querying database for outage #'.$id.'...'); + $outage = outagedb::get_by_id((int)$id); + } + + if (is_null($outage)) { + throw new cli_exception(get_string('clierroroutagenotfound', 'auth_outage'), cli_exception::ERROR_OUTAGE_NOT_FOUND); + } + + $this->verbose('Found outage #'.$outage->id.': '.$outage->get_title()); + return $outage; + } + + /** + * Calculate how many seconds to wait for the outage to start. + * @param outage $outage Outage to consider. + * @return int Seconds until it stars. + * @throws cli_exception + */ + private function wait_for_outage_to_start(outage $outage) { + $this->verbose('Checking outage status...'); + // Outage should not change while waiting to start. + if (outagedb::get_by_id($outage->id) != $outage) { + throw new cli_exception(get_string('clierroroutagechanged', 'auth_outage'), cli_exception::ERROR_OUTAGE_CHANGED); + } + // Outage cannot have already ended. + if ($outage->has_ended($this->time)) { + throw new cli_exception(get_string('clierroroutageended', 'auth_outage'), cli_exception::ERROR_OUTAGE_INVALID); + } + // If outage has started, do not wait. + if ($outage->is_ongoing($this->time)) { + printf("%s\n", get_string('cliwaitforitoutagestarted', 'auth_outage')); + return 0; + } + // Outage nas not started yet. + $countdown = $outage->starttime - $this->time; + printf("%s\n", get_string( + 'cliwaitforitoutagestartingin', + 'auth_outage', + ['countdown' => format_time($countdown)] + )); + return min($countdown, $this->options['sleep']); + } +} diff --git a/classes/local/controllers/infopage.php b/classes/local/controllers/infopage.php new file mode 100644 index 0000000..aed01bd --- /dev/null +++ b/classes/local/controllers/infopage.php @@ -0,0 +1,235 @@ +. + +namespace auth_outage\local\controllers; + +use auth_outage\dml\outagedb; +use auth_outage\local\outage; +use auth_outage\local\outagelib; +use auth_outage\output\renderer; +use coding_exception; +use context_system; +use file_exception; +use invalid_state_exception; +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Controller for the info page. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class infopage { + /** + * @var outage|null The outage to display or null if none found. + */ + private $outage; + + /** + * @var bool Flags if a static version of this page should be displayed (maintenance mode). + */ + private $static; + + /** + * infopage_controller constructor. + * @param mixed[]|null $params Parameters to use or null to get from Moodle API (request). + */ + public function __construct(array $params = null) { + if (is_null($params)) { + $params = [ + 'id' => optional_param('id', null, PARAM_INT), + 'static' => optional_param('static', false, PARAM_BOOL), + 'outage' => null, + ]; + } else { + $defaults = [ + 'id' => null, + 'outage' => null, + 'static' => false, + ]; + $params = array_merge($defaults, $params); + } + + $this->set_parameters($params); + } + + /** + * Given the HTML code for the static page, find the outage id for that page. + * @param string $html Static info page HTML. + * @return int|null Outage id or null if not found. + */ + public static function find_outageid_from_infopage($html) { + if (!is_string($html)) { + throw new coding_exception('$html must be a string.', $html); + } + + $output = []; + if (preg_match('/data-outage-id="(?P\d+)"/', $html, $output)) { + return (int)$output['id']; + } + return null; + } + + /** + * Saves a static info page for the given outage. + * @param outage $outage Outage to generate the info page. + * @param string $file File to save the static info page. + * @throws coding_exception + * @throws file_exception + * @throws invalid_state_exception + */ + public static function save_static_page(outage $outage, $file) { + if (!is_string($file)) { + throw new coding_exception('$file is not a string.', $file); + } + + $info = new infopage(['outage' => $outage, 'static' => true]); + $html = $info->get_output(); + + // Sanity check before writing/overwriting old file. + if (!is_string($html) || ($html == '') || (html_to_text($html) == '')) { + throw new invalid_state_exception('Sanity check failed. Invalid contents on $html.'); + } + + $dir = dirname($file); + if (!file_exists($dir) || !is_dir($dir)) { + throw new file_exception('Directory must exists: '.$dir); + } + file_put_contents($file, $html); + } + + /** + * Updates the static info page by (re)creating or deleting it as needed. + * @param null $file + */ + public static function update_static_page($file = null) { + if (is_null($file)) { + $file = self::get_defaulttemplatefile(); + } + if (!is_string($file)) { + throw new coding_exception('$file is not a string.', $file); + } + + $outage = outagedb::get_next_starting(); + if (is_null($outage)) { + if (file_exists($file)) { + if (is_file($file) && is_writable($file)) { + unlink($file); + } else { + throw new file_exception('Cannot remove: '.$file); + } + } + } else { + self::save_static_page($outage, $file); + } + } + + /** + * @return string The default template file to use for static info page. + */ + public static function get_defaulttemplatefile() { + global $CFG; + return $CFG->dataroot.'/climaintenance.template.html'; + } + + /** + * Generates and returns the HTML for the info page. + * @return string HTML for the info page. + */ + public function get_output() { + ob_start(); + try { + // TODO what if redirection occurs here? + $this->output(); + return ob_get_contents(); + } finally { + ob_end_clean(); + } + } + + /** + * Checks if this page should have admin options, taking in consideration it should happen if generating a static page. + * @return bool True if it should display admin options. + */ + public function has_admin_options() { + return (!$this->static && is_siteadmin()); + } + + /** + * Generates and outputs the HTML for the info page. + * @uses redirect + */ + public function output() { + global $PAGE, $CFG, $OUTPUT; + + if (is_null($this->outage)) { + if ($this->static) { + throw new coding_exception('Cannot render a static info page without an outage.'); + } else { + redirect(new moodle_url('/')); + } + } + + $viewbag = [ + 'admin' => is_siteadmin(), + 'outage' => $this->outage, + ]; + + $PAGE->set_context(context_system::instance()); + if ($this->static) { + $viewbag['admin'] = false; + renderer::get()->output_view('info/static.php', $viewbag); + } else { + $PAGE->set_title($this->outage->get_title()); + $PAGE->set_heading($this->outage->get_title()); + $PAGE->set_url(new moodle_url('/auth/outage/info.php')); + + // No hooks injecting into this page, do it manually. + outagelib::inject(); + + echo $OUTPUT->header(); + require($CFG->dirroot.'/auth/outage/views/info/content.php'); + echo $OUTPUT->footer(); + } + } + + /** + * Adjusts the fields according to the given parameters. + * @param mixed[] $params + */ + private function set_parameters(array $params) { + if (!is_null($params['outage']) && !($params['outage'] instanceof outage)) { + throw new coding_exception('Provided outage is not a valid outage object.', $params['outage']); + } + + if (!is_null($params['id']) && !is_null($params['outage']) && ($params['id'] !== $params['outage']->id)) { + throw new coding_exception('Provided id and outage->id do not match.', $params); + } + + if (is_null($params['id']) && is_null($params['outage'])) { + $params['outage'] = outagedb::get_active(); + } else if (is_null($params['outage'])) { + $params['outage'] = outagedb::get_by_id($params['id']); + } + + $this->outage = $params['outage']; + $this->static = (bool)$params['static']; + } +} diff --git a/classes/local/outage.php b/classes/local/outage.php new file mode 100644 index 0000000..854ca37 --- /dev/null +++ b/classes/local/outage.php @@ -0,0 +1,280 @@ +. + +namespace auth_outage\local; + +use coding_exception; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage class with all information about one specific outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class outage { + /** + * Outage is before warning period. + */ + const STAGE_WAITING = 'waiting'; + + /** + * Outage not started but in warning period. + */ + const STAGE_WARNING = 'warning'; + + /** + * Outage ongoing, it has passed the warning period. + */ + const STAGE_ONGOING = 'ongoing'; + + /** + * Outage finished, it is after the marked finished time. + */ + const STAGE_FINISHED = 'finished'; + + /** + * Outage stopped, it is after the stop time and not marked as finished. + */ + const STAGE_STOPPED = 'stopped'; + + /** + * @var int|null Outage ID (auto generated by the DB). + */ + public $id = null; + + /** + * @var bool|null Maintenance mode auto start flag. + */ + public $autostart = null; + + /** + * @var int|null Start Time timestamp. + */ + public $starttime = null; + + /** + * @var int|null Stop Time timestamp. + */ + public $stoptime = null; + + /** + * @var int|null Warning start timestamp. + */ + public $warntime = null; + + /** + * @var int|null Finished timestamp, null if not marked as finished yet. + */ + public $finished = null; + + /** + * @var string|null Short description of the outage (no HTML). + */ + public $title = null; + + /** + * @var string|null Description of the outage (some HTML allowed). + */ + public $description = null; + + /** + * @var int|null Moodle User Id that created this outage. + */ + public $createdby = null; + + /** + * @var int|null Moodle User Id that last modified this outage. + */ + public $modifiedby = null; + + /** + * @var int|null Timestamp of when this outage was last modified. + */ + public $lastmodified = null; + + /** + * outage constructor. + * @param stdClass|mixed[]|null The data for the outage. + * @throws coding_exception + */ + public function __construct($data = null) { + if (is_null($data)) { + return; + } + if (is_object($data)) { + $data = (array)$data; + } + if (!is_array($data)) { + throw new coding_exception('$data is not an object, an array or null.', $data); + } + + // Load data from array. + foreach ($data as $k => $v) { + if (property_exists(self::class, $k)) { + $this->$k = $v; + } + } + $this->adjust_field_types(); + } + + /** + * Gets at which stage is this outage. + * @param int|null $time Null to check the current stage or a timestamp to check for another time. + * @return int Stage, compare with STAGE_* constants. + * @throws coding_exception + */ + public function get_stage($time = null) { + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time must be an positive int.', $time); + } + + if (!is_null($this->finished) && ($time >= $this->finished)) { + return self::STAGE_FINISHED; + } + if ($time >= $this->stoptime) { + return self::STAGE_STOPPED; + } + if ($time < $this->warntime) { + return self::STAGE_WAITING; + } + if ($time < $this->starttime) { + return self::STAGE_WARNING; + } + return self::STAGE_ONGOING; + } + + /** + * Checks if the outage is active (in warning period or ongoing). + * @param int|null $time Null to check if the outage is active now or another time to use as reference. + * @return bool True if outage is ongoing or during the warning period. + */ + public function is_active($time = null) { + switch ($this->get_stage($time)) { + case self::STAGE_WARNING: + case self::STAGE_ONGOING: + return true; + default: + return false; + } + } + + /** + * Checks if the outage is happening. + * @param int|null $time Null to check if the outage is happening now or another time to use as reference. + * @return bool True if outage has started but not yet stopped. False otherwise including if in warning period. + */ + public function is_ongoing($time = null) { + return ($this->get_stage($time) == self::STAGE_ONGOING); + } + + /** + * Checks if the outage has ended (either marked as finished or after stop time). + * @param int|null $time Null to check if the outage has already ended or another time to use as reference. + * @return bool True if outage has been marked as finished after the provided time or it has already stopped. + */ + public function has_ended($time = null) { + switch ($this->get_stage($time)) { + case self::STAGE_FINISHED: + case self::STAGE_STOPPED: + return true; + default: + return false; + } + } + + /** + * Get the title with properly replaced placeholders such as {{start}} and {{stop}}. + * @return string Title. + */ + public function get_title() { + return $this->replace_placeholders($this->title); + } + + /** + * Gets the duration of the outage (start to actual finish, warning not included). + * @return int|null Duration in seconds or null if not finished. + */ + public function get_duration_actual() { + if (is_null($this->finished)) { + return null; + } + return $this->finished - $this->starttime; + } + + /** + * Gets the planned duration of the outage (start to planned stop, warning not included). + * @return int Duration in seconds. + */ + public function get_duration_planned() { + return $this->stoptime - $this->starttime; + } + + /** + * Get the description with properly replaced placeholders such as {{start}} and {{stop}}. + * @return string Description. + */ + public function get_description() { + return $this->replace_placeholders($this->description); + } + + /** + * Gets the warning duration from the outage (from warning time to start time). + * @return int Warning duration in seconds. + */ + public function get_warning_duration() { + return $this->starttime - $this->warntime; + } + + /** + * Returns the input string with all placeholders replaced. + * @param $str string Input string. + * @return string Output string. + */ + private function replace_placeholders($str) { + return str_replace( + [ + '{{start}}', + '{{stop}}', + '{{duration}}', + ], + [ + userdate($this->starttime, get_string('datetimeformat', 'auth_outage')), + userdate($this->stoptime, get_string('datetimeformat', 'auth_outage')), + format_time($this->get_duration_planned()), + ], + $str + ); + } + + private function adjust_field_types() { + // Adjust int fields. + $fs = ['createdby', 'id', 'lastmodified', 'modifiedby', 'starttime', 'stoptime', 'warntime', 'finished']; + foreach ($fs as $f) { + $this->$f = ($this->$f === null) ? null : (int)$this->$f; + } + + // Adjust bool fields. + $this->autostart = ($this->autostart === null) ? null : (bool)$this->autostart; + } +} diff --git a/classes/local/outagelib.php b/classes/local/outagelib.php new file mode 100644 index 0000000..67dbfb2 --- /dev/null +++ b/classes/local/outagelib.php @@ -0,0 +1,138 @@ +. + +namespace auth_outage\local; + +use auth_outage\dml\outagedb; +use auth_outage\local\controllers\infopage; +use auth_outage\output\renderer; +use Exception; +use moodle_url; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage related functions. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class outagelib { + private static $initialized = false; + + /** + * Initializes admin pages for outage. + * @return renderer The outage renderer for the page. + */ + public static function page_setup() { + global $PAGE; + admin_externalpage_setup('auth_outage_manage'); + $PAGE->set_url(new moodle_url('/auth/outage/manage.php')); + return renderer::get(); + } + + /** + * Will check for ongoing or warning outages and will attach the message bar as required. + */ + public static function inject() { + global $CFG; + + // Many hooks can call it, execute only once. + if (self::$initialized) { + return; + } + self::$initialized = true; + + // Ensure we do not kill the whole website in case of an error. + try { + // Check for a previewing outage, then for an active outage. + $previewid = optional_param('auth_outage_preview', null, PARAM_INT); + $time = time(); + if (is_null($previewid)) { + if (!$active = outagedb::get_active($time)) { + return; + } + $preview = false; + } else { + if (!$active = outagedb::get_by_id($previewid)) { + return; + } + // Delta is in seconds, setting the time our warning bar will consider relative to the outage start time. + $time = $active->starttime + optional_param('auth_outage_delta', 0, PARAM_INT); + if (!$active->is_active($time)) { + return; + } + $preview = true; + } + + // There is a previewing or active outage. + $CFG->additionalhtmltopofbody = renderer::get()->render_warningbar($active, $time, false, $preview). + $CFG->additionalhtmltopofbody; + } catch (Exception $e) { + debugging('Exception occured while injecting our code: '.$e->getMessage()); + debugging($e->getTraceAsString(), DEBUG_DEVELOPER); + } + } + + /** + * Creates a configuration object ensuring all parameters are set, + * loading defaults even if the plugin is not configured. + * @return stdClass Configuration object with all parameters set. + */ + public static function get_config() { + return (object)array_merge(self::get_config_defaults(), (array)get_config('auth_outage')); + } + + /** + * Creates the default configurations. If the plugin is not configured we should use those defaults. + * @return mixed[] Default configuration. + */ + public static function get_config_defaults() { + global $CFG; + + return [ + 'default_autostart' => false, + 'default_duration' => 60, + 'default_warning_duration' => 60, + 'default_warning_title' => get_string('defaultwarningtitlevalue', 'auth_outage'), + 'default_warning_description' => get_string('defaultwarningdescriptionvalue', 'auth_outage'), + 'css' => file_get_contents($CFG->dirroot.'/auth/outage/views/warningbar/warningbar.css'), + ]; + } + + /** + * Executed when outages are modified (created, updated or deleted). + */ + public static function outages_modified() { + infopage::update_static_page(); + self::update_maintenance_later(); + } + + /** + * Calls Moodle API - set_maintenance_later() to set when the next outage starts. + */ + private static function update_maintenance_later() { + $next = outagedb::get_next_autostarting(); + if (is_null($next)) { + unset_config('maintenance_later'); + } else { + set_config('maintenance_later', $next->starttime); + } + } +} diff --git a/classes/models/outage.php b/classes/models/outage.php deleted file mode 100644 index aca44be..0000000 --- a/classes/models/outage.php +++ /dev/null @@ -1,93 +0,0 @@ -. - -/** - * An Outage object with all information about one specific outage. - * - * @package auth_outage - * @author Daniel Thee Roperto - * @copyright Catalyst IT - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace auth_outage\models; - -use auth_outage\outagelib; - -class outage -{ - /** - * @var int Outage ID (auto generated by the DB). - */ - public $id = null; - - /** - * @var int Start Time timestamp. - */ - public $starttime = null; - - /** - * @var int Stop Time timestamp. - */ - public $stoptime = null; - - /** - * @var int Amount of minutes before outage starts to show the warning message. - */ - public $warningduration = null; - - /** - * @var string Short description of the outage (no HTML). - */ - public $title = null; - - /** - * @var string Description of the outage (some HTML allowed). - */ - public $description = null; - - /** - * @var int Moodle User Id that created this outage. - */ - public $createdby = null; - - /** - * @var int Moodle User Id that last modified this outage. - */ - public $modifiedby = null; - - /** - * @var int Timestamp of when this outage was last modified. - */ - public $lastmodified = null; - - /** - * outage constructor. - * @param object|array|null The data for the outage. - */ - public function __construct($data = null) { - if (is_null($data)) { - return; - } - - if (is_object($data) || is_array($data)) { - outagelib::data2object($data, $this); - return; - } - - throw new \InvalidArgumentException('$data must be null (default), an array or an object.'); - } -} \ No newline at end of file diff --git a/classes/outagedb.php b/classes/outagedb.php deleted file mode 100644 index 59e2821..0000000 --- a/classes/outagedb.php +++ /dev/null @@ -1,138 +0,0 @@ -. - -/** - * The DB Context to manipulate Outages. Singleton class. - * - * @package auth_outage - * @author Daniel Thee Roperto - * @copyright Catalyst IT - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace auth_outage; - -use auth_outage\models\outage; - -final class outagedb { - /** - * Private constructor, use static methods instead. - */ - private function __construct() { - } - - /** - * Gets all outage entries. - */ - public static function getall() { - global $DB; - - $outages = []; - - $rs = $DB->get_recordset('auth_outage', null, 'starttime,stoptime,title'); - foreach ($rs as $r) { - $outages[] = new outage($r); - } - $rs->close(); - - return $outages; - } - - /** - * @param $id int Outage id to get. - * @return outage|null Returns the outage or null if not found. - */ - public static function getbyid($id) { - global $DB; - - if (!is_int($id)) { - throw new \InvalidArgumentException('$id must be an int.'); - } - if ($id <= 0) { - throw new \InvalidArgumentException('$id must be positive.'); - } - - $outage = $DB->get_record('auth_outage', ['id' => $id]); - if ($outage === false) { - return null; - } - - return new outage($outage); - } - - /** - * Saves an outage to the database. - * - * @param outage $outage Outage to save. - * @return int Outage ID. - */ - public static function save(outage $outage) { - global $DB, $USER; - - // Do not change the original object. - $outage = clone $outage; - - // Update control fields. - $outage->modifiedby = $USER->id; - $outage->lastmodified = time(); - - if ($outage->id === null) { - // If new outage, set its creator. - $outage->createdby = $USER->id; - // Then create it, log it and adjust its id. - $outage->id = $DB->insert_record('auth_outage', $outage, true); - \auth_outage\event\outage_created::create( - ['objectid' => $outage->id, 'other' => (array)$outage] - )->trigger(); - } else { - // Remove the createdby field so it does not get updated. - unset($outage->createdby); - $DB->update_record('auth_outage', $outage); - // Log it. - \auth_outage\event\outage_updated::create( - ['objectid' => $outage->id, 'other' => (array)$outage] - )->trigger(); - } - - // All done, return the id. - return $outage->id; - } - - /** - * Deletes an outage from the database. - * - * @param $id outage Outage ID to delete - * @throws InvalidArgumentException If ID is not valid. - */ - public static function delete($id) { - global $DB; - - if (!is_int($id)) { - throw new \InvalidArgumentException('$id must be an int.'); - } - if ($id <= 0) { - throw new \InvalidArgumentException('$id must be positive.'); - } - - // Log it. - $previous = $DB->get_record('auth_outage', ['id' => $id], '*', MUST_EXIST); - $event = \auth_outage\event\outage_deleted::create(['objectid' => $id, 'other' => (array)$previous]); - $event->add_record_snapshot('auth_outage', $previous); - $event->trigger(); - - $DB->delete_records('auth_outage', ['id' => $id]); - } -} \ No newline at end of file diff --git a/classes/outagelib.php b/classes/outagelib.php deleted file mode 100644 index 909ee8b..0000000 --- a/classes/outagelib.php +++ /dev/null @@ -1,88 +0,0 @@ -. - -namespace auth_outage; - -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. -} - -/** - * Outage related functions. - * - * @package auth_outage - * @author Daniel Thee Roperto - * @copyright Catalyst IT - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class outagelib -{ - /** - * Initializes admin pages for outage. - * - * @return \renderer_base - */ - public static function pagesetup() { - global $PAGE; - admin_externalpage_setup('auth_outage_manage'); - $PAGE->set_url(new \moodle_url('/auth/outage/list.php')); - return $PAGE->get_renderer('auth_outage'); - } - - /** - * Loads data from an object or array into another object. It ensures no new fields are created in the $obj. - * - * @param $data mixed An object or array. - * @param $obj object Destination object to write the properties. - */ - public static function data2object($data, $obj) { - if (is_object($data)) { - $data = get_object_vars($data); - } - if (!is_array($data)) { - throw new \InvalidArgumentException('$data must be an array or an object.'); - } - if (!is_object($obj)) { - throw new \InvalidArgumentException('$obj must be an object.'); - } - - foreach ($data as $k => $v) { - if (property_exists($obj, $k)) { - if (method_exists($obj, $k)) { - throw new \InvalidArgumentException('$obj has a method called ' . $k); - } - $obj->$k = $v; - } - } - } - - /** - * Parses data from the form ensuring it is valid for an outage object. - * - * @param $data stdClass The input data. - * @return stdClass The parsed data. - */ - public static function parseformdata(\stdClass $data) { - if ($data->description['format'] != '1') { - throw new \InvalidArgumentException('Not implemented for format ' . $data->description['format']); - } - if ($data->id === 0) { - $data->id = null; - } - $data->description = $data->description['text']; - return $data; - } -} \ No newline at end of file diff --git a/classes/output/manage/base_table.php b/classes/output/manage/base_table.php new file mode 100644 index 0000000..8a83ab5 --- /dev/null +++ b/classes/output/manage/base_table.php @@ -0,0 +1,131 @@ +. + +namespace auth_outage\output\manage; + +use auth_outage\local\outage; +use flexible_table; +use html_writer; +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/tablelib.php'); + +/** + * Manage outages table base. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class base_table extends flexible_table { + private static $autoid = 0; + + /** + * Constructor + * @param string|null $id to be used by the table, autogenerated if null. + */ + public function __construct($id = null) { + global $PAGE; + + $id = (is_null($id) ? self::$autoid++ : $id); + parent::__construct('auth_outage_manage_'.$id); + + $this->define_baseurl($PAGE->url); + $this->set_attribute('class', 'generaltable admintable'); + } + + /** + * Create the action buttons HTML code for a specific outage. + * @param outage $outage The outage to generate the buttons. + * @param bool $editdelete If it should display the edit and delete button. + * @return string The HTML code of the action buttons. + */ + protected function set_data_buttons(outage $outage, $editdelete) { + global $OUTPUT; + $buttons = ''; + + // View button. + $buttons .= html_writer::link( + new moodle_url('/auth/outage/info.php', ['id' => $outage->id]), + html_writer::empty_tag('img', [ + 'src' => $OUTPUT->pix_url('t/preview'), + 'alt' => get_string('view'), + 'class' => 'iconsmall', + + ]), + [ + 'title' => get_string('view'), + 'target' => '_blank', + ] + ); + + // Edit button if required. + if ($editdelete) { + $buttons .= html_writer::link( + new moodle_url('/auth/outage/edit.php', ['id' => $outage->id]), + html_writer::empty_tag('img', [ + 'src' => $OUTPUT->pix_url('t/edit'), + 'alt' => get_string('edit'), + 'class' => 'iconsmall', + ]), + ['title' => get_string('edit')] + ); + } + + // Clone button. + $buttons .= html_writer::link( + new moodle_url('/auth/outage/clone.php', ['id' => $outage->id]), + html_writer::empty_tag('img', [ + 'src' => $OUTPUT->pix_url('t/copy'), + 'alt' => get_string('clone', 'auth_outage'), + 'class' => 'iconsmall', + + ]), + ['title' => get_string('clone', 'auth_outage')] + ); + + // Finish button if ongoing. + if ($outage->is_ongoing()) { + $buttons .= html_writer::link( + new moodle_url('/auth/outage/finish.php', ['id' => $outage->id]), + html_writer::empty_tag('img', [ + 'src' => $OUTPUT->pix_url('t/check'), + 'alt' => get_string('finish', 'auth_outage'), + 'class' => 'iconsmall', + ]), + ['title' => get_string('finish', 'auth_outage')] + ); + } + + // Delete button if required. + if ($editdelete) { + $buttons .= html_writer::link( + new moodle_url('/auth/outage/delete.php', ['id' => $outage->id]), + html_writer::empty_tag('img', [ + 'src' => $OUTPUT->pix_url('t/delete'), + 'alt' => get_string('delete'), + 'class' => 'iconsmall', + ]), + ['title' => get_string('delete')] + ); + } + + return ''.$buttons.''; + } +} diff --git a/classes/output/manage/history_table.php b/classes/output/manage/history_table.php new file mode 100644 index 0000000..93a4760 --- /dev/null +++ b/classes/output/manage/history_table.php @@ -0,0 +1,73 @@ +. + +namespace auth_outage\output\manage; + +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/tablelib.php'); + +/** + * Manage outages table. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class history_table extends base_table { + /** + * Constructor + */ + public function __construct() { + parent::__construct(); + + $this->define_columns(['warning', 'starts', 'durationplanned', 'durationactual', 'title', 'actions']); + + $this->define_headers([ + get_string('tableheaderwarnbefore', 'auth_outage'), + get_string('tableheaderstarttime', 'auth_outage'), + get_string('tableheaderdurationplanned', 'auth_outage'), + get_string('tableheaderdurationactual', 'auth_outage'), + get_string('tableheadertitle', 'auth_outage'), + get_string('actions'), + ] + ); + + $this->setup(); + } + + /** + * Sets the data of the table. + * @param outage[] $outages An array with outage objects. + */ + public function set_data(array $outages) { + foreach ($outages as $outage) { + $finished = $outage->get_duration_actual(); + $finished = is_null($finished) ? '-' : format_time($finished); + $this->add_data([ + format_time($outage->get_warning_duration()), + userdate($outage->starttime, get_string('datetimeformat', 'auth_outage')), + format_time($outage->get_duration_planned()), + $finished, + $outage->get_title(), + $this->set_data_buttons($outage, false), + ]); + } + } +} diff --git a/classes/output/manage/planned_table.php b/classes/output/manage/planned_table.php new file mode 100644 index 0000000..84697fb --- /dev/null +++ b/classes/output/manage/planned_table.php @@ -0,0 +1,77 @@ +. + +namespace auth_outage\output\manage; + +use auth_outage\local\outage; +use html_writer; +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/tablelib.php'); + +/** + * Manage outages table. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class planned_table extends base_table { + /** + * Constructor + */ + public function __construct() { + parent::__construct(); + + $this->define_columns(['warning', 'starts', 'duration', 'title', 'actions']); + + $this->define_headers([ + get_string('tableheaderwarnbefore', 'auth_outage'), + get_string('tableheaderstarttime', 'auth_outage'), + get_string('tableheaderduration', 'auth_outage'), + get_string('tableheadertitle', 'auth_outage'), + get_string('actions'), + ] + ); + + $this->setup(); + } + + /** + * Sets the data of the table. + * @param outage[] $outages An array with outage objects. + */ + public function set_data(array $outages) { + foreach ($outages as $outage) { + $title = html_writer::link( + new moodle_url('/auth/outage/edit.php', ['id' => $outage->id]), + $outage->get_title(), + ['title' => get_string('edit')] + ); + + $this->add_data([ + format_time($outage->get_warning_duration()), + userdate($outage->starttime, get_string('datetimeformat', 'auth_outage')), + format_time($outage->get_duration_planned()), + $title, + $this->set_data_buttons($outage, true), + ]); + } + } +} diff --git a/classes/output/renderer.php b/classes/output/renderer.php new file mode 100644 index 0000000..9ff9e3f --- /dev/null +++ b/classes/output/renderer.php @@ -0,0 +1,263 @@ +. + +namespace auth_outage\output; + +use auth_outage\local\outage; +use auth_outage\output\manage\history_table; +use auth_outage\output\manage\planned_table; +use coding_exception; +use html_writer; +use moodle_url; +use plugin_renderer_base; + +defined('MOODLE_INTERNAL') || die(); + +/** + * auth_outage auth_outage_renderer + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends plugin_renderer_base { + /** + * Returns the outage renderer. + * @return renderer The outage renderer. + */ + public static function get() { + global $PAGE; + return $PAGE->get_renderer('auth_outage'); + } + + /** + * Outputs the view in a separate scope to avoid conflicts with variable names. + * @param string $view View PHP file. + * @param mixed[] $viewbag Values to be used in the view. + * @throws coding_exception + */ + public function output_view($view, $viewbag = []) { + global $CFG; + + $viewbag['viewfile'] = $view; + unset($view); + + require($CFG->dirroot.'/auth/outage/views/'.$viewbag['viewfile']); + } + + /** + * Renders the view in a separate scope to avoid conflicts with variable names. + * @param string $view View PHP file. + * @param mixed[] $viewbag Values to be used in the view. + * @return string The rendered view code. + */ + public function render_view($view, $viewbag = []) { + ob_start(); + $this->output_view($view, $viewbag); + $html = ob_get_contents(); + ob_end_clean(); + return $html; + } + + /** + * Renders the subtitle of the page. + * @param string $subtitlekey Key to be used and localized. + * @return string HTML for the subtitle. + * @throws coding_exception + */ + public function rendersubtitle($subtitlekey) { + if (!is_string($subtitlekey)) { + throw new coding_exception('$subtitlekeym is not a string.', $subtitlekey); + } + return html_writer::tag('h2', get_string($subtitlekey, 'auth_outage')); + } + + /** + * Renders a confirmation to delete an outage. + * @param outage $outage Outage to be deleted. + * @return string HTML for the page. + */ + public function renderdeleteconfirmation(outage $outage) { + return $this->rendersubtitle('outagedelete'). + html_writer::tag('p', get_string('outagedeletewarning', 'auth_outage')). + $this->renderoutage($outage, false); + } + + /** + * Renders a confirmation to finish an outage. + * @param outage $outage Outage to be finished. + * @return string HTML for the page. + */ + public function renderfinishconfirmation(outage $outage) { + return $this->rendersubtitle('outagefinish'). + html_writer::tag('p', get_string('outagefinishwarning', 'auth_outage')). + $this->renderoutage($outage, false); + } + + /** + * Outputs the HTML data listing all given outages. + * @param outage[] $future Outages to list as planned. + * @param outage[] $past Outages to list as history. + */ + public function renderoutagelist(array $future, array $past) { + global $OUTPUT; + + // Add 'add' button. + $url = new moodle_url('/auth/outage/new.php'); + $img = html_writer::empty_tag('img', + ['src' => $OUTPUT->pix_url('t/add'), 'alt' => get_string('create'), 'class' => 'iconsmall']); + echo html_writer::tag('p', + html_writer::link( + $url, + $img.' '.get_string('outagecreate', 'auth_outage'), + ['title' => get_string('delete')] + ) + ); + + echo $this->rendersubtitle('outageslistfuture'); + if (empty($future)) { + echo html_writer::tag('p', html_writer::tag('small', get_string('notfound', 'auth_outage'))); + } else { + $table = new planned_table(); + $table->set_data($future); + $table->finish_output(); + } + + echo $this->rendersubtitle('outageslistpast'); + if (empty($past)) { + echo html_writer::tag('p', html_writer::tag('small', get_string('notfound', 'auth_outage'))); + } else { + $table = new history_table(); + $table->set_data($past); + $table->finish_output(); + } + } + + /** + * Renders the warning bar. + * @param outage $outage The outage to show in the warning bar. + * @param int $time Timestamp to send to the outage bar in order to render the outage. + * @param bool $static If the warning bar is rendering in a static page. + * @param bool $preview If in preview mode the warning bar will not check if we are back online. + * @return string HTML of the warning bar. + * @throws coding_exception + * @SuppressWarnings("unused") because $viewbag is used inside require() + */ + public function render_warningbar(outage $outage, $time, $static, $preview) { + global $CFG; + if (!is_int($time) || ($time <= 0)) { + throw new coding_exception('$time is not an positive int or null.', $time); + } + if (!is_bool($static)) { + throw new coding_exception('$static is not a bool.'); + } + if (!is_bool($preview)) { + throw new coding_exception('$preview is not a bool.'); + } + + $viewbag = [ + 'time' => $time, + 'outage' => $outage, + 'static' => $static, + 'preview' => $preview, + ]; + + return $this->render_view('warningbar/warningbar.php', $viewbag); + } + + /** + * Returns the HTML for displaying and outage information. + * @param outage $outage Outage to display. + * @param bool $buttons If should display management buttons (edit, delete, etc). + * @return string The formatted HTML. + */ + private function renderoutage(outage $outage, $buttons) { + global $OUTPUT; + + if ($outage->createdby == 0) { + $created = get_string('na', 'auth_outage'); + } else { + $created = core_user::get_user($outage->createdby, 'firstname,lastname', MUST_EXIST); + $created = html_writer::link( + new moodle_url('/user/profile.php', ['id' => $outage->createdby]), + trim($created->firstname.' '.$created->lastname) + ); + } + + if ($outage->modifiedby == 0) { + $modified = get_string('na', 'auth_outage'); + } else { + $modified = core_user::get_user($outage->modifiedby, 'firstname,lastname', MUST_EXIST); + $modified = html_writer::link( + new moodle_url('/user/profile.php', ['id' => $outage->modifiedby]), + trim($modified->firstname.' '.$modified->lastname) + ); + } + + $url = new moodle_url('/auth/outage/edit.php', ['id' => $outage->id]); + $img = html_writer::empty_tag( + 'img', + ['src' => $OUTPUT->pix_url('t/edit'), 'alt' => get_string('edit'), 'class' => 'iconsmall'] + ); + $linkedit = html_writer::link($url, $img, ['title' => get_string('edit')]); + + $url = new moodle_url('/auth/outage/delete.php', ['id' => $outage->id]); + $img = html_writer::empty_tag( + 'img', + ['src' => $OUTPUT->pix_url('t/delete'), 'alt' => get_string('delete'), 'class' => 'iconsmall'] + ); + $linkdelete = html_writer::link($url, $img, ['title' => get_string('delete')]); + + $finished = $outage->finished; + if (is_null($finished)) { + $finished = get_string('na', 'auth_outage'); + } else { + $finished = userdate($finished, get_string('datetimeformat', 'auth_outage')); + } + + return html_writer::div( + html_writer::tag('blockquote', + html_writer::div(html_writer::tag('b', $outage->get_title(), ['data-id' => $outage->id])). + html_writer::div(html_writer::tag('i', $outage->get_description())). + html_writer::div( + html_writer::tag('b', get_string('tableheaderwarnbefore', 'auth_outage').': '). + format_time($outage->get_warning_duration()) + ). + html_writer::div( + html_writer::tag('b', get_string('tableheaderstarttime', 'auth_outage').': '). + userdate($outage->starttime, get_string('datetimeformat', 'auth_outage')) + ). + html_writer::div( + html_writer::tag('b', get_string('tableheaderdurationplanned', 'auth_outage').': '). + format_time($outage->get_duration_planned()) + ). + html_writer::div( + html_writer::tag('b', get_string('tableheaderdurationactual', 'auth_outage').': '). + $finished + ). + html_writer::div( + html_writer::tag('small', + 'Created by '.$created. + ', modified by '.$modified.' on '. + userdate($outage->lastmodified, get_string('datetimeformat', 'auth_outage')) + ) + ). + ($buttons ? html_writer::div($linkedit.$linkdelete) : '') + ) + ); + } +} diff --git a/classes/task/update_static_page.php b/classes/task/update_static_page.php new file mode 100644 index 0000000..bcb5cc4 --- /dev/null +++ b/classes/task/update_static_page.php @@ -0,0 +1,46 @@ +. + +namespace auth_outage\task; + +use auth_outage\local\controllers\infopage; +use core\task\scheduled_task; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Tasks information. + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class update_static_page extends scheduled_task { + /** + * Gets the name of this event. + * @return string Name of this event. + */ + public function get_name() { + return get_string('taskupdatestaticpage', 'auth_outage'); + } + + /** + * Executes the event. + */ + public function execute() { + infopage::update_static_page(); + } +} diff --git a/cli/create.php b/cli/create.php new file mode 100644 index 0000000..9bc1239 --- /dev/null +++ b/cli/create.php @@ -0,0 +1,49 @@ +. + +/** + * CLI for creating outages. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\local\cli\cli_exception; +use auth_outage\local\cli\create; +use auth_outage\local\outagelib; + +define('CLI_SCRIPT', true); +require_once(__DIR__.'/../../../config.php'); + +$cli = new create(); + +$config = outagelib::get_config(); +$cli->set_defaults([ + 'help' => false, + 'warn' => (int)($config->default_warning_duration), + 'start' => null, + 'duration' => (int)($config->default_duration), + 'title' => $config->default_warning_title, + 'description' => $config->default_warning_description, +]); + +try { + $cli->execute(); +} catch (cli_exception $e) { + cli_error($e->getMessage()); +} diff --git a/cli/finish.php b/cli/finish.php new file mode 100644 index 0000000..a840f57 --- /dev/null +++ b/cli/finish.php @@ -0,0 +1,39 @@ +. + +/** + * CLI for finishing an outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\local\cli\cli_exception; +use auth_outage\local\cli\finish; + +define('CLI_SCRIPT', true); +require_once(__DIR__.'/../../../config.php'); + +$cli = new finish(); + +try { + $cli->execute(); +} catch (cli_exception $e) { + cli_error($e->getMessage()); +} + diff --git a/cli/waitforit.php b/cli/waitforit.php new file mode 100644 index 0000000..fb8c0fa --- /dev/null +++ b/cli/waitforit.php @@ -0,0 +1,39 @@ +. + +/** + * CLI for waiting (blocking) until an outage starts. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\local\cli\cli_exception; +use auth_outage\local\cli\waitforit; + +define('CLI_SCRIPT', true); +require_once(__DIR__.'/../../../config.php'); + +$cli = new waitforit(); + +try { + $cli->execute(); +} catch (cli_exception $e) { + cli_error($e->getMessage()); +} + diff --git a/clone.php b/clone.php new file mode 100644 index 0000000..7552ded --- /dev/null +++ b/clone.php @@ -0,0 +1,59 @@ +. + +/** + * Clone outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\dml\outagedb; +use auth_outage\form\outage\edit; +use auth_outage\local\outagelib; + +require_once(__DIR__.'/../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir.'/formslib.php'); + +$renderer = outagelib::page_setup(); + +$mform = new edit(); + +if ($mform->is_cancelled()) { + redirect('/auth/outage/manage.php'); +} else if ($outage = $mform->get_data()) { + $id = outagedb::save($outage); + redirect('/auth/outage/manage.php#auth_outage_id_'.$id); +} + +$id = required_param('id', PARAM_INT); +$outage = outagedb::get_by_id($id); +if ($outage == null) { + throw new invalid_parameter_exception('Outage #'.$id.' not found.'); +} + +// Remove outage id to force creating a new one. +$outage->id = null; +$mform->set_data($outage); + +$PAGE->navbar->add($outage->get_title()); +echo $OUTPUT->header(); +echo $renderer->rendersubtitle('outageclone'); +$mform->display(); +echo $OUTPUT->footer(); diff --git a/db/install.xml b/db/install.xml index f375742..fd3696c 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -7,14 +7,16 @@ + + - + @@ -24,4 +26,4 @@
-
\ No newline at end of file + diff --git a/db/tasks.php b/db/tasks.php new file mode 100644 index 0000000..dd78933 --- /dev/null +++ b/db/tasks.php @@ -0,0 +1,36 @@ +. + +/** + * Tasks information. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$tasks = [ + [ + 'classname' => 'auth_outage\task\update_static_page', + 'blocking' => 0, + 'minute' => '23', + 'hour' => '*', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*', + ], +]; diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100644 index 0000000..c4aabe9 --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,51 @@ +. + +/** + * Outage plugin upgrade code + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); + +/** + * @param int $oldversion the version we are upgrading from + * @return bool result + * @SuppressWarnings("unused") + */ +function xmldb_auth_outage_upgrade($oldversion) { + global $DB; + $dbman = $DB->get_manager(); + + if ($oldversion < 2016092200) { + // Define field autostart to be added to auth_outage. + $table = new xmldb_table('auth_outage'); + $field = new xmldb_field('autostart', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'finished'); + + // Conditionally launch add field autostart. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Outage savepoint reached. + upgrade_plugin_savepoint(true, 2016092200, 'auth', 'outage'); + } + + return true; +} diff --git a/remove.php b/delete.php similarity index 71% rename from remove.php rename to delete.php index b5db3f0..93a8aa9 100644 --- a/remove.php +++ b/delete.php @@ -19,32 +19,32 @@ * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -use auth_outage\models\outage; -use auth_outage\outagedb; -use auth_outage\outagelib; +use auth_outage\dml\outagedb; +use auth_outage\form\outage\delete; +use auth_outage\local\outagelib; -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/formslib.php'); +require_once(__DIR__.'/../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir.'/formslib.php'); -$renderer = outagelib::pagesetup(); +$renderer = outagelib::page_setup(); -$mform = new \auth_outage\forms\outage\delete(); +$mform = new delete(); if ($mform->is_cancelled()) { - redirect('/auth/outage/list.php'); + redirect('/auth/outage/manage.php'); } else if ($fromform = $mform->get_data()) { outagedb::delete($fromform->id); - redirect('/auth/outage/list.php'); + redirect('/auth/outage/manage.php'); } $id = required_param('id', PARAM_INT); -$outage = outagedb::getbyid($id); +$outage = outagedb::get_by_id($id); if ($outage == null) { - throw new invalid_parameter_exception('Outage #' . $id . ' not found.'); + throw new invalid_parameter_exception('Outage #'.$id.' not found.'); } $dataid = new stdClass(); diff --git a/docs/2016-09-06_screenshot.png b/docs/2016-09-06_screenshot.png new file mode 100644 index 0000000..1ce7244 Binary files /dev/null and b/docs/2016-09-06_screenshot.png differ diff --git a/create.php b/edit.php similarity index 57% rename from create.php rename to edit.php index 329a938..b5a50e5 100644 --- a/create.php +++ b/edit.php @@ -19,33 +19,38 @@ * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -use auth_outage\models\outage; -use auth_outage\outagedb; -use auth_outage\outagelib; +use auth_outage\dml\outagedb; +use auth_outage\form\outage\edit; +use auth_outage\local\outagelib; -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/formslib.php'); +require_once(__DIR__.'/../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir.'/formslib.php'); -outagelib::pagesetup(); +$renderer = outagelib::page_setup(); + +$mform = new edit(); -$mform = new \auth_outage\forms\outage\edit(); if ($mform->is_cancelled()) { - redirect('/auth/outage/list.php'); -} else if ($fromform = $mform->get_data()) { - $fromform = outagelib::parseformdata($fromform); - $outage = new outage($fromform); + redirect('/auth/outage/manage.php'); +} else if ($outage = $mform->get_data()) { $id = outagedb::save($outage); - redirect('/auth/outage/list.php#auth_outage_id_' . $id); + redirect('/auth/outage/manage.php#auth_outage_id_'.$id); } -$PAGE->navbar->add(get_string('outagecreate', 'auth_outage')); +$id = required_param('id', PARAM_INT); +$outage = outagedb::get_by_id($id); +if ($outage == null) { + throw new invalid_parameter_exception('Outage #'.$id.' not found.'); +} +$mform->set_data($outage); + +$PAGE->navbar->add($outage->get_title()); echo $OUTPUT->header(); - +echo $renderer->rendersubtitle('outageedit'); $mform->display(); - echo $OUTPUT->footer(); diff --git a/change.php b/finish.php similarity index 54% rename from change.php rename to finish.php index af70983..84b1a3e 100644 --- a/change.php +++ b/finish.php @@ -15,46 +15,46 @@ // along with Moodle. If not, see . /** - * Create new outage. + * Mark an outage as finished. * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -use auth_outage\models\outage; -use auth_outage\outagedb; -use auth_outage\outagelib; +use auth_outage\dml\outagedb; +use auth_outage\form\outage\finish; +use auth_outage\local\outagelib; -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/formslib.php'); +require_once(__DIR__.'/../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir.'/formslib.php'); -$renderer = outagelib::pagesetup(); - -$mform = new \auth_outage\forms\outage\edit(); +$renderer = outagelib::page_setup(); +$mform = new finish(); if ($mform->is_cancelled()) { - redirect('/auth/outage/list.php'); + redirect('/auth/outage/manage.php'); } else if ($fromform = $mform->get_data()) { - $fromform = outagelib::parseformdata($fromform); - $outage = new outage($fromform); - $id = outagedb::save($outage); - redirect('/auth/outage/list.php#auth_outage_id_' . $id); + outagedb::finish($fromform->id); + redirect('/auth/outage/manage.php'); } $id = required_param('id', PARAM_INT); -$outage = outagedb::getbyid($id); +$outage = outagedb::get_by_id($id); if ($outage == null) { - throw new invalid_parameter_exception('Outage #' . $id . ' not found.'); + throw new invalid_parameter_exception('Outage #'.$id.' not found.'); } -$data = get_object_vars($outage); -$data['description'] = ['text' => $data['description'], 'format' => '1']; -$mform->set_data($data); -$PAGE->navbar->add($outage->title); +$dataid = new stdClass(); +$dataid->id = $outage->id; +$mform->set_data($dataid); + echo $OUTPUT->header(); -echo $renderer->rendersubtitle('modifyoutage'); + +echo $renderer->renderfinishconfirmation($outage); + $mform->display(); + echo $OUTPUT->footer(); diff --git a/info.php b/info.php new file mode 100644 index 0000000..b0db69c --- /dev/null +++ b/info.php @@ -0,0 +1,31 @@ +. + +/** + * Shows the information about an outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\local\controllers\infopage; + +require_once(__DIR__.'/../../config.php'); + +$info = new infopage(); +$info->output(); diff --git a/lang/en/auth_outage.php b/lang/en/auth_outage.php index 0febcae..b12b871 100644 --- a/lang/en/auth_outage.php +++ b/lang/en/auth_outage.php @@ -24,27 +24,98 @@ */ $string['auth_outagedescription'] = 'Auxiliary plugin that warns users about a future outage and prevents them from logging in once the outage starts.'; -$string['defaultwarningmessage'] = 'Default Warning Message'; -$string['defaultwarningmessagedescription'] = 'Default warning message for outages. Use [from] and [until] placeholders as required.'; -$string['defaultwarningmessagevalue'] = 'There is an scheduled maintenance from [from] to [until] and our system will not be available during that time.'; -$string['defaultwarningtime'] = 'Default Warning Time'; -$string['defaultwarningtimedescription'] = 'Default warning time (in minutes) for outages.'; -$string['description'] = 'Public description'; +$string['clicreatehelp'] = 'Creates a new outage.'; +$string['clicreateparamautostart'] = 'must be Y or N, sets if the outage automatically triggers maintenance mode.'; +$string['clicreateparamblock'] = 'blocks until outage starts.'; +$string['clicreateparamclone'] = 'clone another outage except for the start time.'; +$string['clicreateparamdescription'] = 'the description of the outage.'; +$string['clicreateparamduration'] = 'how many seconds should the outage last.'; +$string['clicreateparamhelp'] = 'shows parameters help.'; +$string['clicreateparamonlyid'] = 'only outputs the new outage id, useful for scripts.'; +$string['clicreateparamstart'] = 'in how many seconds should this outage start. Required.'; +$string['clicreateparamtitle'] = 'the title of the outage.'; +$string['clicreateparamwarn'] = 'how many seconds before it starts to display a warning.'; +$string['clifinishhelp'] = 'Finishes an ongoing outage.'; +$string['clifinishnotongoing'] = 'Outage is not ongoing.'; +$string['clifinishparamhelp'] = 'shows parameters help.'; +$string['clifinishparamactive'] = 'finishes the currently active outage.'; +$string['clifinishparamoutageid'] = 'the id of the outage to finish.'; +$string['cliwaitforiterroridxoractive'] = 'You must use --outageid=# or --active parameter but not both.'; +$string['cliwaitforithelp'] = 'Waits until an outage starts.'; +$string['cliwaitforitoutagestarted'] = 'Outage started!'; +$string['cliwaitforitoutagestartingin'] = 'Outage starting in {$a->countdown}.'; +$string['cliwaitforitparamactive'] = 'wait for the currently active outage.'; +$string['cliwaitforitparamhelp'] = 'shows parameters help.'; +$string['cliwaitforitparamoutageid'] = 'the id of the outage to wait until it starts.'; +$string['cliwaitforitparamsleep'] = 'maximum amount of seconds before status output.'; +$string['cliwaitforitparamverbose'] = 'enable verbose mode.'; +$string['clierrorinvalidvalue'] = 'Invalid value for parameter: {$a->param}'; +$string['clierrormissingparamaters'] = 'You must specify the start time, use --help for more information.'; +$string['clierroroutagechanged'] = 'Outage was changed while waiting.'; +$string['clierroroutageended'] = 'Outage has already ended.'; +$string['clierroroutagenotfound'] = 'Outage not found.'; +$string['clioutagecreated'] = 'Outage created, id: {$a->id}'; +$string['clone'] = 'Clone'; +$string['datetimeformat'] = '%a %d %h %Y at %I:%M%P %Z'; +$string['defaultlayoutcss'] = 'Layout CSS'; +$string['defaultlayoutcssdescription'] = 'This CSS code will be used to display the Outage Warning Bar.'; +$string['defaultoutageautostart'] = 'Outage Auto Start'; +$string['defaultoutageautostartdescription'] = 'If the outage should automatically trigger maintenance mode once it starts, locking down the whole site.'; +$string['defaultoutageduration'] = 'Outage Duration'; +$string['defaultoutagedurationdescription'] = 'Default duration (in minutes) of an outage.'; +$string['defaultwarningduration'] = 'Warning Duration'; +$string['defaultwarningdurationdescription'] = 'Default warning time (in minutes) for outages.'; +$string['defaultwarningtitle'] = 'Title'; +$string['defaultwarningtitledescription'] = 'Default title for outages. Use {{start}} and {{stop}} placeholders as required.'; +$string['defaultwarningtitlevalue'] = 'System down from {{start}} for {{duration}}.'; +$string['defaultwarningdescription'] = 'Description'; +$string['defaultwarningdescriptiondescription'] = 'Default warning message for outages. Use {{start}} and {{stop}} placeholders as required.'; +$string['defaultwarningdescriptionvalue'] = 'There is an scheduled maintenance from {{start}} to {{stop}} and our system will not be available during that time.'; +$string['description'] = 'Public Description'; +$string['description_help'] = 'A full description of the outage, publicly visible by all users.'; +$string['finish'] = 'Finish'; +$string['info15secondsbefore'] = '15 seconds before'; +$string['infoendofoutage'] = 'end of outage'; +$string['infofrom'] = 'From:'; +$string['infountil'] = 'Until:'; +$string['infostart'] = 'start'; +$string['infostartofwarning'] = 'start of warning'; +$string['infopagestaticgenerated'] = 'This warning was generated on {$a->time}.'; $string['menudefaults'] = 'Default Settings'; $string['menumanage'] = 'Manage'; -$string['modify'] = 'Modify'; -$string['modifyoutage'] = 'Modify Outage'; +$string['messageoutagebackonline'] = 'We are back online!'; +$string['messageoutagebackonlinedescription'] = 'You may resume browsing safely.'; +$string['messageoutageongoing'] = 'Back online at {$a->stop}.'; +$string['messageoutagewarning'] = 'Shutting down in {{countdown}}'; +$string['na'] = 'n/a'; +$string['notfound'] = 'No outages found.'; +$string['outageedit'] = 'Edit Outage'; +$string['outageclone'] = 'Clone Outage'; $string['outagecreate'] = 'Create Outage'; -$string['outageremove'] = 'Remove Outage'; -$string['outageremovewarning'] = 'You are about to permanently remove the outage below. This cannot be undone.'; -$string['outageslist'] = 'Outages List'; -$string['pluginname'] = 'Outage'; -$string['starttimeerrornotinfuture'] = 'Start time must be in the future.'; +$string['outagedelete'] = 'Delete Outage'; +$string['outagedeletewarning'] = 'You are about to permanently delete the outage below. This cannot be undone.'; +$string['outageduration'] = 'Outage Duration'; +$string['outagedurationerrorinvalid'] = 'Outage duration must be positive.'; +$string['outageduration_help'] = 'How long the outage lasts after it starts.'; +$string['outagefinish'] = 'Finish Outage'; +$string['outagefinishwarning'] = 'You are about to mark this outage as finished. The system will be immediately back online.'; +$string['outageslistfuture'] = 'Planned outages'; +$string['outageslistpast'] = 'Outage history'; +$string['pluginname'] = 'Outage manager'; $string['starttime'] = 'Start date and time'; -$string['stoptimeerrornotafterstart'] = 'Stop time must be after start time.'; -$string['stoptime'] = 'Stop date and time'; +$string['starttime_help'] = 'At which date and time the outage starts, preventing general access to the system.'; +$string['tableheaderduration'] = 'Duration'; +$string['tableheaderdurationplanned'] = 'Planned Duration'; +$string['tableheaderdurationactual'] = 'Actual Duration'; +$string['tableheaderstarttime'] = 'Starts on'; +$string['tableheaderwarnbefore'] = 'Warns before'; +$string['tableheadertitle'] = 'Title'; +$string['taskupdatestaticpage'] = 'Update static outage page'; +$string['textplaceholdershint'] = 'You can use {{start}}, {{stop}} and {{duration}} as placeholders on the title and description.'; $string['titleerrorinvalid'] = 'Title cannot be left blank.'; $string['titleerrortoolong'] = 'Title cannot have more than {$a} characters.'; $string['title'] = 'Title'; -$string['warningdurationerrorinvalid'] = 'Warning duration cannot be zero.'; +$string['title_help'] = 'A short title to for this outage. It will be displayed on the warning bar and on the calendar.'; +$string['warningdurationerrorinvalid'] = 'Warning duration must be positive.'; $string['warningduration'] = 'Warning duration'; +$string['warningduration_help'] = 'How long before the start of the outage should the warning be displayed.'; diff --git a/lib.php b/lib.php index 56943f8..91effb7 100644 --- a/lib.php +++ b/lib.php @@ -19,7 +19,15 @@ * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; + +function auth_outage_extend_navigation_user() { + \auth_outage\local\outagelib::inject(); +} + +function auth_outage_extend_navigation_frontpage() { + \auth_outage\local\outagelib::inject(); +} diff --git a/list.php b/manage.php similarity index 75% rename from list.php rename to manage.php index dd2c840..507ee08 100644 --- a/list.php +++ b/manage.php @@ -19,20 +19,20 @@ * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -use auth_outage\outagedb; -use auth_outage\outagelib; +use auth_outage\dml\outagedb; +use auth_outage\local\outagelib; -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); +require_once(__DIR__.'/../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); -$renderer = outagelib::pagesetup(); +$renderer = outagelib::page_setup(); echo $OUTPUT->header(); -echo $renderer->renderoutagelist(outagedb::getall()); +$renderer->renderoutagelist(outagedb::get_all_unended(), outagedb::get_all_ended()); echo $OUTPUT->footer(); diff --git a/new.php b/new.php new file mode 100644 index 0000000..4cb1d1a --- /dev/null +++ b/new.php @@ -0,0 +1,60 @@ +. + +/** + * Create new outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\dml\outagedb; +use auth_outage\form\outage\edit; +use auth_outage\local\outage; +use auth_outage\local\outagelib; + +require_once(__DIR__.'/../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir.'/formslib.php'); + +outagelib::page_setup(); + +$mform = new edit(); +if ($mform->is_cancelled()) { + redirect('/auth/outage/manage.php'); +} else if ($outage = $mform->get_data()) { + $id = outagedb::save($outage); + redirect('/auth/outage/manage.php#auth_outage_id_'.$id); +} + +$config = outagelib::get_config(); +$defaults = new outage([ + 'starttime' => time(), + 'stoptime' => time() + ($config->default_duration * 60), + 'warntime' => time() - ($config->default_warning_duration * 60), + 'title' => $config->default_warning_title, + 'description' => $config->default_warning_description, +]); +$mform->set_data($defaults); + +$PAGE->navbar->add(get_string('outagecreate', 'auth_outage')); +echo $OUTPUT->header(); + +$mform->display(); + +echo $OUTPUT->footer(); diff --git a/renderer.php b/renderer.php index 0ec3f45..20d7473 100644 --- a/renderer.php +++ b/renderer.php @@ -14,115 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -use auth_outage\models\outage; -use auth_outage\models\outageform; - -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. -} +defined('MOODLE_INTERNAL') || die(); /** - * auth_outage auth_outage_renderer + * auth_outage auth_outage_renderer should just extend our renderer class in the classes directory. + * This is done to keep code organized and make easier to run tests and check coverage. * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class auth_outage_renderer extends plugin_renderer_base -{ - public function rendersubtitle($subtitlekey) { - if (!is_string($subtitlekey)) { - throw new \InvalidArgumentException('$subtitle is not a string.'); - } - return html_writer::tag('h2', get_string($subtitlekey, 'auth_outage')); - } - - public function renderdeleteconfirmation(outage $outage) { - return $this->rendersubtitle('outageremove') - . html_writer::tag('p', get_string('outageremovewarning', 'auth_outage')) - . $this->renderoutage($outage, false); - } - - public function renderoutagelist(array $outages) { - global $OUTPUT; - - $html = $this->rendersubtitle('outageslist'); - - // Generate list of outages. - foreach ($outages as $outage) { - $html .= $this->renderoutage($outage, true); - } - - // Add 'add' button. - $url = new moodle_url('/auth/outage/create.php'); - $img = html_writer::empty_tag('img', - ['src' => $OUTPUT->pix_url('t/add'), 'alt' => get_string('create'), 'class' => 'iconsmall']); - $html .= html_writer::tag('p', - html_writer::link( - $url, - $img . ' ' . get_string('outagecreate', 'auth_outage'), - ['title' => get_string('remove')] - ) - ); - - return $html; - } - - private function renderoutage(outage $outage, $buttons) { - global $OUTPUT; - - $created = core_user::get_user($outage->createdby, 'firstname,lastname', MUST_EXIST); - $created = html_writer::link( - new moodle_url('/user/profile.php', ['id' => $outage->createdby]), - trim($created->firstname . ' ' . $created->lastname) - ); - - $modified = core_user::get_user($outage->modifiedby, 'firstname,lastname', MUST_EXIST); - $modified = html_writer::link( - new moodle_url('/user/profile.php', ['id' => $outage->modifiedby]), - trim($modified->firstname . ' ' . $modified->lastname) - ); - - $url = new moodle_url('/auth/outage/change.php', ['id' => $outage->id]); - $img = html_writer::empty_tag( - 'img', - ['src' => $OUTPUT->pix_url('t/edit'), 'alt' => get_string('modify', 'auth_outage'), 'class' => 'iconsmall'] - ); - $linkedit = html_writer::link($url, $img, ['title' => get_string('modify', 'auth_outage')]); - - $url = new moodle_url('/auth/outage/remove.php', ['id' => $outage->id]); - $img = html_writer::empty_tag( - 'img', - ['src' => $OUTPUT->pix_url('t/delete'), 'alt' => get_string('remove'), 'class' => 'iconsmall'] - ); - $linkdelete = html_writer::link($url, $img, ['title' => get_string('remove')]); - - // TODO use language pack below, solve together with Issue #12. - return html_writer::div( - html_writer::span( - html_writer::tag('b', $outage->title, ['data-id' => $outage->id]) - . html_writer::empty_tag('br') - . html_writer::tag('i', $outage->description) - . html_writer::empty_tag('br') - . html_writer::tag('b', 'Warning: ') - . userdate($outage->starttime - ($outage->warningduration * 60)) - . html_writer::empty_tag('br') - . html_writer::tag('b', 'Starts: ') - . userdate($outage->starttime, '%d %h %Y %l:%M%P') - . html_writer::empty_tag('br') - . html_writer::tag('b', 'Stops: ') - . userdate($outage->stoptime, '%d %h %Y %l:%M%P') - . html_writer::empty_tag('br') - . html_writer::tag('small', - 'Created by ' . $created - . ', modified by ' . $modified . ' on ' - . userdate($outage->lastmodified, '%d %h %Y %l:%M%P') - ) - . html_writer::empty_tag('br') - . ($buttons ? $linkedit . $linkdelete . html_writer::empty_tag('br') : '') - . html_writer::empty_tag('br') - ) - ); - } +class auth_outage_renderer extends auth_outage\output\renderer { } diff --git a/settings.php b/settings.php index 1507e1b..7faac82 100644 --- a/settings.php +++ b/settings.php @@ -19,28 +19,59 @@ * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use auth_outage\local\outagelib; + defined('MOODLE_INTERNAL') || die; -// FIXME If plugin not installed, it is still generating the category Outage under Auth Plugins. - -if ($hassiteconfig) { +if ($hassiteconfig && is_enabled_auth('outage')) { + $defaults = outagelib::get_config_defaults(); // Configure default settings page. $settings->visiblename = get_string('menudefaults', 'auth_outage'); - $settings->add( - new admin_setting_configtext('auth_outage_warning_period', - get_string('defaultwarningtime', 'auth_outage'), - get_string('defaultwarningtimedescription', 'auth_outage'), - 120, PARAM_INT)); - $settings->add( - new admin_setting_configtextarea('auth_outage_warning_text', - get_string('defaultwarningmessage', 'auth_outage'), - get_string('defaultwarningmessagedescription', 'auth_outage'), - get_string('defaultwarningmessagevalue', 'auth_outage'), - PARAM_TEXT) - ); + $settings->add(new admin_setting_configcheckbox( + 'auth_outage/default_autostart', + get_string('defaultoutageautostart', 'auth_outage'), + get_string('defaultoutageautostartdescription', 'auth_outage'), + $defaults['default_autostart'], + PARAM_BOOL + )); + $settings->add(new admin_setting_configtext( + 'auth_outage/default_duration', + get_string('defaultoutageduration', 'auth_outage'), + get_string('defaultoutagedurationdescription', 'auth_outage'), + $defaults['default_duration'], + PARAM_INT + )); + $settings->add(new admin_setting_configtext( + 'auth_outage/default_warning_duration', + get_string('defaultwarningduration', 'auth_outage'), + get_string('defaultwarningdurationdescription', 'auth_outage'), + $defaults['default_warning_duration'], + PARAM_INT + )); + $settings->add(new admin_setting_configtext( + 'auth_outage/default_warning_title', + get_string('defaultwarningtitle', 'auth_outage'), + get_string('defaultwarningtitledescription', 'auth_outage'), + $defaults['default_warning_title'], + PARAM_TEXT + )); + $settings->add(new admin_setting_configtextarea( + 'auth_outage/default_warning_description', + get_string('defaultwarningdescription', 'auth_outage'), + get_string('defaultwarningdescriptiondescription', 'auth_outage'), + $defaults['default_warning_description'], + PARAM_TEXT + )); + $settings->add(new admin_setting_configtextarea( + 'auth_outage/css', + get_string('defaultlayoutcss', 'auth_outage'), + get_string('defaultlayoutcssdescription', 'auth_outage'), + $defaults['css'], + PARAM_TEXT + )); // Create category for Outage. $ADMIN->add('authsettings', new admin_category('auth_outage', get_string('pluginname', 'auth_outage'))); // Add settings page toconfigure defaults. @@ -52,7 +83,7 @@ if ($hassiteconfig) { new admin_externalpage( 'auth_outage_manage', get_string('menumanage', 'auth_outage'), - new moodle_url($CFG->wwwroot . '/auth/outage/list.php') + new moodle_url($CFG->wwwroot.'/auth/outage/manage.php') ) ); -} \ No newline at end of file +} diff --git a/tests/cli/cli_test.php b/tests/cli/cli_test.php new file mode 100644 index 0000000..400acc5 --- /dev/null +++ b/tests/cli/cli_test.php @@ -0,0 +1,82 @@ +. + +use auth_outage\local\cli\cli_exception; +use auth_outage\local\cli\create; + +defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__.'/cli_testcase.php'); + +/** + * Tests performed on CLI base and exception class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \auth_outage\local\cli\clibase + * @covers \auth_outage\local\cli\cli_exception + * @SuppressWarnings("public") + */ +class cli_test extends cli_testcase { + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 2 + */ + public function test_invalidargumentparam() { + $this->set_parameters(['--aninvalidparameter']); + new create(); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 2 + */ + public function test_invalidargumentgiven() { + new create(['anotherinvalidparameter']); + } + + public function test_setreferencetime() { + $cli = new create(['start' => 0]); + $cli->set_referencetime(1); + $cli->set_referencetime(60 * 60 * 24 * 7); + } + + /** + * @expectedException coding_exception + */ + public function test_setreferencetime_invalid() { + $this->set_parameters(['--start=60']); + $cli = new create(); + $cli->set_referencetime(-1); + } + + public function test_help() { + $this->set_parameters(['-h']); + $cli = new create(); + $output = $this->execute($cli); + self::assertContains('-h', $output); + self::assertContains('--help', $output); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 1 + */ + public function test_exception() { + throw new cli_exception('An CLI exception.'); + } +} diff --git a/tests/cli/cli_testcase.php b/tests/cli/cli_testcase.php new file mode 100644 index 0000000..5bf709c --- /dev/null +++ b/tests/cli/cli_testcase.php @@ -0,0 +1,61 @@ +. + +use auth_outage\local\cli\clibase; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Tests performed on CLIs. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cli_testcase extends advanced_testcase { + public function setUp() { + $this->resetAfterTest(true); + $this->set_parameters([]); + parent::setUp(); + } + + /** + * Mocks the command line parameters. + * @param string[] $options Options to use as parameters. + */ + protected function set_parameters(array $options) { + array_unshift($options, 'cli.php'); + $_SERVER['argv'] = $options; + $_SERVER['argc'] = count($options); + } + + /** + * Executes the CLI. + * @param clibase $cli CLI to execute. + * @return string The output text. + */ + protected function execute(clibase $cli) { + ob_start(); + try { + $cli->execute(); + $text = ob_get_contents(); + return $text; + } finally { + ob_end_clean(); + } + } +} diff --git a/tests/cli/create_test.php b/tests/cli/create_test.php new file mode 100644 index 0000000..a02abfe --- /dev/null +++ b/tests/cli/create_test.php @@ -0,0 +1,276 @@ +. + +use auth_outage\dml\outagedb; +use auth_outage\local\cli\cli_exception; +use auth_outage\local\cli\create; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__.'/cli_testcase.php'); + +/** + * Tests performed on CLI create class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @SuppressWarnings("public") Allow this test to have as many tests as necessary. + */ +class create_test extends cli_testcase { + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 4 + */ + public function test_noarguments() { + $cli = new create(); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_invalidparam_notanumber() { + $cli = new create(['start' => 'some day']); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_invalidparam_negative() { + $cli = new create(['start' => -1]); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_invalidparam_emptystring() { + $cli = new create(['start' => 0, 'title' => '']); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_invalidparam_notastring() { + $cli = new create(['start' => 0, 'title' => true]); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->execute($cli); + } + + public function test_help() { + $this->set_parameters(['--help']); + $cli = new create(); + $output = $this->execute($cli); + self::assertContains('Creates', $output); + self::assertContains('--help', $output); + } + + public function test_options() { + $cli = new create(); + + $options = $cli->generate_options(); + foreach (array_keys($options) as $k) { + self::assertTrue(is_string($k)); + } + + $shorts = $cli->generate_shortcuts(); + foreach ($shorts as $s) { + self::assertArrayHasKey($s, $options); + } + } + + public function test_create_withoptions() { + $this->set_parameters([ + '--autostart=true', + '--warn=10', + '--start=0', + '--duration=30', + '--title=A Title', + '--description=A Description', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $text = $this->execute($cli); + self::assertContains('created', $text); + // Check creted outage. + list(, $id) = explode(':', $text); + $id = (int)$id; + $outage = outagedb::get_by_id($id); + self::assertSame($now, $outage->starttime); + self::assertSame(10, $outage->get_warning_duration()); + self::assertSame(30, $outage->get_duration_planned()); + self::assertNull($outage->finished); + self::assertSame('A Title', $outage->title); + self::assertSame('A Description', $outage->description); + } + + public function test_create_onlyid() { + $this->set_parameters([ + '--onlyid', + '--autostart=N', + '--warn=10', + '--start=0', + '--duration=30', + '--title=Title', + '--description=Description', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $id = $this->execute($cli); + // Check if the id contains is only a number (parameter onlyid). + $id = trim($id); + self::assertTrue(is_number($id)); + $id = (int)$id; + // Check creted outage. + $outage = outagedb::get_by_id($id); + self::assertSame($now, $outage->starttime); + self::assertSame($outage->starttime - 10, $outage->warntime); + self::assertSame($outage->starttime + 30, $outage->stoptime); + self::assertNull($outage->finished); + self::assertSame('Title', $outage->title); + self::assertSame('Description', $outage->description); + } + + public function test_create_withdefaults() { + $this->set_parameters([ + '--warn=100', + '--start=50', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $cli->set_defaults([ + 'autostart' => false, + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $text = $this->execute($cli); + self::assertContains('created', $text); + // Check creted outage. + list(, $id) = explode(':', $text); + $id = (int)$id; + $outage = outagedb::get_by_id($id); + self::assertSame($now + 50, $outage->starttime, 'Wrong starttime.'); + self::assertSame($outage->starttime - 100, $outage->warntime, 'Wrong warntime.'); + self::assertSame($outage->starttime + 300, $outage->stoptime, 'Wrong stoptime.'); + self::assertNull($outage->finished); + self::assertSame('Default Title', $outage->title); + self::assertSame('Default Description', $outage->description); + } + + public function test_create_withclone() { + self::setAdminUser(); + $now = time(); + // Create the outage to clone. + $original = new outage([ + 'autostart' => false, + 'warntime' => $now - 120, + 'starttime' => $now, + 'stoptime' => $now + 120, + 'title' => 'Title', + 'description' => 'Description', + ]); + $id = outagedb::save($original); + // Clone it using CLI. + $this->set_parameters([ + '--onlyid', + '--start=60', + '--clone='.$id, + ]); + $cli = new create(); + $cli->set_referencetime($now); + $id = trim($this->execute($cli)); + // Check cloned data. + $cloned = outagedb::get_by_id((int)$id); + self::assertSame($now + 60, $cloned->starttime); + self::assertSame($original->get_warning_duration(), $cloned->get_warning_duration()); + self::assertSame($original->get_duration_planned(), $cloned->get_duration_planned()); + self::assertSame($original->title, $cloned->title); + self::assertSame($original->description, $cloned->description); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_create_withclone_invalid() { + $this->set_parameters([ + '--start=60', + '--clone=-1', + ]); + $cli = new create(); + $this->execute($cli); + } + + public function test_create_withblock() { + // Not an extensive test in the blocking API, cliwaitforit tests should cover them deeper. + $this->set_parameters([ + '--autostart=N', + '--block', + '--warn=60', + '--start=0', + '--duration=600', + '--title=Title', + '--description=Description', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $text = $this->execute($cli); + self::assertContains('created', $text); + self::assertContains('started', $text); + } +} diff --git a/tests/cli/finish_test.php b/tests/cli/finish_test.php new file mode 100644 index 0000000..6a67e06 --- /dev/null +++ b/tests/cli/finish_test.php @@ -0,0 +1,141 @@ +. + +use auth_outage\dml\outagedb; +use auth_outage\local\cli\cli_exception; +use auth_outage\local\cli\finish; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__.'/cli_testcase.php'); + +/** + * Tests performed on CLI finish class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \auth_outage\local\cli\finish + */ +class finish_test extends cli_testcase { + public function test_constructor() { + $cli = new finish(); + self::assertNotNull($cli); + } + + public function test_options() { + $cli = new finish(); + + $options = $cli->generate_options(); + foreach (array_keys($options) as $k) { + self::assertTrue(is_string($k)); + } + + $shorts = $cli->generate_shortcuts(); + foreach ($shorts as $s) { + self::assertArrayHasKey($s, $options); + } + } + + public function test_help() { + $this->set_parameters(['--help']); + $cli = new finish(); + $text = $this->execute($cli); + self::assertContains('Finishes', $text); + self::assertContains('--help', $text); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 4 + */ + public function test_noarguments() { + $cli = new finish(); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 5 + */ + public function test_endedoutage() { + self::setAdminUser(); + $now = time(); + $id = outagedb::save(new outage([ + 'autostart' => false, + 'warntime' => $now - 200, + 'starttime' => $now - 100, + 'stoptime' => $now - 50, + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-id='.$id]); + $cli = new finish(); + $cli->set_referencetime($now); + $this->execute($cli); + } + + public function test_finish() { + self::setAdminUser(); + $now = time(); + $id = outagedb::save(new outage([ + 'autostart' => false, + 'warntime' => $now - 200, + 'starttime' => $now - 100, + 'stoptime' => $now + 100, + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-id='.$id]); + $cli = new finish(); + $cli->set_referencetime($now); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 6 + */ + public function test_activenotfound() { + self::setAdminUser(); + $this->set_parameters(['-a']); + $cli = new finish(); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_invalidid() { + self::setAdminUser(); + $this->set_parameters(['-id=theid']); + $cli = new finish(); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 6 + */ + public function test_idnotfound() { + self::setAdminUser(); + $this->set_parameters(['-id=99999']); + $cli = new finish(); + $this->execute($cli); + } +} diff --git a/tests/cli/waitforit_test.php b/tests/cli/waitforit_test.php new file mode 100644 index 0000000..8f59940 --- /dev/null +++ b/tests/cli/waitforit_test.php @@ -0,0 +1,190 @@ +. + +use auth_outage\dml\outagedb; +use auth_outage\local\cli\waitforit; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__.'/cli_testcase.php'); + +/** + * Tests performed on CLI waitforit class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \auth_outage\local\cli\waitforit + * @SuppressWarnings("public") + */ +class waitforit_test extends cli_testcase { + public function test_constructor() { + $cli = new waitforit(); + self::assertNotNull($cli); + } + + public function test_generateoptions() { + $cli = new waitforit(); + $options = $cli->generate_options(); + foreach (array_keys($options) as $k) { + self::assertTrue(is_string($k)); + } + } + + public function test_generateshortcuts() { + $cli = new waitforit(); + $options = $cli->generate_options(); + $shorts = $cli->generate_shortcuts(); + foreach ($shorts as $s) { + self::assertArrayHasKey($s, $options); + } + } + + public function test_help() { + $this->set_parameters(['--help']); + $cli = new waitforit(); + $text = $this->execute($cli); + self::assertContains('Waits', $text); + self::assertContains('--help', $text); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_bothparams() { + $this->set_parameters(['--outageid=1', '--active']); + $cli = new waitforit(); + $cli->execute(); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 3 + */ + public function test_invalidoutageid() { + $this->set_parameters(['-id=-1']); + $cli = new waitforit(); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 6 + */ + public function test_outagenotfound() { + $this->set_parameters(['-a']); + $cli = new waitforit(); + $this->execute($cli); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 5 + */ + public function test_endedoutage() { + self::setAdminUser(); + $now = time(); + $id = outagedb::save(new outage([ + 'autostart' => false, + 'warntime' => $now - 200, + 'starttime' => $now - 100, + 'stoptime' => $now - 50, + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-id='.$id]); + $cli = new waitforit(); + $cli->set_referencetime($now); + $this->execute($cli); + } + + public function test_activeverbose() { + self::setAdminUser(); + $now = time(); + outagedb::save(new outage([ + 'autostart' => false, + 'warntime' => $now - 10, + 'starttime' => $now + 1, + 'stoptime' => $now + 10, + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-v', '--active']); + $cli = new waitforit(); + $cli->set_referencetime($now); + $output = $this->execute($cli); + self::assertContains('Verbose mode', $output); + self::assertContains('starting in 1 sec', $output); + self::assertContains('started', $output); + } + + public function test_countdown() { + self::setAdminUser(); + $now = time(); + outagedb::save(new outage([ + 'autostart' => false, + 'warntime' => $now, + 'starttime' => $now + 45, + 'stoptime' => $now + (60 * 60), + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-v', '--active', '--sleep=30']); + $cli = new waitforit(); + $cli->set_referencetime($now); + $cli->set_sleepcallback(function ($sleep) use (&$now) { + $now += $sleep; + return $now; + }); + $output = $this->execute($cli); + self::assertContains("starting in 45", $output); + self::assertContains("sleep 30 second", $output); + self::assertContains("starting in 15", $output); + self::assertContains("sleep 15 second", $output); + self::assertContains("started!", $output); + } + + /** + * @expectedException auth_outage\local\cli\cli_exception + * @expectedExceptionCode 7 + */ + public function test_outagechanged() { + self::setAdminUser(); + $now = time(); + $id = outagedb::save(new outage([ + 'autostart' => false, + 'warntime' => $now, + 'starttime' => $now + (2 * 60 * 60), + 'stoptime' => $now + (60 * 60), + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-v', '--active', '--sleep=30']); + $cli = new waitforit(); + $cli->set_referencetime($now); + $cli->set_sleepcallback(function () use ($id) { + // Change outage when not expected to. + $outage = outagedb::get_by_id($id); + $outage->title = 'New title!'; + outagedb::save($outage); + // Pretend it is time to start, but it should get an error instead. + return $outage->starttime; + }); + $this->execute($cli); + } +} diff --git a/tests/events_test.php b/tests/events_test.php new file mode 100644 index 0000000..54bef26 --- /dev/null +++ b/tests/events_test.php @@ -0,0 +1,116 @@ +. + +use auth_outage\dml\outagedb; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Tests performed on outage class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \auth_outage\event\outage_created + * @covers \auth_outage\event\outage_updated + * @covers \auth_outage\event\outage_deleted + */ +class events_test extends advanced_testcase { + public function test_save() { + global $DB; + self::setAdminUser(); + $this->resetAfterTest(false); + + // Save new outage. + $now = time(); + $id = outagedb::save(new outage([ + 'autostart' => false, + 'warntime' => $now - 60, + 'starttime' => 60, + 'stoptime' => 120, + 'title' => 'Title', + 'description' => 'Description', + ])); + + // Check existance. + $event = $DB->get_record_select( + 'event', + "(eventtype = 'auth_outage' AND instance = :outageid)", + ['outageid' => $id], + 'id', + IGNORE_MISSING + ); + self::assertTrue(is_object($event)); + + // Another test will use it. + return [$id, $event->id]; + } + + /** + * @param int[] $ids + * @depends test_save + */ + public function test_update($ids) { + global $DB; + + self::setAdminUser(); + $this->resetAfterTest(false); + + list($idoutage, $idevent) = $ids; + $outage = outagedb::get_by_id($idoutage); + $outage->starttime += 10; + outagedb::save($outage); + + // Should still exist. + $event = $DB->get_record_select( + 'event', + "(eventtype = 'auth_outage' AND instance = :idoutage)", + ['idoutage' => $idoutage], + 'id', + IGNORE_MISSING + ); + self::assertTrue(is_object($event)); + self::assertSame($idevent, $event->id); + + return $ids; + } + + /** + * @param int[] $ids + * @depends test_update + */ + public function test_delete($ids) { + global $DB; + + self::setAdminUser(); + $this->resetAfterTest(true); + list($idoutage, $idevent) = $ids; + + outagedb::delete($idoutage); + + // Should not exist. + $event = $DB->get_record_select( + 'event', + "(eventtype = 'auth_outage' AND instance = :idoutage) OR (id = :idevent)", + ['idoutage' => $idoutage, 'idevent' => $idevent], + 'id', + IGNORE_MISSING + ); + self::assertFalse($event); + } +} diff --git a/tests/infopagecontroller_test.php b/tests/infopagecontroller_test.php new file mode 100644 index 0000000..8ed2fdb --- /dev/null +++ b/tests/infopagecontroller_test.php @@ -0,0 +1,97 @@ +. + +use auth_outage\local\controllers\infopage; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Tests performed on infopage_controller class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \auth_outage\infopage_controller + */ +class infopagecontroller_test extends advanced_testcase { + public function setUp() { + if (file_exists($this->get_file())) { + if (is_file($this->get_file())) { + unlink($this->get_file()); + } else { + self::fail('Invalid temp file: '.$this->get_file()); + } + } + } + + /** + * Return a temporary file name to use for this test. + * @return string Default file. + */ + public function get_file() { + return sys_get_temp_dir().'/phpunit_authoutage.tmp'; + } + + public function test_staticpage_output() { + global $PAGE; + $this->resetAfterTest(true); + + $PAGE->set_context(context_system::instance()); + $now = time(); + $outage = new outage([ + 'id' => 1, + 'starttime' => $now + (60 * 60), + 'warntime' => $now - (60 * 60), + 'stoptime' => $now + (2 * 60 * 60), + 'title' => 'Outage Title at {{start}}', + 'description' => 'This is an important outage, starting at {{start}}.', + ]); + $info = new infopage(['static' => true, 'outage' => $outage]); + $html = $info->get_output(); + self::assertContains('', $html); + self::assertContains('', $html); + self::assertContains($outage->get_title(), $html); + self::assertContains($outage->get_description(), $html); + self::assertSame($outage->id, infopage::find_outageid_from_infopage($html)); + } + + public function test_staticpage_file() { + $now = time(); + $outage = new outage([ + 'id' => 1, + 'warntime' => $now - 100, + 'starttime' => $now + 100, + 'stoptime' => $now + 200, + 'title' => 'Title', + 'description' => 'Description', + ]); + infopage::save_static_page($outage, $this->get_file()); + self::assertFileExists($this->get_file()); + + $id = infopage::find_outageid_from_infopage(file_get_contents($this->get_file())); + self::assertSame($outage->id, $id); + + unlink($this->get_file()); + } + + public function test_getdefaulttemplatefile() { + $file = infopage::get_defaulttemplatefile(); + self::assertTrue(is_string($file)); + self::assertContains('template', $file); + } +} diff --git a/tests/outage_test.php b/tests/outage_test.php index b62097d..a364fa6 100644 --- a/tests/outage_test.php +++ b/tests/outage_test.php @@ -14,22 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); + /** * Tests performed on outage class. * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \auth_outage\local\outage */ - -use auth_outage\models\outage; - -defined('MOODLE_INTERNAL') || die(); - - -class outage_test extends basic_testcase -{ +class outage_test extends basic_testcase { public function test_constructor() { $outage = new outage(); // Very important, this should never change. @@ -39,4 +37,148 @@ class outage_test extends basic_testcase self::assertNull($v); } } + + public function test_isongoing() { + $now = time(); + + // In the past. + $outage = new outage([ + 'starttime' => $now - (3 * 60 * 60), + 'stoptime' => $now - (2 * 60 * 60), + 'warntime' => $now - (2 * 60 * 60), + 'title' => '', + 'description' => '', + ]); + self::assertFalse($outage->is_ongoing($now)); + + // In the present (ongoing). + $outage = new outage([ + 'starttime' => $now - (1 * 60 * 60), + 'stoptime' => $now + (1 * 60 * 60), + 'warntime' => $now - (2 * 60 * 60), + 'title' => '', + 'description' => '', + ]); + self::assertTrue($outage->is_ongoing($now)); + + // In the future. + $outage = new outage([ + 'starttime' => $now + (1 * 60 * 60), + 'stoptime' => $now + (2 * 60 * 60), + 'warntime' => $now - (2 * 60 * 60), + 'title' => '', + 'description' => '', + ]); + self::assertFalse($outage->is_ongoing($now)); + } + + public function test_isactive() { + $now = time(); + + // In the past. + $outage = new outage([ + 'starttime' => $now - (3 * 60 * 60), + 'stoptime' => $now - (2 * 60 * 60), + 'warntime' => $now - (2 * 60 * 60), + 'title' => '', + 'description' => '', + ]); + self::assertFalse($outage->is_active($now)); + + // In the present (ongoing). + $outage = new outage([ + 'starttime' => $now - (1 * 60 * 60), + 'stoptime' => $now + (1 * 60 * 60), + 'warntime' => $now - (2 * 60 * 60), + 'title' => '', + 'description' => '', + ]); + self::assertTrue($outage->is_active($now)); + + // In the future (warning). + $outage = new outage([ + 'starttime' => $now + (1 * 60 * 60), + 'stoptime' => $now + (2 * 60 * 60), + 'warntime' => $now - (2 * 60 * 60), + 'title' => '', + 'description' => '', + ]); + self::assertTrue($outage->is_active($now)); + + // In the future (not warning). + $outage = new outage([ + 'starttime' => $now + (2 * 60 * 60), + 'stoptime' => $now + (3 * 60 * 60), + 'warntime' => $now + (1 * 60 * 60), + 'title' => '', + 'description' => '', + ]); + self::assertFalse($outage->is_active($now)); + } + + public function test_stages() { + $now = time(); + + $outage = new outage([ + 'warntime' => $now + 10, + 'starttime' => $now + 20, + 'stoptime' => $now + 30, + 'title' => 'Outage Waiting', + ]); + self::assertSame(outage::STAGE_WAITING, $outage->get_stage($now)); + self::assertFalse($outage->is_active($now)); + self::assertFalse($outage->is_ongoing($now)); + + $outage = new outage([ + 'warntime' => $now - 10, + 'starttime' => $now + 20, + 'stoptime' => $now + 30, + 'title' => 'Outage Warning', + ]); + self::assertSame(outage::STAGE_WARNING, $outage->get_stage($now)); + self::assertTrue($outage->is_active($now)); + self::assertFalse($outage->is_ongoing($now)); + + $outage = new outage([ + 'warntime' => $now - 20, + 'starttime' => $now - 10, + 'stoptime' => $now + 30, + 'title' => 'Outage Ongoing', + ]); + self::assertSame(outage::STAGE_ONGOING, $outage->get_stage($now)); + self::assertTrue($outage->is_active($now)); + self::assertTrue($outage->is_ongoing($now)); + + $outage = new outage([ + 'warntime' => $now - 50, + 'starttime' => $now - 40, + 'stoptime' => $now - 30, + 'title' => 'Outage Stopped', + ]); + self::assertSame(outage::STAGE_STOPPED, $outage->get_stage($now)); + self::assertFalse($outage->is_active($now)); + self::assertFalse($outage->is_ongoing($now)); + + $outage = new outage([ + 'warntime' => $now - 50, + 'starttime' => $now - 40, + 'finished' => $now - 30, + 'stoptime' => $now - 20, + 'title' => 'Outage Finished before Stop', + ]); + self::assertSame(outage::STAGE_FINISHED, $outage->get_stage($now)); + self::assertFalse($outage->is_active($now)); + self::assertFalse($outage->is_ongoing($now)); + + $outage = new outage([ + 'warntime' => $now - 50, + 'starttime' => $now - 40, + 'stoptime' => $now - 30, + 'finished' => $now - 20, + 'title' => 'Outage Finished after Stop', + ]); + self::assertSame(outage::STAGE_FINISHED, $outage->get_stage($now)); + self::assertFalse($outage->is_active($now)); + self::assertFalse($outage->is_ongoing($now)); + } } diff --git a/tests/outagedb_test.php b/tests/outagedb_test.php index 5226c64..27f53b3 100644 --- a/tests/outagedb_test.php +++ b/tests/outagedb_test.php @@ -14,23 +14,65 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +use auth_outage\dml\outagedb; +use auth_outage\local\outage; + +defined('MOODLE_INTERNAL') || die(); + /** * Tests performed on outage class. * * @package auth_outage * @author Daniel Thee Roperto - * @copyright Catalyst IT + * @copyright 2016 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +class outagedb_test extends advanced_testcase { + /** + * Creates an array of ids in from the given outages array. + * @param outage[] $outages An array of outages. + * @return int[] An array with the keys of the outages as values. + */ + private static function createidarray(array $outages) { + $ids = []; + foreach ($outages as $outage) { + $ids[] = $outage->id; + } + return $ids; + } -use auth_outage\models\outage; -use auth_outage\outagedb; + /** + * Helper function to create an outage then save it to the database. + * + * @param bool $autostart If outage should automatically start. + * @param int $now Timestamp for now, such as time(). + * @param int $warning In how many hours the warning starts. Can be negative. + * @param int $start In how many hours this outage starts. Can be negative. + * @param int $stop In how many hours this outage finishes. Can be negative. + * @param string $title Title for the outage. + * @param int|null $finished In how many hours this outage is marked as finished. Can be negative or null. + * @return int Id the of created outage. + */ + private static function saveoutage($autostart, $now, $warning, $start, $stop, $title, $finished = null) { + return outagedb::save(new outage([ + 'autostart' => $autostart, + 'warntime' => $now + ($warning * 60 * 60), + 'starttime' => $now + ($start * 60 * 60), + 'stoptime' => $now + ($stop * 60 * 60), + 'finished' => is_null($finished) ? null : ($now + ($finished * 60 * 60)), + 'title' => $title, + 'description' => 'Test Outage Description.', + ])); + } -defined('MOODLE_INTERNAL') || die(); + /** + * Ensure DB tests run as admin. + */ + public function setUp() { + parent::setUp(); + $this->setAdminUser(); + } - -class outagedb_test extends advanced_testcase -{ /** * Make sure we can save and update. */ @@ -44,6 +86,21 @@ class outagedb_test extends advanced_testcase outagedb::save($outage); } + public function test_saved_fields() { + $this->resetAfterTest(true); + for ($i = 0; $i < 4; $i++) { + $expected = $this->createoutage($i); + $expected->id = outagedb::save($expected); + $actual = outagedb::get_by_id($expected->id); + // Ignore the following fields. + $expected->lastmodified = $actual->lastmodified; + $expected->createdby = $actual->createdby; + $expected->modifiedby = $actual->modifiedby; + // Check if fields are the same. + self::assertEquals($expected, $actual, 'Failed for $i='.$i); + } + } + /** * Make sure we can get existing entries and null if not found. */ @@ -52,12 +109,12 @@ class outagedb_test extends advanced_testcase // Create something. $id = outagedb::save($this->createoutage(1)); // Get should work. - $outage = outagedb::getbyid($id); + $outage = outagedb::get_by_id($id); self::assertNotNull($outage); // Delete it. outagedb::delete($id); // Get should be null. - $outage = outagedb::getbyid($id); + $outage = outagedb::get_by_id($id); self::assertNull($outage); } @@ -71,7 +128,27 @@ class outagedb_test extends advanced_testcase // Delete it. outagedb::delete($id); // Should not exist anymore. - self::assertNull(outagedb::getbyid($id)); + self::assertNull(outagedb::get_by_id($id)); + } + + /** + * Make sure we can finish outages. + */ + public function test_finish() { + $now = time(); + $this->resetAfterTest(true); + // Create it. + $id = self::saveoutage(false, $now, -3, -2, 2, 'An ongoing outage.'); + $outage = outagedb::get_by_id($id); + self::assertTrue($outage->is_active($now)); + self::assertTrue($outage->is_ongoing($now)); + self::assertSame(null, $outage->finished); + // Finish it. + outagedb::finish($id, $now); + $outage = outagedb::get_by_id($id); + self::assertSame($now, $outage->finished); + self::assertFalse($outage->is_active($now)); + self::assertFalse($outage->is_ongoing($now)); } /** @@ -81,21 +158,20 @@ class outagedb_test extends advanced_testcase $this->resetAfterTest(true); $amount = 10; // Should start empty. - $outages = outagedb::getall(); + $outages = outagedb::get_all(); self::assertSame([], $outages); // Create some stuff outages. for ($i = 0; $i < $amount; $i++) { outagedb::save($this->createoutage($i)); } // Count entries created. - self::assertSame($amount, count(outagedb::getall())); + self::assertSame($amount, count(outagedb::get_all())); } /** * Perform some tests on the data itself, checking values after inserted and updated. */ public function test_basiccrud() { - return; $this->resetAfterTest(true); // Create some outages. @@ -109,11 +185,11 @@ class outagedb_test extends advanced_testcase // With all created outages. foreach ($outages as $id => $outage) { // Get it. - $inserted = outagedb::getbyid($id); + $inserted = outagedb::get_by_id($id); self::assertNotNull($inserted); // Check its data. - foreach (['starttime', 'stoptime', 'warningduration', 'title', 'description'] as $field) { - self::assertSame($outage->$field, $inserted->$field, 'Field ' . $field . ' does not match.'); + foreach (['starttime', 'stoptime', 'warntime', 'title', 'description'] as $field) { + self::assertSame($outage->$field, $inserted->$field, 'Field '.$field.' does not match.'); } // Check generated data. self::assertGreaterThan(0, $inserted->id); @@ -121,19 +197,138 @@ class outagedb_test extends advanced_testcase self::assertNotNull($inserted->createdby); self::assertNotNull($inserted->modifiedby); // Change it. - $inserted->title = 'Title ID' . $id; + $inserted->title = 'Title ID'.$id; outagedb::save($inserted); // Get it again and check data. - $updated = outagedb::getbyid($id); - self::assertSame('Title ID' . $id, $updated->title); + $updated = outagedb::get_by_id($id); + self::assertSame('Title ID'.$id, $updated->title); self::assertSame($inserted->description, $updated->description); // Delete it. outagedb::delete($id); - $deleted = outagedb::getbyid($id); + $deleted = outagedb::get_by_id($id); self::assertNull($deleted); } } + public function test_getactive() { + $this->resetAfterTest(true); + + // Have a consistent time for now (no seconds variation), helps debugging. + $now = time(); + + self::assertEquals([], outagedb::get_all(), 'Ensure there are no other outages that can affect the test.'); + self::assertNull(outagedb::get_active($now), 'There should be no active outage at this point.'); + + self::saveoutage(false, $now, 1, 2, 3, 'An outage that starts in the future and is not in warning period.'); + self::assertNull(outagedb::get_active($now), 'No active outages yet.'); + + self::saveoutage(false, $now, -3, -2, -1, 'An outage that is already in the past.'); + self::assertNull(outagedb::get_active($now), 'No active outages yet.'); + + $activeid = self::saveoutage(false, $now, -2, 1, 2, 'An outage in warning period.'); + self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); + + self::saveoutage(false, $now, -1, 2, 3, + 'Another outage in warning period, but ignored as it starts after the previous one.'); + self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); + + self::saveoutage(false, $now, -3, -2, 2, 'An finished outage.', -1); + self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); + + $activeid = self::saveoutage(false, $now, -3, -2, 2, 'An ongoing outage.'); + self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); + + self::saveoutage(false, $now, -3, -1, 1, 'Another ongoing outage but ignored because it started after the previous one.'); + self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); + + self::saveoutage(false, $now, -3, -2, 1, + 'Another ongoing outage starting at the same time, but ignored as it stops before the previous one.'); + self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); + } + + public function test_getallunended() { + $this->resetAfterTest(true); + + // Have a consistent time for now (no seconds variation), helps debugging. + $now = time(); + + self::assertEquals([], outagedb::get_all(), 'Ensure there are no other outages that can affect the test.'); + self::assertEquals([], outagedb::get_all_unended($now), 'There should be no future outages at this point.'); + + self::saveoutage(false, $now, -3, -2, -1, 'A past outage.'); + self::assertEquals([], outagedb::get_all_unended($now), 'No future outages yet.'); + + self::saveoutage(false, $now, -3, -2, 2, 'A finished outage.', -1); + self::assertEquals([], outagedb::get_all_unended($now), 'No future outages yet.'); + + $id1 = self::saveoutage(false, $now, 2, 3, 4, 'A future outage.'); + self::assertEquals([$id1], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + + $id2 = self::saveoutage(false, $now, 1, 4, 5, 'Another future outage.'); + self::assertEquals([$id1, $id2], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + + $id3 = self::saveoutage(false, $now, 1, 3, 5, 'Yet another future outage.'); + self::assertEquals([$id3, $id1, $id2], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + + $id4 = self::saveoutage(false, $now, -2, 1, 2, 'An outage in warning period.'); + self::assertEquals([$id4, $id3, $id1, $id2], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + + $id5 = self::saveoutage(false, $now, -1, 2, 3, 'Another outage in warning period.'); + self::assertEquals([$id4, $id5, $id3, $id1, $id2], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + + $id6 = self::saveoutage(false, $now, -3, -2, 2, 'An ongoing outage.'); + self::assertEquals([$id6, $id4, $id5, $id3, $id1, $id2], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + + $id7 = self::saveoutage(false, $now, -3, -1, 1, 'Another ongoing outage.'); + self::assertEquals([$id6, $id7, $id4, $id5, $id3, $id1, $id2], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + + $id8 = self::saveoutage(false, $now, -3, -2, 1, 'Yet another ongoing outage.'); + self::assertEquals([$id6, $id8, $id7, $id4, $id5, $id3, $id1, $id2], + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); + } + + public function test_getallended() { + $this->resetAfterTest(true); + + // Have a consistent time for now (no seconds variation), helps debugging. + $now = time(); + + self::assertEquals([], outagedb::get_all(), 'Ensure there are no other outages that can affect the test.'); + self::assertEquals([], outagedb::get_all_ended($now), 'There should be no future outages at this point.'); + + self::saveoutage(false, $now, -2, 1, 2, 'An outage in warning period.'); + self::assertEquals([], outagedb::get_all_ended($now), 'No past outages yet.'); + + self::saveoutage(false, $now, -3, -2, 2, 'An ongoing outage.'); + self::assertEquals([], outagedb::get_all_ended($now), 'No past outages yet.'); + + self::saveoutage(false, $now, 2, 3, 4, 'A future outage.'); + self::assertEquals([], outagedb::get_all_ended($now), 'No past outages yet.'); + + $id1 = self::saveoutage(false, $now, -8, -6, -4, 'A past outage.'); + self::assertEquals([$id1], + self::createidarray(outagedb::get_all_ended($now)), 'Wrong past data.'); + + $id2 = self::saveoutage(false, $now, -8, -7, -5, 'Another past outage.'); + self::assertEquals([$id1, $id2], + self::createidarray(outagedb::get_all_ended($now)), 'Wrong past data.'); + + $id3 = self::saveoutage(false, $now, -8, -5, -3, 'Yet another past outage.'); + self::assertEquals([$id3, $id1, $id2], + self::createidarray(outagedb::get_all_ended($now)), 'Wrong past data.'); + + $id4 = self::saveoutage(false, $now, -3, -2, 2, 'A finished outage.', -1); + self::assertEquals([$id4, $id3, $id1, $id2], + self::createidarray(outagedb::get_all_ended($now)), 'Wrong past data.'); + } + /** * Helper function to create an outage for tests. * @@ -142,11 +337,12 @@ class outagedb_test extends advanced_testcase */ private function createoutage($i) { return new outage([ + 'autostart' => ($i % 2 == 0), 'starttime' => $i * 100, 'stoptime' => $i * 100 + 50, - 'warningduration' => $i * 60, - 'title' => 'The Title ' . $i, - 'description' => 'A description in HTML.' + 'warntime' => $i * 60, + 'title' => 'The Title '.$i, + 'description' => 'A description in HTML.', ]); } } diff --git a/tests/outagelib_test.php b/tests/outagelib_test.php deleted file mode 100644 index ce4c5a1..0000000 --- a/tests/outagelib_test.php +++ /dev/null @@ -1,52 +0,0 @@ -. - -/** - * Tests performed on outageutils class. - * - * @package auth_outage - * @author Daniel Thee Roperto - * @copyright Catalyst IT - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -use auth_outage\outagelib; - -defined('MOODLE_INTERNAL') || die(); - - -class outagelib_test extends basic_testcase -{ - public function test_data2object() { - // Using object data, no new fields. - $obj = new stdClass(); - $obj->foo = 'bar'; - $obj->number = 42; - $data = new stdClass(); - $data->foo = 'not bar'; - outagelib::data2object($data, $obj); - self::assertEquals(get_object_vars($obj), ['foo' => 'not bar', 'number' => 42], 'Invalid result.'); - self::assertEquals(get_object_vars($data), ['foo' => 'not bar'], 'Data should not change.'); - - // Using array data, with new fields. - $obj = new stdClass(); - $obj->foo = 'bar'; - $obj->number = 42; - $data = ['foo' => 'foobar', 'flag' => false]; - outagelib::data2object($data, $obj); - self::assertEquals(get_object_vars($obj), ['foo' => 'foobar', 'number' => 42], 'Invalid result.'); - } -} diff --git a/tests/phpunit.xml b/tests/phpunit.xml new file mode 100644 index 0000000..7466503 --- /dev/null +++ b/tests/phpunit.xml @@ -0,0 +1,31 @@ + + + + + + ./ + + + + + + ../classes + + + diff --git a/version.php b/version.php index 6a77e7d..faadccf 100644 --- a/version.php +++ b/version.php @@ -25,12 +25,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. -} +defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016090500; // The current plugin version (Date: YYYYMMDDXX). -$plugin->release = $plugin->version; // Same as version -$plugin->requires = 2014051200; // Requires Moodle 2.7 or later. $plugin->component = "auth_outage"; -$plugin->maturity = MATURITY_ALPHA; // Not suitable for PRODUCTION environments yet! +$plugin->version = 2016092207; // The current plugin version (Date: YYYYMMDDXX). +$plugin->release = 'Build '.$plugin->version; // Human-readable release information. +$plugin->requires = 2014051200; // Requires Moodle 2.7 or later. +$plugin->maturity = MATURITY_ALPHA; // Not suitable for PRODUCTION environments yet! diff --git a/views/info/content.php b/views/info/content.php new file mode 100644 index 0000000..f99f2bd --- /dev/null +++ b/views/info/content.php @@ -0,0 +1,76 @@ +. + +/** + * View included by the renderer to output the outage information page. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +if ($viewbag['admin']) { + $adminlinks = []; + foreach ([ + 'startofwarning' => -$viewbag['outage']->get_warning_duration(), + '15secondsbefore' => -15, + 'start' => 0, + 'endofoutage' => $viewbag['outage']->get_duration_planned() - 1, + ] as $title => $delta) { + $adminlinks[] = html_writer::link( + new moodle_url( + '/auth/outage/info.php', + [ + 'id' => $viewbag['outage']->id, + 'auth_outage_preview' => $viewbag['outage']->id, + 'auth_outage_delta' => $delta, + ] + ), + get_string('info'.$title, 'auth_outage') + ); + } + + $admineditlink = html_writer::link( + new moodle_url('/auth/outage/edit.php', ['id' => $viewbag['outage']->id]), + get_string('outageedit', 'auth_outage') + ); +} +?> + +
+ +
+ + starttime, get_string('datetimeformat', 'auth_outage')); ?> +
+
+ + stoptime, get_string('datetimeformat', 'auth_outage')); ?> +
+
get_description(); ?>
+ + + + + +
diff --git a/views/info/static.php b/views/info/static.php new file mode 100644 index 0000000..8a14411 --- /dev/null +++ b/views/info/static.php @@ -0,0 +1,72 @@ +. + +/** + * View included by the renderer to output the static outage information page. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\output\renderer; + +defined('MOODLE_INTERNAL') || die(); + +global $SITE; + +// The Meta Refresh will ensure the page keeps refreshing every 5 minutes until outage is over. +?> + + + + <?php echo strip_tags($SITE->fullname); ?> + + + + + + + +render_warningbar($viewbag['outage'], $viewbag['outage']->starttime, false, false); +?> + +
+

fullname); ?>

+
+ +
+

get_title(); ?>

+ render_view('info/content.php', $viewbag); ?> +
+ + + + + diff --git a/views/warningbar/warningbar.css b/views/warningbar/warningbar.css new file mode 100644 index 0000000..6b4d497 --- /dev/null +++ b/views/warningbar/warningbar.css @@ -0,0 +1,104 @@ +/* +This file is used as default value for the 'auth_outage_css' settings. +If you need to make changes here, remember to update your settings inside Moodle. +*/ + +#auth_outage_warningbar_box { + background-color: red; + box-sizing: content-box; + color: white; + height: 90px; + left: 0; + padding: 0; + position: fixed; + text-align: center; + top: 0; + transition: background 3s ease-out; + width: 100%; + z-index: 9999; +} + +#auth_outage_warningbar_box.auth_outage_warning_period { + background: repeating-linear-gradient( + -45deg, + #ff7c00, + #ff7c00 10px, + #ff6c00 10px, + #ff6c00 20px + ); + background-color: #ff7c00; +} + +#auth_outage_warningbar_box.auth_outage_imminent_period { + background: repeating-linear-gradient( + -45deg, + #a000a0, + #a000a0 10px, + #800080 10px, + #800080 20px + ); + background-color: #800080; +} + +#auth_outage_warningbar_box.auth_outage_ongoing_period { + background: repeating-linear-gradient( + -45deg, + #ee0000, + #ee0000 10px, + #cc0000 10px, + #cc0000 20px + ); + background-color: #ee0000; +} + +#auth_outage_warningbar_box.auth_outage_finished_period { + background: repeating-linear-gradient( + -45deg, + #00aa00, + #00aa00 10px, + #009900 10px, + #009900 20px + ); + background-color: #009900; +} + +.auth_outage_warningbar_center { + margin-top: -45px; + position: relative; + top: 50%; +} + +#auth_outage_warningbar_message { + font-size: 200%; + font-weight: bold; + margin: 10px 0; +} + +a.auth_outage_warningbar_box_title { + color: white; +} + +a.auth_outage_warningbar_box_finish { + background-color: white; + border: 1px solid black; + border-radius: 5px; + color: darkgray; + font-weight: bold; + margin-left: 10px; + padding-left: 5px; + padding-right: 10px; + text-decoration: none; + transition: background-color 200ms linear; +} + +a.auth_outage_warningbar_box_finish:hover { + background-color: black; +} + +.navbar.navbar-fixed-top { + top: 90px; +} + +.auth_outage_warningbar_spacer { + height: 80px; +} diff --git a/views/warningbar/warningbar.js b/views/warningbar/warningbar.js new file mode 100644 index 0000000..c359e4c --- /dev/null +++ b/views/warningbar/warningbar.js @@ -0,0 +1,122 @@ +var auth_outage_warningbar = { + init: function (params) { + this.preview = params.preview; + this.countdown = params.countdown; + this.ongoing = params.ongoing; + this.backonline = params.backonline; + this.backonlinedescription = params.backonlinedescription; + this.servertime = params.servertime; + this.checkfinishedurl = params.checkfinishedurl; + this.starts = params.starts; + this.stops = params.stops; + this.clienttime = Date.now(); + this.finished = false; + this.divtext = document.getElementById('auth_outage_warningbar_message'); + this.divtitle = document.getElementById('auth_outage_warningbar_title'); + this.divblock = document.getElementById('auth_outage_warningbar_box'); + this.finishbutton = document.getElementById('auth_outage_warningbar_button'); + this.startWarning(); + }, + + startWarning: function () { + if (this.finishbutton) { + this.finishbutton.style.display = 'none'; + } + this.divblock.className = 'auth_outage_warning_period'; + this.tickWarning(); + }, + + tickWarning: function () { + var elapsed = Math.round((Date.now() - this.clienttime) / 1000); + var missing = (this.starts - this.servertime) - elapsed; + + if (missing <= 0) { + this.startOngoing(); + } else { + if (missing <= 10) { + this.divblock.className = 'auth_outage_imminent_period'; + } + this.divtext.innerHTML = this.countdown.replace('{{countdown}}', this.seconds2hms(missing)); + + var $this = this; + setTimeout(function () { + $this.tickWarning(); + }, 1000); + } + }, + + startOngoing: function () { + this.divblock.className = 'auth_outage_ongoing_period'; + if (this.finishbutton) { + this.finishbutton.style.display = ''; + } + this.divtext.innerHTML = this.ongoing; + this.tickOngoing(); + }, + + tickOngoing: function () { + if (this.finished) { + return; + } + + if (this.preview) { + // If one second before finish time, enfore finish. Otherwise, never finish it. + if (this.servertime === this.stops - 1) { + this.finish(); + } + else { + return; + } + } + + var $this = this; + + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onreadystatechange = function () { + $this.ajaxCheckFinished(this); + }; + xmlhttp.open("GET", this.checkfinishedurl, true); + xmlhttp.send(); + + setTimeout(function () { + $this.tickOngoing(); + }, (5 * 60 * 1000)); // Check every 5 minutes. + }, + + ajaxCheckFinished: function (ajax) { + if (ajax.readyState === XMLHttpRequest.DONE) { + if (ajax.status === 200) { + if (ajax.responseText.trim() === 'finished') { + this.finish(); + } + } + } + }, + + finish: function () { + this.divblock.className = 'auth_outage_finished_period'; + if (this.finishbutton) { + this.finishbutton.style.display = 'none'; + } + this.divtext.innerHTML = this.backonline; + this.divtitle.innerHTML = this.backonlinedescription; + }, + + seconds2hms: function (seconds) { + var minutes = Math.floor(seconds / 60); + var hours = Math.floor(minutes / 60); + seconds %= 60; + minutes %= 60; + // Cross-browser simple solution for padding zeroes. + if (minutes < 10) { + minutes = "0" + minutes; + } + if (seconds < 10) { + seconds = "0" + seconds; + } + return hours + ':' + minutes + ':' + seconds; + } +}; + +// auth_outage_countdown is used outside this js file. +/* jshint unused:false */ diff --git a/views/warningbar/warningbar.php b/views/warningbar/warningbar.php new file mode 100644 index 0000000..69207be --- /dev/null +++ b/views/warningbar/warningbar.php @@ -0,0 +1,93 @@ +. + +/** + * View included by the renderer to output the outage warning bar. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright 2016 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\local\outagelib; + +defined('MOODLE_INTERNAL') || die(); + +global $OUTPUT; + +$start = userdate($viewbag['outage']->starttime, get_string('datetimeformat', 'auth_outage')); +$stop = userdate($viewbag['outage']->stoptime, get_string('datetimeformat', 'auth_outage')); + +$countdown = get_string('messageoutagewarning', 'auth_outage', ['start' => $start, 'stop' => $stop]); +$ongoing = get_string('messageoutageongoing', 'auth_outage', ['start' => $start, 'stop' => $stop]); +$message = $viewbag['outage']->is_ongoing($viewbag['time']) ? $ongoing : ''; + +$infolink = new moodle_url('/auth/outage/info.php', ['id' => $viewbag['outage']->id]); + +$title = $viewbag['outage']->get_title(); +if (!$viewbag['static']) { + $title = html_writer::link( + $infolink, + $title, + ['target' => '_blank', 'class' => 'auth_outage_warningbar_box_title'] + ); + if (is_siteadmin()) { + $url = new moodle_url('/auth/outage/finish.php', ['id' => $viewbag['outage']->id]); + $text = html_writer::empty_tag('img', [ + 'src' => $OUTPUT->pix_url('t/check'), + 'alt' => get_string('finish', 'auth_outage'), + 'class' => 'iconsmall', + ]).' '.get_string('finish', 'auth_outage'); + $attr = [ + 'title' => get_string('finish', 'auth_outage'), + 'class' => 'auth_outage_warningbar_box_finish', + ]; + $title .= ' '.html_writer::span(html_writer::link($url, $text, $attr), '', ['id' => 'auth_outage_warningbar_button']); + } +} + +echo html_writer::tag('style', outagelib::get_config()->css); +?> + +
+
+
+
+
+
+ +
 
+ + + +