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:
Damyon Wiese
2015-05-05 03:19:25 +02:00
committed by Eloy Lafuente (stronk7)
parent 890078bf03
commit fcc383db0f
8 changed files with 268 additions and 107 deletions
+31 -18
View File
@@ -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';
+4 -31
View File
@@ -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
View File
@@ -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');
}
}