MDL-50085 Mustache : Strictly define the search paths for templates.
This change moves several repeated sections of code that was searching for templates and valid template locations to a new class. It adds unit tests for the new class and verifies subsystem support for templates.
This commit is contained in:
committed by
Eloy Lafuente (stronk7)
parent
890078bf03
commit
fcc383db0f
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
namespace tool_templatelibrary;
|
||||
|
||||
use core\output\mustache_template_finder;
|
||||
use stdClass;
|
||||
use core_component;
|
||||
use coding_exception;
|
||||
@@ -43,7 +44,7 @@ class api {
|
||||
* @param string $search Search string to optionally filter the list of templates.
|
||||
* @return array[string] Where each template is in the form "component/templatename".
|
||||
*/
|
||||
public static function list_templates($component = '', $search = '') {
|
||||
public static function list_templates($component = '', $search = '', $themename = '') {
|
||||
global $CFG;
|
||||
|
||||
$templatedirs = array();
|
||||
@@ -51,41 +52,53 @@ class api {
|
||||
|
||||
if ($component != '') {
|
||||
// Just look at one component for templates.
|
||||
$dir = core_component::get_component_directory($component);
|
||||
if (!$dir) {
|
||||
return $templatedirs;
|
||||
}
|
||||
$dirs = mustache_template_finder::get_template_directories_for_component($component, $themename);
|
||||
|
||||
$templatedirs[$component] = $dir . '/templates';
|
||||
$templatedirs[$component] = $dirs;
|
||||
} else {
|
||||
|
||||
// Look at all the templates dirs for all installed plugins.
|
||||
$dir = $CFG->libdir . '/templates';
|
||||
if (!empty($dir) && is_dir($dir)) {
|
||||
$templatedirs['core'] = $dir;
|
||||
// Look at all the templates dirs for core.
|
||||
$templatedirs['core'] = mustache_template_finder::get_template_directories_for_component('core', $themename);
|
||||
|
||||
// Look at all the templates dirs for subsystems.
|
||||
$subsystems = core_component::get_core_subsystems();
|
||||
foreach ($subsystems as $subsystem => $dir) {
|
||||
$dir .= '/templates';
|
||||
if (is_dir($dir)) {
|
||||
$dirs = mustache_template_finder::get_template_directories_for_component('core_' . $subsystem, $themename);
|
||||
$templatedirs['core_' . $subsystem] = $dirs;
|
||||
}
|
||||
}
|
||||
|
||||
// Look at all the templates dirs for plugins.
|
||||
$plugintypes = core_component::get_plugin_types();
|
||||
foreach ($plugintypes as $type => $dir) {
|
||||
$plugins = core_component::get_plugin_list_with_file($type, 'templates', false);
|
||||
foreach ($plugins as $plugin => $dir) {
|
||||
if (!empty($dir) && is_dir($dir)) {
|
||||
$templatedirs[$type . '_' . $plugin] = $dir;
|
||||
$pluginname = $type . '_' . $plugin;
|
||||
$dirs = mustache_template_finder::get_template_directories_for_component($pluginname, $themename);
|
||||
$templatedirs[$pluginname] = $dirs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($templatedirs as $templatecomponent => $dir) {
|
||||
// List it.
|
||||
$files = glob($dir . '/*.mustache');
|
||||
foreach ($templatedirs as $templatecomponent => $dirs) {
|
||||
foreach ($dirs as $dir) {
|
||||
// List it.
|
||||
$files = glob($dir . '/*.mustache');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$templatename = basename($file, '.mustache');
|
||||
if ($search == '' || strpos($templatename, $search) !== false) {
|
||||
$results[] = $templatecomponent . '/' . $templatename;
|
||||
foreach ($files as $file) {
|
||||
$templatename = basename($file, '.mustache');
|
||||
if ($search == '' || strpos($templatename, $search) !== false) {
|
||||
$results[$templatecomponent . '/' . $templatename] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$results = array_keys($results);
|
||||
sort($results);
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ class list_templates_page implements renderable, templatable {
|
||||
foreach ($components as $component) {
|
||||
$info = new stdClass();
|
||||
$info->component = $component;
|
||||
if ($component == 'core') {
|
||||
$info->name = get_string('core_component', 'tool_templatelibrary');
|
||||
if (strpos($component, 'core') === 0) {
|
||||
$info->name = get_string('coresubsystem', 'tool_templatelibrary', $component);
|
||||
} else {
|
||||
$info->name = $pluginmanager->plugin_name($component);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
$string['all'] = 'All components';
|
||||
$string['component'] = 'Component';
|
||||
$string['core_component'] = 'Moodle core';
|
||||
$string['coresubsystem'] = 'Core subsystem ({$a})';
|
||||
$string['documentation'] = 'Documentation';
|
||||
$string['example'] = 'Example';
|
||||
$string['noresults'] = 'No results';
|
||||
|
||||
@@ -85,38 +85,11 @@ class external extends external_api {
|
||||
$template = $params['template'];
|
||||
$themename = $params['themename'];
|
||||
|
||||
// Check if this is a valid component.
|
||||
$componentdir = core_component::get_component_directory($component);
|
||||
if (empty($componentdir)) {
|
||||
throw new moodle_exception('filenotfound', 'error');
|
||||
}
|
||||
// Places to look.
|
||||
$candidates = array();
|
||||
// Theme dir.
|
||||
$root = $CFG->dirroot;
|
||||
$templatename = $component . '/' . $template;
|
||||
|
||||
$themeconfig = theme_config::load($themename);
|
||||
|
||||
$candidate = "${root}/theme/${themename}/templates/${component}/${template}.mustache";
|
||||
$candidates[] = $candidate;
|
||||
// Theme parents dir.
|
||||
foreach ($themeconfig->parents as $theme) {
|
||||
$candidate = "${root}/theme/${theme}/templates/${component}/${template}.mustache";
|
||||
$candidates[] = $candidate;
|
||||
}
|
||||
// Component dir.
|
||||
$candidate = "${componentdir}/templates/${template}.mustache";
|
||||
$candidates[] = $candidate;
|
||||
$templatestr = false;
|
||||
foreach ($candidates as $candidate) {
|
||||
if (file_exists($candidate)) {
|
||||
$templatestr = file_get_contents($candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($templatestr === false) {
|
||||
throw new moodle_exception('filenotfound', 'error');
|
||||
}
|
||||
// Will throw exceptions if the template does not exist.
|
||||
$filename = mustache_template_finder::get_template_filename($templatename, $themename);
|
||||
$templatestr = file_get_contents($filename);
|
||||
|
||||
return $templatestr;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace core\output;
|
||||
use coding_exception;
|
||||
|
||||
/**
|
||||
* Perform some custom name mapping for template file names (strip leading component/).
|
||||
* Perform some custom name mapping for template file names.
|
||||
*
|
||||
* @copyright 2015 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
@@ -36,22 +36,22 @@ use coding_exception;
|
||||
*/
|
||||
class mustache_filesystem_loader extends \Mustache_Loader_FilesystemLoader {
|
||||
|
||||
/**
|
||||
* Provide a default no-args constructor (we don't really need anything).
|
||||
*/
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for getting a Mustache template file name.
|
||||
* Strips the leading component as we are already limited to the correct directories.
|
||||
* Use the leading component to restrict us specific directories.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string Template file name
|
||||
*/
|
||||
protected function getFileName($name) {
|
||||
if (strpos($name, '/') === false) {
|
||||
throw new coding_exception('Templates names must be specified as "componentname/templatename" (' . $name . ' requested) ');
|
||||
}
|
||||
list($component, $templatename) = explode('/', $name, 2);
|
||||
if (strpos($templatename, '/') !== false) {
|
||||
throw new coding_exception('Templates cannot be placed in sub directories (' . $name . ' requested)');
|
||||
}
|
||||
return parent::getFileName($templatename);
|
||||
// Call the Moodle template finder.
|
||||
return mustache_template_finder::get_template_filename($name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* List the valid locations to search for a template with a given name.
|
||||
*
|
||||
* @package core
|
||||
* @category output
|
||||
* @copyright 2015 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\output;
|
||||
|
||||
use coding_exception;
|
||||
use moodle_exception;
|
||||
use core_component;
|
||||
|
||||
/**
|
||||
* Get information about valid locations for mustache templates.
|
||||
*
|
||||
* @copyright 2015 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 2.9
|
||||
*/
|
||||
class mustache_template_finder {
|
||||
|
||||
/**
|
||||
* Helper function for getting a list of valid template directories for a specific component.
|
||||
*
|
||||
* @param string $component
|
||||
*
|
||||
* @return string[] List of valid directories for templates for this compoonent. Directories are not checked for existence.
|
||||
*/
|
||||
public static function get_template_directories_for_component($component, $themename = '') {
|
||||
global $CFG, $PAGE;
|
||||
|
||||
// Default the param.
|
||||
if ($themename == '') {
|
||||
$themename = $PAGE->theme->name;
|
||||
}
|
||||
|
||||
// Clean params for safety.
|
||||
$component = clean_param($component, PARAM_COMPONENT);
|
||||
$themename = clean_param($themename, PARAM_COMPONENT);
|
||||
|
||||
// Validate the component.
|
||||
$dirs = array();
|
||||
$compdirectory = core_component::get_component_directory($component);
|
||||
if (!$compdirectory) {
|
||||
throw new coding_exception("Component was not valid:" . s($component));
|
||||
}
|
||||
|
||||
// Find the parent themes.
|
||||
$parents = array();
|
||||
if ($themename === $PAGE->theme->name) {
|
||||
$parents = $PAGE->theme->parents;
|
||||
} else {
|
||||
$themeconfig = theme_config::load($themename);
|
||||
$parents = $themeconfig->parents;
|
||||
}
|
||||
|
||||
// First check the theme.
|
||||
$dirs[] = $CFG->dirroot . '/theme/' . $themename . '/templates/' . $component . '/';
|
||||
// Now check the parent themes.
|
||||
// Search each of the parent themes second.
|
||||
foreach ($parents as $parent) {
|
||||
$dirs[] = $CFG->dirroot . '/theme/' . $parent . '/templates/' . $component . '/';
|
||||
}
|
||||
|
||||
$dirs[] = $compdirectory . '/templates/';
|
||||
|
||||
return $dirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for getting a filename for a template from the template name.
|
||||
*
|
||||
* @param string $name - This is the componentname/templatename combined.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_template_filename($name, $themename = '') {
|
||||
global $CFG, $PAGE;
|
||||
|
||||
if (strpos($name, '/') === false) {
|
||||
throw new coding_exception('Templates names must be specified as "componentname/templatename"' .
|
||||
' (' . $name . ' requested) ');
|
||||
}
|
||||
list($component, $templatename) = explode('/', $name, 2);
|
||||
$component = clean_param($component, PARAM_COMPONENT);
|
||||
if (strpos($templatename, '/') !== false) {
|
||||
throw new coding_exception('Templates cannot be placed in sub directories (' . $name . ' requested)');
|
||||
}
|
||||
|
||||
$dirs = self::get_template_directories_for_component($component, $themename);
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
$candidate = $dir . $templatename . '.mustache';
|
||||
if (file_exists($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new moodle_exception('filenotfound', 'error');
|
||||
}
|
||||
}
|
||||
+2
-45
@@ -118,53 +118,10 @@ class renderer_base {
|
||||
|
||||
$themename = $this->page->theme->name;
|
||||
$themerev = theme_get_revision();
|
||||
$target = $this->target;
|
||||
|
||||
$cachedir = make_localcache_directory("mustache/$themerev/$themename/$target");
|
||||
$loaderoptions = array();
|
||||
|
||||
// Where are all the places we should look for templates?
|
||||
|
||||
$suffix = $this->component;
|
||||
if ($this->subtype !== null) {
|
||||
$suffix .= '_' . $this->subtype;
|
||||
}
|
||||
|
||||
// Start with an empty list.
|
||||
$loader = new Mustache_Loader_CascadingLoader(array());
|
||||
$loaderdir = $CFG->dirroot . '/theme/' . $themename . '/templates/' . $suffix;
|
||||
if (is_dir($loaderdir)) {
|
||||
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
|
||||
}
|
||||
|
||||
// Search each of the parent themes second.
|
||||
foreach ($this->page->theme->parents as $parent) {
|
||||
$loaderdir = $CFG->dirroot . '/theme/' . $parent . '/templates/' . $suffix;
|
||||
if (is_dir($loaderdir)) {
|
||||
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
|
||||
}
|
||||
}
|
||||
|
||||
// Look in a components templates dir for a base implementation.
|
||||
|
||||
$compdirectory = core_component::get_component_directory($suffix);
|
||||
if ($compdirectory) {
|
||||
$loaderdir = $compdirectory . '/templates';
|
||||
if (is_dir($loaderdir)) {
|
||||
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
|
||||
}
|
||||
}
|
||||
|
||||
// Look in the core templates dir as a final fallback.
|
||||
|
||||
$compdirectory = $CFG->libdir;
|
||||
if ($compdirectory) {
|
||||
$loaderdir = $compdirectory . '/templates';
|
||||
if (is_dir($loaderdir)) {
|
||||
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
|
||||
}
|
||||
}
|
||||
$cachedir = make_localcache_directory("mustache/$themerev/$themename");
|
||||
|
||||
$loader = new \core\output\mustache_filesystem_loader();
|
||||
$stringhelper = new \core\output\mustache_string_helper();
|
||||
$jshelper = new \core\output\mustache_javascript_helper($this->page->requires);
|
||||
$pixhelper = new \core\output\mustache_pix_helper($this);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Unit tests for lib/classes/output/mustache_template_finder.php
|
||||
*
|
||||
* @package core
|
||||
* @category phpunit
|
||||
* @copyright 2015 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\output\mustache_template_finder;
|
||||
|
||||
/**
|
||||
* Unit tests for the Mustache template finder class (contains logic about
|
||||
* resolving mustache template locations.
|
||||
*/
|
||||
class core_output_mustache_template_finder_testcase extends advanced_testcase {
|
||||
|
||||
public function test_get_template_directories_for_component() {
|
||||
global $CFG;
|
||||
|
||||
// Test a plugin.
|
||||
$dirs = mustache_template_finder::get_template_directories_for_component('mod_assign', 'clean');
|
||||
|
||||
$correct = array(
|
||||
'theme/clean/templates/mod_assign/',
|
||||
'theme/bootstrapbase/templates/mod_assign/',
|
||||
'mod/assign/templates/'
|
||||
);
|
||||
foreach ($dirs as $index => $dir) {
|
||||
$this->assertSame($dir, $CFG->dirroot . '/' . $correct[$index]);
|
||||
}
|
||||
// Test a subsystem.
|
||||
$dirs = mustache_template_finder::get_template_directories_for_component('core_user', 'clean');
|
||||
|
||||
$correct = array(
|
||||
'theme/clean/templates/core_user/',
|
||||
'theme/bootstrapbase/templates/core_user/',
|
||||
'user/templates/'
|
||||
);
|
||||
foreach ($dirs as $index => $dir) {
|
||||
$this->assertSame($dir, $CFG->dirroot . '/' . $correct[$index]);
|
||||
}
|
||||
// Test core.
|
||||
$dirs = mustache_template_finder::get_template_directories_for_component('core', 'clean');
|
||||
|
||||
$correct = array(
|
||||
'theme/clean/templates/core/',
|
||||
'theme/bootstrapbase/templates/core/',
|
||||
'lib/templates/'
|
||||
);
|
||||
foreach ($dirs as $index => $dir) {
|
||||
$this->assertSame($dir, $CFG->dirroot . '/' . $correct[$index]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException coding_exception
|
||||
*/
|
||||
public function test_invalid_get_template_directories_for_component() {
|
||||
// Test something invalid.
|
||||
$dirs = mustache_template_finder::get_template_directories_for_component('octopus', 'clean');
|
||||
}
|
||||
|
||||
public function test_get_template_filename() {
|
||||
global $CFG;
|
||||
|
||||
$filename = mustache_template_finder::get_template_filename('core/pix_icon', 'clean');
|
||||
$correct = $CFG->dirroot . '/lib/templates/pix_icon.mustache';
|
||||
$this->assertSame($correct, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException moodle_exception
|
||||
*/
|
||||
public function test_invalid_get_template_filename() {
|
||||
// Test something invalid.
|
||||
$dirs = mustache_template_finder::get_template_filename('core/octopus', 'clean');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user