Skip to content

Commit

Permalink
MDL-50085 Mustache : Strictly define the search paths for templates.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Damyon Wiese authored and stronk7 committed May 5, 2015
1 parent 890078b commit fcc383d
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 107 deletions.
49 changes: 31 additions & 18 deletions admin/tool/templatelibrary/classes/api.php
Expand Up @@ -23,6 +23,7 @@
*/
namespace tool_templatelibrary;

use core\output\mustache_template_finder;
use stdClass;
use core_component;
use coding_exception;
Expand All @@ -43,49 +44,61 @@ 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();
$results = array();

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;
}

Expand Down
Expand Up @@ -59,8 +59,8 @@ public function export_for_template(renderer_base $output) {
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);
}
Expand Down
Expand Up @@ -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';
Expand Down
35 changes: 4 additions & 31 deletions lib/classes/output/external.php
Expand Up @@ -85,38 +85,11 @@ public static function load_template($component, $template, $themename) {
$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;
}
Expand Down
20 changes: 10 additions & 10 deletions lib/classes/output/mustache_filesystem_loader.php
Expand Up @@ -28,30 +28,30 @@
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
* @since 2.9
*/
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);
}
}
120 changes: 120 additions & 0 deletions lib/classes/output/mustache_template_finder.php
@@ -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');
}
}
47 changes: 2 additions & 45 deletions lib/outputrenderers.php
Expand Up @@ -118,53 +118,10 @@ protected function get_mustache() {

$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);
Expand Down

0 comments on commit fcc383d

Please sign in to comment.