From 710c222b5a964dcc804542c33a7d325e53f50c55 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 20 Sep 2019 10:46:47 -0400 Subject: [PATCH] Add a new Templates::fileModified() hookable method that is called whenever a change is detected to a template file. Plus update the Template and Templates class to make it possible for runtime modification of the templates path. --- wire/core/Paths.php | 6 ++-- wire/core/ProcessWire.php | 2 +- wire/core/Template.php | 65 +++++++++++++++++++++++++-------------- wire/core/Templates.php | 57 ++++++++++++++++++++++++++++------ wire/core/Wire.php | 2 +- 5 files changed, 95 insertions(+), 37 deletions(-) diff --git a/wire/core/Paths.php b/wire/core/Paths.php index a3912ced..f6f4f60b 100644 --- a/wire/core/Paths.php +++ b/wire/core/Paths.php @@ -140,7 +140,7 @@ class Paths extends WireData { * */ public function set($key, $value) { - $value = self::normalizeSeparators($value); + if(DIRECTORY_SEPARATOR != '/') $value = self::normalizeSeparators($value); if($key == 'root') { $this->_root = $value; return $this; @@ -159,7 +159,7 @@ class Paths extends WireData { */ public function get($key) { static $_http = null; - if($key == 'root') return $this->_root; + if($key === 'root') return $this->_root; $http = ''; if(is_object($key)) { $key = "$key"; @@ -174,7 +174,7 @@ class Paths extends WireData { $key = substr($key, 4); $key[0] = strtolower($key[0]); } - if($key == 'root') { + if($key === 'root') { $value = $http . $this->_root; } else { $value = parent::get($key); diff --git a/wire/core/ProcessWire.php b/wire/core/ProcessWire.php index c2dedf41..70a61a4f 100644 --- a/wire/core/ProcessWire.php +++ b/wire/core/ProcessWire.php @@ -424,7 +424,7 @@ class ProcessWire extends Wire { $fieldtypes = $this->wire('fieldtypes', new Fieldtypes(), true); $fields = $this->wire('fields', new Fields(), true); $fieldgroups = $this->wire('fieldgroups', new Fieldgroups(), true); - $templates = $this->wire('templates', new Templates($fieldgroups, $config->paths->templates), true); + $templates = $this->wire('templates', new Templates($fieldgroups), true); $pages = $this->wire('pages', new Pages($this), true); $this->initVar('fieldtypes', $fieldtypes); diff --git a/wire/core/Template.php b/wire/core/Template.php index 047834ba..fed740fb 100644 --- a/wire/core/Template.php +++ b/wire/core/Template.php @@ -792,9 +792,11 @@ class Template extends WireData implements Saveable, Exportable { if(empty($value)) return; if(strpos($value, '/') === false) { + // value is basename $value = $this->config->paths->templates . $value; } else if(strpos($value, $this->config->paths->root) !== 0) { + // value is path outside of our installation root, which we do not accept $value = $this->config->paths->templates . basename($value); } @@ -878,35 +880,52 @@ class Template extends WireData implements Saveable, Exportable { */ public function filename() { - if($this->filename) return $this->filename; + /** @var Config $config */ + $config = $this->wire('config'); + $path = $config->paths->templates; + $ext = '.' . $config->templateExtension; + $altFilename = $this->altFilename; - if(!$this->settings['name']) throw new WireException("Template must be assigned a name before 'filename' can be accessed"); + if(!$this->settings['name']) { + throw new WireException("Template must be assigned a name before 'filename' can be accessed"); + } - if($this->altFilename) { - $altFilename = $this->wire('templates')->path . basename($this->altFilename, "." . $this->config->templateExtension) . "." . $this->config->templateExtension; - $this->filename = $altFilename; + if($altFilename) { + $filename = $path . basename($altFilename, $ext) . $ext; } else { - $this->filename = $this->wire('templates')->path . $this->settings['name'] . '.' . $this->config->templateExtension; + $filename = $path . $this->settings['name'] . $ext; + } + + if($filename !== $this->filename) { + // first set of filename, or filename/path has been changed + $this->filenameExists = null; + $this->filename = $filename; } - - $isModified = false; - $fileExists = $this->filenameExists(); - if($fileExists) { - $modified = filemtime($this->filename); - if($modified > $this->modified) { - $isModified = true; - $this->modified = $modified; - } - if($isModified || !$this->ns) { - // determine namespace - $this->ns = $this->wire('files')->getNamespace($this->filename); - // tell it to save the template after the request is finished - $this->addHookAfter('ProcessWire::finished', $this, 'hookFinished'); + if($this->filenameExists === null) { + $this->filenameExists = file_exists($filename); + if($this->filenameExists) { + // if filename exists, keep track of last modification time + $isModified = false; + $modified = filemtime($filename); + if($modified > $this->modified) { + $isModified = true; + $this->modified = $modified; + } + if($isModified || !$this->ns) { + // determine namespace + $files = $this->wire('files'); + /** @var WireFileTools $files */ + $templates = $this->wire('templates'); + /** @var Templates $templates */ + $this->ns = $files->getNamespace($filename); + $templates->fileModified($this); + } } } - return $this->filename; + + return $filename; } /** @@ -928,11 +947,11 @@ class Template extends WireData implements Saveable, Exportable { * * #pw-group-files * - * @return string + * @return bool * */ public function filenameExists() { - if(!is_null($this->filenameExists)) return $this->filenameExists; + if($this->filenameExists !== null) return $this->filenameExists; $this->filenameExists = file_exists($this->filename()); return $this->filenameExists; } diff --git a/wire/core/Templates.php b/wire/core/Templates.php index ce1b2e4b..6b5d9e05 100644 --- a/wire/core/Templates.php +++ b/wire/core/Templates.php @@ -5,7 +5,7 @@ * * Manages and provides access to all the Template instances * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2019 by Ryan Cramer * https://processwire.com * * #pw-summary Manages and provides access to all the Templates. @@ -16,6 +16,7 @@ * @method bool|Saveable|Template clone(Saveable $item, $name = '') #pw-internal * @method array getExportData(Template $template) Export Template data for external use. #pw-advanced * @method array setImportData(Template $template, array $data) Given an array of Template export data, import it to the given Template. #pw-advanced + * @method void fileModified(Template $template) Hook called when a template detects that its file has been modified. #pw-hooker * */ class Templates extends WireSaveableItems { @@ -30,26 +31,26 @@ class Templates extends WireSaveableItems { * WireArray of all Template instances * */ - protected $templatesArray; - + protected $templatesArray; + /** - * Path where Template files are stored + * Templates that had changed files during this request + * + * @var array Array of Template objects indexed by id * */ - protected $path; + protected $fileModTemplates = array(); /** * Construct the Templates * * @param Fieldgroups $fieldgroups Reference to the Fieldgroups - * @param string $path Path to where template files are stored * */ - public function __construct(Fieldgroups $fieldgroups, $path) { + public function __construct(Fieldgroups $fieldgroups) { $fieldgroups->wire($this); $this->fieldgroups = $fieldgroups; $this->templatesArray = $this->wire(new TemplatesArray()); - $this->path = $path; } /** @@ -113,7 +114,7 @@ class Templates extends WireSaveableItems { * */ public function get($key) { - if($key == 'path') return $this->path; + if($key == 'path') return $this->wire('config')->paths->templates; $value = $this->templatesArray->get($key); if(is_null($value)) $value = parent::get($key); return $value; @@ -652,6 +653,44 @@ class Templates extends WireSaveableItems { return $updated; } + /** + * Hook called when a Template detects that its file has changed + * + * Note that the hook is not called until something in the system (like a page render) asks for the template’s filename. + * That’s because it would not be efficient for PW to check the file for every template in the system on every request. + * + * #pw-hooker + * + * @param Template $template + * @since 3.0.141 + * + */ + public function ___fileModified(Template $template) { + if(empty($this->fileModTemplates)) { + // add hook on first call + $this->addHookAfter('ProcessWire::finished', $this, '_hookFinished'); + } + $this->fileModTemplates[$template->id] = $template; + } + + /** + * Saves templates that had modified files to update 'modified' and 'ns' properties after the request is complete + * + * #pw-internal + * + * @param HookEvent $e + * @since 3.0.141 + * + */ + public function _hookFinished(HookEvent $e) { + if($e) {} + foreach($this->fileModTemplates as $id => $template) { + if($template->isChanged('modified') || $template->isChanged('ns')) { + $template->save(); + } + } + $this->fileModTemplates = array(); + } /** * FUTURE USE: Is the parent/child relationship allowed? diff --git a/wire/core/Wire.php b/wire/core/Wire.php index 806c5dbd..561a3d1b 100644 --- a/wire/core/Wire.php +++ b/wire/core/Wire.php @@ -1682,7 +1682,7 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { */ public function wire($name = '', $value = null, $lock = false) { - if(is_null($this->_wire)) { + if($this->_wire === null) { // this object has not yet been wired! use last known current instance as fallback // note this condition is unsafe in multi-instance mode $wire = ProcessWire::getCurrentInstance();