base = $base; $this->tests = $includeTests; $this->project = $project; $cacheClass = defined('SS_MANIFESTCACHE') ? SS_MANIFESTCACHE : 'ManifestCache_File'; $this->cache = new $cacheClass('templatemanifest'.($includeTests ? '_tests' : '')); $this->cacheKey = $this->getCacheKey($includeTests); if ($forceRegen) { $this->regenerate(); } } /** * @return string */ public function getBase() { return $this->base; } /** * Generate a unique cache key to avoid manifest cache collisions. * We compartmentalise based on the base path, the given project, and whether * or not we intend to include tests. * @param boolean $includeTests * @return string */ public function getCacheKey($includeTests = false) { return sha1(sprintf( "manifest-%s-%s-%s", $this->base, $this->project, (int) $includeTests // cast true to 1, false to 0 ) ); } /** * Returns a map of all template information. The map is in the following * format: * * * array( * 'moduletemplate' => array( * 'main' => '/path/to/module/templates/Main.ss' * ), * 'include' => array( * 'include' => '/path/to/module/templates/Includes/Include.ss' * ), * 'page' => array( * 'themes' => array( * 'simple' => array( * 'main' => '/path/to/theme/Page.ss' * 'Layout' => '/path/to/theme/Layout/Page.ss' * ) * ) * ) * ) * * * @return array */ public function getTemplates() { if (!$this->inited) { $this->init(); } return $this->templates; } /** * Returns a set of possible candidate templates that match a certain * template name. * * This is the same as extracting an individual array element from * {@link SS_TemplateManifest::getTemplates()}. * * @param string $name * @return array */ public function getTemplate($name) { if (!$this->inited) { $this->init(); } $name = strtolower($name); if (array_key_exists($name, $this->templates)) { return $this->templates[$name]; } else { return array(); } } /** * Returns the correct candidate template. In order of importance, application * project code, current theme and finally modules. * * @param string $name * @param string $theme - theme name * * @return array */ public function getCandidateTemplate($name, $theme = null) { $found = array(); $candidates = $this->getTemplate($name); // theme overrides modules if ($theme && isset($candidates['themes'][$theme])) { $found = array_merge($candidates, $candidates['themes'][$theme]); } // project overrides theme if ($this->project && isset($candidates[$this->project])) { $found = array_merge($found, $candidates[$this->project]); } $found = ($found) ? $found : $candidates; if (isset($found['themes'])) unset($found['themes']); if (isset($found[$this->project])) unset($found[$this->project]); return $found; } /** * Regenerates the manifest by scanning the base path. * * @param bool $cache */ public function regenerate($cache = true) { $finder = new ManifestFileFinder(); $finder->setOptions(array( 'name_regex' => '/\.ss$/', 'include_themes' => true, 'ignore_tests' => !$this->tests, 'file_callback' => array($this, 'handleFile') )); $finder->find($this->base); if ($cache) { $this->cache->save($this->templates, $this->cacheKey); } $this->inited = true; } public function handleFile($basename, $pathname, $depth) { $projectFile = false; $theme = null; // Template in theme if (preg_match( '#'.preg_quote($this->base.'/'.THEMES_DIR).'/([^/_]+)(_[^/]+)?/(.*)$#', $pathname, $matches )) { $theme = $matches[1]; $relPath = $matches[3]; // Template in project } elseif (preg_match( '#'.preg_quote($this->base.'/'.$this->project).'/(.*)$#', $pathname, $matches )) { $projectFile = true; $relPath = $matches[1]; // Template in module } elseif (preg_match( '#'.preg_quote($this->base).'/([^/]+)/(.*)$#', $pathname, $matches )) { $relPath = $matches[2]; } else { throw new \LogicException("Can't determine meaning of path: $pathname"); } // If a templates subfolder is used, ignore that if (preg_match('#'.preg_quote(self::TEMPLATES_DIR).'/(.*)$#', $relPath, $matches)) { $relPath = $matches[1]; } // Layout and Content folders have special meaning if (preg_match('#^(.*/)?(Layout|Content|Includes)/([^/]+)$#', $relPath, $matches)) { $type = $matches[2]; $relPath = "$matches[1]$matches[3]"; } else { $type = "main"; } $name = strtolower(substr($relPath, 0, -3)); $name = str_replace('/', '\\', $name); if ($theme) { $this->templates[$name]['themes'][$theme][$type] = $pathname; } else if ($projectFile) { $this->templates[$name][$this->project][$type] = $pathname; } else { $this->templates[$name][$type] = $pathname; } // If we've found a template in a subdirectory, then allow its use for a non-namespaced class // as well. This was a common SilverStripe 3 approach, where templates were placed into // subfolders to suit the whim of the developer. if (strpos($name, '\\') !== false) { $name2 = substr($name, strrpos($name, '\\') + 1); // In of these cases, the template will only be provided if it isn't already set. This // matches SilverStripe 3 prioritisation. if ($theme) { if (!isset($this->templates[$name2]['themes'][$theme][$type])) { $this->templates[$name2]['themes'][$theme][$type] = $pathname; } } else if ($projectFile) { if (!isset($this->templates[$name2][$this->project][$type])) { $this->templates[$name2][$this->project][$type] = $pathname; } } else { if (!isset($this->templates[$name2][$type])) { $this->templates[$name2][$type] = $pathname; } } } } protected function init() { if ($data = $this->cache->load($this->cacheKey)) { $this->templates = $data; $this->inited = true; } else { $this->regenerate(); } } }