diff --git a/classes/outagedb.php b/classes/outagedb.php index 7d43f90..23fd9cc 100644 --- a/classes/outagedb.php +++ b/classes/outagedb.php @@ -120,6 +120,9 @@ class outagedb { self::calendar_update($outage); } + // Trigger static page update. + outagelib::updatestaticinfopagefile(); + // All done, return the id. return $outage->id; } @@ -149,6 +152,9 @@ class outagedb { // Delete it and remove from calendar. $DB->delete_records('auth_outage', ['id' => $id]); self::calendar_delete($id); + + // Trigger static page update. + outagelib::updatestaticinfopagefile(); } /** @@ -276,6 +282,37 @@ class outagedb { 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. + */ + public static function get_next_starting($time = null) { + global $DB; + + if ($time === null) { + $time = time(); + } + if (!is_int($time) || ($time <= 0)) { + throw new InvalidArgumentException('$time must be null or an positive int.'); + } + + $select = ':datetime <= starttime'; // End condition. + $data = $DB->get_records_select( + 'auth_outage', + $select, + ['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)); + } + /** * 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 4ee3275..5a84b44 100644 --- a/classes/outagelib.php +++ b/classes/outagelib.php @@ -16,8 +16,10 @@ namespace auth_outage; +use auth_outage\models\outage; use auth_outage_renderer; use Exception; +use InvalidArgumentException; use moodle_url; defined('MOODLE_INTERNAL') || die(); @@ -118,4 +120,81 @@ class outagelib { 'css' => file_get_contents($CFG->dirroot . '/auth/outage/views/warningbar.css'), ]; } + + /** + * 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 Exception + */ + public static function savestaticinfopage(outage $outage, $file) { + if (!is_string($file)) { + throw new InvalidArgumentException('$file is not a string.'); + } + + $html = self::get_renderer()->renderoutagepagestatic($outage); + + // Sanity check before writing/overwriting old file. + if (!is_string($html) || ($html == '')) { + throw new Exception('Sanity check failed. Invalid contents on $html.'); + } + + $dir = dirname($file); + if (!file_exists($dir) || !is_dir($dir)) { + throw new 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 + * @throws Exception + */ + public static function updatestaticinfopagefile($file = null) { + if (is_null($file)) { + $file = self::get_defaulttemplatefile(); + } + if (!is_string($file)) { + throw new InvalidArgumentException('$file is not a string.'); + } + + $outage = outagedb::get_next_starting(); + if (is_null($outage)) { + if (file_exists($file)) { + if (is_file($file)) { + unlink($file); + } else { + throw new Exception('Cannot remove non-file: ' . $file); + } + } + } else { + self::savestaticinfopage($outage, $file); + } + } + + /** + * Given the HTML code for the static page, find the outage id for that page. + * @param $html Static info page HTML. + * @return int|null Outage id or null if not found. + */ + public static function get_outageidfrominfopage($html) { + if (!is_string($html)) { + throw new InvalidArgumentException('$html must be a string.'); + } + + $output = []; + if (preg_match('/data-outage-id="(?P\d+)"/', $html, $output)) { + return (int)$output['id']; + } + return null; + } + + /** + * @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'; + } } diff --git a/info.php b/info.php index 046c0bc..7862f79 100644 --- a/info.php +++ b/info.php @@ -34,16 +34,19 @@ if (is_null($outage)) { redirect(new moodle_url('/')); } -$PAGE->set_context(context_system::instance()); -$PAGE->set_title($outage->get_title()); -$PAGE->set_heading($outage->get_title()); -$PAGE->set_url(new \moodle_url('/auth/outage/info.php')); +if (optional_param('static', false, PARAM_BOOL)) { + echo outagelib::get_renderer()->renderoutagepagestatic($outage); +} else { + $PAGE->set_title($outage->get_title()); + $PAGE->set_heading($outage->get_title()); + $PAGE->set_url(new \moodle_url('/auth/outage/info.php')); -// No hooks injecting into this page, do it manually. -outagelib::inject(); + // No hooks injecting into this page, do it manually. + outagelib::inject(); -echo $OUTPUT->header(); + echo $OUTPUT->header(); -echo outagelib::get_renderer()->renderoutagepage($outage); + echo outagelib::get_renderer()->renderoutagepage($outage); -echo $OUTPUT->footer(); + echo $OUTPUT->footer(); +} diff --git a/lang/en/auth_outage.php b/lang/en/auth_outage.php index e937d52..77338a6 100644 --- a/lang/en/auth_outage.php +++ b/lang/en/auth_outage.php @@ -77,6 +77,7 @@ $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['messageoutageongoing'] = 'Back online at {$a->stop}.'; diff --git a/renderer.php b/renderer.php index 1978587..dd319c7 100644 --- a/renderer.php +++ b/renderer.php @@ -37,7 +37,7 @@ class auth_outage_renderer extends plugin_renderer_base { */ public function rendersubtitle($subtitlekey) { if (!is_string($subtitlekey)) { - throw new \InvalidArgumentException('$subtitle is not a string.'); + throw new InvalidArgumentException('$subtitle is not a string.'); } return html_writer::tag('h2', get_string($subtitlekey, 'auth_outage')); } @@ -186,8 +186,9 @@ class auth_outage_renderer extends plugin_renderer_base { } /** - * @param outage $outage - * @param null $time + * Renders the outage page. + * @param outage $outage Outage to be rendered. + * @param null $time Time to use as refence. Null for current time. * @return string * @SuppressWarnings("unused") because $admineditlink is used inside require(...) */ @@ -197,8 +198,8 @@ class auth_outage_renderer extends plugin_renderer_base { if (is_null($time)) { $time = time(); } - if (!is_int($time)) { - throw new \InvalidArgumentException('$time is not an int or null.'); + if (!is_int($time) || ($time <= 0)) { + throw new InvalidArgumentException('$time is not an positive int or null.'); } $adminlinks = []; @@ -226,6 +227,8 @@ class auth_outage_renderer extends plugin_renderer_base { get_string('outageedit', 'auth_outage') ); + $static = false; + ob_start(); require($CFG->dirroot . '/auth/outage/views/infopage.php'); $html = ob_get_contents(); @@ -233,6 +236,26 @@ class auth_outage_renderer extends plugin_renderer_base { return $html; } + /** + * Generates the HTML for a static info page. + * @param outage $outage Outage to generate the page. + * @return string The HTML code. + * @SuppressWarnings("unused") because variables are used in require(...) + */ + public function renderoutagepagestatic(outage $outage) { + global $PAGE, $CFG; + $PAGE->set_context(context_system::instance()); + + $static = true; + $time = $outage->starttime; + + ob_start(); + require($CFG->dirroot . '/auth/outage/views/infopagestatic.php'); + $html = ob_get_contents(); + ob_end_clean(); + return $html; + } + /** * Renders the warning bar. * @param outage $outage The outage to show in the warning bar. @@ -246,8 +269,8 @@ class auth_outage_renderer extends plugin_renderer_base { if (is_null($time)) { $time = time(); } - if (!is_int($time)) { - throw new \InvalidArgumentException('$time is not an int or null.'); + if (!is_int($time) || ($time <= 0)) { + throw new InvalidArgumentException('$time is not an positive int or null.'); } $start = userdate($outage->starttime, get_string('datetimeformat', 'auth_outage')); diff --git a/tests/cli/outagelib_test.php b/tests/cli/outagelib_test.php new file mode 100644 index 0000000..2b43f19 --- /dev/null +++ b/tests/cli/outagelib_test.php @@ -0,0 +1,74 @@ +. + +use auth_outage\models\outage; +use auth_outage\outagelib; + +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\outagelib + */ +class outagelib_test extends advanced_testcase { + /** + * Gets a temp file to use in the test. Deleted every time a test starts. + * @return string A temporary file name. + */ + public function get_file() { + return sys_get_temp_dir() . '/phpunit_authoutage.tmp'; + } + + 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()); + } + } + } + + public function test_staticpage() { + $now = time(); + $outage = new outage([ + 'id' => 1, + 'warntime' => $now - 100, + 'starttime' => $now + 100, + 'stoptime' => $now + 200, + 'title' => 'Title', + 'description' => 'Description', + ]); + outagelib::savestaticinfopage($outage, $this->get_file()); + self::assertFileExists($this->get_file()); + + $id = outagelib::get_outageidfrominfopage(file_get_contents($this->get_file())); + self::assertSame($outage->id, $id); + + unlink($this->get_file()); + } + + public function test_getdefaulttemplatefile() { + $file = outagelib::get_defaulttemplatefile(); + self::assertTrue(is_string($file)); + self::assertContains('template', $file); + } +} diff --git a/tests/renderer_test.php b/tests/renderer_test.php new file mode 100644 index 0000000..996a5d7 --- /dev/null +++ b/tests/renderer_test.php @@ -0,0 +1,54 @@ +. + +use auth_outage\models\outage; +use auth_outage\outagelib; + +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_renderer + */ +class renderer_test extends advanced_testcase { + public function test_staticpage() { + global $PAGE; + $this->resetAfterTest(true); + + $PAGE->set_context(context_system::instance()); + $renderer = outagelib::get_renderer(); + $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}}.', + ]); + $html = $renderer->renderoutagepagestatic($outage); + self::assertContains('', $html); + self::assertContains('', $html); + self::assertContains($outage->get_title(), $html); + self::assertContains($outage->get_description(), $html); + self::assertSame($outage->id, outagelib::get_outageidfrominfopage($html)); + } +} diff --git a/views/infopage.php b/views/infopage.php index e5e1084..fc2ae51 100644 --- a/views/infopage.php +++ b/views/infopage.php @@ -39,7 +39,7 @@ if (!defined('MOODLE_INTERNAL')) {
get_description(); ?>
- + -is_ongoing($time)): ?> +is_ongoing($time)): ?>