From 2cfda63922c8b30fadbafbe0f9a753ca201799e4 Mon Sep 17 00:00:00 2001 From: Daniel Thee Roperto Date: Thu, 15 Sep 2016 18:24:36 +1000 Subject: [PATCH] Issue #30 - Add finished flag to mark the real time that an outage was finished. --- classes/forms/outage/delete.php | 13 ---- classes/forms/outage/finish.php | 47 ++++++++++++ classes/models/outage.php | 131 ++++++++++++++++++++++++-------- classes/outagedb.php | 54 ++++++++++--- classes/outagelib.php | 30 +------- classes/tables/manage.php | 56 +++++++++----- db/install.xml | 3 +- finish.php | 59 ++++++++++++++ lang/en/auth_outage.php | 4 + manage.php | 2 +- renderer.php | 72 ++++++++++++------ tests/outage_test.php | 66 ++++++++++++++++ tests/outagedb_test.php | 84 +++++++++++++------- tests/outagelib_test.php | 50 ------------ version.php | 2 +- views/warningbar.css | 19 ++++- views/warningbar.php | 26 ++++++- 17 files changed, 508 insertions(+), 210 deletions(-) create mode 100644 classes/forms/outage/finish.php create mode 100644 finish.php delete mode 100644 tests/outagelib_test.php diff --git a/classes/forms/outage/delete.php b/classes/forms/outage/delete.php index 2928912..fdaf5ef 100644 --- a/classes/forms/outage/delete.php +++ b/classes/forms/outage/delete.php @@ -43,17 +43,4 @@ class delete extends \moodleform { $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; - } } diff --git a/classes/forms/outage/finish.php b/classes/forms/outage/finish.php new file mode 100644 index 0000000..8ea525f --- /dev/null +++ b/classes/forms/outage/finish.php @@ -0,0 +1,47 @@ +. + +namespace auth_outage\forms\outage; + +use moodleform; + +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 finish confirmation 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 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/models/outage.php b/classes/models/outage.php index 4fec627..9ebd2ba 100644 --- a/classes/models/outage.php +++ b/classes/models/outage.php @@ -16,7 +16,7 @@ namespace auth_outage\models; -use auth_outage\outagelib; +use InvalidArgumentException; /** * Outage class with all information about one specific outage. @@ -27,6 +27,31 @@ use auth_outage\outagelib; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class outage { + /** + * Outage is before warning period. + */ + const STAGE_WAITING = 1; + + /** + * Outage not started but in warning period. + */ + const STAGE_WARNING = 2; + + /** + * Outage ongoing, it has passed the warning period. + */ + const STAGE_ONGOING = 3; + + /** + * Outage finished, it is after the marked finished time. + */ + const STAGE_FINISHED = 4; + + /** + * Outage stopped, it is after the stop time and not marked as finished. + */ + const STAGE_STOPPED = 4; + /** * @var int Outage ID (auto generated by the DB). */ @@ -47,6 +72,11 @@ class outage { */ public $warntime = null; + /** + * @var int|null Finished timestamp, null if not marked as finished yet. + */ + public $finished = null; + /** * @var string Short description of the outage (no HTML). */ @@ -75,25 +105,59 @@ class outage { /** * outage constructor. * @param object|array|null The data for the outage. + * @throws InvalidArgumentException */ public function __construct($data = null) { if (is_null($data)) { return; } - - if (is_object($data) || is_array($data)) { - outagelib::data2object($data, $this); - - // Adjust field types as needed. - $fields = ['createdby', 'id', 'lastmodified', 'modifiedby', 'starttime', 'stoptime', 'warntime']; - foreach ($fields as $f) { - $this->$f = ($this->$f === null) ? null : (int)$this->$f; - } - - return; + if (is_object($data)) { + $data = (array)$data; + } + if (!is_array($data)) { + throw new InvalidArgumentException('$data is not an object, an array or null.'); } - throw new \InvalidArgumentException('$data must be null (default), an array or an object.'); + // Load data from array. + foreach ($data as $k => $v) { + if (property_exists(self::class, $k)) { + $this->$k = $v; + } + } + + // 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; + } + } + + /** + * 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. + */ + public function get_stage($time = null) { + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new InvalidArgumentException('$time must be an positive int.'); + } + + 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; } /** @@ -102,17 +166,13 @@ class outage { * @return bool True if outage is ongoing or during the warning period. */ public function is_active($time = null) { - if ($time === null) { - $time = time(); + switch ($this->get_stage($time)) { + case self::STAGE_WARNING: + case self::STAGE_ONGOING: + return true; + default: + return false; } - if (!is_int($time) || ($time <= 0)) { - throw new \InvalidArgumentException('$time must be an positive int.'); - } - if (is_null($this->warntime) || is_null($this->stoptime)) { - return false; - } - - return (($this->warntime <= $time) && ($time < $this->stoptime)); } /** @@ -121,17 +181,22 @@ class outage { * @return bool True if outage has started but not yet stopped. False otherwise including if in warning period. */ public function is_ongoing($time = null) { - if ($time === null) { - $time = time(); - } - if (!is_int($time) || ($time <= 0)) { - throw new \InvalidArgumentException('$time must be an positive int.'); - } - if (is_null($this->starttime) || is_null($this->stoptime)) { - return false; - } + return ($this->get_stage($time) == self::STAGE_ONGOING); + } - return (($this->starttime <= $time) && ($time < $this->stoptime)); + /** + * 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; + } } /** diff --git a/classes/outagedb.php b/classes/outagedb.php index 81557b1..f15e505 100644 --- a/classes/outagedb.php +++ b/classes/outagedb.php @@ -171,10 +171,12 @@ class outagedb { throw new InvalidArgumentException('$time must be null or an int.'); } + $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', - '(warntime <= :datetime1 AND stoptime >= :datetime2)', - ['datetime1' => $time, 'datetime2' => $time], + $select, + ['datetime1' => $time, 'datetime2' => $time, 'datetime3' => $time], 'starttime ASC, stoptime DESC, title ASC', '*', 0, @@ -187,11 +189,11 @@ class outagedb { } /** - * Gets all future outages not in warning period. + * Gets all outages that have not ended yet. * @param int|null $time Timestamp considered to check for outages, null for current date/time. - * @return array An array of outages or an empty array if no future outage found. + * @return array An array of outages or an empty array if no unded outages were found. */ - public static function get_all_future($time = null) { + public static function get_all_unended($time = null) { global $DB; if ($time === null) { @@ -205,8 +207,8 @@ class outagedb { $rs = $DB->get_recordset_select( 'auth_outage', - 'stoptime >= :datetime', - ['datetime' => $time], + ':datetime1 < stoptime AND (finished IS NULL OR :datetime2 < finished)', + ['datetime1' => $time, 'datetime2' => $time], 'starttime ASC, stoptime DESC, title ASC', '*'); foreach ($rs as $r) { @@ -218,11 +220,11 @@ class outagedb { } /** - * Gets all past outages. + * Gets all ended outages. * @param int|null $time Timestamp considered to check for outages, null for current date/time. - * @return array An array of outages or an empty array if no past outage found. + * @return array An array of outages or an empty array if no ended outages found. */ - public static function get_all_past($time = null) { + public static function get_all_ended($time = null) { global $DB; if ($time === null) { @@ -236,8 +238,8 @@ class outagedb { $rs = $DB->get_recordset_select( 'auth_outage', - 'stoptime < :datetime', - ['datetime' => $time], + ':datetime1 >= stoptime OR (finished IS NOT NULL AND :datetime2 >= finished)', + ['datetime1' => $time, 'datetime2' => $time], 'stoptime DESC, starttime DESC, title ASC', '*'); foreach ($rs as $r) { @@ -248,6 +250,34 @@ class outagedb { 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. + */ + public static function finish($id, $time = null) { + if (is_null($time)) { + $time = time(); + } + if (!is_int($time)) { + throw new InvalidArgumentException('$time must be an int or null.'); + } + + $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); + } + /** * Create an event on the calendar for this outage. * @param outage $outage Outage to be added to the calendar. diff --git a/classes/outagelib.php b/classes/outagelib.php index 3d00819..1dce55c 100644 --- a/classes/outagelib.php +++ b/classes/outagelib.php @@ -17,6 +17,7 @@ namespace auth_outage; use auth_outage_renderer; +use moodle_url; if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. @@ -40,7 +41,7 @@ class outagelib { public static function pagesetup() { global $PAGE; admin_externalpage_setup('auth_outage_manage'); - $PAGE->set_url(new \moodle_url('/auth/outage/manage.php')); + $PAGE->set_url(new moodle_url('/auth/outage/manage.php')); return self::get_renderer(); } @@ -87,31 +88,4 @@ class outagelib { $CFG->additionalhtmltopofbody = self::get_renderer()->renderoutagebar($active, $time) . $CFG->additionalhtmltopofbody; } - - /** - * 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; - } - } - } } diff --git a/classes/tables/manage.php b/classes/tables/manage.php index 8b1ac37..6126cb0 100644 --- a/classes/tables/manage.php +++ b/classes/tables/manage.php @@ -18,6 +18,8 @@ namespace auth_outage\tables; use auth_outage\models\outage; use flexible_table; +use html_writer; +use moodle_url; require_once($CFG->libdir . '/tablelib.php'); @@ -42,12 +44,13 @@ class manage extends flexible_table { $id = (is_null($id) ? self::$autoid++ : $id); parent::__construct('auth_outage_manage_' . $id); - $this->define_columns(['starttime', 'stopsafter', 'warnbefore', 'title', '']); + $this->define_columns(['starttime', 'stopsafter', 'warnbefore', 'finished', 'title', '']); $this->define_headers([ get_string('tableheaderwarnbefore', 'auth_outage'), get_string('tableheaderstarttime', 'auth_outage'), get_string('tableheaderstopsafter', 'auth_outage'), + get_string('tableheaderfinishedat', 'auth_outage'), get_string('tableheadertitle', 'auth_outage'), get_string('actions'), ] @@ -60,7 +63,7 @@ class manage extends flexible_table { /** * Sets the data of the table. - * @param array $outages An array with outage objects. + * @param outage[] $outages An array with outage objects. * @param bool $editdelete If it should display the edit and delete button. */ public function set_data(array $outages, $editdelete) { @@ -71,17 +74,21 @@ class manage extends flexible_table { foreach ($outages as $outage) { $title = $outage->get_title(); if ($editdelete) { - $title = \html_writer::link( - new \moodle_url('/auth/outage/edit.php', ['id' => $outage->id]), + $title = html_writer::link( + new moodle_url('/auth/outage/edit.php', ['id' => $outage->id]), $title, ['title' => get_string('edit')] ); } + $finished = $outage->finished; + $finished = is_null($finished) ? '-' : userdate($finished, get_string('datetimeformat', 'auth_outage')); + $this->add_data([ format_time($outage->get_warning_duration()), userdate($outage->starttime, get_string('datetimeformat', 'auth_outage')), format_time($outage->get_duration()), + $finished, $title, $this->set_data_buttons($outage, $editdelete), ]); @@ -99,9 +106,9 @@ class manage extends flexible_table { $buttons = ''; // View button. - $buttons .= \html_writer::link( - new \moodle_url('/auth/outage/info.php', ['id' => $outage->id]), - \html_writer::empty_tag('img', [ + $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', @@ -113,11 +120,11 @@ class manage extends flexible_table { ] ); - // Edit button. + // 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', [ + $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' @@ -127,9 +134,9 @@ class manage extends flexible_table { } // Clone button. - $buttons .= \html_writer::link( - new \moodle_url('/auth/outage/clone.php', ['id' => $outage->id]), - \html_writer::empty_tag('img', [ + $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', @@ -138,11 +145,24 @@ class manage extends flexible_table { ['title' => get_string('clone', 'auth_outage')] ); - // Delete button. + // 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', [ + $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' diff --git a/db/install.xml b/db/install.xml index 7f6326b..3391324 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -15,6 +15,7 @@ + diff --git a/finish.php b/finish.php new file mode 100644 index 0000000..9826e80 --- /dev/null +++ b/finish.php @@ -0,0 +1,59 @@ +. + +/** + * Mark an outage as finished. + * + * @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\outagedb; +use auth_outage\outagelib; + +require_once('../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); +require_once($CFG->libdir . '/formslib.php'); + +$renderer = outagelib::pagesetup(); + +$mform = new \auth_outage\forms\outage\finish(); +if ($mform->is_cancelled()) { + redirect('/auth/outage/manage.php'); +} else if ($fromform = $mform->get_data()) { + outagedb::finish($fromform->id); + redirect('/auth/outage/manage.php'); +} + +$id = required_param('id', PARAM_INT); +$outage = outagedb::get_by_id($id); +if ($outage == null) { + throw new invalid_parameter_exception('Outage #' . $id . ' not found.'); +} + +$dataid = new stdClass(); +$dataid->id = $outage->id; +$mform->set_data($dataid); + +echo $OUTPUT->header(); + +echo $renderer->renderfinishconfirmation($outage); + +$mform->display(); + +echo $OUTPUT->footer(); diff --git a/lang/en/auth_outage.php b/lang/en/auth_outage.php index 058b564..76277d3 100644 --- a/lang/en/auth_outage.php +++ b/lang/en/auth_outage.php @@ -40,6 +40,7 @@ $string['defaultwarningdescriptiondescription'] = 'Default warning message for o $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:'; @@ -59,11 +60,14 @@ $string['outagedeletewarning'] = 'You are about to permanently delete the outage $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['starttime_help'] = 'At which date and time the outage starts, preventing general access to the system.'; +$string['tableheaderfinishedat'] = 'Finished at'; $string['tableheaderstarttime'] = 'Starts on'; $string['tableheaderstopsafter'] = 'Stops after'; $string['tableheaderwarnbefore'] = 'Warns before'; diff --git a/manage.php b/manage.php index ba6bab1..3a05899 100644 --- a/manage.php +++ b/manage.php @@ -33,6 +33,6 @@ $renderer = outagelib::pagesetup(); echo $OUTPUT->header(); -$renderer->renderoutagelist(outagedb::get_all_future(), outagedb::get_all_past()); +$renderer->renderoutagelist(outagedb::get_all_unended(), outagedb::get_all_ended()); echo $OUTPUT->footer(); diff --git a/renderer.php b/renderer.php index dc9552f..0adcf4c 100644 --- a/renderer.php +++ b/renderer.php @@ -15,7 +15,6 @@ // 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. @@ -53,9 +52,21 @@ class auth_outage_renderer extends plugin_renderer_base { . $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 array $outages Outages to list. + * @param array $future Outages to list as planned. + * @param array $past Outages to list as history. */ public function renderoutagelist(array $future, array $past) { global $OUTPUT; @@ -91,6 +102,12 @@ class auth_outage_renderer extends plugin_renderer_base { } } + /** + * 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; @@ -120,30 +137,37 @@ class auth_outage_renderer extends plugin_renderer_base { ); $linkdelete = html_writer::link($url, $img, ['title' => get_string('delete')]); - // TODO use language pack below, solve together with Issue #12. + $finished = $outage->finished; + $finished = is_null($finished) ? '-' : userdate($finished, get_string('datetimeformat', 'auth_outage')); + 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->warntime, '%d %h %Y %l:%M%P') - . 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::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::empty_tag('br') - . ($buttons ? $linkedit . $linkdelete . html_writer::empty_tag('br') : '') - . html_writer::empty_tag('br') + . 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('tableheaderstopsafter', 'auth_outage') . ': ') + . format_time($outage->get_duration()) + ) + . html_writer::div( + html_writer::tag('b', get_string('tableheaderfinishedat', '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/tests/outage_test.php b/tests/outage_test.php index ff2b471..bc0de2c 100644 --- a/tests/outage_test.php +++ b/tests/outage_test.php @@ -114,4 +114,70 @@ class outage_test extends basic_testcase { ]); 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, + 'finishtime' => $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, + 'finishtime' => $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 2eec538..bd35ee1 100644 --- a/tests/outagedb_test.php +++ b/tests/outagedb_test.php @@ -92,6 +92,26 @@ class outagedb_test extends advanced_testcase { 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($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)); + } + /** * Make sure getall brings all entries. */ @@ -176,6 +196,9 @@ class outagedb_test extends advanced_testcase { '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($now, -3, -2, 2, 'An finished outage.', -1); + self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); + $activeid = self::saveoutage($now, -3, -2, 2, 'An ongoing outage.'); self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); @@ -189,97 +212,106 @@ class outagedb_test extends advanced_testcase { self::assertSame($activeid, outagedb::get_active($now)->id, 'Wrong active outage picked.'); } - public function test_getallfuture() { + 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_future($now), 'There should be no future outages at this point.'); + self::assertEquals([], outagedb::get_all_unended($now), 'There should be no future outages at this point.'); self::saveoutage($now, -3, -2, -1, 'A past outage.'); - self::assertEquals([], outagedb::get_all_future($now), 'No future outages yet.'); + self::assertEquals([], outagedb::get_all_unended($now), 'No future outages yet.'); + + self::saveoutage($now, -3, -2, 2, 'A finished outage.', -1); + self::assertEquals([], outagedb::get_all_unended($now), 'No future outages yet.'); $id1 = self::saveoutage($now, 2, 3, 4, 'A future outage.'); self::assertEquals([$id1], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); $id2 = self::saveoutage($now, 1, 4, 5, 'Another future outage.'); self::assertEquals([$id1, $id2], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); $id3 = self::saveoutage($now, 1, 3, 5, 'Yet another future outage.'); self::assertEquals([$id3, $id1, $id2], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); $id4 = self::saveoutage($now, -2, 1, 2, 'An outage in warning period.'); self::assertEquals([$id4, $id3, $id1, $id2], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); $id5 = self::saveoutage($now, -1, 2, 3, 'Another outage in warning period.'); self::assertEquals([$id4, $id5, $id3, $id1, $id2], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); $id6 = self::saveoutage($now, -3, -2, 2, 'An ongoing outage.'); self::assertEquals([$id6, $id4, $id5, $id3, $id1, $id2], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); $id7 = self::saveoutage($now, -3, -1, 1, 'Another ongoing outage.'); self::assertEquals([$id6, $id7, $id4, $id5, $id3, $id1, $id2], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); $id8 = self::saveoutage($now, -3, -2, 1, 'Yet another ongoing outage.'); self::assertEquals([$id6, $id8, $id7, $id4, $id5, $id3, $id1, $id2], - self::createidarray(outagedb::get_all_future($now)), 'Wrong future data.'); + self::createidarray(outagedb::get_all_unended($now)), 'Wrong future data.'); } - public function test_getallpast() { + 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_past($now), 'There should be no future outages at this point.'); + self::assertEquals([], outagedb::get_all_ended($now), 'There should be no future outages at this point.'); self::saveoutage($now, -2, 1, 2, 'An outage in warning period.'); - self::assertEquals([], outagedb::get_all_past($now), 'No past outages yet.'); + self::assertEquals([], outagedb::get_all_ended($now), 'No past outages yet.'); self::saveoutage($now, -3, -2, 2, 'An ongoing outage.'); - self::assertEquals([], outagedb::get_all_past($now), 'No past outages yet.'); + self::assertEquals([], outagedb::get_all_ended($now), 'No past outages yet.'); self::saveoutage($now, 2, 3, 4, 'A future outage.'); - self::assertEquals([], outagedb::get_all_past($now), 'No past outages yet.'); + self::assertEquals([], outagedb::get_all_ended($now), 'No past outages yet.'); $id1 = self::saveoutage($now, -8, -6, -4, 'A past outage.'); self::assertEquals([$id1], - self::createidarray(outagedb::get_all_past($now)), 'Wrong past data.'); + self::createidarray(outagedb::get_all_ended($now)), 'Wrong past data.'); $id2 = self::saveoutage($now, -8, -7, -5, 'Another past outage.'); self::assertEquals([$id1, $id2], - self::createidarray(outagedb::get_all_past($now)), 'Wrong past data.'); + self::createidarray(outagedb::get_all_ended($now)), 'Wrong past data.'); $id3 = self::saveoutage($now, -8, -5, -3, 'Yet another past outage.'); self::assertEquals([$id3, $id1, $id2], - self::createidarray(outagedb::get_all_past($now)), 'Wrong past data.'); + self::createidarray(outagedb::get_all_ended($now)), 'Wrong past data.'); + + $id4 = self::saveoutage($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 then save it to the database. * - * @param $now int Timestamp for now, such as time(). - * @param $warning int In how many hours the warning starts. Can be negative. - * @param $start int In how many hours this outage starts. Can be negative. - * @param $stop int In how many hours this outage finishes. Can be negative. - * @param $title string Title for the outage. + * @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($now, $warning, $start, $stop, $title) { + private static function saveoutage($now, $warning, $start, $stop, $title, $finished = null) { return outagedb::save(new outage([ + 'warntime' => $now + ($warning * 60 * 60), 'starttime' => $now + ($start * 60 * 60), 'stoptime' => $now + ($stop * 60 * 60), - 'warntime' => $now + ($warning * 60 * 60), + 'finished' => is_null($finished) ? null : ($now + ($finished * 60 * 60)), 'title' => $title, 'description' => 'Test Outage Description.' ])); diff --git a/tests/outagelib_test.php b/tests/outagelib_test.php deleted file mode 100644 index 4a08b5f..0000000 --- a/tests/outagelib_test.php +++ /dev/null @@ -1,50 +0,0 @@ -. - -use auth_outage\outagelib; - -defined('MOODLE_INTERNAL') || die(); - -/** - * 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 - */ -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/version.php b/version.php index c7db839..44662e6 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. } -$plugin->version = 2016090900; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2016091500; // 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"; diff --git a/views/warningbar.css b/views/warningbar.css index 5f1e410..acb7c53 100644 --- a/views/warningbar.css +++ b/views/warningbar.css @@ -34,10 +34,27 @@ If you need to make changes here, remember to update your settings inside Moodle margin: 10px 0; } -.auth_outage_warningbar_box_message A { +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; } diff --git a/views/warningbar.php b/views/warningbar.php index 67fc604..45c0a23 100644 --- a/views/warningbar.php +++ b/views/warningbar.php @@ -27,6 +27,8 @@ if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. } +global $OUTPUT; + $infolink = new moodle_url('/auth/outage/info.php', ['id' => $outage->id]); echo html_writer::tag('style', get_config('auth_outage', 'css')); @@ -35,8 +37,28 @@ echo html_writer::tag('style', get_config('auth_outage', 'css'));
-
- get_title(), ['target' => '_blank']); ?> +
+ get_title(), + ['target' => '_blank', 'class' => 'auth_outage_warningbar_box_title'] + ); + + if (is_siteadmin() && $outage->is_ongoing()) { + $url = new moodle_url('/auth/outage/finish.php', ['id' => $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' + ]; + echo html_writer::link($url, $text, $attr); + } + ?>