mirror of
https://github.com/catalyst/moodle-auth_outage.git
synced 2026-05-16 21:41:31 +02:00
Refactored maintenance_static_page.php due to high complexity (more than 50).
This commit is contained in:
@@ -28,10 +28,6 @@ namespace auth_outage\local\controllers;
|
||||
use auth_outage\local\outage;
|
||||
use coding_exception;
|
||||
use DOMDocument;
|
||||
use finfo;
|
||||
use invalid_parameter_exception;
|
||||
use invalid_state_exception;
|
||||
use moodle_url;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -62,7 +58,7 @@ class maintenance_static_page {
|
||||
} else if (PHPUNIT_TEST) {
|
||||
$html = '<html></html>';
|
||||
} else {
|
||||
$data = self::file_get_data($CFG->wwwroot.'/auth/outage/info.php?auth_outage_hide_warning=1&id='.$outage->id);
|
||||
$data = maintenance_static_page_io::file_get_data($CFG->wwwroot.'/auth/outage/info.php?auth_outage_hide_warning=1&id='.$outage->id);
|
||||
$html = $data['contents'];
|
||||
}
|
||||
|
||||
@@ -94,331 +90,30 @@ class maintenance_static_page {
|
||||
return new maintenance_static_page($dom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the contents of the file or URL.
|
||||
* @param string $file File to get.
|
||||
* @return string Contents of $file or an empty string if failed.
|
||||
*/
|
||||
private static function file_get_data($file) {
|
||||
if (self::is_url($file)) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $file);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$contents = curl_exec($curl);
|
||||
$mime = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($curl);
|
||||
} else {
|
||||
$contents = @file_get_contents($file);
|
||||
$mime = (new finfo(FILEINFO_MIME_TYPE))->buffer($contents); // Not perfect, but try guessing it.
|
||||
}
|
||||
if ($contents === false) {
|
||||
debugging('Cannot fetch: '.$file);
|
||||
$contents = '';
|
||||
$mime = 'unknown';
|
||||
}
|
||||
return ['contents' => $contents, 'mime' => $mime];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given string starts with "http://" or "https://".
|
||||
* @param $url
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_url($url) {
|
||||
return (bool)preg_match('#^http(s)?://#', $url);
|
||||
}
|
||||
|
||||
/** @var DOMDocument */
|
||||
protected $dom;
|
||||
|
||||
/** @var bool */
|
||||
protected $preview = false;
|
||||
/** @var maintenance_static_page_generator */
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* maintenance_static_page constructor.
|
||||
* @param DOMDocument|null $dom
|
||||
* @throws coding_exception
|
||||
*/
|
||||
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;
|
||||
protected function __construct($dom) {
|
||||
$io = new maintenance_static_page_io();
|
||||
$this->generator = new maintenance_static_page_generator($dom, $io);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cli maintenance template file location.
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_file() {
|
||||
global $CFG;
|
||||
if ($this->preview) {
|
||||
return $this->get_resources_folder().'/climaintenance.html';
|
||||
} else {
|
||||
return $CFG->dataroot.'/climaintenance.template.html';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resources folder in dataroot.
|
||||
*
|
||||
* Warning: this folder will be deleted every time the page is regenerated.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_resources_folder() {
|
||||
global $CFG;
|
||||
|
||||
// If you change the path, also change file auth/outage/bootstrap.php as it does not use this reference.
|
||||
$dir = $CFG->dataroot.'/auth_outage/climaintenance';
|
||||
|
||||
if ($this->preview) {
|
||||
$dir = $dir.'/preview';
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the page.
|
||||
* Requests to generate the static page.
|
||||
*/
|
||||
public function generate() {
|
||||
$this->cleanup();
|
||||
|
||||
if (!is_null($this->dom)) {
|
||||
$this->remove_script_tags();
|
||||
$this->update_link_stylesheet();
|
||||
$this->update_link_favicon();
|
||||
$this->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($this->get_template_file(), $html);
|
||||
}
|
||||
$this->generator->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $preview
|
||||
* @return maintenance_static_page
|
||||
* @return maintenance_static_page_io
|
||||
*/
|
||||
public function set_preview($preview) {
|
||||
$this->preview = $preview;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove script tags from DOM.
|
||||
*/
|
||||
private function remove_script_tags() {
|
||||
$scripts = $this->dom->getElementsByTagName('script');
|
||||
// List items to remove without changing the DOM.
|
||||
$remove = [];
|
||||
foreach ($scripts as $node) {
|
||||
$remove[] = $node;
|
||||
}
|
||||
// All listed, now remove them.
|
||||
foreach ($remove as $node) {
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the dataroot as needed.
|
||||
*/
|
||||
private function cleanup() {
|
||||
$resources = $this->get_resources_folder();
|
||||
if (is_dir($resources)) {
|
||||
$this->delete_directory_recursively($resources);
|
||||
}
|
||||
|
||||
$template = $this->get_template_file();
|
||||
if (is_file($template)) {
|
||||
unlink($template);
|
||||
}
|
||||
|
||||
if (!is_null($this->dom)) {
|
||||
mkdir($resources, 0775, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given directory with all its files and subdirectories.
|
||||
* @param string $dir Directory to delete.
|
||||
* @throws coding_exception
|
||||
* @throws invalid_parameter_exception
|
||||
*/
|
||||
private function delete_directory_recursively($dir) {
|
||||
// It should never come from user, but protect against possible attacks anyway.
|
||||
$dir = realpath($dir);
|
||||
$safedir = $this->get_resources_folder();
|
||||
if (substr($dir, 0, strlen($safedir)) !== $safedir) {
|
||||
throw new invalid_parameter_exception('Unsafe to delete: '.$dir);
|
||||
}
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
throw new coding_exception('Not a directory: '.$dir);
|
||||
}
|
||||
$files = scandir($dir);
|
||||
foreach ($files as $file) {
|
||||
if (($file == '.') || ($file == '..')) {
|
||||
continue;
|
||||
}
|
||||
$file = $dir.'/'.$file;
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
continue;
|
||||
}
|
||||
if (is_dir($file)) {
|
||||
$this->delete_directory_recursively($file);
|
||||
continue;
|
||||
}
|
||||
throw new coding_exception('Not a file or directory: '.$file);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and fixes all link rel="stylesheet" tags.
|
||||
*/
|
||||
private function update_link_stylesheet() {
|
||||
$links = $this->dom->getElementsByTagName('link');
|
||||
|
||||
foreach ($links as $link) {
|
||||
$rel = $link->getAttribute("rel");
|
||||
$href = $link->getAttribute("href");
|
||||
if (($rel != 'stylesheet') || ($href == '')) {
|
||||
continue;
|
||||
}
|
||||
$saved = $this->save_url_file($href);
|
||||
if (is_null($saved['url'])) {
|
||||
$url = $href; // Skipped, use original URL.
|
||||
} else {
|
||||
$this->update_link_stylesheet_parse($saved['file'], dirname($href));
|
||||
$url = $this->get_url_for_file($saved['url']);
|
||||
}
|
||||
$link->setAttribute('href', $url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for urls inside filename.
|
||||
* @param string $filename
|
||||
*/
|
||||
private function update_link_stylesheet_parse($filename, $baseref) {
|
||||
global $CFG;
|
||||
|
||||
$contents = file_get_contents($filename);
|
||||
if (!preg_match_all('#url\([\'"]?([^\'"\)]+)#', $contents, $matches)) {
|
||||
return;
|
||||
}
|
||||
foreach ($matches[1] as $original_url) {
|
||||
// Allow incomplete URLs in CSS, assume it is from moodle root.
|
||||
if (self::is_url($original_url)) {
|
||||
$full_url = $original_url;
|
||||
} else if ($original_url[0] == '/') {
|
||||
$full_url = $CFG->wwwroot.$original_url;
|
||||
} else {
|
||||
$full_url = $baseref.'/'.$original_url;
|
||||
}
|
||||
|
||||
$saved = $this->save_url_file($full_url);
|
||||
if (!is_null($saved)) {
|
||||
$final_url = $this->get_url_for_file($saved['url']);
|
||||
$contents = str_replace($original_url, $final_url, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($filename, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and fixes the favicon link tag.
|
||||
*/
|
||||
private function update_link_favicon() {
|
||||
$links = $this->dom->getElementsByTagName('link');
|
||||
|
||||
foreach ($links as $link) {
|
||||
$rel = $link->getAttribute("rel");
|
||||
$href = $link->getAttribute("href");
|
||||
if (($rel != 'shortcut icon') || ($href == '')) {
|
||||
continue;
|
||||
}
|
||||
$link->setAttribute('href', $this->generate_file_url($href)); // Works for most image formats.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and fixes all img tags.
|
||||
*/
|
||||
private function update_images() {
|
||||
$links = $this->dom->getElementsByTagName('img');
|
||||
|
||||
foreach ($links as $link) {
|
||||
$src = $link->getAttribute("src");
|
||||
if ($src == '') {
|
||||
continue;
|
||||
}
|
||||
$link->setAttribute('src', $this->generate_file_url($src)); // Works for most image formats.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the content of the URL into a file, returning the new URL.
|
||||
* @param string $url Input URL.
|
||||
* @return string Output URL.
|
||||
*/
|
||||
private function generate_file_url($url) {
|
||||
$saved = $this->save_url_file($url);
|
||||
if (is_null($saved)) {
|
||||
return $url; // Skipped, use original URL.
|
||||
}
|
||||
return $this->get_url_for_file($saved['url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a URL for a resource file.
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private function get_url_for_file($filename) {
|
||||
return (string)new moodle_url('/auth/outage/file.php', ['file' => $filename]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the content of the URL into a file, returning the local filename.
|
||||
* @param string $url Input URL.
|
||||
* @return string|null Output filename or null if skipped.
|
||||
*/
|
||||
private function save_url_file($url) {
|
||||
global $CFG;
|
||||
|
||||
if (!self::is_url($url)) {
|
||||
debugging('Found a relative url ('.$url.') -- is it using moodle_url()?');
|
||||
return null; // Leave hardcoded URLs as it is.
|
||||
}
|
||||
|
||||
if (substr($url, 0, strlen($CFG->wwwroot)) !== $CFG->wwwroot) {
|
||||
return null; // External URL, leave it.
|
||||
}
|
||||
|
||||
// PHPUnit does not expose a web interface to fetch, point to local file instead.
|
||||
if (PHPUNIT_TEST) {
|
||||
$url = str_replace($CFG->wwwroot, $CFG->dirroot, $url);
|
||||
}
|
||||
|
||||
$data = self::file_get_data($url);
|
||||
|
||||
$mime = trim(base64_encode($data['mime']), '=');
|
||||
$url = sha1($data['contents']).'.'.$mime;
|
||||
$filepath = $this->get_resources_folder().'/'.$url;
|
||||
file_put_contents($filepath, $data['contents']);
|
||||
|
||||
if ($this->preview) {
|
||||
$url = 'preview/'.$url;
|
||||
}
|
||||
|
||||
return ['file' => $filepath, 'url' => $url];
|
||||
public function get_io() {
|
||||
return $this->generator->get_io();
|
||||
}
|
||||
}
|
||||
|
||||
200
classes/local/controllers/maintenance_static_page_generator.php
Normal file
200
classes/local/controllers/maintenance_static_page_generator.php
Normal file
@@ -0,0 +1,200 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* maintenance_static_page_generator class.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
|
||||
namespace auth_outage\local\controllers;
|
||||
|
||||
use coding_exception;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use invalid_state_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* maintenance_static_page_generator class.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
class maintenance_static_page_generator {
|
||||
/** @var DOMDocument */
|
||||
protected $dom;
|
||||
|
||||
/** @var maintenance_static_page_io */
|
||||
protected $io;
|
||||
|
||||
/**
|
||||
* maintenance_static_page_generator constructor.
|
||||
* @param DOMDocument|null $dom
|
||||
* @param maintenance_static_page_io $io
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function __construct($dom, maintenance_static_page_io $io) {
|
||||
if (!is_null($dom) && !($dom instanceof DOMDocument)) {
|
||||
throw new coding_exception('$dom must be null or an DOMDocument object.');
|
||||
}
|
||||
$this->dom = $dom;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the page.
|
||||
*/
|
||||
public function generate() {
|
||||
$this->io->cleanup();
|
||||
|
||||
if (!is_null($this->dom)) {
|
||||
$this->io->create_resources_path();
|
||||
|
||||
$this->remove_script_tags();
|
||||
$this->update_link_stylesheet();
|
||||
$this->update_link_favicon();
|
||||
$this->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.');
|
||||
}
|
||||
|
||||
$this->io->save_template_file($html);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return maintenance_static_page_io
|
||||
*/
|
||||
public function get_io() {
|
||||
return $this->io;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Remove script tags from DOM.
|
||||
*/
|
||||
private function remove_script_tags() {
|
||||
$scripts = $this->dom->getElementsByTagName('script');
|
||||
// List items to remove without changing the DOM.
|
||||
$remove = [];
|
||||
foreach ($scripts as $node) {
|
||||
$remove[] = $node;
|
||||
}
|
||||
// All listed, now remove them.
|
||||
foreach ($remove as $node) {
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and fixes all link rel="stylesheet" tags.
|
||||
*/
|
||||
private function update_link_stylesheet() {
|
||||
$links = $this->dom->getElementsByTagName('link');
|
||||
|
||||
/** @var DOMElement $link */
|
||||
foreach ($links as $link) {
|
||||
$rel = $link->getAttribute("rel");
|
||||
$href = $link->getAttribute("href");
|
||||
if (($rel != 'stylesheet') || ($href == '')) {
|
||||
continue;
|
||||
}
|
||||
$saved = $this->io->save_url_file($href);
|
||||
if (is_null($saved['url'])) {
|
||||
$url = $href; // Skipped, use original URL.
|
||||
} else {
|
||||
$this->update_link_stylesheet_parse($saved['file'], dirname($href));
|
||||
$url = $this->io->get_url_for_file($saved['url']);
|
||||
}
|
||||
$link->setAttribute('href', $url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for urls inside filename.
|
||||
* @param string $filename
|
||||
*/
|
||||
private function update_link_stylesheet_parse($filename, $baseref) {
|
||||
global $CFG;
|
||||
|
||||
$contents = file_get_contents($filename);
|
||||
if (!preg_match_all('#url\([\'"]?([^\'"\)]+)#', $contents, $matches)) {
|
||||
return;
|
||||
}
|
||||
foreach ($matches[1] as $original_url) {
|
||||
// Allow incomplete URLs in CSS, assume it is from moodle root.
|
||||
if (maintenance_static_page_io::is_url($original_url)) {
|
||||
$full_url = $original_url;
|
||||
} else if ($original_url[0] == '/') {
|
||||
$full_url = $CFG->wwwroot.$original_url;
|
||||
} else {
|
||||
$full_url = $baseref.'/'.$original_url;
|
||||
}
|
||||
|
||||
$saved = $this->io->save_url_file($full_url);
|
||||
if (!is_null($saved)) {
|
||||
$final_url = $this->io->get_url_for_file($saved['url']);
|
||||
$contents = str_replace($original_url, $final_url, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($filename, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and fixes the favicon link tag.
|
||||
*/
|
||||
private function update_link_favicon() {
|
||||
$links = $this->dom->getElementsByTagName('link');
|
||||
|
||||
/** @var DOMElement $link */
|
||||
foreach ($links as $link) {
|
||||
$rel = $link->getAttribute("rel");
|
||||
$href = $link->getAttribute("href");
|
||||
if (($rel != 'shortcut icon') || ($href == '')) {
|
||||
continue;
|
||||
}
|
||||
$link->setAttribute('href', $this->io->generate_file_url($href)); // Works for most image formats.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and fixes all img tags.
|
||||
*/
|
||||
private function update_images() {
|
||||
$links = $this->dom->getElementsByTagName('img');
|
||||
|
||||
/** @var DOMElement $link */
|
||||
foreach ($links as $link) {
|
||||
$src = $link->getAttribute("src");
|
||||
if ($src == '') {
|
||||
continue;
|
||||
}
|
||||
$link->setAttribute('src', $this->io->generate_file_url($src)); // Works for most image formats.
|
||||
}
|
||||
}
|
||||
}
|
||||
252
classes/local/controllers/maintenance_static_page_io.php
Normal file
252
classes/local/controllers/maintenance_static_page_io.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* maintenance_static_page_io class.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
|
||||
namespace auth_outage\local\controllers;
|
||||
|
||||
use coding_exception;
|
||||
use finfo;
|
||||
use invalid_parameter_exception;
|
||||
use moodle_url;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* maintenance_static_page_io class.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
class maintenance_static_page_io {
|
||||
/**
|
||||
* Checks if the given string starts with "http://" or "https://".
|
||||
* @param $url
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_url($url) {
|
||||
return (bool)preg_match('#^http(s)?://#', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the contents of the file or URL.
|
||||
* @param string $file File to get.
|
||||
* @return string Contents of $file or an empty string if failed.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function file_get_data($file) {
|
||||
if (!is_string($file)) {
|
||||
throw new coding_exception('$file is not a string.');
|
||||
}
|
||||
|
||||
if (self::is_url($file)) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $file);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$contents = curl_exec($curl);
|
||||
$mime = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($curl);
|
||||
} else {
|
||||
$contents = @file_get_contents($file);
|
||||
$mime = (new finfo(FILEINFO_MIME_TYPE))->buffer($contents); // Not perfect, but try guessing it.
|
||||
}
|
||||
if ($contents === false) {
|
||||
debugging('Cannot fetch: '.$file);
|
||||
$contents = '';
|
||||
$mime = 'unknown';
|
||||
}
|
||||
return ['contents' => $contents, 'mime' => $mime];
|
||||
}
|
||||
|
||||
/** @var bool */
|
||||
protected $preview = false;
|
||||
|
||||
/**
|
||||
* @param boolean $preview
|
||||
*/
|
||||
public function set_preview($preview) {
|
||||
$this->preview = $preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cli maintenance template file location.
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_file() {
|
||||
global $CFG;
|
||||
if ($this->preview) {
|
||||
return $this->get_resources_folder().'/climaintenance.html';
|
||||
} else {
|
||||
return $CFG->dataroot.'/climaintenance.template.html';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resources folder in dataroot.
|
||||
*
|
||||
* Warning: this folder will be deleted every time the page is regenerated.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_resources_folder() {
|
||||
global $CFG;
|
||||
|
||||
// If you change the path, also change file auth/outage/bootstrap.php as it does not use this reference.
|
||||
$dir = $CFG->dataroot.'/auth_outage/climaintenance';
|
||||
|
||||
if ($this->preview) {
|
||||
$dir = $dir.'/preview';
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the dataroot as needed.
|
||||
*/
|
||||
public function cleanup() {
|
||||
$resources = $this->get_resources_folder();
|
||||
if (is_dir($resources)) {
|
||||
$this->delete_directory_recursively($resources);
|
||||
}
|
||||
|
||||
$template = $this->get_template_file();
|
||||
if (is_file($template)) {
|
||||
unlink($template);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the dataroot as needed.
|
||||
*/
|
||||
public function create_resources_path() {
|
||||
mkdir($this->get_resources_folder(), 0775, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the template file with the given string.
|
||||
* @param string $data
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function save_template_file($data) {
|
||||
if (!is_string($data) || ($data === '')) {
|
||||
throw new coding_exception('$data is not a valid string.');
|
||||
}
|
||||
file_put_contents($this->get_template_file(), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given directory with all its files and subdirectories.
|
||||
* @param string $dir Directory to delete.
|
||||
* @throws coding_exception
|
||||
* @throws invalid_parameter_exception
|
||||
*/
|
||||
private function delete_directory_recursively($dir) {
|
||||
// It should never come from user, but protect against possible attacks anyway.
|
||||
$dir = realpath($dir);
|
||||
$safedir = $this->get_resources_folder();
|
||||
if (substr($dir, 0, strlen($safedir)) !== $safedir) {
|
||||
throw new invalid_parameter_exception('Unsafe to delete: '.$dir);
|
||||
}
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
throw new coding_exception('Not a directory: '.$dir);
|
||||
}
|
||||
$files = scandir($dir);
|
||||
foreach ($files as $file) {
|
||||
if (($file == '.') || ($file == '..')) {
|
||||
continue;
|
||||
}
|
||||
$file = $dir.'/'.$file;
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
continue;
|
||||
}
|
||||
if (is_dir($file)) {
|
||||
$this->delete_directory_recursively($file);
|
||||
continue;
|
||||
}
|
||||
throw new coding_exception('Not a file or directory: '.$file);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the content of the URL into a file, returning the new URL.
|
||||
* @param string $url Input URL.
|
||||
* @return string Output URL.
|
||||
*/
|
||||
public function generate_file_url($url) {
|
||||
$saved = $this->save_url_file($url);
|
||||
if (is_null($saved)) {
|
||||
return $url; // Skipped, use original URL.
|
||||
}
|
||||
return $this->get_url_for_file($saved['url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a URL for a resource file.
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public function get_url_for_file($filename) {
|
||||
return (string)new moodle_url('/auth/outage/file.php', ['file' => $filename]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the content of the URL into a file, returning the local filename.
|
||||
* @param string $url Input URL.
|
||||
* @return string|null Output filename or null if skipped.
|
||||
*/
|
||||
public function save_url_file($url) {
|
||||
global $CFG;
|
||||
|
||||
if (!self::is_url($url)) {
|
||||
debugging('Found a relative url ('.$url.') -- is it using moodle_url()?');
|
||||
return null; // Leave hardcoded URLs as it is.
|
||||
}
|
||||
|
||||
if (substr($url, 0, strlen($CFG->wwwroot)) !== $CFG->wwwroot) {
|
||||
return null; // External URL, leave it.
|
||||
}
|
||||
|
||||
// PHPUnit does not expose a web interface to fetch, point to local file instead.
|
||||
if (PHPUNIT_TEST) {
|
||||
$url = str_replace($CFG->wwwroot, $CFG->dirroot, $url);
|
||||
}
|
||||
|
||||
$data = self::file_get_data($url);
|
||||
|
||||
$mime = trim(base64_encode($data['mime']), '=');
|
||||
$url = sha1($data['contents']).'.'.$mime;
|
||||
$filepath = $this->get_resources_folder().'/'.$url;
|
||||
file_put_contents($filepath, $data['contents']);
|
||||
|
||||
if ($this->preview) {
|
||||
$url = 'preview/'.$url;
|
||||
}
|
||||
|
||||
return ['file' => $filepath, 'url' => $url];
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
use auth_outage\local\controllers\maintenance_static_page;
|
||||
use auth_outage\local\controllers\maintenance_static_page_io;
|
||||
use auth_outage\task\update_static_page;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
@@ -42,17 +43,17 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
public function test_templatefile() {
|
||||
global $CFG;
|
||||
$page = maintenance_static_page::create_from_html('<html></html>');
|
||||
self::assertSame($CFG->dataroot.'/climaintenance.template.html', $page->get_template_file());
|
||||
$page->set_preview(true);
|
||||
self::assertSame($CFG->dataroot.'/auth_outage/climaintenance/preview/climaintenance.html', $page->get_template_file());
|
||||
self::assertSame($CFG->dataroot.'/climaintenance.template.html', $page->get_io()->get_template_file());
|
||||
$page->get_io()->set_preview(true);
|
||||
self::assertSame($CFG->dataroot.'/auth_outage/climaintenance/preview/climaintenance.html', $page->get_io()->get_template_file());
|
||||
}
|
||||
|
||||
public function test_resourcesfolder() {
|
||||
global $CFG;
|
||||
$page = maintenance_static_page::create_from_html('<html></html>');
|
||||
self::assertSame($CFG->dataroot.'/auth_outage/climaintenance', $page->get_resources_folder());
|
||||
$page->set_preview(true);
|
||||
self::assertSame($CFG->dataroot.'/auth_outage/climaintenance/preview', $page->get_resources_folder());
|
||||
self::assertSame($CFG->dataroot.'/auth_outage/climaintenance', $page->get_io()->get_resources_folder());
|
||||
$page->get_io()->set_preview(true);
|
||||
self::assertSame($CFG->dataroot.'/auth_outage/climaintenance/preview', $page->get_io()->get_resources_folder());
|
||||
}
|
||||
|
||||
public function test_createfromoutage() {
|
||||
@@ -96,10 +97,10 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
$page->generate();
|
||||
|
||||
// Check for css file.
|
||||
self::assertFileExists($page->get_resources_folder().'/622ef6e83acfcb274cdf37bdb3bffa0923f9a7ad.dGV4dC9wbGFpbg');
|
||||
self::assertFileExists($page->get_io()->get_resources_folder().'/622ef6e83acfcb274cdf37bdb3bffa0923f9a7ad.dGV4dC9wbGFpbg');
|
||||
|
||||
// Check for catalyst.png file referenced in url(..) of css.
|
||||
self::assertFileExists($page->get_resources_folder().'/ff7f7f87a26a908fc72930eaefb6b57306361d16.aW1hZ2UvcG5n');
|
||||
self::assertFileExists($page->get_io()->get_resources_folder().'/ff7f7f87a26a908fc72930eaefb6b57306361d16.aW1hZ2UvcG5n');
|
||||
}
|
||||
|
||||
public function test_updatelinkstylesheet_urls_quoted() {
|
||||
@@ -111,10 +112,10 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
$page->generate();
|
||||
|
||||
// Check for css file.
|
||||
self::assertFileExists($page->get_resources_folder().'/1d84b6d321fef780237f84834b7316c079221a31.dGV4dC9wbGFpbg');
|
||||
self::assertFileExists($page->get_io()->get_resources_folder().'/1d84b6d321fef780237f84834b7316c079221a31.dGV4dC9wbGFpbg');
|
||||
|
||||
// Check for catalyst.png file referenced in url(..) of css.
|
||||
self::assertFileExists($page->get_resources_folder().'/ff7f7f87a26a908fc72930eaefb6b57306361d16.aW1hZ2UvcG5n');
|
||||
self::assertFileExists($page->get_io()->get_resources_folder().'/ff7f7f87a26a908fc72930eaefb6b57306361d16.aW1hZ2UvcG5n');
|
||||
}
|
||||
|
||||
public function test_updatelinkstylesheet_urls_subdir() {
|
||||
@@ -126,10 +127,10 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
$page->generate();
|
||||
|
||||
// Check for css file.
|
||||
self::assertFileExists($page->get_resources_folder().'/4ea71b83ab326a15d0d784b34fcda702b6a7427d.dGV4dC9wbGFpbg');
|
||||
self::assertFileExists($page->get_io()->get_resources_folder().'/4ea71b83ab326a15d0d784b34fcda702b6a7427d.dGV4dC9wbGFpbg');
|
||||
|
||||
// Check for file referenced in url(..) of css.
|
||||
self::assertFileExists($page->get_resources_folder().'/a02a8a442fa82d5205ffb24722d9df7f35161f56.dGV4dC9wbGFpbg');
|
||||
self::assertFileExists($page->get_io()->get_resources_folder().'/a02a8a442fa82d5205ffb24722d9df7f35161f56.dGV4dC9wbGFpbg');
|
||||
}
|
||||
|
||||
public function test_updateimages() {
|
||||
@@ -162,9 +163,9 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
'<html><head><title>Title</title><link rel="shortcut icon" href="'.$link.'""></head>'.
|
||||
'<body>Content</body></html>';
|
||||
$page = maintenance_static_page::create_from_html($html);
|
||||
$page->set_preview(true);
|
||||
$page->get_io()->set_preview(true);
|
||||
$page->generate();
|
||||
$generated = trim(file_get_contents($page->get_template_file()));
|
||||
$generated = trim(file_get_contents($page->get_io()->get_template_file()));
|
||||
|
||||
self::assertNotContains($link, $generated);
|
||||
self::assertContains('http://www.example.com/moodle/auth/outage/file.php?file=preview%2F', $generated);
|
||||
@@ -178,7 +179,7 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
private function generated_page_html($html) {
|
||||
$page = maintenance_static_page::create_from_html($html);
|
||||
$page->generate();
|
||||
$generated = trim(file_get_contents($page->get_template_file()));
|
||||
$generated = trim(file_get_contents($page->get_io()->get_template_file()));
|
||||
return $generated;
|
||||
}
|
||||
|
||||
@@ -215,7 +216,7 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
$page->generate();
|
||||
|
||||
// This checks if content is correct and mime type is correct from the encoded name.
|
||||
$file = $page->get_resources_folder().'/ff7f7f87a26a908fc72930eaefb6b57306361d16.aW1hZ2UvcG5n';
|
||||
$file = $page->get_io()->get_resources_folder().'/ff7f7f87a26a908fc72930eaefb6b57306361d16.aW1hZ2UvcG5n';
|
||||
self::assertFileExists($file);
|
||||
|
||||
// We can still assert the contents really match, not just the hash.
|
||||
@@ -232,4 +233,48 @@ class maintenance_static_page_test extends auth_outage_base_testcase {
|
||||
private function get_fixture_path($file) {
|
||||
return (string)new moodle_url('/auth/outage/tests/phpunit/local/controllers/fixtures/'.$file);
|
||||
}
|
||||
|
||||
public function test_invalid_string_saving_template_empty() {
|
||||
$io = new maintenance_static_page_io();
|
||||
$this->set_expected_exception('coding_exception');
|
||||
$io->save_template_file('');
|
||||
}
|
||||
|
||||
public function test_invalid_string_saving_template_nostring() {
|
||||
$io = new maintenance_static_page_io();
|
||||
$this->set_expected_exception('coding_exception');
|
||||
$io->save_template_file(50);
|
||||
}
|
||||
|
||||
public function test_get_url_for_file() {
|
||||
$io = new maintenance_static_page_io();
|
||||
self::assertSame('http://www.example.com/moodle/auth/outage/file.php?file=img.png', $io->get_url_for_file('img.png'));
|
||||
}
|
||||
|
||||
public function test_is_url() {
|
||||
self::assertTrue(maintenance_static_page_io::is_url('http://catalyst.net.nz'));
|
||||
self::assertTrue(maintenance_static_page_io::is_url('https://www.catalyst-au.net/'));
|
||||
self::assertFalse(maintenance_static_page_io::is_url('/homepage'));
|
||||
self::assertFalse(maintenance_static_page_io::is_url('file://homepage'));
|
||||
}
|
||||
|
||||
public function test_file_get_data() {
|
||||
$file = __DIR__.'/fixtures/catalyst.png';
|
||||
$found = maintenance_static_page_io::file_get_data($file);
|
||||
self::assertSame(file_get_contents($file), $found['contents']);
|
||||
self::assertSame('image/png', $found['mime']);
|
||||
}
|
||||
|
||||
public function test_file_get_data_invalidfile() {
|
||||
$found = maintenance_static_page_io::file_get_data(__DIR__.'/fixtures/invalidfile');
|
||||
self::assertSame('', $found['contents']);
|
||||
self::assertSame('unknown', $found['mime']);
|
||||
self::assertCount(1, phpunit_util::get_debugging_messages());
|
||||
phpunit_util::reset_debugging();
|
||||
}
|
||||
|
||||
public function test_file_get_data_invalidfilename() {
|
||||
$this->set_expected_exception('coding_exception');
|
||||
maintenance_static_page_io::file_get_data(200);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user