Issue #22 - Infopage now is not responsible for generating a static page anymore, hooked into maintenance_static_page.

This commit is contained in:
Daniel Thee Roperto
2016-11-09 14:35:56 +11:00
parent 146ad61ede
commit afd17994fe
9 changed files with 105 additions and 485 deletions

View File

@@ -28,11 +28,8 @@ 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();
@@ -51,11 +48,6 @@ class infopage {
*/
private $outage;
/**
* @var bool Flags if a static version of this page should be displayed (maintenance mode).
*/
private $static;
/**
* infopage_controller constructor.
* @param array $params Parameters to use or null to get from Moodle API (request).
@@ -64,14 +56,12 @@ class infopage {
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);
}
@@ -79,98 +69,6 @@ class infopage {
$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.
* @throws coding_exception
*/
public static function find_outageid_from_infopage($html) {
if (!is_string($html)) {
throw new coding_exception('$html must be a string.', gettype($html));
}
$output = [];
if (preg_match('/data-outage-id="(?P<id>\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();
self::save_static_page_sanitycheck($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 outage|null $outage Outage or null if no scheduled outage.
* @param string|null $file File to update. Null to use default.
* @throws coding_exception
* @throws file_exception
*/
public static function update_static_page($outage, $file = null) {
if (is_null($file)) {
$file = self::get_defaulttemplatefile();
}
if (!is_string($file)) {
throw new coding_exception('$file is not a string.', $file);
}
if (!is_null($outage) && !($outage instanceof outage)) {
throw new coding_exception('$outage must be null or an outage object.');
}
if (is_null($outage)) {
if (file_exists($file)) {
unlink($file);
}
} else {
self::save_static_page($outage, $file);
}
}
/**
* Gets the default template file to use for static info page.
* @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';
}
/**
* Ensures the data to create the page is valid.
* It should never be invalid, but if it is we will get a blank page while the maintenance is ongoing and the
* system administrators may become frustrated to understand why it is not working, let's not provoke them!
* @param string $html The HTML to check.
* @throws invalid_state_exception
*/
public static function save_static_page_sanitycheck($html) {
if (!is_string($html) || (trim($html) == '') || (trim(html_to_text($html)) == '')) {
throw new invalid_state_exception('Sanity check failed. Invalid contents on $html.');
}
}
/**
* Generates and returns the HTML for the info page.
* @return string HTML for the info page.
@@ -187,14 +85,6 @@ class infopage {
return $output;
}
/**
* 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
@@ -203,11 +93,7 @@ class infopage {
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('/'));
}
redirect(new moodle_url('/'));
}
$viewbag = [
@@ -216,24 +102,19 @@ class infopage {
];
$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'));
$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();
// 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->header();
require($CFG->dirroot.'/auth/outage/views/info/content.php');
// Moodle 2.7 did not check for CLI mode, which was fixed later.
if (!($CFG->branch == '27' && CLI_SCRIPT)) {
echo $OUTPUT->footer();
}
// Moodle 2.7 did not check for CLI mode, which was fixed later.
if (!($CFG->branch == '27' && CLI_SCRIPT)) {
echo $OUTPUT->footer();
}
}
@@ -258,6 +139,5 @@ class infopage {
}
$this->outage = $params['outage'];
$this->static = (bool)$params['static'];
}
}

View File

@@ -28,8 +28,8 @@ namespace auth_outage\local\controllers;
use auth_outage\local\outage;
use coding_exception;
use DOMDocument;
use DOMElement;
use invalid_parameter_exception;
use invalid_state_exception;
use moodle_url;
defined('MOODLE_INTERNAL') || die();
@@ -45,32 +45,49 @@ defined('MOODLE_INTERNAL') || die();
class maintenance_static_page {
/**
* Creates a page based on the given outage.
* @param outage $outage
* @param outage|null $outage
* @return maintenance_static_page
* @throws coding_exception
*/
public static function create_from_outage(outage $outage) {
public static function create_from_outage($outage) {
global $CFG;
$html = file_get_contents($CFG->wwwroot.'/auth/outage/info.php?auth_outage_hide_warning=1&id='.$outage->id);
if (!is_null($outage) && !($outage instanceof outage)) {
throw new coding_exception('$outage must be null or an outage object.');
}
if (is_null($outage)) {
$html = null;
} else if (PHPUNIT_TEST) {
$html = '<html></html>';
} else {
$html = file_get_contents($CFG->wwwroot.'/auth/outage/info.php?auth_outage_hide_warning=1&id='.$outage->id);
}
return self::create_from_html($html);
}
/**
* Creates a page based on the given HTML.
* @param string $html
* @param string|null $html
* @return maintenance_static_page
* @throws coding_exception
*/
public static function create_from_html($html) {
if (!is_string($html)) {
if (!is_null($html) && !is_string($html)) {
throw new coding_exception('$html is not valid.');
}
$dom = new DOMDocument();
if (is_null($html)) {
$dom = null;
} else {
$dom = new DOMDocument();
// Let's assume we have no parsing errors as we cannot rely on a badly-formed page anyway.
libxml_use_internal_errors(true);
$dom->loadHTML($html);
libxml_clear_errors();
// Let's assume we have no parsing errors as we cannot rely on a badly-formed page anyway.
libxml_use_internal_errors(true);
$dom->loadHTML($html);
libxml_clear_errors();
}
return new maintenance_static_page($dom);
}
@@ -83,9 +100,13 @@ class maintenance_static_page {
/**
* maintenance_static_page constructor.
* @param DOMDocument $dom
* @param DOMDocument|null $dom
* @throws coding_exception
*/
public function __construct(DOMDocument $dom) {
public function __construct($dom) {
if (!is_null($dom) && !($dom instanceof DOMDocument)) {
throw new coding_exception('$dom must be null or an DOMDocument object.');
}
$this->dom = $dom;
}
@@ -123,12 +144,21 @@ class maintenance_static_page {
* Generates the page.
*/
public function generate() {
self::prepare_dataroot();
self::remove_script_tags();
self::update_link_stylesheet();
self::update_link_favicon();
self::update_images();
file_put_contents(self::get_template_file(), $this->dom->saveHTML());
self::cleanup();
if (!is_null($this->dom)) {
self::remove_script_tags();
self::update_link_stylesheet();
self::update_link_favicon();
self::update_images();
$html = $this->dom->saveHTML();
if (trim($html) == '') {
// Should never happen, but just in case...
throw new invalid_state_exception('Sanity check failed, $html is empty.');
}
file_put_contents(self::get_template_file(), $html);
}
}
/**
@@ -159,12 +189,20 @@ class maintenance_static_page {
/**
* Clean up the dataroot as needed.
*/
private function prepare_dataroot() {
$dir = self::get_resources_folder();
if (is_dir($dir)) {
self::delete_directory_recursively($dir);
private function cleanup() {
$resources = $this->get_resources_folder();
if (is_dir($resources)) {
self::delete_directory_recursively($resources);
}
$template = $this->get_template_file();
if (is_file($template)) {
unlink($template);
}
if (!is_null($this->dom)) {
mkdir($resources, 0775, true);
}
mkdir($dir, 0775, true);
}
/**

View File

@@ -26,7 +26,7 @@
namespace auth_outage\local;
use auth_outage\dml\outagedb;
use auth_outage\local\controllers\infopage;
use auth_outage\local\controllers\maintenance_static_page;
use auth_outage\output\renderer;
use coding_exception;
use Exception;
@@ -156,7 +156,7 @@ class outagelib {
if (is_null($outage)) {
$outage = outagedb::get_next_starting();
}
infopage::update_static_page($outage);
maintenance_static_page::create_from_outage($outage)->generate();
self::update_climaintenance_code($outage);
self::update_maintenance_later($outage);
}

View File

@@ -22,6 +22,8 @@
* @copyright 2016 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use auth_outage\local\controllers\maintenance_static_page;
defined('MOODLE_INTERNAL') || die();
/**
@@ -36,13 +38,8 @@ function xmldb_auth_outage_uninstall() {
// Delete all outage events.
$DB->delete_records('event', ['eventtype' => 'auth_outage']);
// Delete template file.
$template = auth_outage\local\controllers\infopage::get_defaulttemplatefile();
if (file_exists($template)) {
if (!unlink($template)) {
throw new moodle_exception('Cannot remote maintenance template file: '.$template);
}
}
// Delete files.
maintenance_static_page::create_from_outage(null)->generate();
// Remove 'maintenance later' which could have been set for the next outage.
unset_config('maintenance_later');

View File

@@ -51,7 +51,7 @@ class installation_test extends auth_outage_base_testcase {
global $CFG, $DB;
$this->resetAfterTest();
$this->setAdminUser();
static::setAdminUser();
$dbman = $DB->get_manager();
// Create a future outage with autostart.
@@ -77,7 +77,7 @@ class installation_test extends auth_outage_base_testcase {
// Check ...
self::assertSame(0, $DB->count_records_select('event', "eventtype = 'auth_outage'", null),
'The outage events were not removed.');
self::assertFalse(file_exists(auth_outage\local\controllers\infopage::get_defaulttemplatefile()),
self::assertFalse(file_exists($CFG->dataroot.'/climaintenance.php'),
'The maintenance template file was not deleted.');
self::assertFalse(get_config('moodle', 'maintenance_later'),
'Maintenance later must not be set.'); // Issue #57.

View File

@@ -23,10 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use auth_outage\dml\outagedb;
use auth_outage\local\controllers\infopage;
use auth_outage\local\outage;
use auth_outage\task\update_static_page;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/../../base_testcase.php');
@@ -41,20 +39,6 @@ require_once(__DIR__.'/../../base_testcase.php');
* @SuppressWarnings(public) Allow as many methods as needed.
*/
class infopagecontroller_test extends auth_outage_base_testcase {
/**
* Ensures the template file does not exist when starting a test.
*/
public function setUp() {
$file = infopage::get_defaulttemplatefile();
if (file_exists($file)) {
if (is_file($file)) {
unlink($file);
} else {
self::fail('Invalid temp file: '.$file);
}
}
}
/**
* Tests the constructor.
*/
@@ -95,226 +79,6 @@ class infopagecontroller_test extends auth_outage_base_testcase {
new infopage(['outage' => 'My outage']);
}
/**
* Tests the static page contents.
*/
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 <b>important</b> outage, starting at {{start}}.',
]);
$info = new infopage(['static' => true, 'outage' => $outage]);
$html = $info->get_output();
// Must find...
self::assertContains('<!DOCTYPE html>', $html);
self::assertContains('<meta http-equiv="refresh" content="'.(60 * 60).'">', $html); // Issue #53.
self::assertContains('</html>', $html);
self::assertContains($outage->get_title(), $html);
self::assertContains($outage->get_description(), $html);
// Must not find...
self::assertNotContains('<link ', $html);
self::assertNotContains('<a ', $html);
self::assertNotContains('<script ', $html);
// Ensure it has the id encoded in it...
self::assertSame($outage->id, infopage::find_outageid_from_infopage($html));
}
/**
* Tests the static page file contents.
*/
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',
]);
$file = infopage::get_defaulttemplatefile();
infopage::save_static_page($outage, $file);
self::assertFileExists($file);
$id = infopage::find_outageid_from_infopage(file_get_contents($file));
self::assertSame($outage->id, $id);
unlink($file);
}
/**
* Checks if the default template file is a valid string with the name templage.
*/
public function test_getdefaulttemplatefile() {
$file = infopage::get_defaulttemplatefile();
self::assertTrue(is_string($file));
self::assertContains('template', $file);
}
/**
* Tests infopage::find_outageid_from_infopage() with an invalid parameter.
*/
public function test_findoutageid_notstring() {
$this->set_expected_exception('coding_exception');
infopage::find_outageid_from_infopage(new stdClass());
}
/**
* Tests infopage::find_outageid_from_infopage() when the id is not found.
*/
public function test_findoutageid_notfound() {
self::assertNull(
infopage::find_outageid_from_infopage(
'<html><head><title>Hello world!</title></head><body>Done.</body></html>'
)
);
}
/**
* Tests infopage::save_static_page() with an invalid parameter.
*/
public function test_savestaticpage_filenotstring() {
$this->set_expected_exception('coding_exception');
infopage::save_static_page(new outage(), 1);
}
/**
* Tests infopage::save_static_page() with an invalid filename.
*/
public function test_savestaticpage_fileinvalid() {
global $CFG;
$outage = new outage([
'id' => 1,
'warntime' => time() - 100,
'starttime' => time() + 100,
'stoptime' => time() + 200,
'title' => 'Title',
'description' => 'Description',
]);
$this->set_expected_exception('file_exception');
infopage::save_static_page($outage, $CFG->dataroot.'/someinvalidpath/someinvalidfile');
}
/**
* Tests infopage::test_sanity_notstring() with invalid contents: an int.
*/
public function test_sanity_notstring() {
$this->set_expected_exception('invalid_state_exception');
infopage::save_static_page_sanitycheck(404);
}
/**
* Tests infopage::test_sanity_notstring() with invalid contents: an empty string.
*/
public function test_sanity_empty() {
$this->set_expected_exception('invalid_state_exception');
infopage::save_static_page_sanitycheck(' ');
}
/**
* Tests infopage::test_sanity_notstring() with invalid contents: an empty HTML.
*/
public function test_sanity_clearhtml() {
$this->set_expected_exception('invalid_state_exception');
infopage::save_static_page_sanitycheck('<html><head></head><body><b> <!-- Nothing --> </b></body></html>');
}
/**
* Tests updating the static page.
*/
public function test_updatestaticpage() {
$this->resetAfterTest(true);
self::setAdminUser();
$file = infopage::get_defaulttemplatefile();
$now = time();
$outage = new outage([
'autostart' => false,
'warntime' => $now - 100,
'starttime' => $now + 100,
'stoptime' => $now + 200,
'title' => 'Title',
'description' => 'Description',
]);
$id = outagedb::save($outage);
// The method update_static_page should have been called by save().
self::assertFileExists($file);
$idfound = infopage::find_outageid_from_infopage(file_get_contents($file));
self::assertSame($id, $idfound);
unlink($file);
}
/**
* Tests updating the static page when there is no outage.
*/
public function test_updatestaticpage_nooutage() {
infopage::update_static_page(null);
}
/**
* Tests updating the static page when there is no outage but the file existed before.
*/
public function test_updatestaticpage_hasfile() {
$file = infopage::get_defaulttemplatefile();
touch($file);
self::assertFileExists($file);
infopage::update_static_page(null);
self::assertFileNotExists($file);
}
/**
* Tests updating the static page with an invalid filename.
*/
public function test_updatestaticpage_invalidfile() {
$this->set_expected_exception('coding_exception');
infopage::update_static_page(null, 123);
}
/**
* Tests updating the static page with an invalid outage.
*/
public function test_updatestaticpage_invalidoutage() {
$this->set_expected_exception('coding_exception');
infopage::update_static_page("foobar");
}
/**
* Checks if infopage::has_admin_options() works as expected.
*/
public function test_hasadminoptions() {
$this->resetAfterTest(true);
$static = new infopage(['static' => true]);
$nonstatic = new infopage(['static' => false]);
// Now I am guest.
self::assertFalse(is_siteadmin());
self::assertFalse($static->has_admin_options());
self::assertFalse($nonstatic->has_admin_options());
// Now I am admin.
self::setAdminUser();
self::assertTrue(is_siteadmin());
self::assertFalse($static->has_admin_options());
self::assertTrue($nonstatic->has_admin_options());
}
/**
* Tries to output a static page without a defined outage.
*/
public function test_output_static_nooutage() {
$info = new infopage(['static' => true]);
$this->set_expected_exception('coding_exception');
$info->output();
}
/**
* We should have an exception because CLI cannot redirect.
*/
@@ -342,14 +106,4 @@ class infopagecontroller_test extends auth_outage_base_testcase {
$output = $info->get_output();
self::assertContains('auth_outage_info', $output);
}
/**
* Checks if we can create and execute a task to update outage pages.
*/
public function test_tasks() {
$this->resetAfterTest(true);
$task = new update_static_page();
self::assertNotEmpty($task->get_name());
$task->execute();
}
}

View File

@@ -24,6 +24,7 @@
*/
use auth_outage\local\controllers\maintenance_static_page;
use auth_outage\task\update_static_page;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/../../base_testcase.php');
@@ -139,4 +140,26 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
$generated = trim(file_get_contents($page->get_template_file()));
return $generated;
}
/**
* Checks if we can create and execute a task to update outage pages.
*/
public function test_tasks() {
$this->resetAfterTest(true);
$task = new update_static_page();
self::assertNotEmpty($task->get_name());
$task->execute();
}
/**
* Tests updating the static page when there is no outage but the file existed before.
*/
public function test_updatestaticpage_hasfile() {
global $CFG;
$file = $CFG->dataroot.'/climaintenance.template.html';
touch($file);
self::assertFileExists($file);
maintenance_static_page::create_from_outage(null)->generate();
self::assertFileNotExists($file);
}
}

View File

@@ -409,7 +409,7 @@ EOT;
outagedb::save($outage);
// The method outagelib::prepare_next_outage() should have been called by save().
foreach ([infopage::get_defaulttemplatefile(), $CFG->dataroot.'/climaintenance.php'] as $file) {
foreach ([$CFG->dataroot.'/climaintenance.template.html', $CFG->dataroot.'/climaintenance.php'] as $file) {
self::assertFileExists($file);
unlink($file);
}

View File

@@ -1,72 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* View included by the renderer to output the static outage information page.
*
* @package auth_outage
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @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.
?>
<!DOCTYPE html>
<html data-outage-id="<?php echo $viewbag['outage']->id; ?>">
<head>
<title><?php echo strip_tags($SITE->fullname); ?></title>
<meta http-equiv="refresh" content="<?php echo $viewbag['outage']->get_duration_planned(); ?>">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: sans-serif;
}
</style>
</head>
<body>
<?php
// Static page is rendered as if outage started. It is never rendered as admin or preview mode.
echo renderer::get()->render_warningbar($viewbag['outage'], $viewbag['outage']->starttime, true, false);
?>
<header>
<h1><?php echo strip_tags($SITE->fullname); ?></h1>
</header>
<section>
<h2><?php echo $viewbag['outage']->get_title(); ?></h2>
<?php echo renderer::get()->render_view('info/content.php', $viewbag); ?>
</section>
<!-- <?php echo
get_string(
'infopagestaticgenerated',
'auth_outage',
['time' => userdate(time(), get_string('datetimeformat', 'auth_outage'))]
);
?> -->
</body>
</html>