PATH:
home
/
vcomplcotr
/
www
/
wp-content
/
plugins
/
akeebabackupwp
/
app
/
vendor
/
akeeba
/
awf
/
src
/
Mvc
<?php /** * @package awf * @copyright Copyright (c)2014-2026 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU GPL version 3 or later */ namespace Awf\Mvc; use Awf\Application\Application; use Awf\Container\Container; use Awf\Container\ContainerAwareInterface; use Awf\Container\ContainerAwareTrait; use Awf\Exception\App; use Awf\Input\Input; use Awf\Mvc\Engine\EngineInterface; use Awf\Text\Language; use Awf\Text\LanguageAwareInterface; use Awf\Text\LanguageAwareTrait; use Awf\Uri\Uri; use ErrorException; use Exception; use RuntimeException; use Throwable; /** * Class View * * A generic MVC view implementation * * @package Awf\Mvc */ #[\AllowDynamicProperties] class View implements ContainerAwareInterface, LanguageAwareInterface { use ContainerAwareTrait; use LanguageAwareTrait; /** * Current or most recently performed task. * Currently public, it should be reduced to protected in the future * * @var string */ public $task; /** * The mapped task that was performed. * Currently public, it should be reduced to protected in the future * * @var string */ public $doTask; /** * The name of the view * * @var array */ protected $name = null; /** * Registered models * * @var array */ protected $modelInstances = []; /** * The default model * * @var string */ protected $defaultModel = null; /** * Layout name * * @var string */ protected $layout = 'default'; /** * Layout template * * @var string */ protected $layoutTemplate = '_'; /** * The set of search directories for view templates * * @var array */ protected $templatePaths = []; /** * The name of the default template source file. * * @var string */ protected $template = null; /** * The output of the template script. * * @var string */ protected $output = null; /** * A cached copy of the configuration * * @var array */ protected $config = []; /** * The input object * * @var Input */ protected $input = null; /** * Aliases of view templates. For example: * * array('userProfile' => 'users/profile') * * allows you to do something like $this->loadAnyTemplate('userProfile') to display the view template users/profile. * You can also alias one view template with another, e.g. 'users/profile' => 'clients/record' * * @var array */ protected $viewTemplateAliases = []; /** * The object used to locate view templates in the filesystem * * @var ViewTemplateFinder */ protected $viewFinder = null; /** * Used when loading template files to avoid variable scope issues * * @var null */ protected $_tempFilePath = null; /** * Maps view template extensions to view engine classes * * @var array */ protected $viewEngineMap = [ '.blade.php' => 'Awf\\Mvc\\Engine\\BladeEngine', '.php' => 'Awf\\Mvc\\Engine\\PhpEngine', ]; /** * All of the finished, captured sections. * * @var array */ protected $sections = []; /** * The stack of in-progress sections. * * @var array */ protected $sectionStack = []; /** * The number of active rendering operations. * * @var int */ protected $renderCount = 0; /** * Should I be strict about the subtemplate being loaded? * * When false, trying to load template `foo_bar` will fall back to `foo` is `foo_bar` does not exist. * * When true, trying to load template `foo_bar` will throw if `foo_bar` does not exist. * * @var bool * @since 1.0.1 */ protected $strictTpl = false; /** * Should I be strict about the layout being loaded? * * When false, trying to load layout `foo` will fall back to `default` if `foo` does not exist. * * When true, trying to load layout `foo` will throw if `foo` does not exist. * * @var bool * @since 1.0.1 */ protected $strictLayout = false; /** * Constructor * * @param Container|null $container The DI Container.<br/> * Inside it you can have an 'mvc_config' array with the following options:<br/> * name: the name (optional) of the view (defaults to the view class name * suffix).<br/> escape: the name (optional) of the function to use for escaping * strings<br/> template_path: the path (optional) of the layout directory * (defaults to base_path + /views/ + view name<br/> layout: the layout (optional) * to use to display the view<br/> * * @throws App */ public function __construct(?Container $container = null, ?Language $language = null) { /** @deprecated 2.0 You must provide the container */ if (empty($container)) { trigger_error( sprintf('The container argument is mandatory in %s', __METHOD__), E_USER_DEPRECATED ); $container = Application::getInstance()->getContainer(); } $this->setContainer($container); $this->setLanguage($language ?? $container->language); $container->eventDispatcher->trigger('onViewBeforeConstruct', [$this, $container]); $container = $this->getContainer(); // Cache some useful references in the class $this->input = $container->input; $this->config = isset($container['mvc_config']) ? $container['mvc_config'] : []; // Get the view name $this->name = $this->getName(); // Set the default template search path if (array_key_exists('template_path', $this->config)) { // User-defined dirs $this->setTemplatePath($this->config['template_path']); } else { $this->setTemplatePath($this->container->basePath . '/View/' . ucfirst($this->name) . '/tmpl'); } // Set the layout if (array_key_exists('layout', $this->config)) { $this->setLayout($this->config['layout']); } $templatePath = $this->container->templatePath; $fallback = $templatePath . '/' . $this->container->application->getTemplate() . '/html/' . ucfirst($this->container->application->getName()) . '/' . $this->name; $this->addTemplatePath($fallback); // Get extra directories through event dispatchers $extraPathsResults = $this->container->eventDispatcher->trigger('onGetViewTemplatePaths', [$this->getName()]); if (is_array($extraPathsResults) && !empty($extraPathsResults)) { foreach ($extraPathsResults as $somePaths) { if (!empty($somePaths)) { foreach ($somePaths as $aPath) { $this->addTemplatePath($aPath); } } } } // Apply the viewEngineMap if (isset($this->config['viewEngineMap'])) { if (!is_array($this->config['viewEngineMap'])) { $temp = explode(',', $this->config['viewEngineMap']); $this->config['viewEngineMap'] = []; foreach ($temp as $assignment) { $parts = explode('=>', $assignment, 2); if (count($parts) != 2) { continue; } $parts = array_map(function ($x) { return trim($x); }, $parts); $this->config['viewEngineMap'][$parts[0]] = $parts[1]; } } $this->viewEngineMap = array_merge($this->viewEngineMap, $this->config['viewEngineMap']); } // Set the ViewFinder $this->viewFinder = new ViewTemplateFinder($this); if (isset($this->config['viewFinder']) && !empty($this->config['viewFinder']) && is_object($this->config['viewFinder']) && ($this->config['viewFinder'] instanceof ViewTemplateFinder)) { $this->viewFinder = $this->config['viewFinder']; } // Apply the registered view template extensions to the view finder $this->viewFinder->setExtensions(array_keys($this->viewEngineMap)); $this->baseurl = Uri::base(true, $this->container); $container->eventDispatcher->trigger('onViewAfterConstruct', [$this, $container]); } /** * Returns an instance of a view class * * @param string|null $appName The application name [optional] Default: from container or default app if no * container is provided * @param string|null $viewName The view name [optional] Default: the "view" input parameter * @param string|null $viewType The view type [optional] Default: the "format" input parameter or, if not * defined, "html" * @param Container|null $container The container to be attached to the view * * @deprecated 2.0 Use the MVCFactory in the Container instead * @return self * @throws App */ public static function getInstance(?string $appName = null, ?string $viewName = null, ?string $viewType = null, ?Container $container = null, ?Language $language = null) { trigger_error( sprintf( 'Calling %s is deprecated. Use the MVCFactory service of the container instead.', __METHOD__ ), E_USER_DEPRECATED ); return ($container ?? Application::getInstance($appName)->getContainer()) ->mvcFactory->makeView($viewName, $viewType, $language); } /** * Method to get the view name * * The model name by default parsed using the classname, or it can be set * by passing a $config['name'] in the class constructor * * @return string The name of the model * * @throws RuntimeException */ public function getName() { if (empty($this->name)) { $r = null; if (!preg_match('/(.*)\\\\View\\\\(.*)\\\\(.*)/i', get_class($this), $r)) { throw new RuntimeException($this->getContainer()->language->text('AWF_APPLICATION_ERROR_VIEW_GET_NAME'), 500); } $this->name = $r[2]; } return $this->name; } /** * Escapes a value for output in a view script. * * @param mixed $var The output to escape. * * @return mixed The escaped value. */ public function escape($var) { return htmlspecialchars($var ?? '', ENT_COMPAT, 'UTF-8'); } /** * Method to get data from a registered model or a property of the view * * @param string $property The name of the method to call on the Model or the property to get * @param string $default The default value [optional] * @param string $modelName The name of the Model to reference [optional] * * @return mixed The return value of the method */ public function get($property, $default = null, $modelName = null) { // If $model is null we use the default model if (is_null($modelName)) { $model = $this->defaultModel; } else { $model = strtolower($modelName); } // First check to make sure the model requested exists if (isset($this->modelInstances[$model])) { // Model exists, let's build the method name $method = 'get' . ucfirst($property); // Does the method exist? if (method_exists($this->modelInstances[$model], $method)) { // The method exists, let's call it and return what we get $result = $this->modelInstances[$model]->$method(); return $result; } else { $result = $this->modelInstances[$model]->$property(); if (is_null($result)) { return $default; } return $result; } } // If the model doesn't exist, try to fetch a View property else { if (@isset($this->$property)) { return $this->$property; } else { return $default; } } } /** * Returns a named Model object * * @param string $name The Model name. If null we'll use the modelName * variable or, if it's empty, the same name as * the Controller * @param array $config Configuration parameters to the Model. If skipped * we will use $this->config * * @return Model The instance of the Model known to this Controller */ public function getModel($name = null, $config = []) { if (!empty($name)) { $modelName = strtolower($name); } elseif (!empty($this->defaultModel)) { $modelName = strtolower($this->defaultModel); } else { $modelName = strtolower($this->name); } if (!array_key_exists($modelName, $this->modelInstances)) { $mvcConfig = $this->container['mvc_config'] ?? []; $this->container['mvc_config'] = ($config ?: $this->config); $this->modelInstances[$modelName] = $this->container->mvcFactory->makeModel($modelName, $this->getLanguage()); $this->container['mvc_config'] = $mvcConfig; } return $this->modelInstances[$modelName]; } /** * Pushes the default Model to the View * * @param Model $model The model to push */ public function setDefaultModel(Model &$model) { $name = $model->getName(); $this->setDefaultModelName($name); $this->setModel($this->defaultModel, $model); } /** * Set the name of the Model to be used by this View * * @param string $modelName The name of the Model * * @return void */ public function setDefaultModelName($modelName) { $this->defaultModel = $modelName; } /** * Pushes a named model to the View * * @param string $modelName The name of the Model * @param Model $model The actual Model object to push * * @return void */ public function setModel($modelName, Model &$model) { $this->modelInstances[strtolower($modelName)] = $model; } /** * Overrides the default method to execute and display a template script. * Instead of loadTemplate is uses loadAnyTemplate. * * @param string $tpl The name of the template file to parse * * @return boolean True on success * * @throws Exception When the layout file is not found */ public function display($tpl = null) { $this->viewOutput = ''; $method = 'onBefore' . ucfirst($this->doTask); if (method_exists($this, $method)) { $result = $this->$method($tpl); if (!$result) { throw new Exception($this->getContainer()->language->text('AWF_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); } } $results = $this->container->eventDispatcher->trigger('onViewBefore' . ucfirst($this->doTask), [$this]) ?: []; if (in_array(false, $results, true)) { return false; } $this->viewOutput .= $this->loadTemplate($tpl); $method = 'onAfter' . ucfirst($this->doTask); if (method_exists($this, $method)) { if (!($this->$method($tpl))) { throw new Exception($this->getContainer()->language->text('AWF_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); } } $results = $this->container->eventDispatcher->trigger('onViewAfter' . ucfirst($this->doTask), [ $this, &$this->viewOutput, ]) ?: []; if (in_array(false, $results, true)) { return false; } if (is_object($this->viewOutput) && ($this->viewOutput instanceof Exception)) { throw $this->viewOutput; } else { echo $this->viewOutput; return true; } } /** * Our function uses loadAnyTemplate to provide smarter view template loading. * * @param string $tpl The name of the template file to parse * @param boolean $strict Should we use strict naming, i.e. force a non-empty $tpl? * * @return mixed A string if successful, otherwise an Exception */ public function loadTemplate($tpl = null, $strict = false) { $basePath = $this->name . '/'; $strict = $strict || $this->strictTpl; if ($strict) { $paths = [$basePath . $this->getLayout() . ($tpl ? "_$tpl" : '')]; if (!$this->strictLayout) { $paths[] = $basePath . 'default' . ($tpl ? "_$tpl" : ''); } } else { $paths = [ $basePath . $this->getLayout() . ($tpl ? "_$tpl" : ''), $basePath . $this->getLayout(), ]; if (!$this->strictLayout) { $paths[] = $basePath . 'default' . ($tpl ? "_$tpl" : ''); $paths[] = $basePath . 'default'; } } $paths = array_unique($paths); foreach ($paths as $path) { try { $result = $this->loadAnyTemplate($path); } catch (Exception $e) { $result = $e; } if (!($result instanceof Exception)) { break; } } return $result; } /** * Get the layout. * * @return string The layout name */ public function getLayout() { return $this->layout; } /** * Sets the layout name to use * * @param string $layout The layout name or a string in format <template>:<layout file> * * @return string Previous value. */ public function setLayout($layout) { $previous = $this->layout; if (strpos($layout, ':') === false) { $this->layout = $layout; } else { // Convert parameter to array based on : $temp = explode(':', $layout); $this->layout = $temp[1]; // Set layout template $this->layoutTemplate = $temp[0]; } return $previous; } /** * Add an alias for a view template. * * @param string $viewTemplate Existing view template, in the format viewName/layoutName * @param string $alias The alias of the view template (any string will do) * * @return void */ public function alias($viewTemplate, $alias) { $this->viewTemplateAliases[$alias] = $viewTemplate; } /** * Loads a template given any path. The path is in the format viewName/layoutName * * @param string $uri The template path * @param array $forceParams A hash array of variables to be extracted in the local scope of the template * file * @param callable $callback A method to post-process the 3ναluα+3d view template (I use leetspeak here * because of bad quality hosts with broken scanners) * * @return string The output of the template * * @throws Exception When the layout file is not found */ public function loadAnyTemplate($uri = '', $forceParams = [], $callback = null) { if (isset($this->viewTemplateAliases[$uri])) { $uri = $this->viewTemplateAliases[$uri]; } $layoutTemplate = $this->getLayoutTemplate(); $extraPaths = []; if (!empty($this->templatePaths)) { $extraPaths = $this->templatePaths; } // First get the raw view template path $path = $this->viewFinder->resolveUriToPath($uri, $layoutTemplate, $extraPaths); // Now get the parsed view template path $this->_tempFilePath = $this->getEngine($path)->get($path, $forceParams); // We will keep track of the amount of views being rendered so we can flush // the section after the complete rendering operation is done. This will // clear out the sections for any separate views that may be rendered. $this->incrementRender(); // Get the processed template $contents = $this->processTemplate($forceParams); // Once we've finished rendering the view, we'll decrement the render count // so that each sections get flushed out next time a view is created and // no old sections are staying around in the memory of an environment. $this->decrementRender(); $response = isset($callback) ? $callback($this, $contents) : null; if (!is_null($response)) { $contents = $response; } // Once we have the contents of the view, we will flush the sections if we are // done rendering all views so that there is nothing left hanging over when // another view gets rendered in the future by the application developer. $this->flushSectionsIfDoneRendering(); return $contents; } /** * Increment the rendering counter. * * @return void */ public function incrementRender() { $this->renderCount++; } /** * Decrement the rendering counter. * * @return void */ public function decrementRender() { $this->renderCount--; } /** * Check if there are no active render operations. * * @return bool */ public function doneRendering() { return $this->renderCount == 0; } /** * Go through a data array and render a subtemplate against each record (think master-detail views). This is * accessible through Blade templates as @each * * @param string $viewTemplate The view template to use for each subitem, format viewName/layoutName * @param array $data The array of data you want to render. It can be a DataModel\Collection, array, * ... * @param string $eachItemName How to call each item in the loaded subtemplate (passed through $forceParams) * @param string $empty What to display if the array is empty * * @return string * @throws Exception */ public function renderEach($viewTemplate, $data, $eachItemName, $empty = 'raw|') { $result = ''; // If is actually data in the array, we will loop through the data and append // an instance of the partial view to the final result HTML passing in the // iterated value of this data array, allowing the views to access them. if (count($data) > 0) { foreach ($data as $key => $value) { $data = ['key' => $key, $eachItemName => $value]; $result .= $this->loadAnyTemplate($viewTemplate, $data); } } // If there is no data in the array, we will render the contents of the empty // view. Alternatively, the "empty view" could be a raw string that begins // with "raw|" for convenience and to let this know that it is a string. Or // a language string starting with text|. else { if (akeeba_starts_with($empty, 'raw|')) { $result = substr($empty, 4); } elseif (akeeba_starts_with($empty, 'text|')) { $result = $this->getContainer()->language->text(substr($empty, 5)); } else { $result = $this->loadAnyTemplate($empty); } } return $result; } /** * Start injecting content into a section. * * @param string $section * @param string $content * * @return void */ public function startSection($section, $content = '') { if ($content === '') { if (ob_start()) { $this->sectionStack[] = $section; } } else { $this->extendSection($section, $content); } } /** * Stop injecting content into a section and return its contents. * * @return string */ public function yieldSection() { return $this->yieldContent($this->stopSection()); } /** * Stop injecting content into a section. * * @param bool $overwrite * * @return string */ public function stopSection($overwrite = false) { if (empty($this->sectionStack)) { // Let's close the output buffering ob_get_clean(); throw new RuntimeException("Blade template renderer: the section stack is empty"); } $last = array_pop($this->sectionStack); if ($overwrite) { $this->sections[$last] = ob_get_clean(); } else { $this->extendSection($last, ob_get_clean()); } return $last; } /** * Stop injecting content into a section and append it. * * @return string */ public function appendSection() { if (empty($this->sectionStack)) { // Let's close the output buffering ob_get_clean(); throw new RuntimeException("Blade template renderer: the section stack is empty"); } $last = array_pop($this->sectionStack); if (isset($this->sections[$last])) { $this->sections[$last] .= ob_get_clean(); } else { $this->sections[$last] = ob_get_clean(); } return $last; } /** * Get the string contents of a section. * * @param string $section * @param string $default * * @return string */ public function yieldContent($section, $default = '') { $sectionContent = $default; if (isset($this->sections[$section])) { $sectionContent = $this->sections[$section]; } return str_replace('@parent', '', $sectionContent); } /** * Flush all of the section contents. * * @return void */ public function flushSections() { $this->sections = []; $this->sectionStack = []; } /** * Flush all of the section contents if done rendering. * * @return void */ public function flushSectionsIfDoneRendering() { if ($this->doneRendering()) { $this->flushSections(); } } /** * Get the layout template. * * @return string The layout template name */ public function getLayoutTemplate() { return $this->layoutTemplate; } /** * Load a helper file * * @param string $helperClass The last part of the name of the helper * class. * * @return void */ public function loadHelper($helperClass = null) { // Get the helper class name $className = '\\' . ucfirst($this->container->application->getName()) . '\\Helper\\' . ucfirst($helperClass); // This trick autoloads the helper class. We can't instantiate it as // helpers are (supposed to be) abstract classes with static method // interfaces. class_exists($className); } public function getTask() { return $this->task; } /** * @param string $task * * @return $this This for chaining */ public function setTask($task) { $this->task = $task; return $this; } public function getDoTask() { return $this->doTask; } /** * @param string $task * * @return $this This for chaining */ public function setDoTask($task) { $this->doTask = $task; return $this; } /** * Setter for the strictTpl flag * * @param bool $strictTpl The value to set it to * * @see self::$strictTpl * @since 1.0.0 */ public function setStrictTpl(bool $strictTpl): void { $this->strictTpl = $strictTpl; } /** * Setter for the strictLayout flag * * @param bool $strictLayout The value to set it to * * @see self::$strictLayout * @since 1.0.0 */ public function setStrictLayout(bool $strictLayout): void { $this->strictLayout = $strictLayout; } /** * Sets an entire array of search paths for templates or resources. * * @param mixed $path The new search path, or an array of search paths. If null or false, resets to the * current directory only. * * @return void */ protected function setTemplatePath($path) { // Clear out the prior search dirs $this->templatePaths = []; // Actually add the user-specified directories $this->addTemplatePath($path); // Set the alternative template search dir $templatePath = $this->container->templatePath; $fallback = $templatePath . '/' . $this->container->application->getTemplate() . '/html/' . strtoupper($this->container->application->getName()) . '/' . $this->getName(); $this->addTemplatePath($fallback); // Get extra directories through event dispatchers $extraPathsResults = $this->container->eventDispatcher->trigger('onGetViewTemplatePaths', [$this->getName()]); if (is_array($extraPathsResults) && !empty($extraPathsResults)) { foreach ($extraPathsResults as $somePaths) { if (!empty($somePaths)) { foreach ($somePaths as $aPath) { $this->addTemplatePath($aPath); } } } } } /** * Adds to the search path for templates and resources. * * @param mixed $path The directory or stream, or an array of either, to search. * * @return void */ protected function addTemplatePath($path) { // Just force to array settype($path, 'array'); // Loop through the path directories foreach ($path as $dir) { // No surrounding spaces allowed! $dir = trim($dir); // Add trailing separators as needed if (substr($dir, -1) != DIRECTORY_SEPARATOR) { // Directory $dir .= DIRECTORY_SEPARATOR; } // Add to the top of the search dirs array_unshift($this->templatePaths, $dir); } } /** * Get the appropriate view engine for the given view template path. * * @param string $path The path of the view template * * @return EngineInterface * * @throws RuntimeException */ protected function getEngine($path) { foreach ($this->viewEngineMap as $extension => $engine) { if (substr($path, -strlen($extension)) == $extension) { return new $engine($this); } } throw new RuntimeException(sprintf("Unrecognised extension in view template “%s”", $path)); } /** * Get the extension used by the view file. * * @param string $path * * @return string */ protected function getExtension($path) { $extensions = array_keys($this->viewEngineMap); return akeeba_array_first($extensions, function ($key, $value) use ($path) { return akeeba_ends_with($path, $value); }); } /** * Append content to a given section. * * @param string $section * @param string $content * * @return void */ protected function extendSection($section, $content) { if (isset($this->sections[$section])) { $content = str_replace('@parent', $content, $this->sections[$section]); } $this->sections[$section] = $content; } /** * Evaluates the template described in the _tempFilePath property * * @param array $forceParams Forced parameters * * @return string * @throws Exception */ protected function processTemplate(array &$forceParams) { // If the engine returned raw content, return the raw content immediately if ($this->_tempFilePath['type'] == 'raw') { return $this->_tempFilePath['content']; } if (substr($this->_tempFilePath['content'], 0, 4) == 'raw|') { return substr($this->_tempFilePath['content'], 4); } $obLevel = ob_get_level(); ob_start(); // We'll process the contents of the view inside a try/catch block so we can // flush out any stray output that might get out before an error occurs or // an exception is thrown. This prevents any partial views from leaking. try { $this->includeTemplateFile($forceParams); } catch (Throwable $e) { $this->handleViewException($e, $obLevel); } return ob_get_clean(); } /** * Handle a view exception. * * @param Throwable $e The exception to handle * @param int $obLevel The target output buffering level * * @return void * * @throws $e */ protected function handleViewException(Throwable $e, $obLevel) { while (ob_get_level() > $obLevel) { ob_end_clean(); } // Add a message about the view template $realBasePath = realpath($this->container->filesystemBase); $includedPath = str_replace($realBasePath, 'ROOT', realpath($this->_tempFilePath['content'])); $originalPath = str_replace($realBasePath, 'ROOT', realpath($this->_tempFilePath['original'])); if ($includedPath === $originalPath) { // Straight-up PHP file inclusion $message = sprintf(' %s[View template: %s line %d]', PHP_EOL, $includedPath, $e->getLine()); } else { // Compiled template file $message = sprintf(' %s[View template: %s line %d, compiled from %s]', PHP_EOL, $includedPath, $e->getLine(), $originalPath); } // Ommit duplicate messages if (strpos($e->getMessage(), $message) !== false) { $message = ''; } $message = $e->getMessage() . $message; $newException = new ErrorException($message, 0, 1, $e->getFile(), $e->getLine(), $e); throw $newException; } /** * This method makes sure the current scope isn't polluted with variables when including a view template * * @param array $forceParams Forced parameters * * @return void */ private function includeTemplateFile(array &$forceParams) { // Extract forced parameters if (!empty($forceParams)) { extract($forceParams); } include $this->_tempFilePath['content']; } }
[+]
..
[+]
DataView
[+]
DataModel
[-] Controller.php
[edit]
[-] TreeModel.php
[edit]
[-] Model.php
[edit]
[-] View.php
[edit]
[+]
Compiler
[-] DataModel.php
[edit]
[-] ViewTemplateFinder.php
[edit]
[+]
Engine
[-] DataController.php
[edit]
[-] Factory.php
[edit]