Файловый менеджер - Редактировать - /home/wuectly/www/03cbe/fof.zip
Назад
PK Ma!]&/g!� � autoloader/fof.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage autoloader * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2, or later */ defined('FOF_INCLUDED') or die(); /** * The main class autoloader for FOF itself * * @package FrameworkOnFramework * @subpackage autoloader * @since 2.1 */ class FOFAutoloaderFof { /** * An instance of this autoloader * * @var FOFAutoloaderFof */ public static $autoloader = null; /** * The path to the FOF root directory * * @var string */ public static $fofPath = null; /** * Initialise this autoloader * * @return FOFAutoloaderFof */ public static function init() { if (self::$autoloader == null) { self::$autoloader = new self; } return self::$autoloader; } /** * Public constructor. Registers the autoloader with PHP. */ public function __construct() { self::$fofPath = realpath(__DIR__ . '/../'); spl_autoload_register(array($this,'autoload_fof_core')); } /** * The actual autoloader * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_core($class_name) { // Make sure the class has a FOF prefix if (substr($class_name, 0, 3) != 'FOF') { return; } // Remove the prefix $class = substr($class_name, 3); // Change from camel cased (e.g. ViewHtml) into a lowercase array (e.g. 'view','html') $class = preg_replace('/(\s)+/', '_', $class); $class = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class)); $class = explode('_', $class); // First try finding in structured directory format (preferred) $path = self::$fofPath . '/' . implode('/', $class) . '.php'; if (@file_exists($path)) { include_once $path; } // Then try the duplicate last name structured directory format (not recommended) if (!class_exists($class_name, false)) { reset($class); $lastPart = end($class); $path = self::$fofPath . '/' . implode('/', $class) . '/' . $lastPart . '.php'; if (@file_exists($path)) { include_once $path; } } // If it still fails, try looking in the legacy folder (used for backwards compatibility) if (!class_exists($class_name, false)) { $path = self::$fofPath . '/legacy/' . implode('/', $class) . '.php'; if (@file_exists($path)) { include_once $path; } } } } PK Ma!]�x&�zN zN autoloader/component.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage autoloader * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2, or later * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ defined('FOF_INCLUDED') or die(); /** * An autoloader for FOF-powered components. It allows the autoloading of * various classes related to the operation of a component, from Controllers * and Models to Helpers and Fields. If a class doesn't exist, it will be * created on the fly. * * @package FrameworkOnFramework * @subpackage autoloader * @since 2.1 */ class FOFAutoloaderComponent { /** * An instance of this autoloader * * @var FOFAutoloaderComponent */ public static $autoloader = null; /** * The path to the FOF root directory * * @var string */ public static $fofPath = null; /** * An array holding component names and their FOF-ness status * * @var array */ protected static $fofComponents = array(); /** * Initialise this autoloader * * @return FOFAutoloaderComponent */ public static function init() { if (self::$autoloader == null) { self::$autoloader = new self; } return self::$autoloader; } /** * Public constructor. Registers the autoloader with PHP. */ public function __construct() { self::$fofPath = realpath(__DIR__ . '/../'); spl_autoload_register(array($this,'autoload_fof_controller')); spl_autoload_register(array($this,'autoload_fof_model')); spl_autoload_register(array($this,'autoload_fof_view')); spl_autoload_register(array($this,'autoload_fof_table')); spl_autoload_register(array($this,'autoload_fof_helper')); spl_autoload_register(array($this,'autoload_fof_toolbar')); spl_autoload_register(array($this,'autoload_fof_field')); } /** * Returns true if this is a FOF-powered component, i.e. if it has a fof.xml * file in its main directory. * * @param string $component The component's name * * @return boolean */ public function isFOFComponent($component) { if (!isset($fofComponents[$component])) { $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component); $fofComponents[$component] = file_exists($componentPaths['admin'] . '/fof.xml'); } return $fofComponents[$component]; } /** * Creates class aliases. On systems where eval() is enabled it creates a * real class. On other systems it merely creates an alias. The eval() * method is preferred as class_aliases result in the name of the class * being instantiated not being available, making it impossible to create * a class instance without passing a $config array :( * * @param string $original The name of the original (existing) class * @param string $alias The name of the new (aliased) class * @param boolean $autoload Should I try to autoload the $original class? * * @return void */ private function class_alias($original, $alias, $autoload = true) { static $hasEval = null; if (is_null($hasEval)) { $hasEval = false; if (function_exists('ini_get')) { $disabled_functions = ini_get('disabled_functions'); if (!is_string($disabled_functions)) { $hasEval = true; } else { $disabled_functions = explode(',', $disabled_functions); $hasEval = !in_array('eval', $disabled_functions); } } } if (!class_exists($original, $autoload)) { return; } if ($hasEval) { $phpCode = "class $alias extends $original {}"; eval($phpCode); } else { class_alias($original, $alias, $autoload); } } /** * Autoload Controllers * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_controller($class_name) { FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name"); static $isCli = null, $isAdmin = null; if (is_null($isCli) && is_null($isAdmin)) { list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin(); } if (strpos($class_name, 'Controller') === false) { return; } // Change from camel cased into a lowercase array $class_modified = preg_replace('/(\s)+/', '_', $class_name); $class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified)); $parts = explode('_', $class_modified); // We need three parts in the name if (count($parts) != 3) { return; } // We need the second part to be "controller" if ($parts[1] != 'controller') { return; } // Get the information about this class $component_raw = $parts[0]; $component = 'com_' . $parts[0]; $view = $parts[2]; // Is this an FOF 2.1 or later component? if (!$this->isFOFComponent($component)) { return; } // Get the alternate view and class name (opposite singular/plural name) $alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view); $alt_class = FOFInflector::camelize($component_raw . '_controller_' . $alt_view); // Get the component's paths $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component); // Get the proper and alternate paths and file names $file = "/controllers/$view.php"; $altFile = "/controllers/$alt_view.php"; $path = $componentPaths['main']; $altPath = $componentPaths['alt']; // Try to find the proper class in the proper path if (file_exists($path . $file)) { @include_once $path . $file; } // Try to find the proper class in the alternate path if (!class_exists($class_name) && file_exists($altPath . $file)) { @include_once $altPath . $file; } // Try to find the alternate class in the proper path if (!class_exists($alt_class) && file_exists($path . $altFile)) { @include_once $path . $altFile; } // Try to find the alternate class in the alternate path if (!class_exists($alt_class) && file_exists($altPath . $altFile)) { @include_once $altPath . $altFile; } // If the alternate class exists just map the class to the alternate if (!class_exists($class_name) && class_exists($alt_class)) { $this->class_alias($alt_class, $class_name); } // No class found? Map to FOFController elseif (!class_exists($class_name)) { if ($view != 'default') { $defaultClass = FOFInflector::camelize($component_raw . '_controller_default'); $this->class_alias($defaultClass, $class_name); } else { $this->class_alias('FOFController', $class_name); } } } /** * Autoload Models * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_model($class_name) { FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name"); static $isCli = null, $isAdmin = null; if (is_null($isCli) && is_null($isAdmin)) { list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin(); } if (strpos($class_name, 'Model') === false) { return; } // Change from camel cased into a lowercase array $class_modified = preg_replace('/(\s)+/', '_', $class_name); $class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified)); $parts = explode('_', $class_modified); // We need three parts in the name if (count($parts) != 3) { return; } // We need the second part to be "model" if ($parts[1] != 'model') { return; } // Get the information about this class $component_raw = $parts[0]; $component = 'com_' . $parts[0]; $view = $parts[2]; // Is this an FOF 2.1 or later component? if (!$this->isFOFComponent($component)) { return; } // Get the alternate view and class name (opposite singular/plural name) $alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view); $alt_class = FOFInflector::camelize($component_raw . '_model_' . $alt_view); // Get the proper and alternate paths and file names $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component); $file = "/models/$view.php"; $altFile = "/models/$alt_view.php"; $path = $componentPaths['main']; $altPath = $componentPaths['alt']; // Try to find the proper class in the proper path if (file_exists($path . $file)) { @include_once $path . $file; } // Try to find the proper class in the alternate path if (!class_exists($class_name) && file_exists($altPath . $file)) { @include_once $altPath . $file; } // Try to find the alternate class in the proper path if (!class_exists($alt_class) && file_exists($path . $altFile)) { @include_once $path . $altFile; } // Try to find the alternate class in the alternate path if (!class_exists($alt_class) && file_exists($altPath . $altFile)) { @include_once $altPath . $altFile; } // If the alternate class exists just map the class to the alternate if (!class_exists($class_name) && class_exists($alt_class)) { $this->class_alias($alt_class, $class_name); } // No class found? Map to FOFModel elseif (!class_exists($class_name)) { if ($view != 'default') { $defaultClass = FOFInflector::camelize($component_raw . '_model_default'); $this->class_alias($defaultClass, $class_name); } else { $this->class_alias('FOFModel', $class_name, true); } } } /** * Autoload Views * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_view($class_name) { FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name"); static $isCli = null, $isAdmin = null; if (is_null($isCli) && is_null($isAdmin)) { list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin(); } if (strpos($class_name, 'View') === false) { return; } // Change from camel cased into a lowercase array $class_modified = preg_replace('/(\s)+/', '_', $class_name); $class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified)); $parts = explode('_', $class_modified); // We need at least three parts in the name if (count($parts) < 3) { return; } // We need the second part to be "view" if ($parts[1] != 'view') { return; } // Get the information about this class $component_raw = $parts[0]; $component = 'com_' . $parts[0]; $view = $parts[2]; if (count($parts) > 3) { $format = $parts[3]; } else { $input = new FOFInput; $format = $input->getCmd('format', 'html', 'cmd'); } // Is this an FOF 2.1 or later component? if (!$this->isFOFComponent($component)) { return; } // Get the alternate view and class name (opposite singular/plural name) $alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view); $alt_class = FOFInflector::camelize($component_raw . '_view_' . $alt_view); // Get the proper and alternate paths and file names $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component); $protoFile = "/models/$view"; $protoAltFile = "/models/$alt_view"; $path = $componentPaths['main']; $altPath = $componentPaths['alt']; $formats = array($format); if ($format != 'html') { $formats[] = 'raw'; } foreach ($formats as $currentFormat) { $file = $protoFile . '.' . $currentFormat . '.php'; $altFile = $protoAltFile . '.' . $currentFormat . '.php'; // Try to find the proper class in the proper path if (!class_exists($class_name) && file_exists($path . $file)) { @include_once $path . $file; } // Try to find the proper class in the alternate path if (!class_exists($class_name) && file_exists($altPath . $file)) { @include_once $altPath . $file; } // Try to find the alternate class in the proper path if (!class_exists($alt_class) && file_exists($path . $altFile)) { @include_once $path . $altFile; } // Try to find the alternate class in the alternate path if (!class_exists($alt_class) && file_exists($altPath . $altFile)) { @include_once $altPath . $altFile; } } // If the alternate class exists just map the class to the alternate if (!class_exists($class_name) && class_exists($alt_class)) { $this->class_alias($alt_class, $class_name); } // No class found? Map to FOFModel elseif (!class_exists($class_name)) { if ($view != 'default') { $defaultClass = FOFInflector::camelize($component_raw . '_view_default'); $this->class_alias($defaultClass, $class_name); } else { if (!file_exists(self::$fofPath . '/view/' . $format . '.php')) { $default_class = 'FOFView'; } else { $default_class = 'FOFView' . ucfirst($format); } $this->class_alias($default_class, $class_name, true); } } } /** * Autoload Tables * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_table($class_name) { FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name"); static $isCli = null, $isAdmin = null; if (is_null($isCli) && is_null($isAdmin)) { list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin(); } if (strpos($class_name, 'Table') === false) { return; } // Change from camel cased into a lowercase array $class_modified = preg_replace('/(\s)+/', '_', $class_name); $class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified)); $parts = explode('_', $class_modified); // We need three parts in the name if (count($parts) != 3) { return; } // We need the second part to be "model" if ($parts[1] != 'table') { return; } // Get the information about this class $component_raw = $parts[0]; $component = 'com_' . $parts[0]; $view = $parts[2]; // Is this an FOF 2.1 or later component? if (!$this->isFOFComponent($component)) { return; } // Get the alternate view and class name (opposite singular/plural name) $alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view); $alt_class = FOFInflector::camelize($component_raw . '_table_' . $alt_view); // Get the proper and alternate paths and file names $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component); $file = "/tables/$view.php"; $altFile = "/tables/$alt_view.php"; $path = $componentPaths['admin']; // Try to find the proper class in the proper path if (file_exists($path . $file)) { @include_once $path . $file; } // Try to find the alternate class in the proper path if (!class_exists($alt_class) && file_exists($path . $altFile)) { @include_once $path . $altFile; } // If the alternate class exists just map the class to the alternate if (!class_exists($class_name) && class_exists($alt_class)) { $this->class_alias($alt_class, $class_name); } // No class found? Map to FOFModel elseif (!class_exists($class_name)) { if ($view != 'default') { $defaultClass = FOFInflector::camelize($component_raw . '_table_default'); $this->class_alias($defaultClass, $class_name); } else { $this->class_alias('FOFTable', $class_name, true); } } } /** * Autoload Helpers * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_helper($class_name) { FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name"); static $isCli = null, $isAdmin = null; if (is_null($isCli) && is_null($isAdmin)) { list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin(); } if (strpos($class_name, 'Helper') === false) { return; } // Change from camel cased into a lowercase array $class_modified = preg_replace('/(\s)+/', '_', $class_name); $class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified)); $parts = explode('_', $class_modified); // We need three parts in the name if (count($parts) != 3) { return; } // We need the second part to be "model" if ($parts[1] != 'helper') { return; } // Get the information about this class $component_raw = $parts[0]; $component = 'com_' . $parts[0]; $view = $parts[2]; // Is this an FOF 2.1 or later component? if (!$this->isFOFComponent($component)) { return; } // Get the alternate view and class name (opposite singular/plural name) $alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view); $alt_class = FOFInflector::camelize($component_raw . '_helper_' . $alt_view); // Get the proper and alternate paths and file names $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component); $file = "/helpers/$view.php"; $altFile = "/helpers/$alt_view.php"; $path = $componentPaths['main']; $altPath = $componentPaths['alt']; // Try to find the proper class in the proper path if (file_exists($path . $file)) { @include_once $path . $file; } // Try to find the proper class in the alternate path if (!class_exists($class_name) && file_exists($altPath . $file)) { @include_once $altPath . $file; } // Try to find the alternate class in the proper path if (!class_exists($alt_class) && file_exists($path . $altFile)) { @include_once $path . $altFile; } // Try to find the alternate class in the alternate path if (!class_exists($alt_class) && file_exists($altPath . $altFile)) { @include_once $altPath . $altFile; } // If the alternate class exists just map the class to the alternate if (!class_exists($class_name) && class_exists($alt_class)) { $this->class_alias($alt_class, $class_name); } } /** * Autoload Toolbars * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_toolbar($class_name) { FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name"); static $isCli = null, $isAdmin = null; if (is_null($isCli) && is_null($isAdmin)) { list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin(); } if (strpos($class_name, 'Toolbar') === false) { return; } // Change from camel cased into a lowercase array $class_modified = preg_replace('/(\s)+/', '_', $class_name); $class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified)); $parts = explode('_', $class_modified); // We need two parts in the name if (count($parts) != 2) { return; } // We need the second part to be "model" if ($parts[1] != 'toolbar') { return; } // Get the information about this class $component_raw = $parts[0]; $component = 'com_' . $parts[0]; $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs(); // Get the proper and alternate paths and file names $file = "/components/$component/toolbar.php"; $path = ($isAdmin || $isCli) ? $platformDirs['admin'] : $platformDirs['public']; $altPath = ($isAdmin || $isCli) ? $platformDirs['public'] : $platformDirs['admin']; // Try to find the proper class in the proper path if (file_exists($path . $file)) { @include_once $path . $file; } // Try to find the proper class in the alternate path if (!class_exists($class_name) && file_exists($altPath . $file)) { @include_once $altPath . $file; } // No class found? Map to FOFToolbar if (!class_exists($class_name)) { $this->class_alias('FOFToolbar', $class_name, true); } } /** * Autoload Fields * * @param string $class_name The name of the class to load * * @return void */ public function autoload_fof_field($class_name) { FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name"); // @todo } } PK Ma!] �N N less/less.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage less * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * This class is taken near verbatim (changes marked with **FOF** comment markers) from: * * lessphp v0.3.9 * http://leafo.net/lessphp * * LESS css compiler, adapted from http://lesscss.org * * Copyright 2012, Leaf Corcoran <leafot@gmail.com> * Licensed under MIT or GPLv3, see LICENSE * * THIS IS THIRD PARTY CODE. Code comments are mostly useless placeholders to * stop phpcs from complaining... * * @package FrameworkOnFramework * @since 2.0 */ class FOFLess { public static $VERSION = "v0.3.9"; protected static $TRUE = array("keyword", "true"); protected static $FALSE = array("keyword", "false"); protected $libFunctions = array(); protected $registeredVars = array(); protected $preserveComments = false; /** * Prefix of abstract properties * * @var string */ public $vPrefix = '@'; /** * Prefix of abstract blocks * * @var string */ public $mPrefix = '$'; public $parentSelector = '&'; public $importDisabled = false; public $importDir = ''; protected $numberPrecision = null; /** * Set to the parser that generated the current line when compiling * so we know how to create error messages * * @var FOFLessParser */ protected $sourceParser = null; protected $sourceLoc = null; public static $defaultValue = array("keyword", ""); /** * Uniquely identify imports * * @var integer */ protected static $nextImportId = 0; /** * Attempts to find the path of an import url, returns null for css files * * @param string $url The URL of the import * * @return string|null */ protected function findImport($url) { foreach ((array) $this->importDir as $dir) { $full = $dir . (substr($dir, -1) != '/' ? '/' : '') . $url; if ($this->fileExists($file = $full . '.less') || $this->fileExists($file = $full)) { return $file; } } return null; } /** * Does file $name exists? It's a simple proxy to JFile for now * * @param string $name The file we check for existence * * @return boolean */ protected function fileExists($name) { /** FOF - BEGIN CHANGE * */ return FOFPlatform::getInstance()->getIntegrationObject('filesystem')->fileExists($name); /** FOF - END CHANGE * */ } /** * Compresslist * * @param array $items Items * @param string $delim Delimiter * * @return array */ public static function compressList($items, $delim) { if (!isset($items[1]) && isset($items[0])) { return $items[0]; } else { return array('list', $delim, $items); } } /** * Quote for regular expression * * @param string $what What to quote * * @return string Quoted string */ public static function preg_quote($what) { return preg_quote($what, '/'); } /** * Try import * * @param string $importPath Import path * @param stdObject $parentBlock Parent block * @param string $out Out * * @return boolean */ protected function tryImport($importPath, $parentBlock, $out) { if ($importPath[0] == "function" && $importPath[1] == "url") { $importPath = $this->flattenList($importPath[2]); } $str = $this->coerceString($importPath); if ($str === null) { return false; } $url = $this->compileValue($this->lib_e($str)); // Don't import if it ends in css if (substr_compare($url, '.css', -4, 4) === 0) { return false; } $realPath = $this->findImport($url); if ($realPath === null) { return false; } if ($this->importDisabled) { return array(false, "/* import disabled */"); } $this->addParsedFile($realPath); $parser = $this->makeParser($realPath); $root = $parser->parse(file_get_contents($realPath)); // Set the parents of all the block props foreach ($root->props as $prop) { if ($prop[0] == "block") { $prop[1]->parent = $parentBlock; } } /** * Copy mixins into scope, set their parents, bring blocks from import * into current block * TODO: need to mark the source parser these came from this file */ foreach ($root->children as $childName => $child) { if (isset($parentBlock->children[$childName])) { $parentBlock->children[$childName] = array_merge( $parentBlock->children[$childName], $child ); } else { $parentBlock->children[$childName] = $child; } } $pi = pathinfo($realPath); $dir = $pi["dirname"]; list($top, $bottom) = $this->sortProps($root->props, true); $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir); return array(true, $bottom, $parser, $dir); } /** * Compile Imported Props * * @param array $props Props * @param stdClass $block Block * @param string $out Out * @param FOFLessParser $sourceParser Source parser * @param string $importDir Import dir * * @return void */ protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) { $oldSourceParser = $this->sourceParser; $oldImport = $this->importDir; // TODO: this is because the importDir api is stupid $this->importDir = (array) $this->importDir; array_unshift($this->importDir, $importDir); foreach ($props as $prop) { $this->compileProp($prop, $block, $out); } $this->importDir = $oldImport; $this->sourceParser = $oldSourceParser; } /** * Recursively compiles a block. * * A block is analogous to a CSS block in most cases. A single LESS document * is encapsulated in a block when parsed, but it does not have parent tags * so all of it's children appear on the root level when compiled. * * Blocks are made up of props and children. * * Props are property instructions, array tuples which describe an action * to be taken, eg. write a property, set a variable, mixin a block. * * The children of a block are just all the blocks that are defined within. * This is used to look up mixins when performing a mixin. * * Compiling the block involves pushing a fresh environment on the stack, * and iterating through the props, compiling each one. * * @param stdClass $block Block * * @see FOFLess::compileProp() * * @return void */ protected function compileBlock($block) { switch ($block->type) { case "root": $this->compileRoot($block); break; case null: $this->compileCSSBlock($block); break; case "media": $this->compileMedia($block); break; case "directive": $name = "@" . $block->name; if (!empty($block->value)) { $name .= " " . $this->compileValue($this->reduce($block->value)); } $this->compileNestedBlock($block, array($name)); break; default: $this->throwError("unknown block type: $block->type\n"); } } /** * Compile CSS block * * @param stdClass $block Block to compile * * @return void */ protected function compileCSSBlock($block) { $env = $this->pushEnv(); $selectors = $this->compileSelectors($block->tags); $env->selectors = $this->multiplySelectors($selectors); $out = $this->makeOutputBlock(null, $env->selectors); $this->scope->children[] = $out; $this->compileProps($block, $out); // Mixins carry scope with them! $block->scope = $env; $this->popEnv(); } /** * Compile media * * @param stdClass $media Media * * @return void */ protected function compileMedia($media) { $env = $this->pushEnv($media); $parentScope = $this->mediaParent($this->scope); $query = $this->compileMediaQuery($this->multiplyMedia($env)); $this->scope = $this->makeOutputBlock($media->type, array($query)); $parentScope->children[] = $this->scope; $this->compileProps($media, $this->scope); if (count($this->scope->lines) > 0) { $orphanSelelectors = $this->findClosestSelectors(); if (!is_null($orphanSelelectors)) { $orphan = $this->makeOutputBlock(null, $orphanSelelectors); $orphan->lines = $this->scope->lines; array_unshift($this->scope->children, $orphan); $this->scope->lines = array(); } } $this->scope = $this->scope->parent; $this->popEnv(); } /** * Media parent * * @param stdClass $scope Scope * * @return stdClass */ protected function mediaParent($scope) { while (!empty($scope->parent)) { if (!empty($scope->type) && $scope->type != "media") { break; } $scope = $scope->parent; } return $scope; } /** * Compile nested block * * @param stdClass $block Block * @param array $selectors Selectors * * @return void */ protected function compileNestedBlock($block, $selectors) { $this->pushEnv($block); $this->scope = $this->makeOutputBlock($block->type, $selectors); $this->scope->parent->children[] = $this->scope; $this->compileProps($block, $this->scope); $this->scope = $this->scope->parent; $this->popEnv(); } /** * Compile root * * @param stdClass $root Root * * @return void */ protected function compileRoot($root) { $this->pushEnv(); $this->scope = $this->makeOutputBlock($root->type); $this->compileProps($root, $this->scope); $this->popEnv(); } /** * Compile props * * @param type $block Something * @param type $out Something * * @return void */ protected function compileProps($block, $out) { foreach ($this->sortProps($block->props) as $prop) { $this->compileProp($prop, $block, $out); } } /** * Sort props * * @param type $props X * @param type $split X * * @return type */ protected function sortProps($props, $split = false) { $vars = array(); $imports = array(); $other = array(); foreach ($props as $prop) { switch ($prop[0]) { case "assign": if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) { $vars[] = $prop; } else { $other[] = $prop; } break; case "import": $id = self::$nextImportId++; $prop[] = $id; $imports[] = $prop; $other[] = array("import_mixin", $id); break; default: $other[] = $prop; } } if ($split) { return array(array_merge($vars, $imports), $other); } else { return array_merge($vars, $imports, $other); } } /** * Compile media query * * @param type $queries Queries * * @return string */ protected function compileMediaQuery($queries) { $compiledQueries = array(); foreach ($queries as $query) { $parts = array(); foreach ($query as $q) { switch ($q[0]) { case "mediaType": $parts[] = implode(" ", array_slice($q, 1)); break; case "mediaExp": if (isset($q[2])) { $parts[] = "($q[1]: " . $this->compileValue($this->reduce($q[2])) . ")"; } else { $parts[] = "($q[1])"; } break; case "variable": $parts[] = $this->compileValue($this->reduce($q)); break; } } if (count($parts) > 0) { $compiledQueries[] = implode(" and ", $parts); } } $out = "@media"; if (!empty($parts)) { $out .= " " . implode($this->formatter->selectorSeparator, $compiledQueries); } return $out; } /** * Multiply media * * @param type $env X * @param type $childQueries X * * @return type */ protected function multiplyMedia($env, $childQueries = null) { if (is_null($env) || !empty($env->block->type) && $env->block->type != "media") { return $childQueries; } // Plain old block, skip if (empty($env->block->type)) { return $this->multiplyMedia($env->parent, $childQueries); } $out = array(); $queries = $env->block->queries; if (is_null($childQueries)) { $out = $queries; } else { foreach ($queries as $parent) { foreach ($childQueries as $child) { $out[] = array_merge($parent, $child); } } } return $this->multiplyMedia($env->parent, $out); } /** * Expand parent selectors * * @param type &$tag Tag * @param type $replace Replace * * @return type */ protected function expandParentSelectors(&$tag, $replace) { $parts = explode("$&$", $tag); $count = 0; foreach ($parts as &$part) { $part = str_replace($this->parentSelector, $replace, $part, $c); $count += $c; } $tag = implode($this->parentSelector, $parts); return $count; } /** * Find closest selectors * * @return array */ protected function findClosestSelectors() { $env = $this->env; $selectors = null; while ($env !== null) { if (isset($env->selectors)) { $selectors = $env->selectors; break; } $env = $env->parent; } return $selectors; } /** * Multiply $selectors against the nearest selectors in env * * @param array $selectors The selectors * * @return array */ protected function multiplySelectors($selectors) { // Find parent selectors $parentSelectors = $this->findClosestSelectors(); if (is_null($parentSelectors)) { // Kill parent reference in top level selector foreach ($selectors as &$s) { $this->expandParentSelectors($s, ""); } return $selectors; } $out = array(); foreach ($parentSelectors as $parent) { foreach ($selectors as $child) { $count = $this->expandParentSelectors($child, $parent); // Don't prepend the parent tag if & was used if ($count > 0) { $out[] = trim($child); } else { $out[] = trim($parent . ' ' . $child); } } } return $out; } /** * Reduces selector expressions * * @param array $selectors The selector expressions * * @return array */ protected function compileSelectors($selectors) { $out = array(); foreach ($selectors as $s) { if (is_array($s)) { list(, $value) = $s; $out[] = trim($this->compileValue($this->reduce($value))); } else { $out[] = $s; } } return $out; } /** * Equality check * * @param mixed $left Left operand * @param mixed $right Right operand * * @return boolean True if equal */ protected function eq($left, $right) { return $left == $right; } /** * Pattern match * * @param type $block X * @param type $callingArgs X * * @return boolean */ protected function patternMatch($block, $callingArgs) { /** * Match the guards if it has them * any one of the groups must have all its guards pass for a match */ if (!empty($block->guards)) { $groupPassed = false; foreach ($block->guards as $guardGroup) { foreach ($guardGroup as $guard) { $this->pushEnv(); $this->zipSetArgs($block->args, $callingArgs); $negate = false; if ($guard[0] == "negate") { $guard = $guard[1]; $negate = true; } $passed = $this->reduce($guard) == self::$TRUE; if ($negate) { $passed = !$passed; } $this->popEnv(); if ($passed) { $groupPassed = true; } else { $groupPassed = false; break; } } if ($groupPassed) { break; } } if (!$groupPassed) { return false; } } $numCalling = count($callingArgs); if (empty($block->args)) { return $block->isVararg || $numCalling == 0; } // No args $i = -1; // Try to match by arity or by argument literal foreach ($block->args as $i => $arg) { switch ($arg[0]) { case "lit": if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i])) { return false; } break; case "arg": // No arg and no default value if (!isset($callingArgs[$i]) && !isset($arg[2])) { return false; } break; case "rest": // Rest can be empty $i--; break 2; } } if ($block->isVararg) { // Not having enough is handled above return true; } else { $numMatched = $i + 1; // Greater than becuase default values always match return $numMatched >= $numCalling; } } /** * Pattern match all * * @param type $blocks X * @param type $callingArgs X * * @return type */ protected function patternMatchAll($blocks, $callingArgs) { $matches = null; foreach ($blocks as $block) { if ($this->patternMatch($block, $callingArgs)) { $matches[] = $block; } } return $matches; } /** * Attempt to find blocks matched by path and args * * @param array $searchIn Block to search in * @param string $path The path to search for * @param array $args Arguments * @param array $seen Your guess is as good as mine; that's third party code * * @return null */ protected function findBlocks($searchIn, $path, $args, $seen = array()) { if ($searchIn == null) { return null; } if (isset($seen[$searchIn->id])) { return null; } $seen[$searchIn->id] = true; $name = $path[0]; if (isset($searchIn->children[$name])) { $blocks = $searchIn->children[$name]; if (count($path) == 1) { $matches = $this->patternMatchAll($blocks, $args); if (!empty($matches)) { // This will return all blocks that match in the closest // scope that has any matching block, like lessjs return $matches; } } else { $matches = array(); foreach ($blocks as $subBlock) { $subMatches = $this->findBlocks($subBlock, array_slice($path, 1), $args, $seen); if (!is_null($subMatches)) { foreach ($subMatches as $sm) { $matches[] = $sm; } } } return count($matches) > 0 ? $matches : null; } } if ($searchIn->parent === $searchIn) { return null; } return $this->findBlocks($searchIn->parent, $path, $args, $seen); } /** * Sets all argument names in $args to either the default value * or the one passed in through $values * * @param array $args Arguments * @param array $values Values * * @return void */ protected function zipSetArgs($args, $values) { $i = 0; $assignedValues = array(); foreach ($args as $a) { if ($a[0] == "arg") { if ($i < count($values) && !is_null($values[$i])) { $value = $values[$i]; } elseif (isset($a[2])) { $value = $a[2]; } else { $value = null; } $value = $this->reduce($value); $this->set($a[1], $value); $assignedValues[] = $value; } $i++; } // Check for a rest $last = end($args); if ($last[0] == "rest") { $rest = array_slice($values, count($args) - 1); $this->set($last[1], $this->reduce(array("list", " ", $rest))); } $this->env->arguments = $assignedValues; } /** * Compile a prop and update $lines or $blocks appropriately * * @param array $prop Prop * @param stdClass $block Block * @param string $out Out * * @return void */ protected function compileProp($prop, $block, $out) { // Set error position context $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1; switch ($prop[0]) { case 'assign': list(, $name, $value) = $prop; if ($name[0] == $this->vPrefix) { $this->set($name, $value); } else { $out->lines[] = $this->formatter->property($name, $this->compileValue($this->reduce($value))); } break; case 'block': list(, $child) = $prop; $this->compileBlock($child); break; case 'mixin': list(, $path, $args, $suffix) = $prop; $args = array_map(array($this, "reduce"), (array) $args); $mixins = $this->findBlocks($block, $path, $args); if ($mixins === null) { // Throw error here?? break; } foreach ($mixins as $mixin) { $haveScope = false; if (isset($mixin->parent->scope)) { $haveScope = true; $mixinParentEnv = $this->pushEnv(); $mixinParentEnv->storeParent = $mixin->parent->scope; } $haveArgs = false; if (isset($mixin->args)) { $haveArgs = true; $this->pushEnv(); $this->zipSetArgs($mixin->args, $args); } $oldParent = $mixin->parent; if ($mixin != $block) { $mixin->parent = $block; } foreach ($this->sortProps($mixin->props) as $subProp) { if ($suffix !== null && $subProp[0] == "assign" && is_string($subProp[1]) && $subProp[1][0] != $this->vPrefix) { $subProp[2] = array( 'list', ' ', array($subProp[2], array('keyword', $suffix)) ); } $this->compileProp($subProp, $mixin, $out); } $mixin->parent = $oldParent; if ($haveArgs) { $this->popEnv(); } if ($haveScope) { $this->popEnv(); } } break; case 'raw': $out->lines[] = $prop[1]; break; case "directive": list(, $name, $value) = $prop; $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)) . ';'; break; case "comment": $out->lines[] = $prop[1]; break; case "import"; list(, $importPath, $importId) = $prop; $importPath = $this->reduce($importPath); if (!isset($this->env->imports)) { $this->env->imports = array(); } $result = $this->tryImport($importPath, $block, $out); $this->env->imports[$importId] = $result === false ? array(false, "@import " . $this->compileValue($importPath) . ";") : $result; break; case "import_mixin": list(, $importId) = $prop; $import = $this->env->imports[$importId]; if ($import[0] === false) { $out->lines[] = $import[1]; } else { list(, $bottom, $parser, $importDir) = $import; $this->compileImportedProps($bottom, $block, $out, $parser, $importDir); } break; default: $this->throwError("unknown op: {$prop[0]}\n"); } } /** * Compiles a primitive value into a CSS property value. * * Values in lessphp are typed by being wrapped in arrays, their format is * typically: * * array(type, contents [, additional_contents]*) * * The input is expected to be reduced. This function will not work on * things like expressions and variables. * * @param array $value Value * * @return void */ protected function compileValue($value) { switch ($value[0]) { case 'list': // [1] - delimiter // [2] - array of values return implode($value[1], array_map(array($this, 'compileValue'), $value[2])); case 'raw_color': if (!empty($this->formatter->compressColors)) { return $this->compileValue($this->coerceColor($value)); } return $value[1]; case 'keyword': // [1] - the keyword return $value[1]; case 'number': // Format: [1] - the number -- [2] - the unit list(, $num, $unit) = $value; if ($this->numberPrecision !== null) { $num = round($num, $this->numberPrecision); } return $num . $unit; case 'string': // [1] - contents of string (includes quotes) list(, $delim, $content) = $value; foreach ($content as &$part) { if (is_array($part)) { $part = $this->compileValue($part); } } return $delim . implode($content) . $delim; case 'color': /** * Format: * * [1] - red component (either number or a %) * [2] - green component * [3] - blue component * [4] - optional alpha component */ list(, $r, $g, $b) = $value; $r = round($r); $g = round($g); $b = round($b); if (count($value) == 5 && $value[4] != 1) { // Return an rgba value return 'rgba(' . $r . ',' . $g . ',' . $b . ',' . $value[4] . ')'; } $h = sprintf("#%02x%02x%02x", $r, $g, $b); if (!empty($this->formatter->compressColors)) { // Converting hex color to short notation (e.g. #003399 to #039) if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { $h = '#' . $h[1] . $h[3] . $h[5]; } } return $h; case 'function': list(, $name, $args) = $value; return $name . '(' . $this->compileValue($args) . ')'; default: // Assumed to be unit $this->throwError("unknown value type: $value[0]"); } } /** * Lib is number * * @param type $value X * * @return boolean */ protected function lib_isnumber($value) { return $this->toBool($value[0] == "number"); } /** * Lib is string * * @param type $value X * * @return boolean */ protected function lib_isstring($value) { return $this->toBool($value[0] == "string"); } /** * Lib is color * * @param type $value X * * @return boolean */ protected function lib_iscolor($value) { return $this->toBool($this->coerceColor($value)); } /** * Lib is keyword * * @param type $value X * * @return boolean */ protected function lib_iskeyword($value) { return $this->toBool($value[0] == "keyword"); } /** * Lib is pixel * * @param type $value X * * @return boolean */ protected function lib_ispixel($value) { return $this->toBool($value[0] == "number" && $value[2] == "px"); } /** * Lib is percentage * * @param type $value X * * @return boolean */ protected function lib_ispercentage($value) { return $this->toBool($value[0] == "number" && $value[2] == "%"); } /** * Lib is em * * @param type $value X * * @return boolean */ protected function lib_isem($value) { return $this->toBool($value[0] == "number" && $value[2] == "em"); } /** * Lib is rem * * @param type $value X * * @return boolean */ protected function lib_isrem($value) { return $this->toBool($value[0] == "number" && $value[2] == "rem"); } /** * LIb rgba hex * * @param type $color X * * @return boolean */ protected function lib_rgbahex($color) { $color = $this->coerceColor($color); if (is_null($color)) { $this->throwError("color expected for rgbahex"); } return sprintf("#%02x%02x%02x%02x", isset($color[4]) ? $color[4] * 255 : 255, $color[1], $color[2], $color[3]); } /** * Lib argb * * @param type $color X * * @return type */ protected function lib_argb($color) { return $this->lib_rgbahex($color); } /** * Utility func to unquote a string * * @param string $arg Arg * * @return string */ protected function lib_e($arg) { switch ($arg[0]) { case "list": $items = $arg[2]; if (isset($items[0])) { return $this->lib_e($items[0]); } return self::$defaultValue; case "string": $arg[1] = ""; return $arg; case "keyword": return $arg; default: return array("keyword", $this->compileValue($arg)); } } /** * Lib sprintf * * @param type $args X * * @return type */ protected function lib__sprintf($args) { if ($args[0] != "list") { return $args; } $values = $args[2]; $string = array_shift($values); $template = $this->compileValue($this->lib_e($string)); $i = 0; if (preg_match_all('/%[dsa]/', $template, $m)) { foreach ($m[0] as $match) { $val = isset($values[$i]) ? $this->reduce($values[$i]) : array('keyword', ''); // Lessjs compat, renders fully expanded color, not raw color if ($color = $this->coerceColor($val)) { $val = $color; } $i++; $rep = $this->compileValue($this->lib_e($val)); $template = preg_replace('/' . self::preg_quote($match) . '/', $rep, $template, 1); } } $d = $string[0] == "string" ? $string[1] : '"'; return array("string", $d, array($template)); } /** * Lib floor * * @param type $arg X * * @return array */ protected function lib_floor($arg) { $value = $this->assertNumber($arg); return array("number", floor($value), $arg[2]); } /** * Lib ceil * * @param type $arg X * * @return array */ protected function lib_ceil($arg) { $value = $this->assertNumber($arg); return array("number", ceil($value), $arg[2]); } /** * Lib round * * @param type $arg X * * @return array */ protected function lib_round($arg) { $value = $this->assertNumber($arg); return array("number", round($value), $arg[2]); } /** * Lib unit * * @param type $arg X * * @return array */ protected function lib_unit($arg) { if ($arg[0] == "list") { list($number, $newUnit) = $arg[2]; return array("number", $this->assertNumber($number), $this->compileValue($this->lib_e($newUnit))); } else { return array("number", $this->assertNumber($arg), ""); } } /** * Helper function to get arguments for color manipulation functions. * takes a list that contains a color like thing and a percentage * * @param array $args Args * * @return array */ protected function colorArgs($args) { if ($args[0] != 'list' || count($args[2]) < 2) { return array(array('color', 0, 0, 0), 0); } list($color, $delta) = $args[2]; $color = $this->assertColor($color); $delta = floatval($delta[1]); return array($color, $delta); } /** * Lib darken * * @param type $args X * * @return type */ protected function lib_darken($args) { list($color, $delta) = $this->colorArgs($args); $hsl = $this->toHSL($color); $hsl[3] = $this->clamp($hsl[3] - $delta, 100); return $this->toRGB($hsl); } /** * Lib lighten * * @param type $args X * * @return type */ protected function lib_lighten($args) { list($color, $delta) = $this->colorArgs($args); $hsl = $this->toHSL($color); $hsl[3] = $this->clamp($hsl[3] + $delta, 100); return $this->toRGB($hsl); } /** * Lib saturate * * @param type $args X * * @return type */ protected function lib_saturate($args) { list($color, $delta) = $this->colorArgs($args); $hsl = $this->toHSL($color); $hsl[2] = $this->clamp($hsl[2] + $delta, 100); return $this->toRGB($hsl); } /** * Lib desaturate * * @param type $args X * * @return type */ protected function lib_desaturate($args) { list($color, $delta) = $this->colorArgs($args); $hsl = $this->toHSL($color); $hsl[2] = $this->clamp($hsl[2] - $delta, 100); return $this->toRGB($hsl); } /** * Lib spin * * @param type $args X * * @return type */ protected function lib_spin($args) { list($color, $delta) = $this->colorArgs($args); $hsl = $this->toHSL($color); $hsl[1] = $hsl[1] + $delta % 360; if ($hsl[1] < 0) { $hsl[1] += 360; } return $this->toRGB($hsl); } /** * Lib fadeout * * @param type $args X * * @return type */ protected function lib_fadeout($args) { list($color, $delta) = $this->colorArgs($args); $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta / 100); return $color; } /** * Lib fadein * * @param type $args X * * @return type */ protected function lib_fadein($args) { list($color, $delta) = $this->colorArgs($args); $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta / 100); return $color; } /** * Lib hue * * @param type $color X * * @return type */ protected function lib_hue($color) { $hsl = $this->toHSL($this->assertColor($color)); return round($hsl[1]); } /** * Lib saturation * * @param type $color X * * @return type */ protected function lib_saturation($color) { $hsl = $this->toHSL($this->assertColor($color)); return round($hsl[2]); } /** * Lib lightness * * @param type $color X * * @return type */ protected function lib_lightness($color) { $hsl = $this->toHSL($this->assertColor($color)); return round($hsl[3]); } /** * Get the alpha of a color * Defaults to 1 for non-colors or colors without an alpha * * @param string $value Value * * @return string */ protected function lib_alpha($value) { if (!is_null($color = $this->coerceColor($value))) { return isset($color[4]) ? $color[4] : 1; } } /** * Set the alpha of the color * * @param array $args Args * * @return string */ protected function lib_fade($args) { list($color, $alpha) = $this->colorArgs($args); $color[4] = $this->clamp($alpha / 100.0); return $color; } /** * Third party code; your guess is as good as mine * * @param array $arg Arg * * @return string */ protected function lib_percentage($arg) { $num = $this->assertNumber($arg); return array("number", $num * 100, "%"); } /** * mixes two colors by weight * mix(@color1, @color2, @weight); * http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method * * @param array $args Args * * @return string */ protected function lib_mix($args) { if ($args[0] != "list" || count($args[2]) < 3) { $this->throwError("mix expects (color1, color2, weight)"); } list($first, $second, $weight) = $args[2]; $first = $this->assertColor($first); $second = $this->assertColor($second); $first_a = $this->lib_alpha($first); $second_a = $this->lib_alpha($second); $weight = $weight[1] / 100.0; $w = $weight * 2 - 1; $a = $first_a - $second_a; $w1 = (($w * $a == -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0; $w2 = 1.0 - $w1; $new = array('color', $w1 * $first[1] + $w2 * $second[1], $w1 * $first[2] + $w2 * $second[2], $w1 * $first[3] + $w2 * $second[3], ); if ($first_a != 1.0 || $second_a != 1.0) { $new[] = $first_a * $weight + $second_a * ($weight - 1); } return $this->fixColor($new); } /** * Third party code; your guess is as good as mine * * @param array $arg Arg * * @return string */ protected function lib_contrast($args) { if ($args[0] != 'list' || count($args[2]) < 3) { return array(array('color', 0, 0, 0), 0); } list($inputColor, $darkColor, $lightColor) = $args[2]; $inputColor = $this->assertColor($inputColor); $darkColor = $this->assertColor($darkColor); $lightColor = $this->assertColor($lightColor); $hsl = $this->toHSL($inputColor); if ($hsl[3] > 50) { return $darkColor; } return $lightColor; } /** * Assert color * * @param type $value X * @param type $error X * * @return type */ protected function assertColor($value, $error = "expected color value") { $color = $this->coerceColor($value); if (is_null($color)) { $this->throwError($error); } return $color; } /** * Assert number * * @param type $value X * @param type $error X * * @return type */ protected function assertNumber($value, $error = "expecting number") { if ($value[0] == "number") { return $value[1]; } $this->throwError($error); } /** * To HSL * * @param type $color X * * @return type */ protected function toHSL($color) { if ($color[0] == 'hsl') { return $color; } $r = $color[1] / 255; $g = $color[2] / 255; $b = $color[3] / 255; $min = min($r, $g, $b); $max = max($r, $g, $b); $L = ($min + $max) / 2; if ($min == $max) { $S = $H = 0; } else { if ($L < 0.5) { $S = ($max - $min) / ($max + $min); } else { $S = ($max - $min) / (2.0 - $max - $min); } if ($r == $max) { $H = ($g - $b) / ($max - $min); } elseif ($g == $max) { $H = 2.0 + ($b - $r) / ($max - $min); } elseif ($b == $max) { $H = 4.0 + ($r - $g) / ($max - $min); } } $out = array('hsl', ($H < 0 ? $H + 6 : $H) * 60, $S * 100, $L * 100, ); if (count($color) > 4) { // Copy alpha $out[] = $color[4]; } return $out; } /** * To RGB helper * * @param type $comp X * @param type $temp1 X * @param type $temp2 X * * @return type */ protected function toRGB_helper($comp, $temp1, $temp2) { if ($comp < 0) { $comp += 1.0; } elseif ($comp > 1) { $comp -= 1.0; } if (6 * $comp < 1) { return $temp1 + ($temp2 - $temp1) * 6 * $comp; } if (2 * $comp < 1) { return $temp2; } if (3 * $comp < 2) { return $temp1 + ($temp2 - $temp1) * ((2 / 3) - $comp) * 6; } return $temp1; } /** * Converts a hsl array into a color value in rgb. * Expects H to be in range of 0 to 360, S and L in 0 to 100 * * @param type $color X * * @return type */ protected function toRGB($color) { if ($color == 'color') { return $color; } $H = $color[1] / 360; $S = $color[2] / 100; $L = $color[3] / 100; if ($S == 0) { $r = $g = $b = $L; } else { $temp2 = $L < 0.5 ? $L * (1.0 + $S) : $L + $S - $L * $S; $temp1 = 2.0 * $L - $temp2; $r = $this->toRGB_helper($H + 1 / 3, $temp1, $temp2); $g = $this->toRGB_helper($H, $temp1, $temp2); $b = $this->toRGB_helper($H - 1 / 3, $temp1, $temp2); } // $out = array('color', round($r*255), round($g*255), round($b*255)); $out = array('color', $r * 255, $g * 255, $b * 255); if (count($color) > 4) { // Copy alpha $out[] = $color[4]; } return $out; } /** * Clamp * * @param type $v X * @param type $max X * @param type $min X * * @return type */ protected function clamp($v, $max = 1, $min = 0) { return min($max, max($min, $v)); } /** * Convert the rgb, rgba, hsl color literals of function type * as returned by the parser into values of color type. * * @param type $func X * * @return type */ protected function funcToColor($func) { $fname = $func[1]; if ($func[2][0] != 'list') { // Need a list of arguments return false; } $rawComponents = $func[2][2]; if ($fname == 'hsl' || $fname == 'hsla') { $hsl = array('hsl'); $i = 0; foreach ($rawComponents as $c) { $val = $this->reduce($c); $val = isset($val[1]) ? floatval($val[1]) : 0; if ($i == 0) { $clamp = 360; } elseif ($i < 3) { $clamp = 100; } else { $clamp = 1; } $hsl[] = $this->clamp($val, $clamp); $i++; } while (count($hsl) < 4) { $hsl[] = 0; } return $this->toRGB($hsl); } elseif ($fname == 'rgb' || $fname == 'rgba') { $components = array(); $i = 1; foreach ($rawComponents as $c) { $c = $this->reduce($c); if ($i < 4) { if ($c[0] == "number" && $c[2] == "%") { $components[] = 255 * ($c[1] / 100); } else { $components[] = floatval($c[1]); } } elseif ($i == 4) { if ($c[0] == "number" && $c[2] == "%") { $components[] = 1.0 * ($c[1] / 100); } else { $components[] = floatval($c[1]); } } else { break; } $i++; } while (count($components) < 3) { $components[] = 0; } array_unshift($components, 'color'); return $this->fixColor($components); } return false; } /** * Reduce * * @param type $value X * @param type $forExpression X * * @return type */ protected function reduce($value, $forExpression = false) { switch ($value[0]) { case "interpolate": $reduced = $this->reduce($value[1]); $var = $this->compileValue($reduced); $res = $this->reduce(array("variable", $this->vPrefix . $var)); if (empty($value[2])) { $res = $this->lib_e($res); } return $res; case "variable": $key = $value[1]; if (is_array($key)) { $key = $this->reduce($key); $key = $this->vPrefix . $this->compileValue($this->lib_e($key)); } $seen = & $this->env->seenNames; if (!empty($seen[$key])) { $this->throwError("infinite loop detected: $key"); } $seen[$key] = true; $out = $this->reduce($this->get($key, self::$defaultValue)); $seen[$key] = false; return $out; case "list": foreach ($value[2] as &$item) { $item = $this->reduce($item, $forExpression); } return $value; case "expression": return $this->evaluate($value); case "string": foreach ($value[2] as &$part) { if (is_array($part)) { $strip = $part[0] == "variable"; $part = $this->reduce($part); if ($strip) { $part = $this->lib_e($part); } } } return $value; case "escape": list(, $inner) = $value; return $this->lib_e($this->reduce($inner)); case "function": $color = $this->funcToColor($value); if ($color) { return $color; } list(, $name, $args) = $value; if ($name == "%") { $name = "_sprintf"; } $f = isset($this->libFunctions[$name]) ? $this->libFunctions[$name] : array($this, 'lib_' . $name); if (is_callable($f)) { if ($args[0] == 'list') { $args = self::compressList($args[2], $args[1]); } $ret = call_user_func($f, $this->reduce($args, true), $this); if (is_null($ret)) { return array("string", "", array( $name, "(", $args, ")" )); } // Convert to a typed value if the result is a php primitive if (is_numeric($ret)) { $ret = array('number', $ret, ""); } elseif (!is_array($ret)) { $ret = array('keyword', $ret); } return $ret; } // Plain function, reduce args $value[2] = $this->reduce($value[2]); return $value; case "unary": list(, $op, $exp) = $value; $exp = $this->reduce($exp); if ($exp[0] == "number") { switch ($op) { case "+": return $exp; case "-": $exp[1] *= -1; return $exp; } } return array("string", "", array($op, $exp)); } if ($forExpression) { switch ($value[0]) { case "keyword": if ($color = $this->coerceColor($value)) { return $color; } break; case "raw_color": return $this->coerceColor($value); } } return $value; } /** * Coerce a value for use in color operation * * @param type $value X * * @return null */ protected function coerceColor($value) { switch ($value[0]) { case 'color': return $value; case 'raw_color': $c = array("color", 0, 0, 0); $colorStr = substr($value[1], 1); $num = hexdec($colorStr); $width = strlen($colorStr) == 3 ? 16 : 256; for ($i = 3; $i > 0; $i--) { // It's 3 2 1 $t = $num % $width; $num /= $width; $c[$i] = $t * (256 / $width) + $t * floor(16 / $width); } return $c; case 'keyword': $name = $value[1]; if (isset(self::$cssColors[$name])) { $rgba = explode(',', self::$cssColors[$name]); if (isset($rgba[3])) { return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]); } return array('color', $rgba[0], $rgba[1], $rgba[2]); } return null; } } /** * Make something string like into a string * * @param type $value X * * @return null */ protected function coerceString($value) { switch ($value[0]) { case "string": return $value; case "keyword": return array("string", "", array($value[1])); } return null; } /** * Turn list of length 1 into value type * * @param type $value X * * @return type */ protected function flattenList($value) { if ($value[0] == "list" && count($value[2]) == 1) { return $this->flattenList($value[2][0]); } return $value; } /** * To bool * * @param type $a X * * @return type */ protected function toBool($a) { if ($a) { return self::$TRUE; } else { return self::$FALSE; } } /** * Evaluate an expression * * @param type $exp X * * @return type */ protected function evaluate($exp) { list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp; $left = $this->reduce($left, true); $right = $this->reduce($right, true); if ($leftColor = $this->coerceColor($left)) { $left = $leftColor; } if ($rightColor = $this->coerceColor($right)) { $right = $rightColor; } $ltype = $left[0]; $rtype = $right[0]; // Operators that work on all types if ($op == "and") { return $this->toBool($left == self::$TRUE && $right == self::$TRUE); } if ($op == "=") { return $this->toBool($this->eq($left, $right)); } if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) { return $str; } // Type based operators $fname = "op_${ltype}_${rtype}"; if (is_callable(array($this, $fname))) { $out = $this->$fname($op, $left, $right); if (!is_null($out)) { return $out; } } // Make the expression look it did before being parsed $paddedOp = $op; if ($whiteBefore) { $paddedOp = " " . $paddedOp; } if ($whiteAfter) { $paddedOp .= " "; } return array("string", "", array($left, $paddedOp, $right)); } /** * String concatenate * * @param type $left X * @param string $right X * * @return string */ protected function stringConcatenate($left, $right) { if ($strLeft = $this->coerceString($left)) { if ($right[0] == "string") { $right[1] = ""; } $strLeft[2][] = $right; return $strLeft; } if ($strRight = $this->coerceString($right)) { array_unshift($strRight[2], $left); return $strRight; } } /** * Make sure a color's components don't go out of bounds * * @param type $c X * * @return int */ protected function fixColor($c) { foreach (range(1, 3) as $i) { if ($c[$i] < 0) { $c[$i] = 0; } if ($c[$i] > 255) { $c[$i] = 255; } } return $c; } /** * Op number color * * @param type $op X * @param type $lft X * @param type $rgt X * * @return type */ protected function op_number_color($op, $lft, $rgt) { if ($op == '+' || $op == '*') { return $this->op_color_number($op, $rgt, $lft); } } /** * Op color number * * @param type $op X * @param type $lft X * @param int $rgt X * * @return type */ protected function op_color_number($op, $lft, $rgt) { if ($rgt[0] == '%') { $rgt[1] /= 100; } return $this->op_color_color($op, $lft, array_fill(1, count($lft) - 1, $rgt[1])); } /** * Op color color * * @param type $op X * @param type $left X * @param type $right X * * @return type */ protected function op_color_color($op, $left, $right) { $out = array('color'); $max = count($left) > count($right) ? count($left) : count($right); foreach (range(1, $max - 1) as $i) { $lval = isset($left[$i]) ? $left[$i] : 0; $rval = isset($right[$i]) ? $right[$i] : 0; switch ($op) { case '+': $out[] = $lval + $rval; break; case '-': $out[] = $lval - $rval; break; case '*': $out[] = $lval * $rval; break; case '%': $out[] = $lval % $rval; break; case '/': if ($rval == 0) { $this->throwError("evaluate error: can't divide by zero"); } $out[] = $lval / $rval; break; default: $this->throwError('evaluate error: color op number failed on op ' . $op); } } return $this->fixColor($out); } /** * Lib red * * @param type $color X * * @return type */ public function lib_red($color) { $color = $this->coerceColor($color); if (is_null($color)) { $this->throwError('color expected for red()'); } return $color[1]; } /** * Lib green * * @param type $color X * * @return type */ public function lib_green($color) { $color = $this->coerceColor($color); if (is_null($color)) { $this->throwError('color expected for green()'); } return $color[2]; } /** * Lib blue * * @param type $color X * * @return type */ public function lib_blue($color) { $color = $this->coerceColor($color); if (is_null($color)) { $this->throwError('color expected for blue()'); } return $color[3]; } /** * Operator on two numbers * * @param type $op X * @param type $left X * @param type $right X * * @return type */ protected function op_number_number($op, $left, $right) { $unit = empty($left[2]) ? $right[2] : $left[2]; $value = 0; switch ($op) { case '+': $value = $left[1] + $right[1]; break; case '*': $value = $left[1] * $right[1]; break; case '-': $value = $left[1] - $right[1]; break; case '%': $value = $left[1] % $right[1]; break; case '/': if ($right[1] == 0) { $this->throwError('parse error: divide by zero'); } $value = $left[1] / $right[1]; break; case '<': return $this->toBool($left[1] < $right[1]); case '>': return $this->toBool($left[1] > $right[1]); case '>=': return $this->toBool($left[1] >= $right[1]); case '=<': return $this->toBool($left[1] <= $right[1]); default: $this->throwError('parse error: unknown number operator: ' . $op); } return array("number", $value, $unit); } /** * Make output block * * @param type $type X * @param type $selectors X * * @return stdclass */ protected function makeOutputBlock($type, $selectors = null) { $b = new stdclass; $b->lines = array(); $b->children = array(); $b->selectors = $selectors; $b->type = $type; $b->parent = $this->scope; return $b; } /** * The state of execution * * @param type $block X * * @return stdclass */ protected function pushEnv($block = null) { $e = new stdclass; $e->parent = $this->env; $e->store = array(); $e->block = $block; $this->env = $e; return $e; } /** * Pop something off the stack * * @return type */ protected function popEnv() { $old = $this->env; $this->env = $this->env->parent; return $old; } /** * Set something in the current env * * @param type $name X * @param type $value X * * @return void */ protected function set($name, $value) { $this->env->store[$name] = $value; } /** * Get the highest occurrence entry for a name * * @param type $name X * @param type $default X * * @return type */ protected function get($name, $default = null) { $current = $this->env; $isArguments = $name == $this->vPrefix . 'arguments'; while ($current) { if ($isArguments && isset($current->arguments)) { return array('list', ' ', $current->arguments); } if (isset($current->store[$name])) { return $current->store[$name]; } else { $current = isset($current->storeParent) ? $current->storeParent : $current->parent; } } return $default; } /** * Inject array of unparsed strings into environment as variables * * @param type $args X * * @return void * * @throws Exception */ protected function injectVariables($args) { $this->pushEnv(); /** FOF -- BEGIN CHANGE * */ $parser = new FOFLessParser($this, __METHOD__); /** FOF -- END CHANGE * */ foreach ($args as $name => $strValue) { if ($name[0] != '@') { $name = '@' . $name; } $parser->count = 0; $parser->buffer = (string) $strValue; if (!$parser->propertyValue($value)) { throw new Exception("failed to parse passed in variable $name: $strValue"); } $this->set($name, $value); } } /** * Initialize any static state, can initialize parser for a file * * @param type $fname X */ public function __construct($fname = null) { if ($fname !== null) { // Used for deprecated parse method $this->_parseFile = $fname; } } /** * Compile * * @param type $string X * @param type $name X * * @return type */ public function compile($string, $name = null) { $locale = setlocale(LC_NUMERIC, 0); setlocale(LC_NUMERIC, "C"); $this->parser = $this->makeParser($name); $root = $this->parser->parse($string); $this->env = null; $this->scope = null; $this->formatter = $this->newFormatter(); if (!empty($this->registeredVars)) { $this->injectVariables($this->registeredVars); } // Used for error messages $this->sourceParser = $this->parser; $this->compileBlock($root); ob_start(); $this->formatter->block($this->scope); $out = ob_get_clean(); setlocale(LC_NUMERIC, $locale); return $out; } /** * Compile file * * @param type $fname X * @param type $outFname X * * @return type * * @throws Exception */ public function compileFile($fname, $outFname = null) { if (!is_readable($fname)) { throw new Exception('load error: failed to find ' . $fname); } $pi = pathinfo($fname); $oldImport = $this->importDir; $this->importDir = (array) $this->importDir; $this->importDir[] = $pi['dirname'] . '/'; $this->allParsedFiles = array(); $this->addParsedFile($fname); $out = $this->compile(file_get_contents($fname), $fname); $this->importDir = $oldImport; if ($outFname !== null) { /** FOF - BEGIN CHANGE * */ return FOFPlatform::getInstance()->getIntegrationObject('filesystem')->fileWrite($outFname, $out); /** FOF - END CHANGE * */ } return $out; } /** * Compile only if changed input has changed or output doesn't exist * * @param type $in X * @param type $out X * * @return boolean */ public function checkedCompile($in, $out) { if (!is_file($out) || filemtime($in) > filemtime($out)) { $this->compileFile($in, $out); return true; } return false; } /** * Execute lessphp on a .less file or a lessphp cache structure * * The lessphp cache structure contains information about a specific * less file having been parsed. It can be used as a hint for future * calls to determine whether or not a rebuild is required. * * The cache structure contains two important keys that may be used * externally: * * compiled: The final compiled CSS * updated: The time (in seconds) the CSS was last compiled * * The cache structure is a plain-ol' PHP associative array and can * be serialized and unserialized without a hitch. * * @param mixed $in Input * @param bool $force Force rebuild? * * @return array lessphp cache structure */ public function cachedCompile($in, $force = false) { // Assume no root $root = null; if (is_string($in)) { $root = $in; } elseif (is_array($in) and isset($in['root'])) { if ($force or !isset($in['files'])) { /** * If we are forcing a recompile or if for some reason the * structure does not contain any file information we should * specify the root to trigger a rebuild. */ $root = $in['root']; } elseif (isset($in['files']) and is_array($in['files'])) { foreach ($in['files'] as $fname => $ftime) { if (!file_exists($fname) or filemtime($fname) > $ftime) { /** * One of the files we knew about previously has changed * so we should look at our incoming root again. */ $root = $in['root']; break; } } } } else { /** * TODO: Throw an exception? We got neither a string nor something * that looks like a compatible lessphp cache structure. */ return null; } if ($root !== null) { // If we have a root value which means we should rebuild. $out = array(); $out['root'] = $root; $out['compiled'] = $this->compileFile($root); $out['files'] = $this->allParsedFiles(); $out['updated'] = time(); return $out; } else { // No changes, pass back the structure // we were given initially. return $in; } } // // This is deprecated /** * Parse and compile buffer * * @param null $str X * @param type $initialVariables X * * @return type * * @throws Exception * * @deprecated 2.0 */ public function parse($str = null, $initialVariables = null) { if (is_array($str)) { $initialVariables = $str; $str = null; } $oldVars = $this->registeredVars; if ($initialVariables !== null) { $this->setVariables($initialVariables); } if ($str == null) { if (empty($this->_parseFile)) { throw new exception("nothing to parse"); } $out = $this->compileFile($this->_parseFile); } else { $out = $this->compile($str); } $this->registeredVars = $oldVars; return $out; } /** * Make parser * * @param type $name X * * @return FOFLessParser */ protected function makeParser($name) { /** FOF -- BEGIN CHANGE * */ $parser = new FOFLessParser($this, $name); /** FOF -- END CHANGE * */ $parser->writeComments = $this->preserveComments; return $parser; } /** * Set Formatter * * @param type $name X * * @return void */ public function setFormatter($name) { $this->formatterName = $name; } /** * New formatter * * @return FOFLessFormatterLessjs */ protected function newFormatter() { /** FOF -- BEGIN CHANGE * */ $className = "FOFLessFormatterLessjs"; /** FOF -- END CHANGE * */ if (!empty($this->formatterName)) { if (!is_string($this->formatterName)) return $this->formatterName; /** FOF -- BEGIN CHANGE * */ $className = "FOFLessFormatter" . ucfirst($this->formatterName); /** FOF -- END CHANGE * */ } return new $className; } /** * Set preserve comments * * @param type $preserve X * * @return void */ public function setPreserveComments($preserve) { $this->preserveComments = $preserve; } /** * Register function * * @param type $name X * @param type $func X * * @return void */ public function registerFunction($name, $func) { $this->libFunctions[$name] = $func; } /** * Unregister function * * @param type $name X * * @return void */ public function unregisterFunction($name) { unset($this->libFunctions[$name]); } /** * Set variables * * @param type $variables X * * @return void */ public function setVariables($variables) { $this->registeredVars = array_merge($this->registeredVars, $variables); } /** * Unset variable * * @param type $name X * * @return void */ public function unsetVariable($name) { unset($this->registeredVars[$name]); } /** * Set import dir * * @param type $dirs X * * @return void */ public function setImportDir($dirs) { $this->importDir = (array) $dirs; } /** * Add import dir * * @param type $dir X * * @return void */ public function addImportDir($dir) { $this->importDir = (array) $this->importDir; $this->importDir[] = $dir; } /** * All parsed files * * @return type */ public function allParsedFiles() { return $this->allParsedFiles; } /** * Add parsed file * * @param type $file X * * @return void */ protected function addParsedFile($file) { $this->allParsedFiles[realpath($file)] = filemtime($file); } /** * Uses the current value of $this->count to show line and line number * * @param type $msg X * * @return void */ protected function throwError($msg = null) { if ($this->sourceLoc >= 0) { $this->sourceParser->throwError($msg, $this->sourceLoc); } throw new exception($msg); } /** * Compile file $in to file $out if $in is newer than $out * Returns true when it compiles, false otherwise * * @param type $in X * @param type $out X * @param self $less X * * @return type */ public static function ccompile($in, $out, $less = null) { if ($less === null) { $less = new self; } return $less->checkedCompile($in, $out); } /** * Compile execute * * @param type $in X * @param type $force X * @param self $less X * * @return type */ public static function cexecute($in, $force = false, $less = null) { if ($less === null) { $less = new self; } return $less->cachedCompile($in, $force); } protected static $cssColors = array( 'aliceblue' => '240,248,255', 'antiquewhite' => '250,235,215', 'aqua' => '0,255,255', 'aquamarine' => '127,255,212', 'azure' => '240,255,255', 'beige' => '245,245,220', 'bisque' => '255,228,196', 'black' => '0,0,0', 'blanchedalmond' => '255,235,205', 'blue' => '0,0,255', 'blueviolet' => '138,43,226', 'brown' => '165,42,42', 'burlywood' => '222,184,135', 'cadetblue' => '95,158,160', 'chartreuse' => '127,255,0', 'chocolate' => '210,105,30', 'coral' => '255,127,80', 'cornflowerblue' => '100,149,237', 'cornsilk' => '255,248,220', 'crimson' => '220,20,60', 'cyan' => '0,255,255', 'darkblue' => '0,0,139', 'darkcyan' => '0,139,139', 'darkgoldenrod' => '184,134,11', 'darkgray' => '169,169,169', 'darkgreen' => '0,100,0', 'darkgrey' => '169,169,169', 'darkkhaki' => '189,183,107', 'darkmagenta' => '139,0,139', 'darkolivegreen' => '85,107,47', 'darkorange' => '255,140,0', 'darkorchid' => '153,50,204', 'darkred' => '139,0,0', 'darksalmon' => '233,150,122', 'darkseagreen' => '143,188,143', 'darkslateblue' => '72,61,139', 'darkslategray' => '47,79,79', 'darkslategrey' => '47,79,79', 'darkturquoise' => '0,206,209', 'darkviolet' => '148,0,211', 'deeppink' => '255,20,147', 'deepskyblue' => '0,191,255', 'dimgray' => '105,105,105', 'dimgrey' => '105,105,105', 'dodgerblue' => '30,144,255', 'firebrick' => '178,34,34', 'floralwhite' => '255,250,240', 'forestgreen' => '34,139,34', 'fuchsia' => '255,0,255', 'gainsboro' => '220,220,220', 'ghostwhite' => '248,248,255', 'gold' => '255,215,0', 'goldenrod' => '218,165,32', 'gray' => '128,128,128', 'green' => '0,128,0', 'greenyellow' => '173,255,47', 'grey' => '128,128,128', 'honeydew' => '240,255,240', 'hotpink' => '255,105,180', 'indianred' => '205,92,92', 'indigo' => '75,0,130', 'ivory' => '255,255,240', 'khaki' => '240,230,140', 'lavender' => '230,230,250', 'lavenderblush' => '255,240,245', 'lawngreen' => '124,252,0', 'lemonchiffon' => '255,250,205', 'lightblue' => '173,216,230', 'lightcoral' => '240,128,128', 'lightcyan' => '224,255,255', 'lightgoldenrodyellow' => '250,250,210', 'lightgray' => '211,211,211', 'lightgreen' => '144,238,144', 'lightgrey' => '211,211,211', 'lightpink' => '255,182,193', 'lightsalmon' => '255,160,122', 'lightseagreen' => '32,178,170', 'lightskyblue' => '135,206,250', 'lightslategray' => '119,136,153', 'lightslategrey' => '119,136,153', 'lightsteelblue' => '176,196,222', 'lightyellow' => '255,255,224', 'lime' => '0,255,0', 'limegreen' => '50,205,50', 'linen' => '250,240,230', 'magenta' => '255,0,255', 'maroon' => '128,0,0', 'mediumaquamarine' => '102,205,170', 'mediumblue' => '0,0,205', 'mediumorchid' => '186,85,211', 'mediumpurple' => '147,112,219', 'mediumseagreen' => '60,179,113', 'mediumslateblue' => '123,104,238', 'mediumspringgreen' => '0,250,154', 'mediumturquoise' => '72,209,204', 'mediumvioletred' => '199,21,133', 'midnightblue' => '25,25,112', 'mintcream' => '245,255,250', 'mistyrose' => '255,228,225', 'moccasin' => '255,228,181', 'navajowhite' => '255,222,173', 'navy' => '0,0,128', 'oldlace' => '253,245,230', 'olive' => '128,128,0', 'olivedrab' => '107,142,35', 'orange' => '255,165,0', 'orangered' => '255,69,0', 'orchid' => '218,112,214', 'palegoldenrod' => '238,232,170', 'palegreen' => '152,251,152', 'paleturquoise' => '175,238,238', 'palevioletred' => '219,112,147', 'papayawhip' => '255,239,213', 'peachpuff' => '255,218,185', 'peru' => '205,133,63', 'pink' => '255,192,203', 'plum' => '221,160,221', 'powderblue' => '176,224,230', 'purple' => '128,0,128', 'red' => '255,0,0', 'rosybrown' => '188,143,143', 'royalblue' => '65,105,225', 'saddlebrown' => '139,69,19', 'salmon' => '250,128,114', 'sandybrown' => '244,164,96', 'seagreen' => '46,139,87', 'seashell' => '255,245,238', 'sienna' => '160,82,45', 'silver' => '192,192,192', 'skyblue' => '135,206,235', 'slateblue' => '106,90,205', 'slategray' => '112,128,144', 'slategrey' => '112,128,144', 'snow' => '255,250,250', 'springgreen' => '0,255,127', 'steelblue' => '70,130,180', 'tan' => '210,180,140', 'teal' => '0,128,128', 'thistle' => '216,191,216', 'tomato' => '255,99,71', 'transparent' => '0,0,0,0', 'turquoise' => '64,224,208', 'violet' => '238,130,238', 'wheat' => '245,222,179', 'white' => '255,255,255', 'whitesmoke' => '245,245,245', 'yellow' => '255,255,0', 'yellowgreen' => '154,205,50' ); } PK Ma!]�L��4 4 less/formatter/compressed.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage less * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * This class is taken verbatim from: * * lessphp v0.3.9 * http://leafo.net/lessphp * * LESS css compiler, adapted from http://lesscss.org * * Copyright 2012, Leaf Corcoran <leafot@gmail.com> * Licensed under MIT or GPLv3, see LICENSE * * @package FrameworkOnFramework * @since 2.0 */ class FOFLessFormatterCompressed extends FOFLessFormatterClassic { public $disableSingle = true; public $open = "{"; public $selectorSeparator = ","; public $assignSeparator = ":"; public $break = ""; public $compressColors = true; /** * Indent a string by $n positions * * @param integer $n How many positions to indent * * @return string The indented string */ public function indentStr($n = 0) { return ""; } } PK Ma!]E��� � less/formatter/classic.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage less * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * This class is taken verbatim from: * * lessphp v0.3.9 * http://leafo.net/lessphp * * LESS css compiler, adapted from http://lesscss.org * * Copyright 2012, Leaf Corcoran <leafot@gmail.com> * Licensed under MIT or GPLv3, see LICENSE * * @package FrameworkOnFramework * @since 2.0 */ class FOFLessFormatterClassic { public $indentChar = " "; public $break = "\n"; public $open = " {"; public $close = "}"; public $selectorSeparator = ", "; public $assignSeparator = ":"; public $openSingle = " { "; public $closeSingle = " }"; public $disableSingle = false; public $breakSelectors = false; public $compressColors = false; /** * Public constructor */ public function __construct() { $this->indentLevel = 0; } /** * Indent a string by $n positions * * @param integer $n How many positions to indent * * @return string The indented string */ public function indentStr($n = 0) { return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); } /** * Return the code for a property * * @param string $name The name of the property * @param string $value The value of the property * * @return string The CSS code */ public function property($name, $value) { return $name . $this->assignSeparator . $value . ";"; } /** * Is a block empty? * * @param stdClass $block The block to check * * @return boolean True if the block has no lines or children */ protected function isEmpty($block) { if (empty($block->lines)) { foreach ($block->children as $child) { if (!$this->isEmpty($child)) { return false; } } return true; } return false; } /** * Output a CSS block * * @param stdClass $block The block definition to output * * @return void */ public function block($block) { if ($this->isEmpty($block)) { return; } $inner = $pre = $this->indentStr(); $isSingle = !$this->disableSingle && is_null($block->type) && count($block->lines) == 1; if (!empty($block->selectors)) { $this->indentLevel++; if ($this->breakSelectors) { $selectorSeparator = $this->selectorSeparator . $this->break . $pre; } else { $selectorSeparator = $this->selectorSeparator; } echo $pre . implode($selectorSeparator, $block->selectors); if ($isSingle) { echo $this->openSingle; $inner = ""; } else { echo $this->open . $this->break; $inner = $this->indentStr(); } } if (!empty($block->lines)) { $glue = $this->break . $inner; echo $inner . implode($glue, $block->lines); if (!$isSingle && !empty($block->children)) { echo $this->break; } } foreach ($block->children as $child) { $this->block($child); } if (!empty($block->selectors)) { if (!$isSingle && empty($block->children)) { echo $this->break; } if ($isSingle) { echo $this->closeSingle . $this->break; } else { echo $pre . $this->close . $this->break; } $this->indentLevel--; } } } PK Ma!]nM8 8 less/formatter/lessjs.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage less * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * This class is taken verbatim from: * * lessphp v0.3.9 * http://leafo.net/lessphp * * LESS css compiler, adapted from http://lesscss.org * * Copyright 2012, Leaf Corcoran <leafot@gmail.com> * Licensed under MIT or GPLv3, see LICENSE * * @package FrameworkOnFramework * @since 2.0 */ class FOFLessFormatterLessjs extends FOFLessFormatterClassic { public $disableSingle = true; public $breakSelectors = true; public $assignSeparator = ": "; public $selectorSeparator = ","; } PK Ma!]�U*U U less/formatter/joomla.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage less * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * This class is taken verbatim from: * * lessphp v0.3.9 * http://leafo.net/lessphp * * LESS css compiler, adapted from http://lesscss.org * * Copyright 2012, Leaf Corcoran <leafot@gmail.com> * Licensed under MIT or GPLv3, see LICENSE * * @package FrameworkOnFramework * @since 2.1 */ class FOFLessFormatterJoomla extends FOFLessFormatterClassic { public $disableSingle = true; public $breakSelectors = true; public $assignSeparator = ": "; public $selectorSeparator = ","; public $indentChar = "\t"; } PK Ma!]��?V�� �� less/parser/parser.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage less * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * This class is taken verbatim from: * * lessphp v0.3.9 * http://leafo.net/lessphp * * LESS css compiler, adapted from http://lesscss.org * * Copyright 2012, Leaf Corcoran <leafot@gmail.com> * Licensed under MIT or GPLv3, see LICENSE * * Responsible for taking a string of LESS code and converting it into a syntax tree * * @since 2.0 */ class FOFLessParser { // Used to uniquely identify blocks protected static $nextBlockId = 0; protected static $precedence = array( '=<' => 0, '>=' => 0, '=' => 0, '<' => 0, '>' => 0, '+' => 1, '-' => 1, '*' => 2, '/' => 2, '%' => 2, ); protected static $whitePattern; protected static $commentMulti; protected static $commentSingle = "//"; protected static $commentMultiLeft = "/*"; protected static $commentMultiRight = "*/"; // Regex string to match any of the operators protected static $operatorString; // These properties will supress division unless it's inside parenthases protected static $supressDivisionProps = array('/border-radius$/i', '/^font$/i'); protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document"); protected $lineDirectives = array("charset"); /** * if we are in parens we can be more liberal with whitespace around * operators because it must evaluate to a single value and thus is less * ambiguous. * * Consider: * property1: 10 -5; // is two numbers, 10 and -5 * property2: (10 -5); // should evaluate to 5 */ protected $inParens = false; // Caches preg escaped literals protected static $literalCache = array(); /** * Constructor * * @param [type] $lessc [description] * @param string $sourceName [description] */ public function __construct($lessc, $sourceName = null) { $this->eatWhiteDefault = true; // Reference to less needed for vPrefix, mPrefix, and parentSelector $this->lessc = $lessc; // Name used for error messages $this->sourceName = $sourceName; $this->writeComments = false; if (!self::$operatorString) { self::$operatorString = '(' . implode('|', array_map(array('FOFLess', 'preg_quote'), array_keys(self::$precedence))) . ')'; $commentSingle = FOFLess::preg_quote(self::$commentSingle); $commentMultiLeft = FOFLess::preg_quote(self::$commentMultiLeft); $commentMultiRight = FOFLess::preg_quote(self::$commentMultiRight); self::$commentMulti = $commentMultiLeft . '.*?' . $commentMultiRight; self::$whitePattern = '/' . $commentSingle . '[^\n]*\s*|(' . self::$commentMulti . ')\s*|\s+/Ais'; } } /** * Parse text * * @param string $buffer [description] * * @return [type] [description] */ public function parse($buffer) { $this->count = 0; $this->line = 1; // Block stack $this->env = null; $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer); $this->pushSpecialBlock("root"); $this->eatWhiteDefault = true; $this->seenComments = array(); /* * trim whitespace on head * if (preg_match('/^\s+/', $this->buffer, $m)) { * $this->line += substr_count($m[0], "\n"); * $this->buffer = ltrim($this->buffer); * } */ $this->whitespace(); // Parse the entire file $lastCount = $this->count; while (false !== $this->parseChunk()); if ($this->count != strlen($this->buffer)) { $this->throwError(); } // TODO report where the block was opened if (!is_null($this->env->parent)) { throw new exception('parse error: unclosed block'); } return $this->env; } /** * Parse a single chunk off the head of the buffer and append it to the * current parse environment. * Returns false when the buffer is empty, or when there is an error. * * This function is called repeatedly until the entire document is * parsed. * * This parser is most similar to a recursive descent parser. Single * functions represent discrete grammatical rules for the language, and * they are able to capture the text that represents those rules. * * Consider the function lessc::keyword(). (all parse functions are * structured the same) * * The function takes a single reference argument. When calling the * function it will attempt to match a keyword on the head of the buffer. * If it is successful, it will place the keyword in the referenced * argument, advance the position in the buffer, and return true. If it * fails then it won't advance the buffer and it will return false. * * All of these parse functions are powered by lessc::match(), which behaves * the same way, but takes a literal regular expression. Sometimes it is * more convenient to use match instead of creating a new function. * * Because of the format of the functions, to parse an entire string of * grammatical rules, you can chain them together using &&. * * But, if some of the rules in the chain succeed before one fails, then * the buffer position will be left at an invalid state. In order to * avoid this, lessc::seek() is used to remember and set buffer positions. * * Before parsing a chain, use $s = $this->seek() to remember the current * position into $s. Then if a chain fails, use $this->seek($s) to * go back where we started. * * @return boolean */ protected function parseChunk() { if (empty($this->buffer)) { return false; } $s = $this->seek(); // Setting a property if ($this->keyword($key) && $this->assign() && $this->propertyValue($value, $key) && $this->end()) { $this->append(array('assign', $key, $value), $s); return true; } else { $this->seek($s); } // Look for special css blocks if ($this->literal('@', false)) { $this->count--; // Media if ($this->literal('@media')) { if (($this->mediaQueryList($mediaQueries) || true) && $this->literal('{')) { $media = $this->pushSpecialBlock("media"); $media->queries = is_null($mediaQueries) ? array() : $mediaQueries; return true; } else { $this->seek($s); return false; } } if ($this->literal("@", false) && $this->keyword($dirName)) { if ($this->isDirective($dirName, $this->blockDirectives)) { if (($this->openString("{", $dirValue, null, array(";")) || true) && $this->literal("{")) { $dir = $this->pushSpecialBlock("directive"); $dir->name = $dirName; if (isset($dirValue)) { $dir->value = $dirValue; } return true; } } elseif ($this->isDirective($dirName, $this->lineDirectives)) { if ($this->propertyValue($dirValue) && $this->end()) { $this->append(array("directive", $dirName, $dirValue)); return true; } } } $this->seek($s); } // Setting a variable if ($this->variable($var) && $this->assign() && $this->propertyValue($value) && $this->end()) { $this->append(array('assign', $var, $value), $s); return true; } else { $this->seek($s); } if ($this->import($importValue)) { $this->append($importValue, $s); return true; } // Opening parametric mixin if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) && ($this->guards($guards) || true) && $this->literal('{')) { $block = $this->pushBlock($this->fixTags(array($tag))); $block->args = $args; $block->isVararg = $isVararg; if (!empty($guards)) { $block->guards = $guards; } return true; } else { $this->seek($s); } // Opening a simple block if ($this->tags($tags) && $this->literal('{')) { $tags = $this->fixTags($tags); $this->pushBlock($tags); return true; } else { $this->seek($s); } // Closing a block if ($this->literal('}', false)) { try { $block = $this->pop(); } catch (exception $e) { $this->seek($s); $this->throwError($e->getMessage()); } $hidden = false; if (is_null($block->type)) { $hidden = true; if (!isset($block->args)) { foreach ($block->tags as $tag) { if (!is_string($tag) || $tag[0] != $this->lessc->mPrefix) { $hidden = false; break; } } } foreach ($block->tags as $tag) { if (is_string($tag)) { $this->env->children[$tag][] = $block; } } } if (!$hidden) { $this->append(array('block', $block), $s); } // This is done here so comments aren't bundled into he block that was just closed $this->whitespace(); return true; } // Mixin if ($this->mixinTags($tags) && ($this->argumentValues($argv) || true) && ($this->keyword($suffix) || true) && $this->end()) { $tags = $this->fixTags($tags); $this->append(array('mixin', $tags, $argv, $suffix), $s); return true; } else { $this->seek($s); } // Spare ; if ($this->literal(';')) { return true; } // Got nothing, throw error return false; } /** * [isDirective description] * * @param string $dirname [description] * @param [type] $directives [description] * * @return boolean */ protected function isDirective($dirname, $directives) { // TODO: cache pattern in parser $pattern = implode("|", array_map(array("FOFLess", "preg_quote"), $directives)); $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i'; return preg_match($pattern, $dirname); } /** * [fixTags description] * * @param [type] $tags [description] * * @return [type] [description] */ protected function fixTags($tags) { // Move @ tags out of variable namespace foreach ($tags as &$tag) { if ($tag[0] == $this->lessc->vPrefix) { $tag[0] = $this->lessc->mPrefix; } } return $tags; } /** * a list of expressions * * @param [type] &$exps [description] * * @return boolean */ protected function expressionList(&$exps) { $values = array(); while ($this->expression($exp)) { $values[] = $exp; } if (count($values) == 0) { return false; } $exps = FOFLess::compressList($values, ' '); return true; } /** * Attempt to consume an expression. * * @param string &$out [description] * * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code * * @return boolean */ protected function expression(&$out) { if ($this->value($lhs)) { $out = $this->expHelper($lhs, 0); // Look for / shorthand if (!empty($this->env->supressedDivision)) { unset($this->env->supressedDivision); $s = $this->seek(); if ($this->literal("/") && $this->value($rhs)) { $out = array("list", "", array($out, array("keyword", "/"), $rhs)); } else { $this->seek($s); } } return true; } return false; } /** * Recursively parse infix equation with $lhs at precedence $minP * * @param type $lhs [description] * @param type $minP [description] * * @return string */ protected function expHelper($lhs, $minP) { $this->inExp = true; $ss = $this->seek(); while (true) { $whiteBefore = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]); // If there is whitespace before the operator, then we require // whitespace after the operator for it to be an expression $needWhite = $whiteBefore && !$this->inParens; if ($this->match(self::$operatorString . ($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) { if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) { foreach (self::$supressDivisionProps as $pattern) { if (preg_match($pattern, $this->env->currentProperty)) { $this->env->supressedDivision = true; break 2; } } } $whiteAfter = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]); if (!$this->value($rhs)) { break; } // Peek for next operator to see what to do with rhs if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) { $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); } $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter); $ss = $this->seek(); continue; } break; } $this->seek($ss); return $lhs; } /** * Consume a list of values for a property * * @param [type] &$value [description] * @param [type] $keyName [description] * * @return boolean */ public function propertyValue(&$value, $keyName = null) { $values = array(); if ($keyName !== null) { $this->env->currentProperty = $keyName; } $s = null; while ($this->expressionList($v)) { $values[] = $v; $s = $this->seek(); if (!$this->literal(',')) { break; } } if ($s) { $this->seek($s); } if ($keyName !== null) { unset($this->env->currentProperty); } if (count($values) == 0) { return false; } $value = FOFLess::compressList($values, ', '); return true; } /** * [parenValue description] * * @param [type] &$out [description] * * @return boolean */ protected function parenValue(&$out) { $s = $this->seek(); // Speed shortcut if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") { return false; } $inParens = $this->inParens; if ($this->literal("(") && ($this->inParens = true) && $this->expression($exp) && $this->literal(")")) { $out = $exp; $this->inParens = $inParens; return true; } else { $this->inParens = $inParens; $this->seek($s); } return false; } /** * a single value * * @param [type] &$value [description] * * @return boolean */ protected function value(&$value) { $s = $this->seek(); // Speed shortcut if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") { // Negation if ($this->literal("-", false) &&(($this->variable($inner) && $inner = array("variable", $inner)) || $this->unit($inner) || $this->parenValue($inner))) { $value = array("unary", "-", $inner); return true; } else { $this->seek($s); } } if ($this->parenValue($value)) { return true; } if ($this->unit($value)) { return true; } if ($this->color($value)) { return true; } if ($this->func($value)) { return true; } if ($this->string($value)) { return true; } if ($this->keyword($word)) { $value = array('keyword', $word); return true; } // Try a variable if ($this->variable($var)) { $value = array('variable', $var); return true; } // Unquote string (should this work on any type? if ($this->literal("~") && $this->string($str)) { $value = array("escape", $str); return true; } else { $this->seek($s); } // Css hack: \0 if ($this->literal('\\') && $this->match('([0-9]+)', $m)) { $value = array('keyword', '\\' . $m[1]); return true; } else { $this->seek($s); } return false; } /** * an import statement * * @param [type] &$out [description] * * @return boolean */ protected function import(&$out) { $s = $this->seek(); if (!$this->literal('@import')) { return false; } /* * @import "something.css" media; * @import url("something.css") media; * @import url(something.css) media; */ if ($this->propertyValue($value)) { $out = array("import", $value); return true; } } /** * [mediaQueryList description] * * @param [type] &$out [description] * * @return boolean */ protected function mediaQueryList(&$out) { if ($this->genericList($list, "mediaQuery", ",", false)) { $out = $list[2]; return true; } return false; } /** * [mediaQuery description] * * @param [type] &$out [description] * * @return [type] [description] */ protected function mediaQuery(&$out) { $s = $this->seek(); $expressions = null; $parts = array(); if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) { $prop = array("mediaType"); if (isset($only)) { $prop[] = "only"; } if (isset($not)) { $prop[] = "not"; } $prop[] = $mediaType; $parts[] = $prop; } else { $this->seek($s); } if (!empty($mediaType) && !$this->literal("and")) { // ~ } else { $this->genericList($expressions, "mediaExpression", "and", false); if (is_array($expressions)) { $parts = array_merge($parts, $expressions[2]); } } if (count($parts) == 0) { $this->seek($s); return false; } $out = $parts; return true; } /** * [mediaExpression description] * * @param [type] &$out [description] * * @return boolean */ protected function mediaExpression(&$out) { $s = $this->seek(); $value = null; if ($this->literal("(") && $this->keyword($feature) && ($this->literal(":") && $this->expression($value) || true) && $this->literal(")")) { $out = array("mediaExp", $feature); if ($value) { $out[] = $value; } return true; } elseif ($this->variable($variable)) { $out = array('variable', $variable); return true; } $this->seek($s); return false; } /** * An unbounded string stopped by $end * * @param [type] $end [description] * @param [type] &$out [description] * @param [type] $nestingOpen [description] * @param [type] $rejectStrs [description] * * @return boolean */ protected function openString($end, &$out, $nestingOpen = null, $rejectStrs = null) { $oldWhite = $this->eatWhiteDefault; $this->eatWhiteDefault = false; $stop = array("'", '"', "@{", $end); $stop = array_map(array("FOFLess", "preg_quote"), $stop); // $stop[] = self::$commentMulti; if (!is_null($rejectStrs)) { $stop = array_merge($stop, $rejectStrs); } $patt = '(.*?)(' . implode("|", $stop) . ')'; $nestingLevel = 0; $content = array(); while ($this->match($patt, $m, false)) { if (!empty($m[1])) { $content[] = $m[1]; if ($nestingOpen) { $nestingLevel += substr_count($m[1], $nestingOpen); } } $tok = $m[2]; $this->count -= strlen($tok); if ($tok == $end) { if ($nestingLevel == 0) { break; } else { $nestingLevel--; } } if (($tok == "'" || $tok == '"') && $this->string($str)) { $content[] = $str; continue; } if ($tok == "@{" && $this->interpolation($inter)) { $content[] = $inter; continue; } if (in_array($tok, $rejectStrs)) { $count = null; break; } $content[] = $tok; $this->count += strlen($tok); } $this->eatWhiteDefault = $oldWhite; if (count($content) == 0) return false; // Trim the end if (is_string(end($content))) { $content[count($content) - 1] = rtrim(end($content)); } $out = array("string", "", $content); return true; } /** * [string description] * * @param [type] &$out [description] * * @return boolean */ protected function string(&$out) { $s = $this->seek(); if ($this->literal('"', false)) { $delim = '"'; } elseif ($this->literal("'", false)) { $delim = "'"; } else { return false; } $content = array(); // Look for either ending delim , escape, or string interpolation $patt = '([^\n]*?)(@\{|\\\\|' . FOFLess::preg_quote($delim) . ')'; $oldWhite = $this->eatWhiteDefault; $this->eatWhiteDefault = false; while ($this->match($patt, $m, false)) { $content[] = $m[1]; if ($m[2] == "@{") { $this->count -= strlen($m[2]); if ($this->interpolation($inter, false)) { $content[] = $inter; } else { $this->count += strlen($m[2]); // Ignore it $content[] = "@{"; } } elseif ($m[2] == '\\') { $content[] = $m[2]; if ($this->literal($delim, false)) { $content[] = $delim; } } else { $this->count -= strlen($delim); // Delim break; } } $this->eatWhiteDefault = $oldWhite; if ($this->literal($delim)) { $out = array("string", $delim, $content); return true; } $this->seek($s); return false; } /** * [interpolation description] * * @param [type] &$out [description] * * @return boolean */ protected function interpolation(&$out) { $oldWhite = $this->eatWhiteDefault; $this->eatWhiteDefault = true; $s = $this->seek(); if ($this->literal("@{") && $this->openString("}", $interp, null, array("'", '"', ";")) && $this->literal("}", false)) { $out = array("interpolate", $interp); $this->eatWhiteDefault = $oldWhite; if ($this->eatWhiteDefault) { $this->whitespace(); } return true; } $this->eatWhiteDefault = $oldWhite; $this->seek($s); return false; } /** * [unit description] * * @param [type] &$unit [description] * * @return boolean */ protected function unit(&$unit) { // Speed shortcut if (isset($this->buffer[$this->count])) { $char = $this->buffer[$this->count]; if (!ctype_digit($char) && $char != ".") { return false; } } if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) { $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]); return true; } return false; } /** * a # color * * @param [type] &$out [description] * * @return boolean */ protected function color(&$out) { if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) { if (strlen($m[1]) > 7) { $out = array("string", "", array($m[1])); } else { $out = array("raw_color", $m[1]); } return true; } return false; } /** * Consume a list of property values delimited by ; and wrapped in () * * @param [type] &$args [description] * @param [type] $delim [description] * * @return boolean */ protected function argumentValues(&$args, $delim = ',') { $s = $this->seek(); if (!$this->literal('(')) { return false; } $values = array(); while (true) { if ($this->expressionList($value)) { $values[] = $value; } if (!$this->literal($delim)) { break; } else { if ($value == null) { $values[] = null; } $value = null; } } if (!$this->literal(')')) { $this->seek($s); return false; } $args = $values; return true; } /** * Consume an argument definition list surrounded by () * each argument is a variable name with optional value * or at the end a ... or a variable named followed by ... * * @param [type] &$args [description] * @param [type] &$isVararg [description] * @param [type] $delim [description] * * @return boolean */ protected function argumentDef(&$args, &$isVararg, $delim = ',') { $s = $this->seek(); if (!$this->literal('(')) return false; $values = array(); $isVararg = false; while (true) { if ($this->literal("...")) { $isVararg = true; break; } if ($this->variable($vname)) { $arg = array("arg", $vname); $ss = $this->seek(); if ($this->assign() && $this->expressionList($value)) { $arg[] = $value; } else { $this->seek($ss); if ($this->literal("...")) { $arg[0] = "rest"; $isVararg = true; } } $values[] = $arg; if ($isVararg) { break; } continue; } if ($this->value($literal)) { $values[] = array("lit", $literal); } if (!$this->literal($delim)) { break; } } if (!$this->literal(')')) { $this->seek($s); return false; } $args = $values; return true; } /** * Consume a list of tags * This accepts a hanging delimiter * * @param [type] &$tags [description] * @param [type] $simple [description] * @param [type] $delim [description] * * @return boolean */ protected function tags(&$tags, $simple = false, $delim = ',') { $tags = array(); while ($this->tag($tt, $simple)) { $tags[] = $tt; if (!$this->literal($delim)) { break; } } if (count($tags) == 0) { return false; } return true; } /** * List of tags of specifying mixin path * Optionally separated by > (lazy, accepts extra >) * * @param [type] &$tags [description] * * @return boolean */ protected function mixinTags(&$tags) { $s = $this->seek(); $tags = array(); while ($this->tag($tt, true)) { $tags[] = $tt; $this->literal(">"); } if (count($tags) == 0) { return false; } return true; } /** * A bracketed value (contained within in a tag definition) * * @param [type] &$value [description] * * @return boolean */ protected function tagBracket(&$value) { // Speed shortcut if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") { return false; } $s = $this->seek(); if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) { $value = '[' . $c . ']'; // Whitespace? if ($this->whitespace()) { $value .= " "; } // Escape parent selector, (yuck) $value = str_replace($this->lessc->parentSelector, "$&$", $value); return true; } $this->seek($s); return false; } /** * [tagExpression description] * * @param [type] &$value [description] * * @return boolean */ protected function tagExpression(&$value) { $s = $this->seek(); if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { $value = array('exp', $exp); return true; } $this->seek($s); return false; } /** * A single tag * * @param [type] &$tag [description] * @param boolean $simple [description] * * @return boolean */ protected function tag(&$tag, $simple = false) { if ($simple) { $chars = '^@,:;{}\][>\(\) "\''; } else { $chars = '^@,;{}["\''; } $s = $this->seek(); if (!$simple && $this->tagExpression($tag)) { return true; } $hasExpression = false; $parts = array(); while ($this->tagBracket($first)) { $parts[] = $first; } $oldWhite = $this->eatWhiteDefault; $this->eatWhiteDefault = false; while (true) { if ($this->match('([' . $chars . '0-9][' . $chars . ']*)', $m)) { $parts[] = $m[1]; if ($simple) { break; } while ($this->tagBracket($brack)) { $parts[] = $brack; } continue; } if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") { if ($this->interpolation($interp)) { $hasExpression = true; // Don't unescape $interp[2] = true; $parts[] = $interp; continue; } if ($this->literal("@")) { $parts[] = "@"; continue; } } // For keyframes if ($this->unit($unit)) { $parts[] = $unit[1]; $parts[] = $unit[2]; continue; } break; } $this->eatWhiteDefault = $oldWhite; if (!$parts) { $this->seek($s); return false; } if ($hasExpression) { $tag = array("exp", array("string", "", $parts)); } else { $tag = trim(implode($parts)); } $this->whitespace(); return true; } /** * A css function * * @param [type] &$func [description] * * @return boolean */ protected function func(&$func) { $s = $this->seek(); if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) { $fname = $m[1]; $sPreArgs = $this->seek(); $args = array(); while (true) { $ss = $this->seek(); // This ugly nonsense is for ie filter properties if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) { $args[] = array("string", "", array($name, "=", $value)); } else { $this->seek($ss); if ($this->expressionList($value)) { $args[] = $value; } } if (!$this->literal(',')) { break; } } $args = array('list', ',', $args); if ($this->literal(')')) { $func = array('function', $fname, $args); return true; } elseif ($fname == 'url') { // Couldn't parse and in url? treat as string $this->seek($sPreArgs); if ($this->openString(")", $string) && $this->literal(")")) { $func = array('function', $fname, $string); return true; } } } $this->seek($s); return false; } /** * Consume a less variable * * @param [type] &$name [description] * * @return boolean */ protected function variable(&$name) { $s = $this->seek(); if ($this->literal($this->lessc->vPrefix, false) && ($this->variable($sub) || $this->keyword($name))) { if (!empty($sub)) { $name = array('variable', $sub); } else { $name = $this->lessc->vPrefix . $name; } return true; } $name = null; $this->seek($s); return false; } /** * Consume an assignment operator * Can optionally take a name that will be set to the current property name * * @param string $name [description] * * @return boolean */ protected function assign($name = null) { if ($name) { $this->currentProperty = $name; } return $this->literal(':') || $this->literal('='); } /** * Consume a keyword * * @param [type] &$word [description] * * @return boolean */ protected function keyword(&$word) { if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) { $word = $m[1]; return true; } return false; } /** * Consume an end of statement delimiter * * @return boolean */ protected function end() { if ($this->literal(';')) { return true; } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') { // If there is end of file or a closing block next then we don't need a ; return true; } return false; } /** * [guards description] * * @param [type] &$guards [description] * * @return boolean */ protected function guards(&$guards) { $s = $this->seek(); if (!$this->literal("when")) { $this->seek($s); return false; } $guards = array(); while ($this->guardGroup($g)) { $guards[] = $g; if (!$this->literal(",")) { break; } } if (count($guards) == 0) { $guards = null; $this->seek($s); return false; } return true; } /** * A bunch of guards that are and'd together * * @param [type] &$guardGroup [description] * * @todo rename to guardGroup * * @return boolean */ protected function guardGroup(&$guardGroup) { $s = $this->seek(); $guardGroup = array(); while ($this->guard($guard)) { $guardGroup[] = $guard; if (!$this->literal("and")) { break; } } if (count($guardGroup) == 0) { $guardGroup = null; $this->seek($s); return false; } return true; } /** * [guard description] * * @param [type] &$guard [description] * * @return boolean */ protected function guard(&$guard) { $s = $this->seek(); $negate = $this->literal("not"); if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { $guard = $exp; if ($negate) { $guard = array("negate", $guard); } return true; } $this->seek($s); return false; } /* raw parsing functions */ /** * [literal description] * * @param [type] $what [description] * @param [type] $eatWhitespace [description] * * @return boolean */ protected function literal($what, $eatWhitespace = null) { if ($eatWhitespace === null) { $eatWhitespace = $this->eatWhiteDefault; } // Shortcut on single letter if (!isset($what[1]) && isset($this->buffer[$this->count])) { if ($this->buffer[$this->count] == $what) { if (!$eatWhitespace) { $this->count++; return true; } } else { return false; } } if (!isset(self::$literalCache[$what])) { self::$literalCache[$what] = FOFLess::preg_quote($what); } return $this->match(self::$literalCache[$what], $m, $eatWhitespace); } /** * [genericList description] * * @param [type] &$out [description] * @param [type] $parseItem [description] * @param string $delim [description] * @param boolean $flatten [description] * * @return boolean */ protected function genericList(&$out, $parseItem, $delim = "", $flatten = true) { $s = $this->seek(); $items = array(); while ($this->$parseItem($value)) { $items[] = $value; if ($delim) { if (!$this->literal($delim)) { break; } } } if (count($items) == 0) { $this->seek($s); return false; } if ($flatten && count($items) == 1) { $out = $items[0]; } else { $out = array("list", $delim, $items); } return true; } /** * Advance counter to next occurrence of $what * $until - don't include $what in advance * $allowNewline, if string, will be used as valid char set * * @param [type] $what [description] * @param [type] &$out [description] * @param boolean $until [description] * @param boolean $allowNewline [description] * * @return boolean */ protected function to($what, &$out, $until = false, $allowNewline = false) { if (is_string($allowNewline)) { $validChars = $allowNewline; } else { $validChars = $allowNewline ? "." : "[^\n]"; } if (!$this->match('(' . $validChars . '*?)' . FOFLess::preg_quote($what), $m, !$until)) { return false; } if ($until) { // Give back $what $this->count -= strlen($what); } $out = $m[1]; return true; } /** * Try to match something on head of buffer * * @param [type] $regex [description] * @param [type] &$out [description] * @param [type] $eatWhitespace [description] * * @return boolean */ protected function match($regex, &$out, $eatWhitespace = null) { if ($eatWhitespace === null) { $eatWhitespace = $this->eatWhiteDefault; } $r = '/' . $regex . ($eatWhitespace && !$this->writeComments ? '\s*' : '') . '/Ais'; if (preg_match($r, $this->buffer, $out, null, $this->count)) { $this->count += strlen($out[0]); if ($eatWhitespace && $this->writeComments) { $this->whitespace(); } return true; } return false; } /** * Watch some whitespace * * @return boolean */ protected function whitespace() { if ($this->writeComments) { $gotWhite = false; while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) { if (isset($m[1]) && empty($this->commentsSeen[$this->count])) { $this->append(array("comment", $m[1])); $this->commentsSeen[$this->count] = true; } $this->count += strlen($m[0]); $gotWhite = true; } return $gotWhite; } else { $this->match("", $m); return strlen($m[0]) > 0; } } /** * Match something without consuming it * * @param [type] $regex [description] * @param [type] &$out [description] * @param [type] $from [description] * * @return boolean */ protected function peek($regex, &$out = null, $from = null) { if (is_null($from)) { $from = $this->count; } $r = '/' . $regex . '/Ais'; $result = preg_match($r, $this->buffer, $out, null, $from); return $result; } /** * Seek to a spot in the buffer or return where we are on no argument * * @param [type] $where [description] * * @return boolean */ protected function seek($where = null) { if ($where === null) { return $this->count; } else { $this->count = $where; } return true; } /* misc functions */ /** * [throwError description] * * @param string $msg [description] * @param [type] $count [description] * * @return void */ public function throwError($msg = "parse error", $count = null) { $count = is_null($count) ? $this->count : $count; $line = $this->line + substr_count(substr($this->buffer, 0, $count), "\n"); if (!empty($this->sourceName)) { $loc = "$this->sourceName on line $line"; } else { $loc = "line: $line"; } // TODO this depends on $this->count if ($this->peek("(.*?)(\n|$)", $m, $count)) { throw new exception("$msg: failed at `$m[1]` $loc"); } else { throw new exception("$msg: $loc"); } } /** * [pushBlock description] * * @param [type] $selectors [description] * @param [type] $type [description] * * @return stdClass */ protected function pushBlock($selectors = null, $type = null) { $b = new stdclass; $b->parent = $this->env; $b->type = $type; $b->id = self::$nextBlockId++; // TODO: kill me from here $b->isVararg = false; $b->tags = $selectors; $b->props = array(); $b->children = array(); $this->env = $b; return $b; } /** * Push a block that doesn't multiply tags * * @param [type] $type [description] * * @return stdClass */ protected function pushSpecialBlock($type) { return $this->pushBlock(null, $type); } /** * Append a property to the current block * * @param [type] $prop [description] * @param [type] $pos [description] * * @return void */ protected function append($prop, $pos = null) { if ($pos !== null) { $prop[-1] = $pos; } $this->env->props[] = $prop; } /** * Pop something off the stack * * @return [type] [description] */ protected function pop() { $old = $this->env; $this->env = $this->env->parent; return $old; } /** * Remove comments from $text * * @param [type] $text [description] * * @todo: make it work for all functions, not just url * * @return [type] [description] */ protected function removeComments($text) { $look = array( 'url(', '//', '/*', '"', "'" ); $out = ''; $min = null; while (true) { // Find the next item foreach ($look as $token) { $pos = strpos($text, $token); if ($pos !== false) { if (!isset($min) || $pos < $min[1]) { $min = array($token, $pos); } } } if (is_null($min)) break; $count = $min[1]; $skip = 0; $newlines = 0; switch ($min[0]) { case 'url(': if (preg_match('/url\(.*?\)/', $text, $m, 0, $count)) { $count += strlen($m[0]) - strlen($min[0]); } break; case '"': case "'": if (preg_match('/' . $min[0] . '.*?' . $min[0] . '/', $text, $m, 0, $count)) { $count += strlen($m[0]) - 1; } break; case '//': $skip = strpos($text, "\n", $count); if ($skip === false) { $skip = strlen($text) - $count; } else { $skip -= $count; } break; case '/*': if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) { $skip = strlen($m[0]); $newlines = substr_count($m[0], "\n"); } break; } if ($skip == 0) { $count += strlen($min[0]); } $out .= substr($text, 0, $count) . str_repeat("\n", $newlines); $text = substr($text, $count + $skip); $min = null; } return $out . $text; } } PK Ma!]�6̢D �D dispatcher/dispatcher.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage dispatcher * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * FrameworkOnFramework dispatcher class * * FrameworkOnFramework is a set of classes which extend Joomla! 1.5 and later's * MVC framework with features making maintaining complex software much easier, * without tedious repetitive copying of the same code over and over again. * * @package FrameworkOnFramework * @since 1.0 */ class FOFDispatcher extends FOFUtilsObject { /** @var array Configuration variables */ protected $config = array(); /** @var FOFInput Input variables */ protected $input = array(); /** @var string The name of the default view, in case none is specified */ public $defaultView = 'cpanel'; // Variables for FOF's transparent user authentication. You can override them // in your Dispatcher's __construct() method. /** @var int The Time Step for the TOTP used in FOF's transparent user authentication */ protected $fofAuth_timeStep = 6; /** @var string The key for the TOTP, Base32 encoded (watch out; Base32, NOT Base64!) */ protected $fofAuth_Key = null; /** @var array Which formats to be handled by transparent authentication */ protected $fofAuth_Formats = array('json', 'csv', 'xml', 'raw'); /** * Should I logout the transparently authenticated user on logout? * Recommended to leave it on in order to avoid crashing the sessions table. * * @var boolean */ protected $fofAuth_LogoutOnReturn = true; /** @var array Which methods to use to fetch authentication credentials and in which order */ protected $fofAuth_AuthMethods = array( /* HTTP Basic Authentication using encrypted information protected * with a TOTP (the username must be "_fof_auth") */ 'HTTPBasicAuth_TOTP', /* Encrypted information protected with a TOTP passed in the * _fofauthentication query string parameter */ 'QueryString_TOTP', /* HTTP Basic Authentication using a username and password pair in plain text */ 'HTTPBasicAuth_Plaintext', /* Plaintext, JSON-encoded username and password pair passed in the * _fofauthentication query string parameter */ 'QueryString_Plaintext', /* Plaintext username and password in the _fofauthentication_username * and _fofauthentication_username query string parameters */ 'SplitQueryString_Plaintext', ); /** @var bool Did we successfully and transparently logged in a user? */ private $_fofAuth_isLoggedIn = false; /** @var string The calculated encryption key for the _TOTP methods, used if we have to encrypt the reply */ private $_fofAuth_CryptoKey = ''; /** * Get a static (Singleton) instance of a particular Dispatcher * * @param string $option The component name * @param string $view The View name * @param array $config Configuration data * * @staticvar array $instances Holds the array of Dispatchers FOF knows about * * @return FOFDispatcher */ public static function &getAnInstance($option = null, $view = null, $config = array()) { static $instances = array(); $hash = $option . $view; if (!array_key_exists($hash, $instances)) { $instances[$hash] = self::getTmpInstance($option, $view, $config); } return $instances[$hash]; } /** * Gets a temporary instance of a Dispatcher * * @param string $option The component name * @param string $view The View name * @param array $config Configuration data * * @return FOFDispatcher */ public static function &getTmpInstance($option = null, $view = null, $config = array()) { if (array_key_exists('input', $config)) { if ($config['input'] instanceof FOFInput) { $input = $config['input']; } else { if (!is_array($config['input'])) { $config['input'] = (array) $config['input']; } $config['input'] = array_merge($_REQUEST, $config['input']); $input = new FOFInput($config['input']); } } else { $input = new FOFInput; } $config['option'] = !is_null($option) ? $option : $input->getCmd('option', 'com_foobar'); $config['view'] = !is_null($view) ? $view : $input->getCmd('view', ''); $input->set('option', $config['option']); $input->set('view', $config['view']); $config['input'] = $input; $className = ucfirst(str_replace('com_', '', $config['option'])) . 'Dispatcher'; if (!class_exists($className)) { $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']); $searchPaths = array( $componentPaths['main'], $componentPaths['main'] . '/dispatchers', $componentPaths['admin'], $componentPaths['admin'] . '/dispatchers' ); if (array_key_exists('searchpath', $config)) { array_unshift($searchPaths, $config['searchpath']); } $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem'); $path = $filesystem->pathFind( $searchPaths, 'dispatcher.php' ); if ($path) { require_once $path; } } if (!class_exists($className)) { $className = 'FOFDispatcher'; } $instance = new $className($config); return $instance; } /** * Public constructor * * @param array $config The configuration variables */ public function __construct($config = array()) { // Cache the config $this->config = $config; // Get the input for this MVC triad if (array_key_exists('input', $config)) { $this->input = $config['input']; } else { $this->input = new FOFInput; } // Get the default values for the component name $this->component = $this->input->getCmd('option', 'com_foobar'); // Load the component's fof.xml configuration file $configProvider = new FOFConfigProvider; $this->defaultView = $configProvider->get($this->component . '.dispatcher.default_view', $this->defaultView); // Get the default values for the view name $this->view = $this->input->getCmd('view', null); if (empty($this->view)) { // Do we have a task formatted as controller.task? $task = $this->input->getCmd('task', ''); if (!empty($task) && (strstr($task, '.') !== false)) { list($this->view, $task) = explode('.', $task, 2); $this->input->set('task', $task); } } if (empty($this->view)) { $this->view = $this->defaultView; } $this->layout = $this->input->getCmd('layout', null); // Overrides from the config if (array_key_exists('option', $config)) { $this->component = $config['option']; } if (array_key_exists('view', $config)) { $this->view = empty($config['view']) ? $this->view : $config['view']; } if (array_key_exists('layout', $config)) { $this->layout = $config['layout']; } $this->input->set('option', $this->component); $this->input->set('view', $this->view); $this->input->set('layout', $this->layout); if (array_key_exists('authTimeStep', $config)) { $this->fofAuth_timeStep = empty($config['authTimeStep']) ? 6 : $config['authTimeStep']; } } /** * The main code of the Dispatcher. It spawns the necessary controller and * runs it. * * @throws Exception * * @return void|Exception */ public function dispatch() { $platform = FOFPlatform::getInstance(); if (!$platform->authorizeAdmin($this->input->getCmd('option', 'com_foobar'))) { return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')); } $this->transparentAuthentication(); // Merge English and local translations $platform->loadTranslations($this->component); $canDispatch = true; if ($platform->isCli()) { $canDispatch = $canDispatch && $this->onBeforeDispatchCLI(); } $canDispatch = $canDispatch && $this->onBeforeDispatch(); if (!$canDispatch) { // We can set header only if we're not in CLI if(!$platform->isCli()) { $platform->setHeader('Status', '403 Forbidden', true); } return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')); } // Get and execute the controller $option = $this->input->getCmd('option', 'com_foobar'); $view = $this->input->getCmd('view', $this->defaultView); $task = $this->input->getCmd('task', null); if (empty($task)) { $task = $this->getTask($view); } // Pluralise/sungularise the view name for typical tasks if (in_array($task, array('edit', 'add', 'read'))) { $view = FOFInflector::singularize($view); } elseif (in_array($task, array('browse'))) { $view = FOFInflector::pluralize($view); } $this->input->set('view', $view); $this->input->set('task', $task); $config = $this->config; $config['input'] = $this->input; $controller = FOFController::getTmpInstance($option, $view, $config); $status = $controller->execute($task); if (!$this->onAfterDispatch()) { // We can set header only if we're not in CLI if(!$platform->isCli()) { $platform->setHeader('Status', '403 Forbidden', true); } return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')); } $format = $this->input->get('format', 'html', 'cmd'); $format = empty($format) ? 'html' : $format; if ($controller->hasRedirect()) { $controller->redirect(); } } /** * Tries to guess the controller task to execute based on the view name and * the HTTP request method. * * @param string $view The name of the view * * @return string The best guess of the task to execute */ protected function getTask($view) { // Get a default task based on plural/singular view $request_task = $this->input->getCmd('task', null); $task = FOFInflector::isPlural($view) ? 'browse' : 'edit'; // Get a potential ID, we might need it later $id = $this->input->get('id', null, 'int'); if ($id == 0) { $ids = $this->input->get('ids', array(), 'array'); if (!empty($ids)) { $id = array_shift($ids); } } // Check the request method if (!isset($_SERVER['REQUEST_METHOD'])) { $_SERVER['REQUEST_METHOD'] = 'GET'; } $requestMethod = strtoupper($_SERVER['REQUEST_METHOD']); switch ($requestMethod) { case 'POST': case 'PUT': if (!is_null($id)) { $task = 'save'; } break; case 'DELETE': if ($id != 0) { $task = 'delete'; } break; case 'GET': default: // If it's an edit without an ID or ID=0, it's really an add if (($task == 'edit') && ($id == 0)) { $task = 'add'; } // If it's an edit in the frontend, it's really a read elseif (($task == 'edit') && FOFPlatform::getInstance()->isFrontend()) { $task = 'read'; } break; } return $task; } /** * Executes right before the dispatcher tries to instantiate and run the * controller. * * @return boolean Return false to abort */ public function onBeforeDispatch() { return true; } /** * Sets up some environment variables, so we can work as usually on CLI, too. * * @return boolean Return false to abort */ public function onBeforeDispatchCLI() { JLoader::import('joomla.environment.uri'); JLoader::import('joomla.application.component.helper'); // Trick to create a valid url used by JURI $this->_originalPhpScript = ''; // We have no Application Helper (there is no Application!), so I have to define these constants manually $option = $this->input->get('option', '', 'cmd'); if ($option) { $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($option); if (!defined('JPATH_COMPONENT')) { define('JPATH_COMPONENT', $componentPaths['main']); } if (!defined('JPATH_COMPONENT_SITE')) { define('JPATH_COMPONENT_SITE', $componentPaths['site']); } if (!defined('JPATH_COMPONENT_ADMINISTRATOR')) { define('JPATH_COMPONENT_ADMINISTRATOR', $componentPaths['admin']); } } return true; } /** * Executes right after the dispatcher runs the controller. * * @return boolean Return false to abort */ public function onAfterDispatch() { // If we have to log out the user, please do so now if ($this->fofAuth_LogoutOnReturn && $this->_fofAuth_isLoggedIn) { FOFPlatform::getInstance()->logoutUser(); } return true; } /** * Transparently authenticates a user * * @return void */ public function transparentAuthentication() { // Only run when there is no logged in user if (!FOFPlatform::getInstance()->getUser()->guest) { return; } // @todo Check the format $format = $this->input->getCmd('format', 'html'); if (!in_array($format, $this->fofAuth_Formats)) { return; } foreach ($this->fofAuth_AuthMethods as $method) { // If we're already logged in, don't bother if ($this->_fofAuth_isLoggedIn) { continue; } // This will hold our authentication data array (username, password) $authInfo = null; switch ($method) { case 'HTTPBasicAuth_TOTP': if (empty($this->fofAuth_Key)) { continue 2; } if (!isset($_SERVER['PHP_AUTH_USER'])) { continue 2; } if (!isset($_SERVER['PHP_AUTH_PW'])) { continue 2; } if ($_SERVER['PHP_AUTH_USER'] != '_fof_auth') { continue 2; } $encryptedData = $_SERVER['PHP_AUTH_PW']; $authInfo = $this->_decryptWithTOTP($encryptedData); break; case 'QueryString_TOTP': $encryptedData = $this->input->get('_fofauthentication', '', 'raw'); if (empty($encryptedData)) { continue 2; } $authInfo = $this->_decryptWithTOTP($encryptedData); break; case 'HTTPBasicAuth_Plaintext': if (!isset($_SERVER['PHP_AUTH_USER'])) { continue 2; } if (!isset($_SERVER['PHP_AUTH_PW'])) { continue 2; } $authInfo = array( 'username' => $_SERVER['PHP_AUTH_USER'], 'password' => $_SERVER['PHP_AUTH_PW'] ); break; case 'QueryString_Plaintext': $jsonencoded = $this->input->get('_fofauthentication', '', 'raw'); if (empty($jsonencoded)) { continue 2; } $authInfo = json_decode($jsonencoded, true); if (!is_array($authInfo)) { $authInfo = null; } elseif (!array_key_exists('username', $authInfo) || !array_key_exists('password', $authInfo)) { $authInfo = null; } break; case 'SplitQueryString_Plaintext': $authInfo = array( 'username' => $this->input->get('_fofauthentication_username', '', 'raw'), 'password' => $this->input->get('_fofauthentication_password', '', 'raw'), ); if (empty($authInfo['username'])) { $authInfo = null; } break; default: continue 2; break; } // No point trying unless we have a username and password if (!is_array($authInfo)) { continue; } $this->_fofAuth_isLoggedIn = FOFPlatform::getInstance()->loginUser($authInfo); } } /** * Decrypts a transparent authentication message using a TOTP * * @param string $encryptedData The encrypted data * * @codeCoverageIgnore * @return array The decrypted data */ private function _decryptWithTOTP($encryptedData) { if (empty($this->fofAuth_Key)) { $this->_fofAuth_CryptoKey = null; return null; } $totp = new FOFEncryptTotp($this->fofAuth_timeStep); $period = $totp->getPeriod(); $period--; for ($i = 0; $i <= 2; $i++) { $time = ($period + $i) * $this->fofAuth_timeStep; $otp = $totp->getCode($this->fofAuth_Key, $time); $this->_fofAuth_CryptoKey = hash('sha256', $this->fofAuth_Key . $otp); $aes = new FOFEncryptAes($this->_fofAuth_CryptoKey); $ret = $aes->decryptString($encryptedData); $ret = rtrim($ret, "\000"); $ret = json_decode($ret, true); if (!is_array($ret)) { continue; } if (!array_key_exists('username', $ret)) { continue; } if (!array_key_exists('password', $ret)) { continue; } // Successful decryption! return $ret; } // Obviously if we're here we could not decrypt anything. Bail out. $this->_fofAuth_CryptoKey = null; return null; } /** * Creates a decryption key for use with the TOTP decryption method * * @param integer $time The timestamp used for TOTP calculation, leave empty to use current timestamp * * @codeCoverageIgnore * @return string THe encryption key */ private function _createDecryptionKey($time = null) { $totp = new FOFEncryptTotp($this->fofAuth_timeStep); $otp = $totp->getCode($this->fofAuth_Key, $time); $key = hash('sha256', $this->fofAuth_Key . $otp); return $key; } /** * Main function to detect if we're running in a CLI environment and we're admin * * @return array isCLI and isAdmin. It's not an associative array, so we can use list. */ public static function isCliAdmin() { static $isCLI = null; static $isAdmin = null; if (is_null($isCLI) && is_null($isAdmin)) { $isCLI = FOFPlatform::getInstance()->isCli(); $isAdmin = FOFPlatform::getInstance()->isBackend(); } return array($isCLI, $isAdmin); } } PK Ma!]�+u�� � table/behavior.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * FrameworkOnFramework table behavior class. It defines the events which are * called by a Table. * * @codeCoverageIgnore * @package FrameworkOnFramework * @since 2.1 */ abstract class FOFTableBehavior extends FOFUtilsObservableEvent { /** * This event runs before binding data to the table * * @param FOFTable &$table The table which calls this event * @param array &$data The data to bind * * @return boolean True on success */ public function onBeforeBind(&$table, &$data) { return true; } /** * The event which runs after binding data to the table * * @param FOFTable &$table The table which calls this event * @param object|array &$src The data to bind * * @return boolean True on success */ public function onAfterBind(&$table, &$src) { return true; } /** * The event which runs after loading a record from the database * * @param FOFTable &$table The table which calls this event * @param boolean &$result Did the load succeeded? * * @return void */ public function onAfterLoad(&$table, &$result) { } /** * The event which runs before storing (saving) data to the database * * @param FOFTable &$table The table which calls this event * @param boolean $updateNulls Should nulls be saved as nulls (true) or just skipped over (false)? * * @return boolean True to allow saving */ public function onBeforeStore(&$table, $updateNulls) { return true; } /** * The event which runs after storing (saving) data to the database * * @param FOFTable &$table The table which calls this event * * @return boolean True to allow saving without an error */ public function onAfterStore(&$table) { return true; } /** * The event which runs before moving a record * * @param FOFTable &$table The table which calls this event * @param boolean $updateNulls Should nulls be saved as nulls (true) or just skipped over (false)? * * @return boolean True to allow moving */ public function onBeforeMove(&$table, $updateNulls) { return true; } /** * The event which runs after moving a record * * @param FOFTable &$table The table which calls this event * * @return boolean True to allow moving without an error */ public function onAfterMove(&$table) { return true; } /** * The event which runs before reordering a table * * @param FOFTable &$table The table which calls this event * @param string $where The WHERE clause of the SQL query to run on reordering (record filter) * * @return boolean True to allow reordering */ public function onBeforeReorder(&$table, $where = '') { return true; } /** * The event which runs after reordering a table * * @param FOFTable &$table The table which calls this event * * @return boolean True to allow the reordering to complete without an error */ public function onAfterReorder(&$table) { return true; } /** * The event which runs before deleting a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record to delete * * @return boolean True to allow the deletion */ public function onBeforeDelete(&$table, $oid) { return true; } /** * The event which runs after deleting a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record which was deleted * * @return boolean True to allow the deletion without errors */ public function onAfterDelete(&$table, $oid) { return true; } /** * The event which runs before hitting a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record to hit * @param boolean $log Should we log the hit? * * @return boolean True to allow the hit */ public function onBeforeHit(&$table, $oid, $log) { return true; } /** * The event which runs after hitting a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record which was hit * * @return boolean True to allow the hitting without errors */ public function onAfterHit(&$table, $oid) { return true; } /** * The even which runs before copying a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record being copied * * @return boolean True to allow the copy to take place */ public function onBeforeCopy(&$table, $oid) { return true; } /** * The even which runs after copying a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record which was copied (not the new one) * * @return boolean True to allow the copy without errors */ public function onAfterCopy(&$table, $oid) { return true; } /** * The event which runs before a record is (un)published * * @param FOFTable &$table The table which calls this event * @param integer|array &$cid The PK IDs of the records being (un)published * @param integer $publish 1 to publish, 0 to unpublish * * @return boolean True to allow the (un)publish to proceed */ public function onBeforePublish(&$table, &$cid, $publish) { return true; } /** * The event which runs after the object is reset to its default values. * * @param FOFTable &$table The table which calls this event * * @return boolean True to allow the reset to complete without errors */ public function onAfterReset(&$table) { return true; } /** * The even which runs before the object is reset to its default values. * * @param FOFTable &$table The table which calls this event * * @return boolean True to allow the reset to complete */ public function onBeforeReset(&$table) { return true; } } PK Ma!]����G� G� table/nested.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * A class to manage tables holding nested sets (hierarchical data) * * @property int $lft Left value (for nested set implementation) * @property int $rgt Right value (for nested set implementation) * @property string $hash Slug hash (optional; for faster searching) * @property string $slug Node's slug (optional) * @property string $title Title of the node (optional) */ class FOFTableNested extends FOFTable { /** @var int The level (depth) of this node in the tree */ protected $treeDepth = null; /** @var FOFTableNested The root node in the tree */ protected $treeRoot = null; /** @var FOFTableNested The parent node of ourselves */ protected $treeParent = null; /** @var bool Should I perform a nested get (used to query ascendants/descendants) */ protected $treeNestedGet = false; /** @var array A collection of custom, additional where clauses to apply during buildQuery */ protected $whereClauses = array(); /** * Public constructor. Overrides the parent constructor, making sure there are lft/rgt columns which make it * compatible with nested sets. * * @param string $table Name of the database table to model. * @param string $key Name of the primary key field in the table. * @param FOFDatabaseDriver &$db Database driver * @param array $config The configuration parameters array * * @throws \RuntimeException When lft/rgt columns are not found */ public function __construct($table, $key, &$db, $config = array()) { parent::__construct($table, $key, $db, $config); if (!$this->hasField('lft') || !$this->hasField('rgt')) { throw new \RuntimeException("Table " . $this->getTableName() . " is not compatible with FOFTableNested: it does not have lft/rgt columns"); } } /** * Overrides the automated table checks to handle the 'hash' column for faster searching * * @return boolean */ public function check() { // Create a slug if there is a title and an empty slug if ($this->hasField('title') && $this->hasField('slug') && empty($this->slug)) { $this->slug = FOFStringUtils::toSlug($this->title); } // Create the SHA-1 hash of the slug for faster searching (make sure the hash column is CHAR(64) to take // advantage of MySQL's optimised searching for fixed size CHAR columns) if ($this->hasField('hash') && $this->hasField('slug')) { $this->hash = sha1($this->slug); } // Reset cached values $this->resetTreeCache(); return parent::check(); } /** * Delete a node, either the currently loaded one or the one specified in $id. If an $id is specified that node * is loaded before trying to delete it. In the end the data model is reset. If the node has any children nodes * they will be removed before the node itself is deleted. * * @param integer $oid The primary key value of the item to delete * * @throws UnexpectedValueException * * @return boolean True on success */ public function delete($oid = null) { // Load the specified record (if necessary) if (!empty($oid)) { $this->load($oid); } $k = $this->_tbl_key; $pk = (!$oid) ? $this->$k : $oid; // If no primary key is given, return false. if (!$pk) { throw new UnexpectedValueException('Null primary key not allowed.'); } // Execute the logic only if I have a primary key, otherwise I could have weird results // Perform the checks on the current node *BEFORE* starting to delete the children if (!$this->onBeforeDelete($oid)) { return false; } $result = true; // Recursively delete all children nodes as long as we are not a leaf node and $recursive is enabled if (!$this->isLeaf()) { // Get all sub-nodes $table = $this->getClone(); $table->bind($this->getData()); $subNodes = $table->getDescendants(); // Delete all subnodes (goes through the model to trigger the observers) if (!empty($subNodes)) { /** @var FOFTableNested $item */ foreach ($subNodes as $item) { // We have to pass the id, so we are getting it again from the database. // We have to do in this way, since a previous child could have changed our lft and rgt values if(!$item->delete($item->$k)) { // A subnode failed or prevents the delete, continue deleting other nodes, // but preserve the current node (ie the parent) $result = false; } }; // Load it again, since while deleting a children we could have updated ourselves, too $this->load($pk); } } if($result) { // Delete the row by primary key. $query = $this->_db->getQuery(true); $query->delete(); $query->from($this->_tbl); $query->where($this->_tbl_key . ' = ' . $this->_db->q($pk)); $this->_db->setQuery($query)->execute(); $result = $this->onAfterDelete($oid); } return $result; } protected function onAfterDelete($oid) { $db = $this->getDbo(); $myLeft = $this->lft; $myRight = $this->rgt; $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); // Move all siblings to the left $width = $this->rgt - $this->lft + 1; // Wrap everything in a transaction $db->transactionStart(); try { // Shrink lft values $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldLft . ' = ' . $fldLft . ' - '.$width) ->where($fldLft . ' > ' . $db->q($myLeft)); $db->setQuery($query)->execute(); // Shrink rgt values $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldRgt . ' = ' . $fldRgt . ' - '.$width) ->where($fldRgt . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { // Roll back the transaction on error $db->transactionRollback(); throw $e; } return parent::onAfterDelete($oid); } /** * Not supported in nested sets * * @param string $where Ignored * * @return void * * @throws RuntimeException */ public function reorder($where = '') { throw new RuntimeException('reorder() is not supported by FOFTableNested'); } /** * Not supported in nested sets * * @param integer $delta Ignored * @param string $where Ignored * * @return void * * @throws RuntimeException */ public function move($delta, $where = '') { throw new RuntimeException('move() is not supported by FOFTableNested'); } /** * Create a new record with the provided data. It is inserted as the last child of the current node's parent * * @param array $data The data to use in the new record * * @return static The new node */ public function create($data) { $newNode = $this->getClone(); $newNode->reset(); $newNode->bind($data); if ($this->isRoot()) { return $newNode->insertAsChildOf($this); } else { return $newNode->insertAsChildOf($this->getParent()); } } /** * Makes a copy of the record, inserting it as the last child of the given node's parent. * * @param integer|array $cid The primary key value (or values) or the record(s) to copy. * If null, the current record will be copied * * @return self|FOFTableNested The last copied node */ public function copy($cid = null) { //We have to cast the id as array, or the helper function will return an empty set if($cid) { $cid = (array) $cid; } FOFUtilsArray::toInteger($cid); $k = $this->_tbl_key; if (count($cid) < 1) { if ($this->$k) { $cid = array($this->$k); } else { // Even if it's null, let's still create the record $this->create($this->getData()); return $this; } } foreach ($cid as $item) { // Prevent load with id = 0 if (!$item) { continue; } $this->load($item); $this->create($this->getData()); } return $this; } /** * Method to reset class properties to the defaults set in the class * definition. It will ignore the primary key as well as any private class * properties. * * @return void */ public function reset() { $this->resetTreeCache(); parent::reset(); } /** * Insert the current node as a tree root. It is a good idea to never use this method, instead providing a root node * in your schema installation and then sticking to only one root. * * @return self */ public function insertAsRoot() { // You can't insert a node that is already saved i.e. the table has an id if($this->getId()) { throw new RuntimeException(__METHOD__.' can be only used with new nodes'); } // First we need to find the right value of the last parent, a.k.a. the max(rgt) of the table $db = $this->getDbo(); // Get the lft/rgt names $fldRgt = $db->qn($this->getColumnAlias('rgt')); $query = $db->getQuery(true) ->select('MAX(' . $fldRgt . ')') ->from($db->qn($this->getTableName())); $maxRgt = $db->setQuery($query, 0, 1)->loadResult(); if (empty($maxRgt)) { $maxRgt = 0; } $this->lft = ++$maxRgt; $this->rgt = ++$maxRgt; $this->store(); return $this; } /** * Insert the current node as the first (leftmost) child of a parent node. * * WARNING: If it's an existing node it will be COPIED, not moved. * * @param FOFTableNested $parentNode The node which will become our parent * * @return $this for chaining * * @throws Exception * @throws RuntimeException */ public function insertAsFirstChildOf(FOFTableNested &$parentNode) { if($parentNode->lft >= $parentNode->rgt) { throw new RuntimeException('Invalid position values for the parent node'); } // Get a reference to the database $db = $this->getDbo(); // Get the field names $fldRgt = $db->qn($this->getColumnAlias('rgt')); $fldLft = $db->qn($this->getColumnAlias('lft')); // Nullify the PK, so a new record will be created $pk = $this->getKeyName(); $this->$pk = null; // Get the value of the parent node's rgt $myLeft = $parentNode->lft; // Update my lft/rgt values $this->lft = $myLeft + 1; $this->rgt = $myLeft + 2; // Update parent node's right (we added two elements in there, remember?) $parentNode->rgt += 2; // Wrap everything in a transaction $db->transactionStart(); try { // Make a hole (2 queries) $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldLft . ' = ' . $fldLft . '+2') ->where($fldLft . ' > ' . $db->q($myLeft)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldRgt . ' = ' . $fldRgt . '+ 2') ->where($fldRgt . '>' . $db->q($myLeft)); $db->setQuery($query)->execute(); // Insert the new node $this->store(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { // Roll back the transaction on error $db->transactionRollback(); throw $e; } return $this; } /** * Insert the current node as the last (rightmost) child of a parent node. * * WARNING: If it's an existing node it will be COPIED, not moved. * * @param FOFTableNested $parentNode The node which will become our parent * * @return $this for chaining * * @throws Exception * @throws RuntimeException */ public function insertAsLastChildOf(FOFTableNested &$parentNode) { if($parentNode->lft >= $parentNode->rgt) { throw new RuntimeException('Invalid position values for the parent node'); } // Get a reference to the database $db = $this->getDbo(); // Get the field names $fldRgt = $db->qn($this->getColumnAlias('rgt')); $fldLft = $db->qn($this->getColumnAlias('lft')); // Nullify the PK, so a new record will be created $pk = $this->getKeyName(); $this->$pk = null; // Get the value of the parent node's lft $myRight = $parentNode->rgt; // Update my lft/rgt values $this->lft = $myRight; $this->rgt = $myRight + 1; // Update parent node's right (we added two elements in there, remember?) $parentNode->rgt += 2; // Wrap everything in a transaction $db->transactionStart(); try { // Make a hole (2 queries) $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldRgt . ' = ' . $fldRgt . '+2') ->where($fldRgt . '>=' . $db->q($myRight)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldLft . ' = ' . $fldLft . '+2') ->where($fldLft . '>' . $db->q($myRight)); $db->setQuery($query)->execute(); // Insert the new node $this->store(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { // Roll back the transaction on error $db->transactionRollback(); throw $e; } return $this; } /** * Alias for insertAsLastchildOf * * @codeCoverageIgnore * @param FOFTableNested $parentNode * * @return $this for chaining * * @throws Exception */ public function insertAsChildOf(FOFTableNested &$parentNode) { return $this->insertAsLastChildOf($parentNode); } /** * Insert the current node to the left of (before) a sibling node * * WARNING: If it's an existing node it will be COPIED, not moved. * * @param FOFTableNested $siblingNode We will be inserted before this node * * @return $this for chaining * * @throws Exception * @throws RuntimeException */ public function insertLeftOf(FOFTableNested &$siblingNode) { if($siblingNode->lft >= $siblingNode->rgt) { throw new RuntimeException('Invalid position values for the sibling node'); } // Get a reference to the database $db = $this->getDbo(); // Get the field names $fldRgt = $db->qn($this->getColumnAlias('rgt')); $fldLft = $db->qn($this->getColumnAlias('lft')); // Nullify the PK, so a new record will be created $pk = $this->getKeyName(); $this->$pk = null; // Get the value of the parent node's rgt $myLeft = $siblingNode->lft; // Update my lft/rgt values $this->lft = $myLeft; $this->rgt = $myLeft + 1; // Update sibling's lft/rgt values $siblingNode->lft += 2; $siblingNode->rgt += 2; $db->transactionStart(); try { $db->setQuery( $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldLft . ' = ' . $fldLft . '+2') ->where($fldLft . ' >= ' . $db->q($myLeft)) )->execute(); $db->setQuery( $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldRgt . ' = ' . $fldRgt . '+2') ->where($fldRgt . ' > ' . $db->q($myLeft)) )->execute(); $this->store(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { $db->transactionRollback(); throw $e; } return $this; } /** * Insert the current node to the right of (after) a sibling node * * WARNING: If it's an existing node it will be COPIED, not moved. * * @param FOFTableNested $siblingNode We will be inserted after this node * * @return $this for chaining * @throws Exception * @throws RuntimeException */ public function insertRightOf(FOFTableNested &$siblingNode) { if($siblingNode->lft >= $siblingNode->rgt) { throw new RuntimeException('Invalid position values for the sibling node'); } // Get a reference to the database $db = $this->getDbo(); // Get the field names $fldRgt = $db->qn($this->getColumnAlias('rgt')); $fldLft = $db->qn($this->getColumnAlias('lft')); // Nullify the PK, so a new record will be created $pk = $this->getKeyName(); $this->$pk = null; // Get the value of the parent node's lft $myRight = $siblingNode->rgt; // Update my lft/rgt values $this->lft = $myRight + 1; $this->rgt = $myRight + 2; $db->transactionStart(); try { $db->setQuery( $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldRgt . ' = ' . $fldRgt . '+2') ->where($fldRgt . ' > ' . $db->q($myRight)) )->execute(); $db->setQuery( $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($fldLft . ' = ' . $fldLft . '+2') ->where($fldLft . ' > ' . $db->q($myRight)) )->execute(); $this->store(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { $db->transactionRollback(); throw $e; } return $this; } /** * Alias for insertRightOf * * @codeCoverageIgnore * @param FOFTableNested $siblingNode * * @return $this for chaining */ public function insertAsSiblingOf(FOFTableNested &$siblingNode) { return $this->insertRightOf($siblingNode); } /** * Move the current node (and its subtree) one position to the left in the tree, i.e. before its left-hand sibling * * @throws RuntimeException * * @return $this */ public function moveLeft() { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } // If it is a root node we will not move the node (roots don't participate in tree ordering) if ($this->isRoot()) { return $this; } // Are we already the leftmost node? $parentNode = $this->getParent(); if ($parentNode->lft == ($this->lft - 1)) { return $this; } // Get the sibling to the left $db = $this->getDbo(); $table = $this->getClone(); $table->reset(); $leftSibling = $table->whereRaw($db->qn($this->getColumnAlias('rgt')) . ' = ' . $db->q($this->lft - 1)) ->get(0, 1)->current(); // Move the node if (is_object($leftSibling) && ($leftSibling instanceof FOFTableNested)) { return $this->moveToLeftOf($leftSibling); } return false; } /** * Move the current node (and its subtree) one position to the right in the tree, i.e. after its right-hand sibling * * @throws RuntimeException * * @return $this */ public function moveRight() { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } // If it is a root node we will not move the node (roots don't participate in tree ordering) if ($this->isRoot()) { return $this; } // Are we already the rightmost node? $parentNode = $this->getParent(); if ($parentNode->rgt == ($this->rgt + 1)) { return $this; } // Get the sibling to the right $db = $this->getDbo(); $table = $this->getClone(); $table->reset(); $rightSibling = $table->whereRaw($db->qn($this->getColumnAlias('lft')) . ' = ' . $db->q($this->rgt + 1)) ->get(0, 1)->current(); // Move the node if (is_object($rightSibling) && ($rightSibling instanceof FOFTableNested)) { return $this->moveToRightOf($rightSibling); } return false; } /** * Moves the current node (and its subtree) to the left of another node. The other node can be in a different * position in the tree or even under a different root. * * @param FOFTableNested $siblingNode * * @return $this for chaining * * @throws Exception * @throws RuntimeException */ public function moveToLeftOf(FOFTableNested $siblingNode) { // Sanity checks on current and sibling node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if($siblingNode->lft >= $siblingNode->rgt) { throw new RuntimeException('Invalid position values for the sibling node'); } $db = $this->getDbo(); $left = $db->qn($this->getColumnAlias('lft')); $right = $db->qn($this->getColumnAlias('rgt')); // Get node metrics $myLeft = $this->lft; $myRight = $this->rgt; $myWidth = $myRight - $myLeft + 1; // Get sibling metrics $sibLeft = $siblingNode->lft; // Start the transaction $db->transactionStart(); try { // Temporary remove subtree being moved $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set("$left = " . $db->q(0) . " - $left") ->set("$right = " . $db->q(0) . " - $right") ->where($left . ' >= ' . $db->q($myLeft)) ->where($right . ' <= ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Close hole left behind $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth)) ->where($left . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth)) ->where($right . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Make a hole for the new items $newSibLeft = ($sibLeft > $myRight) ? $sibLeft - $myWidth : $sibLeft; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth)) ->where($right . ' >= ' . $db->q($newSibLeft)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth)) ->where($left . ' >= ' . $db->q($newSibLeft)); $db->setQuery($query)->execute(); // Move node and subnodes $moveRight = $newSibLeft - $myLeft; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight)) ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight)) ->where($left . ' <= 0 - ' . $db->q($myLeft)) ->where($right . ' >= 0 - ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { $db->transactionRollback(); throw $e; } // Let's load the record again to fetch the new values for lft and rgt $this->load(); return $this; } /** * Moves the current node (and its subtree) to the right of another node. The other node can be in a different * position in the tree or even under a different root. * * @param FOFTableNested $siblingNode * * @return $this for chaining * * @throws Exception * @throws RuntimeException */ public function moveToRightOf(FOFTableNested $siblingNode) { // Sanity checks on current and sibling node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if($siblingNode->lft >= $siblingNode->rgt) { throw new RuntimeException('Invalid position values for the sibling node'); } $db = $this->getDbo(); $left = $db->qn($this->getColumnAlias('lft')); $right = $db->qn($this->getColumnAlias('rgt')); // Get node metrics $myLeft = $this->lft; $myRight = $this->rgt; $myWidth = $myRight - $myLeft + 1; // Get parent metrics $sibRight = $siblingNode->rgt; // Start the transaction $db->transactionStart(); try { // Temporary remove subtree being moved $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set("$left = " . $db->q(0) . " - $left") ->set("$right = " . $db->q(0) . " - $right") ->where($left . ' >= ' . $db->q($myLeft)) ->where($right . ' <= ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Close hole left behind $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth)) ->where($left . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth)) ->where($right . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Make a hole for the new items $newSibRight = ($sibRight > $myRight) ? $sibRight - $myWidth : $sibRight; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth)) ->where($left . ' > ' . $db->q($newSibRight)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth)) ->where($right . ' > ' . $db->q($newSibRight)); $db->setQuery($query)->execute(); // Move node and subnodes $moveRight = ($sibRight > $myRight) ? $sibRight - $myRight : $sibRight - $myRight + $myWidth; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight)) ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight)) ->where($left . ' <= 0 - ' . $db->q($myLeft)) ->where($right . ' >= 0 - ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { $db->transactionRollback(); throw $e; } // Let's load the record again to fetch the new values for lft and rgt $this->load(); return $this; } /** * Alias for moveToRightOf * * @codeCoverageIgnore * @param FOFTableNested $siblingNode * * @return $this for chaining */ public function makeNextSiblingOf(FOFTableNested $siblingNode) { return $this->moveToRightOf($siblingNode); } /** * Alias for makeNextSiblingOf * * @codeCoverageIgnore * @param FOFTableNested $siblingNode * * @return $this for chaining */ public function makeSiblingOf(FOFTableNested $siblingNode) { return $this->makeNextSiblingOf($siblingNode); } /** * Alias for moveToLeftOf * * @codeCoverageIgnore * @param FOFTableNested $siblingNode * * @return $this for chaining */ public function makePreviousSiblingOf(FOFTableNested $siblingNode) { return $this->moveToLeftOf($siblingNode); } /** * Moves a node and its subtree as a the first (leftmost) child of $parentNode * * @param FOFTableNested $parentNode * * @return $this for chaining * * @throws Exception */ public function makeFirstChildOf(FOFTableNested $parentNode) { // Sanity checks on current and sibling node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if($parentNode->lft >= $parentNode->rgt) { throw new RuntimeException('Invalid position values for the parent node'); } $db = $this->getDbo(); $left = $db->qn($this->getColumnAlias('lft')); $right = $db->qn($this->getColumnAlias('rgt')); // Get node metrics $myLeft = $this->lft; $myRight = $this->rgt; $myWidth = $myRight - $myLeft + 1; // Get parent metrics $parentLeft = $parentNode->lft; // Start the transaction $db->transactionStart(); try { // Temporary remove subtree being moved $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set("$left = " . $db->q(0) . " - $left") ->set("$right = " . $db->q(0) . " - $right") ->where($left . ' >= ' . $db->q($myLeft)) ->where($right . ' <= ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Close hole left behind $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth)) ->where($left . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth)) ->where($right . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Make a hole for the new items $newParentLeft = ($parentLeft > $myRight) ? $parentLeft - $myWidth : $parentLeft; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth)) ->where($right . ' >= ' . $db->q($newParentLeft)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth)) ->where($left . ' > ' . $db->q($newParentLeft)); $db->setQuery($query)->execute(); // Move node and subnodes $moveRight = $newParentLeft - $myLeft + 1; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight)) ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight)) ->where($left . ' <= 0 - ' . $db->q($myLeft)) ->where($right . ' >= 0 - ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { $db->transactionRollback(); throw $e; } // Let's load the record again to fetch the new values for lft and rgt $this->load(); return $this; } /** * Moves a node and its subtree as a the last (rightmost) child of $parentNode * * @param FOFTableNested $parentNode * * @return $this for chaining * * @throws Exception * @throws RuntimeException */ public function makeLastChildOf(FOFTableNested $parentNode) { // Sanity checks on current and sibling node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if($parentNode->lft >= $parentNode->rgt) { throw new RuntimeException('Invalid position values for the parent node'); } $db = $this->getDbo(); $left = $db->qn($this->getColumnAlias('lft')); $right = $db->qn($this->getColumnAlias('rgt')); // Get node metrics $myLeft = $this->lft; $myRight = $this->rgt; $myWidth = $myRight - $myLeft + 1; // Get parent metrics $parentRight = $parentNode->rgt; // Start the transaction $db->transactionStart(); try { // Temporary remove subtree being moved $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set("$left = " . $db->q(0) . " - $left") ->set("$right = " . $db->q(0) . " - $right") ->where($left . ' >= ' . $db->q($myLeft)) ->where($right . ' <= ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Close hole left behind $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' - ' . $db->q($myWidth)) ->where($left . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' - ' . $db->q($myWidth)) ->where($right . ' > ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Make a hole for the new items $newLeft = ($parentRight > $myRight) ? $parentRight - $myWidth : $parentRight; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $left . ' + ' . $db->q($myWidth)) ->where($left . ' >= ' . $db->q($newLeft)); $db->setQuery($query)->execute(); $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($right . ' = ' . $right . ' + ' . $db->q($myWidth)) ->where($right . ' >= ' . $db->q($newLeft)); $db->setQuery($query)->execute(); // Move node and subnodes $moveRight = ($parentRight > $myRight) ? $parentRight - $myRight - 1 : $parentRight - $myRight - 1 + $myWidth; $query = $db->getQuery(true) ->update($db->qn($this->getTableName())) ->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight)) ->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight)) ->where($left . ' <= 0 - ' . $db->q($myLeft)) ->where($right . ' >= 0 - ' . $db->q($myRight)); $db->setQuery($query)->execute(); // Commit the transaction $db->transactionCommit(); } catch (\Exception $e) { $db->transactionRollback(); throw $e; } // Let's load the record again to fetch the new values for lft and rgt $this->load(); return $this; } /** * Alias for makeLastChildOf * * @codeCoverageIgnore * @param FOFTableNested $parentNode * * @return $this for chaining */ public function makeChildOf(FOFTableNested $parentNode) { return $this->makeLastChildOf($parentNode); } /** * Makes the current node a root (and moving its entire subtree along the way). This is achieved by moving the node * to the right of its root node * * @return $this for chaining */ public function makeRoot() { // Make sure we are not a root if ($this->isRoot()) { return $this; } // Get a reference to my root $myRoot = $this->getRoot(); // Double check I am not a root if ($this->equals($myRoot)) { return $this; } // Move myself to the right of my root $this->moveToRightOf($myRoot); $this->treeDepth = 0; return $this; } /** * Gets the level (depth) of this node in the tree. The result is cached in $this->treeDepth for faster retrieval. * * @throws RuntimeException * * @return int|mixed */ public function getLevel() { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if (is_null($this->treeDepth)) { $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $query = $db->getQuery(true) ->select('(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - 1) AS ' . $db->qn('depth')) ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node')) ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent')) ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft) ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt) ->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft)) ->group($db->qn('node') . '.' . $fldLft) ->order($db->qn('node') . '.' . $fldLft . ' ASC'); $this->treeDepth = $db->setQuery($query, 0, 1)->loadResult(); } return $this->treeDepth; } /** * Returns the immediate parent of the current node * * @throws RuntimeException * * @return FOFTableNested */ public function getParent() { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if ($this->isRoot()) { return $this; } if (empty($this->treeParent) || !is_object($this->treeParent) || !($this->treeParent instanceof FOFTableNested)) { $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $query = $db->getQuery(true) ->select($db->qn('parent') . '.' . $fldLft) ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node')) ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent')) ->where($db->qn('node') . '.' . $fldLft . ' > ' . $db->qn('parent') . '.' . $fldLft) ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt) ->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft)) ->order($db->qn('parent') . '.' . $fldLft . ' DESC'); $targetLft = $db->setQuery($query, 0, 1)->loadResult(); $table = $this->getClone(); $table->reset(); $this->treeParent = $table ->whereRaw($fldLft . ' = ' . $db->q($targetLft)) ->get()->current(); } return $this->treeParent; } /** * Is this a top-level root node? * * @return bool */ public function isRoot() { // If lft=1 it is necessarily a root node if ($this->lft == 1) { return true; } // Otherwise make sure its level is 0 return $this->getLevel() == 0; } /** * Is this a leaf node (a node without children)? * * @throws RuntimeException * * @return bool */ public function isLeaf() { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } return ($this->rgt - 1) == $this->lft; } /** * Is this a child node (not root)? * * @codeCoverageIgnore * * @return bool */ public function isChild() { return !$this->isRoot(); } /** * Returns true if we are a descendant of $otherNode * * @param FOFTableNested $otherNode * * @throws RuntimeException * * @return bool */ public function isDescendantOf(FOFTableNested $otherNode) { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if($otherNode->lft >= $otherNode->rgt) { throw new RuntimeException('Invalid position values for the other node'); } return ($otherNode->lft < $this->lft) && ($otherNode->rgt > $this->rgt); } /** * Returns true if $otherNode is ourselves or if we are a descendant of $otherNode * * @param FOFTableNested $otherNode * * @throws RuntimeException * * @return bool */ public function isSelfOrDescendantOf(FOFTableNested $otherNode) { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if($otherNode->lft >= $otherNode->rgt) { throw new RuntimeException('Invalid position values for the other node'); } return ($otherNode->lft <= $this->lft) && ($otherNode->rgt >= $this->rgt); } /** * Returns true if we are an ancestor of $otherNode * * @codeCoverageIgnore * @param FOFTableNested $otherNode * * @return bool */ public function isAncestorOf(FOFTableNested $otherNode) { return $otherNode->isDescendantOf($this); } /** * Returns true if $otherNode is ourselves or we are an ancestor of $otherNode * * @codeCoverageIgnore * @param FOFTableNested $otherNode * * @return bool */ public function isSelfOrAncestorOf(FOFTableNested $otherNode) { return $otherNode->isSelfOrDescendantOf($this); } /** * Is $node this very node? * * @param FOFTableNested $node * * @throws RuntimeException * * @return bool */ public function equals(FOFTableNested &$node) { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } if($node->lft >= $node->rgt) { throw new RuntimeException('Invalid position values for the other node'); } return ( ($this->getId() == $node->getId()) && ($this->lft == $node->lft) && ($this->rgt == $node->rgt) ); } /** * Alias for isDescendantOf * * @codeCoverageIgnore * @param FOFTableNested $otherNode * * @return bool */ public function insideSubtree(FOFTableNested $otherNode) { return $this->isDescendantOf($otherNode); } /** * Returns true if both this node and $otherNode are root, leaf or child (same tree scope) * * @param FOFTableNested $otherNode * * @return bool */ public function inSameScope(FOFTableNested $otherNode) { if ($this->isLeaf()) { return $otherNode->isLeaf(); } elseif ($this->isRoot()) { return $otherNode->isRoot(); } elseif ($this->isChild()) { return $otherNode->isChild(); } else { return false; } } /** * get() will return all ancestor nodes and ourselves * * @return void */ protected function scopeAncestorsAndSelf() { $this->treeNestedGet = true; $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' >= ' . $db->qn('node') . '.' . $fldLft); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' <= ' . $db->qn('node') . '.' . $fldRgt); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft)); } /** * get() will return all ancestor nodes but not ourselves * * @return void */ protected function scopeAncestors() { $this->treeNestedGet = true; $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' > ' . $db->qn('node') . '.' . $fldLft); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' < ' . $db->qn('node') . '.' . $fldRgt); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft)); } /** * get() will return all sibling nodes and ourselves * * @return void */ protected function scopeSiblingsAndSelf() { $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $parent = $this->getParent(); $this->whereRaw($db->qn('node') . '.' . $fldLft . ' > ' . $db->q($parent->lft)); $this->whereRaw($db->qn('node') . '.' . $fldRgt . ' < ' . $db->q($parent->rgt)); } /** * get() will return all sibling nodes but not ourselves * * @codeCoverageIgnore * * @return void */ protected function scopeSiblings() { $this->scopeSiblingsAndSelf(); $this->scopeWithoutSelf(); } /** * get() will return only leaf nodes * * @return void */ protected function scopeLeaves() { $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $this->whereRaw($db->qn('node') . '.' . $fldLft . ' = ' . $db->qn('node') . '.' . $fldRgt . ' - ' . $db->q(1)); } /** * get() will return all descendants (even subtrees of subtrees!) and ourselves * * @return void */ protected function scopeDescendantsAndSelf() { $this->treeNestedGet = true; $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $this->whereRaw($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft); $this->whereRaw($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft)); } /** * get() will return all descendants (even subtrees of subtrees!) but not ourselves * * @return void */ protected function scopeDescendants() { $this->treeNestedGet = true; $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $this->whereRaw($db->qn('node') . '.' . $fldLft . ' > ' . $db->qn('parent') . '.' . $fldLft); $this->whereRaw($db->qn('node') . '.' . $fldLft . ' < ' . $db->qn('parent') . '.' . $fldRgt); $this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft)); } /** * get() will only return immediate descendants (first level children) of the current node * * @return void */ protected function scopeImmediateDescendants() { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $subQuery = $db->getQuery(true) ->select(array( $db->qn('node') . '.' . $fldLft, '(COUNT(*) - 1) AS ' . $db->qn('depth') )) ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node')) ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('parent')) ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft) ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt) ->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft)) ->group($db->qn('node') . '.' . $fldLft) ->order($db->qn('node') . '.' . $fldLft . ' ASC'); $query = $db->getQuery(true) ->select(array( $db->qn('node') . '.' . $fldLft, '(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - (' . $db->qn('sub_tree') . '.' . $db->qn('depth') . ' + 1)) AS ' . $db->qn('depth') )) ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node')) ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent')) ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('sub_parent')) ->join('CROSS', '(' . $subQuery . ') AS ' . $db->qn('sub_tree')) ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft) ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt) ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('sub_parent') . '.' . $fldLft) ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('sub_parent') . '.' . $fldRgt) ->where($db->qn('sub_parent') . '.' . $fldLft . ' = ' . $db->qn('sub_tree') . '.' . $fldLft) ->group($db->qn('node') . '.' . $fldLft) ->having(array( $db->qn('depth') . ' > ' . $db->q(0), $db->qn('depth') . ' <= ' . $db->q(1), )) ->order($db->qn('node') . '.' . $fldLft . ' ASC'); $leftValues = $db->setQuery($query)->loadColumn(); if (empty($leftValues)) { $leftValues = array(0); } array_walk($leftValues, function (&$item, $key) use (&$db) { $item = $db->q($item); }); $this->whereRaw($db->qn('node') . '.' . $fldLft . ' IN (' . implode(',', $leftValues) . ')'); } /** * get() will not return the selected node if it's part of the query results * * @param FOFTableNested $node The node to exclude from the results * * @return void */ public function withoutNode(FOFTableNested $node) { $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $this->whereRaw('NOT(' . $db->qn('node') . '.' . $fldLft . ' = ' . $db->q($node->lft) . ')'); } /** * get() will not return ourselves if it's part of the query results * * @codeCoverageIgnore * * @return void */ protected function scopeWithoutSelf() { $this->withoutNode($this); } /** * get() will not return our root if it's part of the query results * * @codeCoverageIgnore * * @return void */ protected function scopeWithoutRoot() { $rootNode = $this->getRoot(); $this->withoutNode($rootNode); } /** * Returns the root node of the tree this node belongs to * * @return self * * @throws \RuntimeException */ public function getRoot() { // Sanity checks on current node position if($this->lft >= $this->rgt) { throw new RuntimeException('Invalid position values for the current node'); } // If this is a root node return itself (there is no such thing as the root of a root node) if ($this->isRoot()) { return $this; } if (empty($this->treeRoot) || !is_object($this->treeRoot) || !($this->treeRoot instanceof FOFTableNested)) { $this->treeRoot = null; // First try to get the record with the minimum ID $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); $subQuery = $db->getQuery(true) ->select('MIN(' . $fldLft . ')') ->from($db->qn($this->getTableName())); try { $table = $this->getClone(); $table->reset(); $root = $table ->whereRaw($fldLft . ' = (' . (string)$subQuery . ')') ->get(0, 1)->current(); if ($this->isDescendantOf($root)) { $this->treeRoot = $root; } } catch (\RuntimeException $e) { // If there is no root found throw an exception. Basically: your table is FUBAR. throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft); } // If the above method didn't work, get all roots and select the one with the appropriate lft/rgt values if (is_null($this->treeRoot)) { // Find the node with depth = 0, lft < our lft and rgt > our right. That's our root node. $query = $db->getQuery(true) ->select(array( $db->qn('node') . '.' . $fldLft, '(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - 1) AS ' . $db->qn('depth') )) ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node')) ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent')) ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft) ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt) ->where($db->qn('node') . '.' . $fldLft . ' < ' . $db->q($this->lft)) ->where($db->qn('node') . '.' . $fldRgt . ' > ' . $db->q($this->rgt)) ->having($db->qn('depth') . ' = ' . $db->q(0)) ->group($db->qn('node') . '.' . $fldLft); // Get the lft value $targetLeft = $db->setQuery($query)->loadResult(); if (empty($targetLeft)) { // If there is no root found throw an exception. Basically: your table is FUBAR. throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft); } try { $table = $this->getClone(); $table->reset(); $this->treeRoot = $table ->whereRaw($fldLft . ' = ' . $db->q($targetLeft)) ->get(0, 1)->current(); } catch (\RuntimeException $e) { // If there is no root found throw an exception. Basically: your table is FUBAR. throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft); } } } return $this->treeRoot; } /** * Get all ancestors to this node and the node itself. In other words it gets the full path to the node and the node * itself. * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getAncestorsAndSelf() { $this->scopeAncestorsAndSelf(); return $this->get(); } /** * Get all ancestors to this node and the node itself, but not the root node. If you want to * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getAncestorsAndSelfWithoutRoot() { $this->scopeAncestorsAndSelf(); $this->scopeWithoutRoot(); return $this->get(); } /** * Get all ancestors to this node but not the node itself. In other words it gets the path to the node, without the * node itself. * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getAncestors() { $this->scopeAncestorsAndSelf(); $this->scopeWithoutSelf(); return $this->get(); } /** * Get all ancestors to this node but not the node itself and its root. * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getAncestorsWithoutRoot() { $this->scopeAncestors(); $this->scopeWithoutRoot(); return $this->get(); } /** * Get all sibling nodes, including ourselves * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getSiblingsAndSelf() { $this->scopeSiblingsAndSelf(); return $this->get(); } /** * Get all sibling nodes, except ourselves * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getSiblings() { $this->scopeSiblings(); return $this->get(); } /** * Get all leaf nodes in the tree. You may want to use the scopes to narrow down the search in a specific subtree or * path. * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getLeaves() { $this->scopeLeaves(); return $this->get(); } /** * Get all descendant (children) nodes and ourselves. * * Note: all descendant nodes, even descendants of our immediate descendants, will be returned. * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getDescendantsAndSelf() { $this->scopeDescendantsAndSelf(); return $this->get(); } /** * Get only our descendant (children) nodes, not ourselves. * * Note: all descendant nodes, even descendants of our immediate descendants, will be returned. * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getDescendants() { $this->scopeDescendants(); return $this->get(); } /** * Get the immediate descendants (children). Unlike getDescendants it only goes one level deep into the tree * structure. Descendants of descendant nodes will not be returned. * * @codeCoverageIgnore * * @return FOFDatabaseIterator */ public function getImmediateDescendants() { $this->scopeImmediateDescendants(); return $this->get(); } /** * Returns a hashed array where each element's key is the value of the $key column (default: the ID column of the * table) and its value is the value of the $column column (default: title). Each nesting level will have the value * of the $column column prefixed by a number of $separator strings, as many as its nesting level (depth). * * This is useful for creating HTML select elements showing the hierarchy in a human readable format. * * @param string $column * @param null $key * @param string $seperator * * @return array */ public function getNestedList($column = 'title', $key = null, $seperator = ' ') { $db = $this->getDbo(); $fldLft = $db->qn($this->getColumnAlias('lft')); $fldRgt = $db->qn($this->getColumnAlias('rgt')); if (empty($key) || !$this->hasField($key)) { $key = $this->getKeyName(); } if (empty($column)) { $column = 'title'; } $fldKey = $db->qn($this->getColumnAlias($key)); $fldColumn = $db->qn($this->getColumnAlias($column)); $query = $db->getQuery(true) ->select(array( $db->qn('node') . '.' . $fldKey, $db->qn('node') . '.' . $fldColumn, '(COUNT(' . $db->qn('parent') . '.' . $fldKey . ') - 1) AS ' . $db->qn('depth') )) ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node')) ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent')) ->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft) ->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt) ->group($db->qn('node') . '.' . $fldLft) ->order($db->qn('node') . '.' . $fldLft . ' ASC'); $tempResults = $db->setQuery($query)->loadAssocList(); $ret = array(); if (!empty($tempResults)) { foreach ($tempResults as $row) { $ret[$row[$key]] = str_repeat($seperator, $row['depth']) . $row[$column]; } } return $ret; } /** * Locate a node from a given path, e.g. "/some/other/leaf" * * Notes: * - This will only work when you have a "slug" and a "hash" field in your table. * - If the path starts with "/" we will use the root with lft=1. Otherwise the first component of the path is * supposed to be the slug of the root node. * - If the root node is not found you'll get null as the return value * - You will also get null if any component of the path is not found * * @param string $path The path to locate * * @return FOFTableNested|null The found node or null if nothing is found */ public function findByPath($path) { // @todo } public function isValid() { // @todo } public function rebuild() { // @todo } /** * Resets cached values used to speed up querying the tree * * @return static for chaining */ protected function resetTreeCache() { $this->treeDepth = null; $this->treeRoot = null; $this->treeParent = null; $this->treeNestedGet = false; return $this; } /** * Add custom, pre-compiled WHERE clauses for use in buildQuery. The raw WHERE clause you specify is added as is to * the query generated by buildQuery. You are responsible for quoting and escaping the field names and data found * inside the WHERE clause. * * @param string $rawWhereClause The raw WHERE clause to add * * @return $this For chaining */ public function whereRaw($rawWhereClause) { $this->whereClauses[] = $rawWhereClause; return $this; } /** * Builds the query for the get() method * * @return FOFDatabaseQuery */ protected function buildQuery() { $db = $this->getDbo(); $query = $db->getQuery(true) ->select($db->qn('node') . '.*') ->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node')); if ($this->treeNestedGet) { $query ->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent')); } // Apply custom WHERE clauses if (count($this->whereClauses)) { foreach ($this->whereClauses as $clause) { $query->where($clause); } } return $query; } /** * Returns a database iterator to retrieve records. Use the scope methods and the whereRaw method to define what * exactly will be returned. * * @param integer $limitstart How many items to skip from the start, only when $overrideLimits = true * @param integer $limit How many items to return, only when $overrideLimits = true * * @return FOFDatabaseIterator The data collection */ public function get($limitstart = 0, $limit = 0) { $limitstart = max($limitstart, 0); $limit = max($limit, 0); $query = $this->buildQuery(); $db = $this->getDbo(); $db->setQuery($query, $limitstart, $limit); $cursor = $db->execute(); $dataCollection = FOFDatabaseIterator::getIterator($db->name, $cursor, null, $this->config['_table_class']); return $dataCollection; } } PK Ma!]��|� table/dispatcher/behavior.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * FrameworkOnFramework table behavior dispatcher class * * @codeCoverageIgnore * @package FrameworkOnFramework * @since 2.1 */ class FOFTableDispatcherBehavior extends FOFUtilsObservableDispatcher { } PK Ma!]�J�[ [ table/behavior/tags.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * FrameworkOnFramework table behavior class for tags * * @package FrameworkOnFramework * @since 2.1 */ class FOFTableBehaviorTags extends FOFTableBehavior { /** * The event which runs after binding data to the table * * @param FOFTable &$table The table which calls this event * @param object|array &$src The data to bind * @param array $options The options of the table * * @return boolean True on success */ public function onAfterBind(&$table, &$src, $options = array()) { // Bind tags if ($table->hasTags()) { if ((!empty($src['tags']) && $src['tags'][0] != '')) { $table->newTags = $src['tags']; } // Check if the content type exists, and create it if it does not $table->checkContentType(); $tagsTable = clone($table); $tagsHelper = new JHelperTags(); $tagsHelper->typeAlias = $table->getContentType(); // TODO: This little guy here fails because JHelperTags // need a JTable object to work, while our is FOFTable // Need probably to write our own FOFHelperTags // Thank you com_tags if (!$tagsHelper->postStoreProcess($tagsTable)) { $table->setError('Error storing tags'); return false; } } return true; } /** * The event which runs before storing (saving) data to the database * * @param FOFTable &$table The table which calls this event * @param boolean $updateNulls Should nulls be saved as nulls (true) or just skipped over (false)? * * @return boolean True to allow saving */ public function onBeforeStore(&$table, $updateNulls) { if ($table->hasTags()) { $tagsHelper = new JHelperTags(); $tagsHelper->typeAlias = $table->getContentType(); // TODO: JHelperTags sucks in Joomla! 3.1, it requires that tags are // stored in the metadata property. Not our case, therefore we need // to add it in a fake object. We sent a PR to Joomla! CMS to fix // that. Once it's accepted, we'll have to remove the atrocity // here... $tagsTable = clone($table); $tagsHelper->preStoreProcess($tagsTable); } } /** * The event which runs after deleting a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record which was deleted * * @return boolean True to allow the deletion without errors */ public function onAfterDelete(&$table, $oid) { // If this resource has tags, delete the tags first if ($table->hasTags()) { $tagsHelper = new JHelperTags(); $tagsHelper->typeAlias = $table->getContentType(); if (!$tagsHelper->deleteTagData($table, $oid)) { $table->setError('Error deleting Tags'); return false; } } } } PK Ma!]���%� � table/behavior/assets.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * FrameworkOnFramework table behavior class for assets * * @package FrameworkOnFramework * @since 2.1 */ class FOFTableBehaviorAssets extends FOFTableBehavior { /** * The event which runs after storing (saving) data to the database * * @param FOFTable &$table The table which calls this event * * @return boolean True to allow saving */ public function onAfterStore(&$table) { $result = true; $asset_id_field = $table->getColumnAlias('asset_id'); if (in_array($asset_id_field, $table->getKnownFields())) { if (!empty($table->$asset_id_field)) { $currentAssetId = $table->$asset_id_field; } // The asset id field is managed privately by this class. if ($table->isAssetsTracked()) { unset($table->$asset_id_field); } } // Create the object used for inserting/updating data to the database $fields = $table->getTableFields(); // Let's remove the asset_id field, since we unset the property above and we would get a PHP notice if (isset($fields[$asset_id_field])) { unset($fields[$asset_id_field]); } // Asset Tracking if (in_array($asset_id_field, $table->getKnownFields()) && $table->isAssetsTracked()) { $parentId = $table->getAssetParentId(); try{ $name = $table->getAssetName(); } catch(Exception $e) { $table->setError($e->getMessage()); return false; } $title = $table->getAssetTitle(); $asset = JTable::getInstance('Asset'); $asset->loadByName($name); // Re-inject the asset id. $this->$asset_id_field = $asset->id; // Check for an error. $error = $asset->getError(); // Since we are using JTable, there is no way to mock it and test for failures :( // @codeCoverageIgnoreStart if ($error) { $table->setError($error); return false; } // @codeCoverageIgnoreEnd // Specify how a new or moved node asset is inserted into the tree. // Since we're unsetting the table field before, this statement is always true... if (empty($table->$asset_id_field) || $asset->parent_id != $parentId) { $asset->setLocation($parentId, 'last-child'); } // Prepare the asset to be stored. $asset->parent_id = $parentId; $asset->name = $name; $asset->title = $title; if ($table->getRules() instanceof JAccessRules) { $asset->rules = (string) $table->getRules(); } // Since we are using JTable, there is no way to mock it and test for failures :( // @codeCoverageIgnoreStart if (!$asset->check() || !$asset->store()) { $table->setError($asset->getError()); return false; } // @codeCoverageIgnoreEnd // Create an asset_id or heal one that is corrupted. if (empty($table->$asset_id_field) || (($currentAssetId != $table->$asset_id_field) && !empty($table->$asset_id_field))) { // Update the asset_id field in this table. $table->$asset_id_field = (int) $asset->id; $k = $table->getKeyName(); $db = $table->getDbo(); $query = $db->getQuery(true) ->update($db->qn($table->getTableName())) ->set($db->qn($asset_id_field).' = ' . (int) $table->$asset_id_field) ->where($db->qn($k) . ' = ' . (int) $table->$k); $db->setQuery($query)->execute(); } $result = true; } return $result; } /** * The event which runs after binding data to the table * * @param FOFTable &$table The table which calls this event * @param object|array &$src The data to bind * * @return boolean True on success */ public function onAfterBind(&$table, &$src) { // Set rules for assets enabled tables if ($table->isAssetsTracked()) { // Bind the rules. if (isset($src['rules']) && is_array($src['rules'])) { // We have to manually remove any empty value, since they will be converted to int, // and "Inherited" values will become "Denied". Joomla is doing this manually, too. // @todo Should we move this logic inside the setRules method? $rules = array(); foreach ($src['rules'] as $action => $ids) { // Build the rules array. $rules[$action] = array(); foreach ($ids as $id => $p) { if ($p !== '') { $rules[$action][$id] = ($p == '1' || $p == 'true') ? true : false; } } } $table->setRules($rules); } } return true; } /** * The event which runs before deleting a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record to delete * * @return boolean True to allow the deletion */ public function onBeforeDelete(&$table, $oid) { // If tracking assets, remove the asset first. if ($table->isAssetsTracked()) { $k = $table->getKeyName(); // If the table is not loaded, let's try to load it with the id if(!$table->$k) { $table->load($oid); } // If I have an invalid assetName I have to stop try { $name = $table->getAssetName(); } catch(Exception $e) { $table->setError($e->getMessage()); return false; } // Do NOT touch JTable here -- we are loading the core asset table which is a JTable, not a FOFTable $asset = JTable::getInstance('Asset'); if ($asset->loadByName($name)) { // Since we are using JTable, there is no way to mock it and test for failures :( // @codeCoverageIgnoreStart if (!$asset->delete()) { $table->setError($asset->getError()); return false; } // @codeCoverageIgnoreEnd } else { // I'll simply return true even if I couldn't load the asset. In this way I can still // delete a broken record return true; } } return true; } } PK Ma!]�Tʀf f ! table/behavior/contenthistory.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * FrameworkOnFramework table behavior class for content History * * @package FrameworkOnFramework * @since 2.2.0 */ class FOFTableBehaviorContenthistory extends FOFTableBehavior { /** * The event which runs after storing (saving) data to the database * * @param FOFTable &$table The table which calls this event * * @return boolean True to allow saving without an error */ public function onAfterStore(&$table) { $aliasParts = explode('.', $table->getContentType()); $table->checkContentType(); if (JComponentHelper::getParams($aliasParts[0])->get('save_history', 0)) { $historyHelper = new JHelperContenthistory($table->getContentType()); $historyHelper->store($table); } return true; } /** * The event which runs before deleting a record * * @param FOFTable &$table The table which calls this event * @param integer $oid The PK value of the record to delete * * @return boolean True to allow the deletion */ public function onBeforeDelete(&$table, $oid) { $aliasParts = explode('.', $table->getContentType()); if (JComponentHelper::getParams($aliasParts[0])->get('save_history', 0)) { $historyHelper = new JHelperContenthistory($table->getContentType()); $historyHelper->deleteHistory($table); } return true; } } PK Ma!]�=� � � table/relations.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; class FOFTableRelations { /** * Holds all known relation definitions * * @var array */ protected $relations = array( 'child' => array(), 'parent' => array(), 'children' => array(), 'multiple' => array(), ); /** * Holds the default relations' keys * * @var array */ protected $defaultRelation = array( 'child' => null, 'parent' => null, 'children' => null, 'multiple' => null, ); /** * The table these relations are attached to * * @var FOFTable */ protected $table = null; /** * The name of the component used by our attached table * * @var string */ protected $componentName = 'joomla'; /** * The type (table name without prefix and component name) of our attached table * * @var string */ protected $tableType = ''; /** * Create a relations object based on the provided FOFTable instance * * @param FOFTable $table The table instance used to initialise the relations */ public function __construct(FOFTable $table) { // Store the table $this->table = $table; // Get the table's type from its name $tableName = $table->getTableName(); $tableName = str_replace('#__', '', $tableName); $type = explode("_", $tableName); if (count($type) == 1) { $this->tableType = array_pop($type); } else { $this->componentName = array_shift($type); $this->tableType = array_pop($type); } $this->tableType = FOFInflector::singularize($this->tableType); $tableKey = $table->getKeyName(); unset($type); // Scan all table keys and look for foo_bar_id fields. These fields are used to populate parent relations. foreach ($table->getKnownFields() as $field) { // Skip the table key name if ($field == $tableKey) { continue; } if (substr($field, -3) != '_id') { continue; } $parts = explode('_', $field); // If the component type of the field is not set assume 'joomla' if (count($parts) == 2) { array_unshift($parts, 'joomla'); } // Sanity check if (count($parts) != 3) { continue; } // Make sure we skip any references back to ourselves (should be redundant, due to key field check above) if ($parts[1] == $this->tableType) { continue; } // Default item name: the name of the table, singular $itemName = FOFInflector::singularize($parts[1]); // Prefix the item name with the component name if we refer to a different component if ($parts[0] != $this->componentName) { $itemName = $parts[0] . '_' . $itemName; } // Figure out the table class $tableClass = ucfirst($parts[0]) . 'Table' . ucfirst($parts[1]); $default = empty($this->relations['parent']); $this->addParentRelation($itemName, $tableClass, $field, $field, $default); } // Get the relations from the configuration provider $key = $table->getConfigProviderKey() . '.relations'; $configRelations = $table->getConfigProvider()->get($key, array()); if (!empty($configRelations)) { foreach ($configRelations as $relation) { if (empty($relation['type'])) { continue; } if (isset($relation['pivotTable'])) { $this->addMultipleRelation($relation['itemName'], $relation['tableClass'], $relation['localKey'], $relation['ourPivotKey'], $relation['theirPivotKey'], $relation['remoteKey'], $relation['pivotTable'], $relation['default']); } else { $method = 'add' . ucfirst($relation['type']). 'Relation'; if (!method_exists($this, $method)) { continue; } $this->$method($relation['itemName'], $relation['tableClass'], $relation['localKey'], $relation['remoteKey'], $relation['default']); } } } } /** * Add a 1:1 forward (child) relation. This adds relations for the getChild() method. * * In other words: does a table HAVE ONE child * * Parent and child relations works the same way. We have them separated as it makes more sense for us humans to * read code like $item->getParent() and $item->getChild() than $item->getRelatedObject('someRandomKeyName') * * @param string $itemName is how it will be known locally to the getRelatedItem method (singular) * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname * @param string $localKey is the column containing our side of the FK relation, default: our primary key * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id * @param boolean $default add as the default child relation? * * @return void */ public function addChildRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true) { $itemName = $this->normaliseItemName($itemName, false); if (empty($localKey)) { $localKey = $this->table->getKeyName(); } $this->addBespokeSimpleRelation('child', $itemName, $tableClass, $localKey, $remoteKey, $default); } /** * Defining an inverse 1:1 (parent) relation. You must specify at least the $tableClass or the $localKey. * This adds relations for the getParent() method. * * In other words: does a table BELONG TO ONE parent * * Parent and child relations works the same way. We have them separated as it makes more sense for us humans to * read code like $item->getParent() and $item->getChild() than $item->getRelatedObject('someRandomKeyName') * * @param string $itemName is how it will be known locally to the getRelatedItem method (singular) * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id * @param boolean $default Is this the default parent relationship? * * @return void */ public function addParentRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true) { $itemName = $this->normaliseItemName($itemName, false); $this->addBespokeSimpleRelation('parent', $itemName, $tableClass, $localKey, $remoteKey, $default); } /** * Defining a forward 1:∞ (children) relation. This adds relations to the getChildren() method. * * In other words: does a table HAVE MANY children? * * The children relation works very much the same as the parent and child relation. The difference is that the * parent and child relations return a single table object, whereas the children relation returns an iterator to * many objects. * * @param string $itemName is how it will be known locally to the getRelatedItems method (plural) * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname * @param string $localKey is the column containing our side of the FK relation, default: our primary key * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id * @param boolean $default is this the default children relationship? * * @return void */ public function addChildrenRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true) { $itemName = $this->normaliseItemName($itemName, true); if (empty($localKey)) { $localKey = $this->table->getKeyName(); } $this->addBespokeSimpleRelation('children', $itemName, $tableClass, $localKey, $remoteKey, $default); } /** * Defining a ∞:∞ (multiple) relation. This adds relations to the getMultiple() method. * * In other words: is a table RELATED TO MANY other records? * * @param string $itemName is how it will be known locally to the getRelatedItems method (plural) * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname * @param string $localKey is the column containing our side of the FK relation, default: our primary key field name * @param string $ourPivotKey is the column containing our side of the FK relation in the pivot table, default: $localKey * @param string $theirPivotKey is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id * @param string $glueTable is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles) * @param boolean $default is this the default multiple relation? */ public function addMultipleRelation($itemName, $tableClass = null, $localKey = null, $ourPivotKey = null, $theirPivotKey = null, $remoteKey = null, $glueTable = null, $default = true) { $itemName = $this->normaliseItemName($itemName, true); if (empty($localKey)) { $localKey = $this->table->getKeyName(); } $this->addBespokePivotRelation('multiple', $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $glueTable, $default); } /** * Removes a previously defined relation by name. You can optionally specify the relation type. * * @param string $itemName The name of the relation to remove * @param string $type [optional] The relation type (child, parent, children, ...) * * @return void */ public function removeRelation($itemName, $type = null) { $types = array_keys($this->relations); if (in_array($type, $types)) { $types = array($type); } foreach ($types as $type) { foreach ($this->relations[$type] as $key => $relations) { if ($itemName == $key) { unset ($this->relations[$type][$itemName]); // If it's the default one, remove it from the default array, too if($this->defaultRelation[$type] == $itemName) { $this->defaultRelation[$type] = null; } return; } } } } /** * Removes all existing relations * * @param string $type The type or relations to remove, omit to remove all relation types * * @return void */ public function clearRelations($type = null) { $types = array_keys($this->relations); if (in_array($type, $types)) { $types = array($type); } foreach ($types as $type) { $this->relations[$type] = array(); // Remove the relation from the default stack, too $this->defaultRelation[$type] = null; } } /** * Does the named relation exist? You can optionally specify the type. * * @param string $itemName The name of the relation to check * @param string $type [optional] The relation type (child, parent, children, ...) * * @return boolean */ public function hasRelation($itemName, $type = null) { $types = array_keys($this->relations); if (in_array($type, $types)) { $types = array($type); } foreach ($types as $type) { foreach ($this->relations[$type] as $key => $relations) { if ($itemName == $key) { return true; } } } return false; } /** * Get the definition of a relation * * @param string $itemName The name of the relation to check * @param string $type [optional] The relation type (child, parent, children, ...) * * @return array * * @throws RuntimeException When the relation is not found */ public function getRelation($itemName, $type) { $types = array_keys($this->relations); if (in_array($type, $types)) { $types = array($type); } foreach ($types as $type) { foreach ($this->relations[$type] as $key => $relations) { if ($itemName == $key) { $temp = $relations; $temp['type'] = $type; return $temp; } } } throw new RuntimeException("Relation $itemName not found in table {$this->tableType}", 500); } /** * Gets the item referenced by a named relation. You can optionally specify the type. Only single item relation * types will be searched. * * @param string $itemName The name of the relation to use * @param string $type [optional] The relation type (child, parent) * * @return FOFTable * * @throws RuntimeException If the named relation doesn't exist or isn't supposed to return single items */ public function getRelatedItem($itemName, $type = null) { if (empty($type)) { $relation = $this->getRelation($itemName, $type); $type = $relation['type']; } switch ($type) { case 'parent': return $this->getParent($itemName); break; case 'child': return $this->getChild($itemName); break; default: throw new RuntimeException("Invalid relation type $type for returning a single related item", 500); break; } } /** * Gets the iterator for the items referenced by a named relation. You can optionally specify the type. Only * multiple item relation types will be searched. * * @param string $itemName The name of the relation to use * @param string $type [optional] The relation type (children, multiple) * * @return FOFDatabaseIterator * * @throws RuntimeException If the named relation doesn't exist or isn't supposed to return single items */ public function getRelatedItems($itemName, $type = null) { if (empty($type)) { $relation = $this->getRelation($itemName, $type); $type = $relation['type']; } switch ($type) { case 'children': return $this->getChildren($itemName); break; case 'multiple': return $this->getMultiple($itemName); break; case 'siblings': return $this->getSiblings($itemName); break; default: throw new RuntimeException("Invalid relation type $type for returning a collection of related items", 500); break; } } /** * Gets a parent item * * @param string $itemName [optional] The name of the relation to use, skip to use the default parent relation * * @return FOFTable * * @throws RuntimeException When the relation is not found */ public function getParent($itemName = null) { if (empty($itemName)) { $itemName = $this->defaultRelation['parent']; } if (empty($itemName)) { throw new RuntimeException(sprintf('Default parent relation for %s not found', $this->table->getTableName()), 500); } if (!isset($this->relations['parent'][$itemName])) { throw new RuntimeException(sprintf('Parent relation %s for %s not found', $itemName, $this->table->getTableName()), 500); } return $this->getTableFromRelation($this->relations['parent'][$itemName]); } /** * Gets a child item * * @param string $itemName [optional] The name of the relation to use, skip to use the default child relation * * @return FOFTable * * @throws RuntimeException When the relation is not found */ public function getChild($itemName = null) { if (empty($itemName)) { $itemName = $this->defaultRelation['child']; } if (empty($itemName)) { throw new RuntimeException(sprintf('Default child relation for %s not found', $this->table->getTableName()), 500); } if (!isset($this->relations['child'][$itemName])) { throw new RuntimeException(sprintf('Child relation %s for %s not found', $itemName, $this->table->getTableName()), 500); } return $this->getTableFromRelation($this->relations['child'][$itemName]); } /** * Gets an iterator for the children items * * @param string $itemName [optional] The name of the relation to use, skip to use the default children relation * * @return FOFDatabaseIterator * * @throws RuntimeException When the relation is not found */ public function getChildren($itemName = null) { if (empty($itemName)) { $itemName = $this->defaultRelation['children']; } if (empty($itemName)) { throw new RuntimeException(sprintf('Default children relation for %s not found', $this->table->getTableName()), 500); } if (!isset($this->relations['children'][$itemName])) { throw new RuntimeException(sprintf('Children relation %s for %s not found', $itemName, $this->table->getTableName()), 500); } return $this->getIteratorFromRelation($this->relations['children'][$itemName]); } /** * Gets an iterator for the sibling items. This relation is inferred from the parent relation. It returns all * elements on the same table which have the same parent. * * @param string $itemName [optional] The name of the relation to use, skip to use the default children relation * * @return FOFDatabaseIterator * * @throws RuntimeException When the relation is not found */ public function getSiblings($itemName = null) { if (empty($itemName)) { $itemName = $this->defaultRelation['parent']; } if (empty($itemName)) { throw new RuntimeException(sprintf('Default siblings relation for %s not found', $this->table->getTableName()), 500); } if (!isset($this->relations['parent'][$itemName])) { throw new RuntimeException(sprintf('Sibling relation %s for %s not found', $itemName, $this->table->getTableName()), 500); } // Get my table class $tableName = $this->table->getTableName(); $tableName = str_replace('#__', '', $tableName); $tableNameParts = explode('_', $tableName, 2); $tableClass = ucfirst($tableNameParts[0]) . 'Table' . ucfirst(FOFInflector::singularize($tableNameParts[1])); $parentRelation = $this->relations['parent'][$itemName]; $relation = array( 'tableClass' => $tableClass, 'localKey' => $parentRelation['localKey'], 'remoteKey' => $parentRelation['localKey'], ); return $this->getIteratorFromRelation($relation); } /** * Gets an iterator for the multiple items * * @param string $itemName [optional] The name of the relation to use, skip to use the default multiple relation * * @return FOFDatabaseIterator * * @throws RuntimeException When the relation is not found */ public function getMultiple($itemName = null) { if (empty($itemName)) { $itemName = $this->defaultRelation['multiple']; } if (empty($itemName)) { throw new RuntimeException(sprintf('Default multiple relation for %s not found', $this->table->getTableName()), 500); } if (!isset($this->relations['multiple'][$itemName])) { throw new RuntimeException(sprintf('Multiple relation %s for %s not found', $itemName, $this->table->getTableName()), 500); } return $this->getIteratorFromRelation($this->relations['multiple'][$itemName]); } /** * Returns a FOFTable object based on a given relation * * @param array $relation Indexed array holding relation definition. * tableClass => name of the related table class * localKey => name of the local key * remoteKey => name of the remote key * * @return FOFTable * * @throws RuntimeException * @throws InvalidArgumentException */ protected function getTableFromRelation($relation) { // Sanity checks if( !isset($relation['tableClass']) || !isset($relation['remoteKey']) || !isset($relation['localKey']) || !$relation['tableClass'] || !$relation['remoteKey'] || !$relation['localKey'] ) { throw new InvalidArgumentException('Missing array index for the '.__METHOD__.' method. Please check method signature', 500); } // Get a table object from the table class name $tableClass = $relation['tableClass']; $tableClassParts = FOFInflector::explode($tableClass); if(count($tableClassParts) < 3) { throw new InvalidArgumentException('Invalid table class named. It should be something like FooTableBar'); } $table = FOFTable::getInstance($tableClassParts[2], ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1])); // Get the table name $tableName = $table->getTableName(); // Get the remote and local key names $remoteKey = $relation['remoteKey']; $localKey = $relation['localKey']; // Get the local key's value $value = $this->table->$localKey; // If there's no value for the primary key, let's stop here if(!$value) { throw new RuntimeException('Missing value for the primary key of the table '.$this->table->getTableName(), 500); } // This is required to prevent one relation from killing the db cursor used in a different relation... $oldDb = $this->table->getDbo(); $oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE THE DB OBJECT. ARGH! $db = clone $oldDb; $query = $db->getQuery(true) ->select('*') ->from($db->qn($tableName)) ->where($db->qn($remoteKey) . ' = ' . $db->q($value)); $db->setQuery($query, 0, 1); $data = $db->loadObject(); if (!is_object($data)) { throw new RuntimeException(sprintf('Cannot load item from relation against table %s column %s', $tableName, $remoteKey), 500); } $table->bind($data); return $table; } /** * Returns a FOFDatabaseIterator based on a given relation * * @param array $relation Indexed array holding relation definition. * tableClass => name of the related table class * localKey => name of the local key * remoteKey => name of the remote key * pivotTable => name of the pivot table (optional) * theirPivotKey => name of the remote key in the pivot table (mandatory if pivotTable is set) * ourPivotKey => name of our key in the pivot table (mandatory if pivotTable is set) * * @return FOFDatabaseIterator * * @throws RuntimeException * @throws InvalidArgumentException */ protected function getIteratorFromRelation($relation) { // Sanity checks if( !isset($relation['tableClass']) || !isset($relation['remoteKey']) || !isset($relation['localKey']) || !$relation['tableClass'] || !$relation['remoteKey'] || !$relation['localKey'] ) { throw new InvalidArgumentException('Missing array index for the '.__METHOD__.' method. Please check method signature', 500); } if(array_key_exists('pivotTable', $relation)) { if( !isset($relation['theirPivotKey']) || !isset($relation['ourPivotKey']) || !$relation['pivotTable'] || !$relation['theirPivotKey'] || !$relation['ourPivotKey'] ) { throw new InvalidArgumentException('Missing array index for the '.__METHOD__.' method. Please check method signature', 500); } } // Get a table object from the table class name $tableClass = $relation['tableClass']; $tableClassParts = FOFInflector::explode($tableClass); if(count($tableClassParts) < 3) { throw new InvalidArgumentException('Invalid table class named. It should be something like FooTableBar'); } $table = FOFTable::getInstance($tableClassParts[2], ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1])); // Get the table name $tableName = $table->getTableName(); // Get the remote and local key names $remoteKey = $relation['remoteKey']; $localKey = $relation['localKey']; // Get the local key's value $value = $this->table->$localKey; // If there's no value for the primary key, let's stop here if(!$value) { throw new RuntimeException('Missing value for the primary key of the table '.$this->table->getTableName(), 500); } // This is required to prevent one relation from killing the db cursor used in a different relation... $oldDb = $this->table->getDbo(); $oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE THE DB OBJECT. ARGH! $db = clone $oldDb; // Begin the query $query = $db->getQuery(true) ->select('*') ->from($db->qn($tableName)); // Do we have a pivot table? $hasPivot = array_key_exists('pivotTable', $relation); // If we don't have pivot it's a straightforward query if (!$hasPivot) { $query->where($db->qn($remoteKey) . ' = ' . $db->q($value)); } // If we have a pivot table we have to do a subquery else { $subQuery = $db->getQuery(true) ->select($db->qn($relation['theirPivotKey'])) ->from($db->qn($relation['pivotTable'])) ->where($db->qn($relation['ourPivotKey']) . ' = ' . $db->q($value)); $query->where($db->qn($remoteKey) . ' IN (' . $subQuery . ')'); } $db->setQuery($query); $cursor = $db->execute(); $iterator = FOFDatabaseIterator::getIterator($db->name, $cursor, null, $tableClass); return $iterator; } /** * Add any bespoke relation which doesn't involve a pivot table. * * @param string $relationType The type of the relationship (parent, child, children) * @param string $itemName is how it will be known locally to the getRelatedItems method * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id * @param boolean $default is this the default children relationship? * * @return void */ protected function addBespokeSimpleRelation($relationType, $itemName, $tableClass, $localKey, $remoteKey, $default) { $ourPivotKey = null; $theirPivotKey = null; $pivotTable = null; $this->normaliseParameters(false, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable); $this->relations[$relationType][$itemName] = array( 'tableClass' => $tableClass, 'localKey' => $localKey, 'remoteKey' => $remoteKey, ); if ($default) { $this->defaultRelation[$relationType] = $itemName; } } /** * Add any bespoke relation which involves a pivot table. * * @param string $relationType The type of the relationship (multiple) * @param string $itemName is how it will be known locally to the getRelatedItems method * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id * @param string $ourPivotKey is the column containing our side of the FK relation in the pivot table, default: $localKey * @param string $theirPivotKey is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey * @param string $pivotTable is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles) * @param boolean $default is this the default children relationship? * * @return void */ protected function addBespokePivotRelation($relationType, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable, $default) { $this->normaliseParameters(true, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable); $this->relations[$relationType][$itemName] = array( 'tableClass' => $tableClass, 'localKey' => $localKey, 'remoteKey' => $remoteKey, 'ourPivotKey' => $ourPivotKey, 'theirPivotKey' => $theirPivotKey, 'pivotTable' => $pivotTable, ); if ($default) { $this->defaultRelation[$relationType] = $itemName; } } /** * Normalise the parameters of a relation, guessing missing values * * @param boolean $pivot Is this a many to many relation involving a pivot table? * @param string $itemName is how it will be known locally to the getRelatedItems method (plural) * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id * @param string $ourPivotKey is the column containing our side of the FK relation in the pivot table, default: $localKey * @param string $theirPivotKey is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey * @param string $pivotTable is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles) * * @return void */ protected function normaliseParameters($pivot, &$itemName, &$tableClass, &$localKey, &$remoteKey, &$ourPivotKey, &$theirPivotKey, &$pivotTable) { // Get a default table class if none is provided if (empty($tableClass)) { $tableClassParts = explode('_', $itemName, 3); if (count($tableClassParts) == 1) { array_unshift($tableClassParts, $this->componentName); } if ($tableClassParts[0] == 'joomla') { $tableClassParts[0] = 'J'; } $tableClass = ucfirst($tableClassParts[0]) . 'Table' . ucfirst(FOFInflector::singularize($tableClassParts[1])); } // Make sure we have both a local and remote key if (empty($localKey) && empty($remoteKey)) { // WARNING! If we have a pivot table, this behavior is wrong! // Infact if we have `parts` and `groups` the local key should be foobar_part_id and the remote one foobar_group_id. // However, this isn't a real issue because: // 1. we have no way to detect the local key of a multiple relation // 2. this scenario never happens, since, in this class, if we're adding a multiple relation we always supply the local key $tableClassParts = FOFInflector::explode($tableClass); $localKey = $tableClassParts[0] . '_' . $tableClassParts[2] . '_id'; $remoteKey = $localKey; } elseif (empty($localKey) && !empty($remoteKey)) { $localKey = $remoteKey; } elseif (!empty($localKey) && empty($remoteKey)) { if($pivot) { $tableClassParts = FOFInflector::explode($tableClass); $remoteKey = $tableClassParts[0] . '_' . $tableClassParts[2] . '_id'; } else { $remoteKey = $localKey; } } // If we don't have a pivot table nullify the relevant variables and return if (!$pivot) { $ourPivotKey = null; $theirPivotKey = null; $pivotTable = null; return; } if (empty($ourPivotKey)) { $ourPivotKey = $localKey; } if (empty($theirPivotKey)) { $theirPivotKey = $remoteKey; } if (empty($pivotTable)) { $pivotTable = '#__' . strtolower($this->componentName) . '_' . strtolower(FOFInflector::pluralize($this->tableType)) . '_'; $itemNameParts = explode('_', $itemName); $lastPart = array_pop($itemNameParts); $pivotTable .= strtolower($lastPart); } } /** * Normalises the format of a relation name * * @param string $itemName The raw relation name * @param boolean $pluralise Should I pluralise the name? If not, I will singularise it * * @return string The normalised relation key name */ protected function normaliseItemName($itemName, $pluralise = false) { // Explode the item name $itemNameParts = explode('_', $itemName); // If we have multiple parts the first part is considered to be the component name if (count($itemNameParts) > 1) { $prefix = array_shift($itemNameParts); } else { $prefix = null; } // If we still have multiple parts we need to pluralise/singularise the last part and join everything in // CamelCase format if (count($itemNameParts) > 1) { $name = array_pop($itemNameParts); $name = $pluralise ? FOFInflector::pluralize($name) : FOFInflector::singularize($name); $itemNameParts[] = $name; $itemName = FOFInflector::implode($itemNameParts); } // Otherwise we singularise/pluralise the remaining part else { $name = array_pop($itemNameParts); $itemName = $pluralise ? FOFInflector::pluralize($name) : FOFInflector::singularize($name); } if (!empty($prefix)) { $itemName = $prefix . '_' . $itemName; } return $itemName; } }PK Ma!]�y��c �c table/table.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage table * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Protect from unauthorized access defined('FOF_INCLUDED') or die; /** * Normally this shouldn't be required. Some PHP versions, however, seem to * require this. Why? No idea whatsoever. If I remove it, FOF crashes on some * hosts. Same PHP version on another host and no problem occurs. Any takers? */ if (class_exists('FOFTable', false)) { return; } if (!interface_exists('JTableInterface', true)) { interface JTableInterface {} } /** * FrameworkOnFramework Table class. The Table is one part controller, one part * model and one part data adapter. It's supposed to handle operations for single * records. * * @package FrameworkOnFramework * @since 1.0 */ class FOFTable extends FOFUtilsObject implements JTableInterface { /** * Cache array for instances * * @var array */ protected static $instances = array(); /** * Include paths for searching for FOFTable classes. * * @var array */ protected static $_includePaths = array(); /** * The configuration parameters array * * @var array */ protected $config = array(); /** * Name of the database table to model. * * @var string */ protected $_tbl = ''; /** * Name of the primary key field in the table. * * @var string */ protected $_tbl_key = ''; /** * FOFDatabaseDriver object. * * @var FOFDatabaseDriver */ protected $_db; /** * Should rows be tracked as ACL assets? * * @var boolean */ protected $_trackAssets = false; /** * Does the resource support joomla tags? * * @var boolean */ protected $_has_tags = false; /** * The rules associated with this record. * * @var JAccessRules A JAccessRules object. */ protected $_rules; /** * Indicator that the tables have been locked. * * @var boolean */ protected $_locked = false; /** * If this is set to true, it triggers automatically plugin events for * table actions * * @var boolean */ protected $_trigger_events = false; /** * Table alias used in queries * * @var string */ protected $_tableAlias = false; /** * Array with alias for "special" columns such as ordering, hits etc etc * * @var array */ protected $_columnAlias = array(); /** * If set to true, it enabled automatic checks on fields based on columns properties * * @var boolean */ protected $_autoChecks = false; /** * Array with fields that should be skipped by automatic checks * * @var array */ protected $_skipChecks = array(); /** * Does the table actually exist? We need that to avoid PHP notices on * table-less views. * * @var boolean */ protected $_tableExists = true; /** * The asset key for items in this table. It's usually something in the * com_example.viewname format. They asset name will be this key appended * with the item's ID, e.g. com_example.viewname.123 * * @var string */ protected $_assetKey = ''; /** * The input data * * @var FOFInput */ protected $input = null; /** * Extended query including joins with other tables * * @var FOFDatabaseQuery */ protected $_queryJoin = null; /** * The prefix for the table class * * @var string */ protected $_tablePrefix = ''; /** * The known fields for this table * * @var array */ protected $knownFields = array(); /** * A list of table fields, keyed per table * * @var array */ protected static $tableFieldCache = array(); /** * A list of tables in the database * * @var array */ protected static $tableCache = array(); /** * An instance of FOFConfigProvider to provision configuration overrides * * @var FOFConfigProvider */ protected $configProvider = null; /** * FOFTableDispatcherBehavior for dealing with extra behaviors * * @var FOFTableDispatcherBehavior */ protected $tableDispatcher = null; /** * List of default behaviors to apply to the table * * @var array */ protected $default_behaviors = array('tags', 'assets'); /** * The relations object of the table. It's lazy-loaded by getRelations(). * * @var FOFTableRelations */ protected $_relations = null; /** * The configuration provider's key for this table, e.g. foobar.tables.bar for the #__foobar_bars table. This is set * automatically by the constructor * * @var string */ protected $_configProviderKey = ''; /** * The content type of the table. Required if using tags or content history behaviour * * @var string */ protected $contentType = null; /** * Returns a static object instance of a particular table type * * @param string $type The table name * @param string $prefix The prefix of the table class * @param array $config Optional configuration variables * * @return FOFTable */ public static function getInstance($type, $prefix = 'JTable', $config = array()) { return self::getAnInstance($type, $prefix, $config); } /** * Returns a static object instance of a particular table type * * @param string $type The table name * @param string $prefix The prefix of the table class * @param array $config Optional configuration variables * * @return FOFTable */ public static function &getAnInstance($type = null, $prefix = 'JTable', $config = array()) { // Make sure $config is an array if (is_object($config)) { $config = (array) $config; } elseif (!is_array($config)) { $config = array(); } // Guess the component name if (!array_key_exists('input', $config)) { $config['input'] = new FOFInput; } if ($config['input'] instanceof FOFInput) { $tmpInput = $config['input']; } else { $tmpInput = new FOFInput($config['input']); } $option = $tmpInput->getCmd('option', ''); $tmpInput->set('option', $option); $config['input'] = $tmpInput; if (!in_array($prefix, array('Table', 'JTable'))) { preg_match('/(.*)Table$/', $prefix, $m); $option = 'com_' . strtolower($m[1]); } if (array_key_exists('option', $config)) { $option = $config['option']; } $config['option'] = $option; if (!array_key_exists('view', $config)) { $config['view'] = $config['input']->getCmd('view', 'cpanel'); } if (is_null($type)) { if ($prefix == 'JTable') { $prefix = 'Table'; } $type = $config['view']; } $type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type); $tableClass = $prefix . ucfirst($type); $config['_table_type'] = $type; $config['_table_class'] = $tableClass; $configProvider = new FOFConfigProvider; $configProviderKey = $option . '.views.' . FOFInflector::singularize($type) . '.config.'; if (!array_key_exists($tableClass, self::$instances)) { if (!class_exists($tableClass)) { $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']); $searchPaths = array( $componentPaths['main'] . '/tables', $componentPaths['admin'] . '/tables' ); if (array_key_exists('tablepath', $config)) { array_unshift($searchPaths, $config['tablepath']); } $altPath = $configProvider->get($configProviderKey . 'table_path', null); if ($altPath) { array_unshift($searchPaths, $componentPaths['admin'] . '/' . $altPath); } $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem'); $path = $filesystem->pathFind( $searchPaths, strtolower($type) . '.php' ); if ($path) { require_once $path; } } if (!class_exists($tableClass)) { $tableClass = 'FOFTable'; } $component = str_replace('com_', '', $config['option']); $tbl_common = $component . '_'; if (!array_key_exists('tbl', $config)) { $config['tbl'] = strtolower('#__' . $tbl_common . strtolower(FOFInflector::pluralize($type))); } $altTbl = $configProvider->get($configProviderKey . 'tbl', null); if ($altTbl) { $config['tbl'] = $altTbl; } if (!array_key_exists('tbl_key', $config)) { $keyName = FOFInflector::singularize($type); $config['tbl_key'] = strtolower($tbl_common . $keyName . '_id'); } $altTblKey = $configProvider->get($configProviderKey . 'tbl_key', null); if ($altTblKey) { $config['tbl_key'] = $altTblKey; } if (!array_key_exists('db', $config)) { $config['db'] = FOFPlatform::getInstance()->getDbo(); } // Assign the correct table alias if (array_key_exists('table_alias', $config)) { $table_alias = $config['table_alias']; } else { $configProviderTableAliasKey = $option . '.tables.' . FOFInflector::singularize($type) . '.tablealias'; $table_alias = $configProvider->get($configProviderTableAliasKey, false ); } // Can we use the FOF cache? if (!array_key_exists('use_table_cache', $config)) { $config['use_table_cache'] = FOFPlatform::getInstance()->isGlobalFOFCacheEnabled(); } $alt_use_table_cache = $configProvider->get($configProviderKey . 'use_table_cache', null); if (!is_null($alt_use_table_cache)) { $config['use_table_cache'] = $alt_use_table_cache; } // Create a new table instance $instance = new $tableClass($config['tbl'], $config['tbl_key'], $config['db'], $config); $instance->setInput($tmpInput); $instance->setTablePrefix($prefix); $instance->setTableAlias($table_alias); // Determine and set the asset key for this table $assetKey = 'com_' . $component . '.' . strtolower(FOFInflector::singularize($type)); $assetKey = $configProvider->get($configProviderKey . 'asset_key', $assetKey); $instance->setAssetKey($assetKey); if (array_key_exists('trigger_events', $config)) { $instance->setTriggerEvents($config['trigger_events']); } if (version_compare(JVERSION, '3.1', 'ge')) { if (array_key_exists('has_tags', $config)) { $instance->setHasTags($config['has_tags']); } $altHasTags = $configProvider->get($configProviderKey . 'has_tags', null); if ($altHasTags) { $instance->setHasTags($altHasTags); } } else { $instance->setHasTags(false); } $configProviderFieldmapKey = $option . '.tables.' . FOFInflector::singularize($type) . '.field'; $aliases = $configProvider->get($configProviderFieldmapKey, $instance->_columnAlias); $instance->_columnAlias = array_merge($instance->_columnAlias, $aliases); self::$instances[$tableClass] = $instance; } return self::$instances[$tableClass]; } /** * Force an instance inside class cache. Setting arguments to null nukes all or part of the cache * * @param string|null $key TableClass to replace. Set it to null to nuke the entire cache * @param FOFTable|null $instance Instance to replace. Set it to null to nuke $key instances * * @return bool Did I correctly switch the instance? */ public static function forceInstance($key = null, $instance = null) { if(is_null($key)) { self::$instances = array(); return true; } elseif($key && isset(self::$instances[$key])) { // I'm forcing an instance, but it's not a FOFTable, abort! abort! if(!$instance || ($instance && $instance instanceof FOFTable)) { self::$instances[$key] = $instance; return true; } } return false; } /** * Class Constructor. * * @param string $table Name of the database table to model. * @param string $key Name of the primary key field in the table. * @param FOFDatabaseDriver &$db Database driver * @param array $config The configuration parameters array */ public function __construct($table, $key, &$db, $config = array()) { $this->_tbl = $table; $this->_tbl_key = $key; $this->_db = $db; // Make sure the use FOF cache information is in the config if (!array_key_exists('use_table_cache', $config)) { $config['use_table_cache'] = FOFPlatform::getInstance()->isGlobalFOFCacheEnabled(); } $this->config = $config; // Load the configuration provider $this->configProvider = new FOFConfigProvider; // Load the behavior dispatcher $this->tableDispatcher = new FOFTableDispatcherBehavior; // Initialise the table properties. if ($fields = $this->getTableFields()) { // Do I have anything joined? $j_fields = $this->getQueryJoinFields(); if ($j_fields) { $fields = array_merge($fields, $j_fields); } $this->setKnownFields(array_keys($fields), true); $this->reset(); } else { $this->_tableExists = false; } // Get the input if (array_key_exists('input', $config)) { if ($config['input'] instanceof FOFInput) { $this->input = $config['input']; } else { $this->input = new FOFInput($config['input']); } } else { $this->input = new FOFInput; } // Set the $name/$_name variable $component = $this->input->getCmd('option', 'com_foobar'); if (array_key_exists('option', $config)) { $component = $config['option']; } $this->input->set('option', $component); // Apply table behaviors $type = explode("_", $this->_tbl); $type = $type[count($type) - 1]; $this->_configProviderKey = $component . '.tables.' . FOFInflector::singularize($type); $configKey = $this->_configProviderKey . '.behaviors'; if (isset($config['behaviors'])) { $behaviors = (array) $config['behaviors']; } elseif ($behaviors = $this->configProvider->get($configKey, null)) { $behaviors = explode(',', $behaviors); } else { $behaviors = $this->default_behaviors; } if (is_array($behaviors) && count($behaviors)) { foreach ($behaviors as $behavior) { $this->addBehavior($behavior); } } // If we are tracking assets, make sure an access field exists and initially set the default. $asset_id_field = $this->getColumnAlias('asset_id'); $access_field = $this->getColumnAlias('access'); if (in_array($asset_id_field, $this->getKnownFields())) { JLoader::import('joomla.access.rules'); $this->_trackAssets = true; } // If the access property exists, set the default. if (in_array($access_field, $this->getKnownFields())) { $this->$access_field = (int) FOFPlatform::getInstance()->getConfig()->get('access'); } $this->config = $config; } /** * Replace the entire known fields array * * @param array $fields A simple array of known field names * @param boolean $initialise Should we initialise variables to null? * * @return void */ public function setKnownFields($fields, $initialise = false) { $this->knownFields = $fields; if ($initialise) { foreach ($this->knownFields as $field) { $this->$field = null; } } } /** * Get the known fields array * * @return array */ public function getKnownFields() { return $this->knownFields; } /** * Does the specified field exist? * * @param string $fieldName The field name to search (it's OK to use aliases) * * @return bool */ public function hasField($fieldName) { $search = $this->getColumnAlias($fieldName); return in_array($search, $this->knownFields); } /** * Add a field to the known fields array * * @param string $field The name of the field to add * @param boolean $initialise Should we initialise the variable to null? * * @return void */ public function addKnownField($field, $initialise = false) { if (!in_array($field, $this->knownFields)) { $this->knownFields[] = $field; if ($initialise) { $this->$field = null; } } } /** * Remove a field from the known fields array * * @param string $field The name of the field to remove * * @return void */ public function removeKnownField($field) { if (in_array($field, $this->knownFields)) { $pos = array_search($field, $this->knownFields); unset($this->knownFields[$pos]); } } /** * Adds a behavior to the table * * @param string $name The name of the behavior * @param array $config Optional Behavior configuration * * @return boolean */ public function addBehavior($name, $config = array()) { // First look for ComponentnameTableViewnameBehaviorName (e.g. FoobarTableItemsBehaviorTags) if (isset($this->config['option'])) { $option_name = str_replace('com_', '', $this->config['option']); $behaviorClass = $this->config['_table_class'] . 'Behavior' . ucfirst(strtolower($name)); if (class_exists($behaviorClass)) { $behavior = new $behaviorClass($this->tableDispatcher, $config); return true; } // Then look for ComponentnameTableBehaviorName (e.g. FoobarTableBehaviorTags) $option_name = str_replace('com_', '', $this->config['option']); $behaviorClass = ucfirst($option_name) . 'TableBehavior' . ucfirst(strtolower($name)); if (class_exists($behaviorClass)) { $behavior = new $behaviorClass($this->tableDispatcher, $config); return true; } } // Nothing found? Return false. $behaviorClass = 'FOFTableBehavior' . ucfirst(strtolower($name)); if (class_exists($behaviorClass) && $this->tableDispatcher) { $behavior = new $behaviorClass($this->tableDispatcher, $config); return true; } return false; } /** * Sets the events trigger switch state * * @param boolean $newState The new state of the switch (what else could it be?) * * @return void */ public function setTriggerEvents($newState = false) { $this->_trigger_events = $newState ? true : false; } /** * Gets the events trigger switch state * * @return boolean */ public function getTriggerEvents() { return $this->_trigger_events; } /** * Gets the has tags switch state * * @return bool */ public function hasTags() { return $this->_has_tags; } /** * Sets the has tags switch state * * @param bool $newState */ public function setHasTags($newState = false) { $this->_has_tags = false; // Tags are available only in 3.1+ if (version_compare(JVERSION, '3.1', 'ge')) { $this->_has_tags = $newState ? true : false; } } /** * Set the class prefix * * @param string $prefix The prefix */ public function setTablePrefix($prefix) { $this->_tablePrefix = $prefix; } /** * Sets fields to be skipped from automatic checks. * * @param array/string $skip Fields to be skipped by automatic checks * * @return void */ public function setSkipChecks($skip) { $this->_skipChecks = (array) $skip; } /** * Method to load a row from the database by primary key and bind the fields * to the FOFTable instance properties. * * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match. If not * set the instance property value is used. * @param boolean $reset True to reset the default values before loading the new row. * * @return boolean True if successful. False if row not found. * * @throws RuntimeException * @throws UnexpectedValueException */ public function load($keys = null, $reset = true) { if (!$this->_tableExists) { $result = false; return $this->onAfterLoad($result); } if (empty($keys)) { // If empty, use the value of the current key $keyName = $this->_tbl_key; if (isset($this->$keyName)) { $keyValue = $this->$keyName; } else { $keyValue = null; } // If empty primary key there's is no need to load anything if (empty($keyValue)) { $result = true; return $this->onAfterLoad($result); } $keys = array($keyName => $keyValue); } elseif (!is_array($keys)) { // Load by primary key. $keys = array($this->_tbl_key => $keys); } if ($reset) { $this->reset(); } // Initialise the query. $query = $this->_db->getQuery(true); $query->select($this->_tbl . '.*'); $query->from($this->_tbl); // Joined fields are ok, since I initialized them in the constructor $fields = $this->getKnownFields(); foreach ($keys as $field => $value) { // Check that $field is in the table. if (!in_array($field, $fields)) { throw new UnexpectedValueException(sprintf('Missing field in table %s : %s.', $this->_tbl, $field)); } // Add the search tuple to the query. $query->where($this->_db->qn($this->_tbl . '.' . $field) . ' = ' . $this->_db->q($value)); } // Do I have any joined table? $j_query = $this->getQueryJoin(); if ($j_query) { if ($j_query->select && $j_query->select->getElements()) { //$query->select($this->normalizeSelectFields($j_query->select->getElements(), true)); $query->select($j_query->select->getElements()); } if ($j_query->join) { foreach ($j_query->join as $join) { $t = (string) $join; // Joomla doesn't provide any access to the "name" variable, so I have to work with strings... if (stripos($t, 'inner') !== false) { $query->innerJoin($join->getElements()); } elseif (stripos($t, 'left') !== false) { $query->leftJoin($join->getElements()); } elseif (stripos($t, 'right') !== false) { $query->rightJoin($join->getElements()); } elseif (stripos($t, 'outer') !== false) { $query->outerJoin($join->getElements()); } } } } $this->_db->setQuery($query); $row = $this->_db->loadAssoc(); // Check that we have a result. if (empty($row)) { $result = false; return $this->onAfterLoad($result); } // Bind the object with the row and return. $result = $this->bind($row); $this->onAfterLoad($result); return $result; } /** * Based on fields properties (nullable column), checks if the field is required or not * * @return boolean */ public function check() { if (!$this->_autoChecks) { return true; } $fields = $this->getTableFields(); // No fields? Why in the hell am I here? if(!$fields) { return false; } $result = true; $known = $this->getKnownFields(); $skipFields[] = $this->_tbl_key; if(in_array($this->getColumnAlias('title'), $known) && in_array($this->getColumnAlias('slug'), $known)) $skipFields[] = $this->getColumnAlias('slug'); if(in_array($this->getColumnAlias('hits'), $known)) $skipFields[] = $this->getColumnAlias('hits'); if(in_array($this->getColumnAlias('created_on'), $known)) $skipFields[] = $this->getColumnAlias('created_on'); if(in_array($this->getColumnAlias('created_by'), $known)) $skipFields[] = $this->getColumnAlias('created_by'); if(in_array($this->getColumnAlias('modified_on'), $known)) $skipFields[] = $this->getColumnAlias('modified_on'); if(in_array($this->getColumnAlias('modified_by'), $known)) $skipFields[] = $this->getColumnAlias('modified_by'); if(in_array($this->getColumnAlias('locked_by'), $known)) $skipFields[] = $this->getColumnAlias('locked_by'); if(in_array($this->getColumnAlias('locked_on'), $known)) $skipFields[] = $this->getColumnAlias('locked_on'); // Let's merge it with custom skips $skipFields = array_merge($skipFields, $this->_skipChecks); foreach ($fields as $field) { $fieldName = $field->Field; if (empty($fieldName)) { $fieldName = $field->column_name; } // Field is not nullable but it's null, set error if ($field->Null == 'NO' && $this->$fieldName == '' && !in_array($fieldName, $skipFields)) { $text = str_replace('#__', 'COM_', $this->getTableName()) . '_ERR_' . $fieldName; $this->setError(JText::_(strtoupper($text))); $result = false; } } return $result; } /** * Method to reset class properties to the defaults set in the class * definition. It will ignore the primary key as well as any private class * properties. * * @return void */ public function reset() { if (!$this->onBeforeReset()) { return false; } // Get the default values for the class from the table. $fields = $this->getTableFields(); $j_fields = $this->getQueryJoinFields(); if ($j_fields) { $fields = array_merge($fields, $j_fields); } if (is_array($fields) && !empty($fields)) { foreach ($fields as $k => $v) { // If the property is not the primary key or private, reset it. if ($k != $this->_tbl_key && (strpos($k, '_') !== 0)) { $this->$k = $v->Default; } } if (!$this->onAfterReset()) { return false; } } } /** * Clones the current object, after resetting it * * @return static */ public function getClone() { $clone = clone $this; $clone->reset(); $key = $this->getKeyName(); $clone->$key = null; return $clone; } /** * Generic check for whether dependencies exist for this object in the db schema * * @param integer $oid The primary key of the record to delete * @param array $joins Any joins to foreign table, used to determine if dependent records exist * * @return boolean True if the record can be deleted */ public function canDelete($oid = null, $joins = null) { $k = $this->_tbl_key; if ($oid) { $this->$k = intval($oid); } if (is_array($joins)) { $db = $this->_db; $query = $db->getQuery(true) ->select($db->qn('master') . '.' . $db->qn($k)) ->from($db->qn($this->_tbl) . ' AS ' . $db->qn('master')); $tableNo = 0; foreach ($joins as $table) { $tableNo++; $query->select( array( 'COUNT(DISTINCT ' . $db->qn('t' . $tableNo) . '.' . $db->qn($table['idfield']) . ') AS ' . $db->qn($table['idalias']) ) ); $query->join('LEFT', $db->qn($table['name']) . ' AS ' . $db->qn('t' . $tableNo) . ' ON ' . $db->qn('t' . $tableNo) . '.' . $db->qn($table['joinfield']) . ' = ' . $db->qn('master') . '.' . $db->qn($k) ); } $query->where($db->qn('master') . '.' . $db->qn($k) . ' = ' . $db->q($this->$k)); $query->group($db->qn('master') . '.' . $db->qn($k)); $this->_db->setQuery((string) $query); if (version_compare(JVERSION, '3.0', 'ge')) { try { $obj = $this->_db->loadObject(); } catch (Exception $e) { $this->setError($e->getMessage()); } } else { if (!$obj = $this->_db->loadObject()) { $this->setError($this->_db->getErrorMsg()); return false; } } $msg = array(); $i = 0; foreach ($joins as $table) { $k = $table['idalias']; if ($obj->$k > 0) { $msg[] = JText::_($table['label']); } $i++; } if (count($msg)) { $option = $this->input->getCmd('option', 'com_foobar'); $comName = str_replace('com_', '', $option); $tview = str_replace('#__' . $comName . '_', '', $this->_tbl); $prefix = $option . '_' . $tview . '_NODELETE_'; foreach ($msg as $key) { $this->setError(JText::_($prefix . $key)); } return false; } else { return true; } } return true; } /** * Method to bind an associative array or object to the FOFTable instance.This * method only binds properties that are publicly accessible and optionally * takes an array of properties to ignore when binding. * * @param mixed $src An associative array or object to bind to the FOFTable instance. * @param mixed $ignore An optional array or space separated list of properties to ignore while binding. * * @return boolean True on success. * * @throws InvalidArgumentException */ public function bind($src, $ignore = array()) { if (!$this->onBeforeBind($src)) { return false; } // If the source value is not an array or object return false. if (!is_object($src) && !is_array($src)) { throw new InvalidArgumentException(sprintf('%s::bind(*%s*)', get_class($this), gettype($src))); } // If the source value is an object, get its accessible properties. if (is_object($src)) { $src = get_object_vars($src); } // If the ignore value is a string, explode it over spaces. if (!is_array($ignore)) { $ignore = explode(' ', $ignore); } // Bind the source value, excluding the ignored fields. foreach ($this->getKnownFields() as $k) { // Only process fields not in the ignore array. if (!in_array($k, $ignore)) { if (isset($src[$k])) { $this->$k = $src[$k]; } } } $result = $this->onAfterBind($src); return $result; } /** * Method to store a row in the database from the FOFTable instance properties. * If a primary key value is set the row with that primary key value will be * updated with the instance property values. If no primary key value is set * a new row will be inserted into the database with the properties from the * FOFTable instance. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. */ public function store($updateNulls = false) { if (!$this->onBeforeStore($updateNulls)) { return false; } $k = $this->_tbl_key; if ($this->$k == 0) { $this->$k = null; } // Create the object used for inserting/updating data to the database $fields = $this->getTableFields(); $properties = $this->getKnownFields(); $keys = array(); foreach ($properties as $property) { // 'input' property is a reserved name if (isset($fields[$property])) { $keys[] = $property; } } $updateObject = array(); foreach ($keys as $key) { $updateObject[$key] = $this->$key; } $updateObject = (object)$updateObject; /** * While the documentation for update/insertObject and execute() say they return a boolean, * not all of the implemtnations. Depending on the version of J! and the specific driver, * they may return a database object, or boolean, or a mix, or toss an exception. So try/catch, * and test for false. */ try { // If a primary key exists update the object, otherwise insert it. if ($this->$k) { $result = $this->_db->updateObject($this->_tbl, $updateObject, $this->_tbl_key, $updateNulls); } else { $result = $this->_db->insertObject($this->_tbl, $updateObject, $this->_tbl_key); } if ($result === false) { $this->setError($this->_db->getErrorMsg()); return false; } } catch (Exception $e) { $this->setError($e->getMessage()); } $this->bind($updateObject); if ($this->_locked) { $this->_unlock(); } $result = $this->onAfterStore(); return $result; } /** * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause. * Negative numbers move the row up in the sequence and positive numbers move it down. * * @param integer $delta The direction and magnitude to move the row in the ordering sequence. * @param string $where WHERE clause to use for limiting the selection of rows to compact the * ordering values. * * @return mixed Boolean True on success. * * @throws UnexpectedValueException */ public function move($delta, $where = '') { if (!$this->onBeforeMove($delta, $where)) { return false; } // If there is no ordering field set an error and return false. $ordering_field = $this->getColumnAlias('ordering'); if (!in_array($ordering_field, $this->getKnownFields())) { throw new UnexpectedValueException(sprintf('%s does not support ordering.', $this->_tbl)); } // If the change is none, do nothing. if (empty($delta)) { $result = $this->onAfterMove(); return $result; } $k = $this->_tbl_key; $row = null; $query = $this->_db->getQuery(true); // If the table is not loaded, return false if (empty($this->$k)) { return false; } // Select the primary key and ordering values from the table. $query->select(array($this->_db->qn($this->_tbl_key), $this->_db->qn($ordering_field))); $query->from($this->_tbl); // If the movement delta is negative move the row up. if ($delta < 0) { $query->where($this->_db->qn($ordering_field) . ' < ' . $this->_db->q((int) $this->$ordering_field)); $query->order($this->_db->qn($ordering_field) . ' DESC'); } // If the movement delta is positive move the row down. elseif ($delta > 0) { $query->where($this->_db->qn($ordering_field) . ' > ' . $this->_db->q((int) $this->$ordering_field)); $query->order($this->_db->qn($ordering_field) . ' ASC'); } // Add the custom WHERE clause if set. if ($where) { $query->where($where); } // Select the first row with the criteria. $this->_db->setQuery($query, 0, 1); $row = $this->_db->loadObject(); // If a row is found, move the item. if (!empty($row)) { // Update the ordering field for this instance to the row's ordering value. $query = $this->_db->getQuery(true); $query->update($this->_tbl); $query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $row->$ordering_field)); $query->where($this->_tbl_key . ' = ' . $this->_db->q($this->$k)); $this->_db->setQuery($query); $this->_db->execute(); // Update the ordering field for the row to this instance's ordering value. $query = $this->_db->getQuery(true); $query->update($this->_tbl); $query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $this->$ordering_field)); $query->where($this->_tbl_key . ' = ' . $this->_db->q($row->$k)); $this->_db->setQuery($query); $this->_db->execute(); // Update the instance value. $this->$ordering_field = $row->$ordering_field; } else { // Update the ordering field for this instance. $query = $this->_db->getQuery(true); $query->update($this->_tbl); $query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $this->$ordering_field)); $query->where($this->_tbl_key . ' = ' . $this->_db->q($this->$k)); $this->_db->setQuery($query); $this->_db->execute(); } $result = $this->onAfterMove(); return $result; } /** * Change the ordering of the records of the table * * @param string $where The WHERE clause of the SQL used to fetch the order * * @return boolean True is successful * * @throws UnexpectedValueException */ public function reorder($where = '') { if (!$this->onBeforeReorder($where)) { return false; } // If there is no ordering field set an error and return false. $order_field = $this->getColumnAlias('ordering'); if (!in_array($order_field, $this->getKnownFields())) { throw new UnexpectedValueException(sprintf('%s does not support ordering.', $this->_tbl_key)); } $k = $this->_tbl_key; // Get the primary keys and ordering values for the selection. $query = $this->_db->getQuery(true); $query->select($this->_tbl_key . ', ' . $this->_db->qn($order_field)); $query->from($this->_tbl); $query->where($this->_db->qn($order_field) . ' >= ' . $this->_db->q(0)); $query->order($this->_db->qn($order_field)); // Setup the extra where and ordering clause data. if ($where) { $query->where($where); } $this->_db->setQuery($query); $rows = $this->_db->loadObjectList(); // Compact the ordering values. foreach ($rows as $i => $row) { // Make sure the ordering is a positive integer. if ($row->$order_field >= 0) { // Only update rows that are necessary. if ($row->$order_field != $i + 1) { // Update the row ordering field. $query = $this->_db->getQuery(true); $query->update($this->_tbl); $query->set($this->_db->qn($order_field) . ' = ' . $this->_db->q($i + 1)); $query->where($this->_tbl_key . ' = ' . $this->_db->q($row->$k)); $this->_db->setQuery($query); $this->_db->execute(); } } } $result = $this->onAfterReorder(); return $result; } /** * Check out (lock) a record * * @param integer $userId The locking user's ID * @param integer $oid The primary key value of the record to lock * * @return boolean True on success */ public function checkout($userId, $oid = null) { $fldLockedBy = $this->getColumnAlias('locked_by'); $fldLockedOn = $this->getColumnAlias('locked_on'); if (!(in_array($fldLockedBy, $this->getKnownFields()) || in_array($fldLockedOn, $this->getKnownFields()))) { return true; } $k = $this->_tbl_key; if ($oid !== null) { $this->$k = $oid; } // No primary key defined, stop here if (!$this->$k) { return false; } $date = FOFPlatform::getInstance()->getDate(); if (method_exists($date, 'toSql')) { $time = $date->toSql(); } else { $time = $date->toMySQL(); } $query = $this->_db->getQuery(true) ->update($this->_db->qn($this->_tbl)) ->set( array( $this->_db->qn($fldLockedBy) . ' = ' . $this->_db->q((int) $userId), $this->_db->qn($fldLockedOn) . ' = ' . $this->_db->q($time) ) ) ->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($this->$k)); $this->_db->setQuery((string) $query); $this->$fldLockedBy = $userId; $this->$fldLockedOn = $time; return $this->_db->execute(); } /** * Check in (unlock) a record * * @param integer $oid The primary key value of the record to unlock * * @return boolean True on success */ public function checkin($oid = null) { $fldLockedBy = $this->getColumnAlias('locked_by'); $fldLockedOn = $this->getColumnAlias('locked_on'); if (!(in_array($fldLockedBy, $this->getKnownFields()) || in_array($fldLockedOn, $this->getKnownFields()))) { return true; } $k = $this->_tbl_key; if ($oid !== null) { $this->$k = $oid; } if ($this->$k == null) { return false; } $query = $this->_db->getQuery(true) ->update($this->_db->qn($this->_tbl)) ->set( array( $this->_db->qn($fldLockedBy) . ' = 0', $this->_db->qn($fldLockedOn) . ' = ' . $this->_db->q($this->_db->getNullDate()) ) ) ->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($this->$k)); $this->_db->setQuery((string) $query); $this->$fldLockedBy = 0; $this->$fldLockedOn = ''; return $this->_db->execute(); } /** * Is a record locked? * * @param integer $with The userid to preform the match with. If an item is checked * out by this user the function will return false. * @param integer $unused_against Junk inherited from JTable; ignore * * @throws UnexpectedValueException * * @return boolean True if the record is locked by another user */ public function isCheckedOut($with = 0, $unused_against = null) { $against = null; $fldLockedBy = $this->getColumnAlias('locked_by'); $k = $this->_tbl_key; // If no primary key is given, return false. if ($this->$k === null) { throw new UnexpectedValueException('Null primary key not allowed.'); } if (isset($this) && is_a($this, 'FOFTable') && !$against) { $against = $this->get($fldLockedBy); } // Item is not checked out, or being checked out by the same user if (!$against || $against == $with) { return false; } $session = JTable::getInstance('session'); return $session->exists($against); } /** * Copy (duplicate) one or more records * * @param integer|array $cid The primary key value (or values) or the record(s) to copy * * @return boolean True on success */ public function copy($cid = null) { //We have to cast the id as array, or the helper function will return an empty set if($cid) { $cid = (array) $cid; } FOFUtilsArray::toInteger($cid); $k = $this->_tbl_key; if (count($cid) < 1) { if ($this->$k) { $cid = array($this->$k); } else { $this->setError("No items selected."); return false; } } $created_by = $this->getColumnAlias('created_by'); $created_on = $this->getColumnAlias('created_on'); $modified_by = $this->getColumnAlias('modified_by'); $modified_on = $this->getColumnAlias('modified_on'); $locked_byName = $this->getColumnAlias('locked_by'); $checkin = in_array($locked_byName, $this->getKnownFields()); foreach ($cid as $item) { // Prevent load with id = 0 if (!$item) { continue; } $this->load($item); if ($checkin) { // We're using the checkin and the record is used by someone else if ($this->isCheckedOut($item)) { continue; } } if (!$this->onBeforeCopy($item)) { continue; } $this->$k = null; $this->$created_by = null; $this->$created_on = null; $this->$modified_on = null; $this->$modified_by = null; // Let's fire the event only if everything is ok if ($this->store()) { $this->onAfterCopy($item); } $this->reset(); } return true; } /** * Publish or unpublish records * * @param integer|array $cid The primary key value(s) of the item(s) to publish/unpublish * @param integer $publish 1 to publish an item, 0 to unpublish * @param integer $user_id The user ID of the user (un)publishing the item. * * @return boolean True on success, false on failure (e.g. record is locked) */ public function publish($cid = null, $publish = 1, $user_id = 0) { $enabledName = $this->getColumnAlias('enabled'); $locked_byName = $this->getColumnAlias('locked_by'); // Mhm... you called the publish method on a table without publish support... if(!in_array($enabledName, $this->getKnownFields())) { return false; } //We have to cast the id as array, or the helper function will return an empty set if($cid) { $cid = (array) $cid; } FOFUtilsArray::toInteger($cid); $user_id = (int) $user_id; $publish = (int) $publish; $k = $this->_tbl_key; if (count($cid) < 1) { if ($this->$k) { $cid = array($this->$k); } else { $this->setError("No items selected."); return false; } } if (!$this->onBeforePublish($cid, $publish)) { return false; } $query = $this->_db->getQuery(true) ->update($this->_db->qn($this->_tbl)) ->set($this->_db->qn($enabledName) . ' = ' . (int) $publish); $checkin = in_array($locked_byName, $this->getKnownFields()); if ($checkin) { $query->where( ' (' . $this->_db->qn($locked_byName) . ' = 0 OR ' . $this->_db->qn($locked_byName) . ' = ' . (int) $user_id . ')', 'AND' ); } // TODO Rewrite this statment using IN. Check if it work in SQLServer and PostgreSQL $cids = $this->_db->qn($k) . ' = ' . implode(' OR ' . $this->_db->qn($k) . ' = ', $cid); $query->where('(' . $cids . ')'); $this->_db->setQuery((string) $query); if (version_compare(JVERSION, '3.0', 'ge')) { try { $this->_db->execute(); } catch (Exception $e) { $this->setError($e->getMessage()); } } else { if (!$this->_db->execute()) { $this->setError($this->_db->getErrorMsg()); return false; } } if (count($cid) == 1 && $checkin) { if ($this->_db->getAffectedRows() == 1) { $this->checkin($cid[0]); if ($this->$k == $cid[0]) { $this->$enabledName = $publish; } } } $this->setError(''); return true; } /** * Delete a record * * @param integer $oid The primary key value of the item to delete * * @throws UnexpectedValueException * * @return boolean True on success */ public function delete($oid = null) { if ($oid) { $this->load($oid); } $k = $this->_tbl_key; $pk = (!$oid) ? $this->$k : $oid; // If no primary key is given, return false. if (!$pk) { throw new UnexpectedValueException('Null primary key not allowed.'); } // Execute the logic only if I have a primary key, otherwise I could have weird results if (!$this->onBeforeDelete($oid)) { return false; } // Delete the row by primary key. $query = $this->_db->getQuery(true); $query->delete(); $query->from($this->_tbl); $query->where($this->_tbl_key . ' = ' . $this->_db->q($pk)); $this->_db->setQuery($query); $this->_db->execute(); $result = $this->onAfterDelete($oid); return $result; } /** * Register a hit on a record * * @param integer $oid The primary key value of the record * @param boolean $log Should I log the hit? * * @return boolean True on success */ public function hit($oid = null, $log = false) { if (!$this->onBeforeHit($oid, $log)) { return false; } // If there is no hits field, just return true. $hits_field = $this->getColumnAlias('hits'); if (!in_array($hits_field, $this->getKnownFields())) { return true; } $k = $this->_tbl_key; $pk = ($oid) ? $oid : $this->$k; // If no primary key is given, return false. if (!$pk) { $result = false; } else { // Check the row in by primary key. $query = $this->_db->getQuery(true) ->update($this->_tbl) ->set($this->_db->qn($hits_field) . ' = (' . $this->_db->qn($hits_field) . ' + 1)') ->where($this->_tbl_key . ' = ' . $this->_db->q($pk)); $this->_db->setQuery($query)->execute(); // In order to update the table object, I have to load the table if(!$this->$k) { $query = $this->_db->getQuery(true) ->select($this->_db->qn($hits_field)) ->from($this->_db->qn($this->_tbl)) ->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($pk)); $this->$hits_field = $this->_db->setQuery($query)->loadResult(); } else { // Set table values in the object. $this->$hits_field++; } $result = true; } if ($result) { $result = $this->onAfterHit($oid); } return $result; } /** * Export the item as a CSV line * * @param string $separator CSV separator. Tip: use "\t" to get a TSV file instead. * * @return string The CSV line */ public function toCSV($separator = ',') { $csv = array(); foreach (get_object_vars($this) as $k => $v) { if (!in_array($k, $this->getKnownFields())) { continue; } $csv[] = '"' . str_replace('"', '""', $v) . '"'; } $csv = implode($separator, $csv); return $csv; } /** * Exports the table in array format * * @return array */ public function getData() { $ret = array(); foreach (get_object_vars($this) as $k => $v) { if (!in_array($k, $this->getKnownFields())) { continue; } $ret[$k] = $v; } return $ret; } /** * Get the header for exporting item list to CSV * * @param string $separator CSV separator. Tip: use "\t" to get a TSV file instead. * * @return string The CSV file's header */ public function getCSVHeader($separator = ',') { $csv = array(); foreach (get_object_vars($this) as $k => $v) { if (!in_array($k, $this->getKnownFields())) { continue; } $csv[] = '"' . str_replace('"', '\"', $k) . '"'; } $csv = implode($separator, $csv); return $csv; } /** * Get the columns from a database table. * * @param string $tableName Table name. If null current table is used * * @return mixed An array of the field names, or false if an error occurs. */ public function getTableFields($tableName = null) { // Should I load the cached data? $useCache = array_key_exists('use_table_cache', $this->config) ? $this->config['use_table_cache'] : false; // Make sure we have a list of tables in this db if (empty(self::$tableCache)) { if ($useCache) { // Try to load table cache from a cache file $cacheData = FOFPlatform::getInstance()->getCache('tables', null); // Unserialise the cached data, or set the table cache to empty // if the cache data wasn't loaded. if (!is_null($cacheData)) { self::$tableCache = json_decode($cacheData, true); } else { self::$tableCache = array(); } } // This check is true if the cache data doesn't exist / is not loaded if (empty(self::$tableCache)) { self::$tableCache = $this->_db->getTableList(); if ($useCache) { FOFPlatform::getInstance()->setCache('tables', json_encode(self::$tableCache)); } } } // Make sure the cached table fields cache is loaded if (empty(self::$tableFieldCache)) { if ($useCache) { // Try to load table cache from a cache file $cacheData = FOFPlatform::getInstance()->getCache('tablefields', null); // Unserialise the cached data, or set to empty if the cache // data wasn't loaded. if (!is_null($cacheData)) { $decoded = json_decode($cacheData, true); $tableCache = array(); if (count($decoded)) { foreach ($decoded as $myTableName => $tableFields) { $temp = array(); if (is_array($tableFields)) { foreach($tableFields as $field => $def) { $temp[$field] = (object)$def; } $tableCache[$myTableName] = $temp; } elseif (is_object($tableFields) || is_bool($tableFields)) { $tableCache[$myTableName] = $tableFields; } } } self::$tableFieldCache = $tableCache; } else { self::$tableFieldCache = array(); } } } if (!$tableName) { $tableName = $this->_tbl; } // Try to load again column specifications if the table is not loaded OR if it's loaded and // the previous call returned an error if (!array_key_exists($tableName, self::$tableFieldCache) || (isset(self::$tableFieldCache[$tableName]) && !self::$tableFieldCache[$tableName])) { // Lookup the fields for this table only once. $name = $tableName; $prefix = $this->_db->getPrefix(); if (substr($name, 0, 3) == '#__') { $checkName = $prefix . substr($name, 3); } else { $checkName = $name; } if (!in_array($checkName, self::$tableCache)) { // The table doesn't exist. Return false. self::$tableFieldCache[$tableName] = false; } elseif (version_compare(JVERSION, '3.0', 'ge')) { $fields = $this->_db->getTableColumns($name, false); if (empty($fields)) { $fields = false; } self::$tableFieldCache[$tableName] = $fields; } else { $fields = $this->_db->getTableFields($name, false); if (!isset($fields[$name])) { $fields = false; } self::$tableFieldCache[$tableName] = $fields[$name]; } // PostgreSQL date type compatibility if (($this->_db->name == 'postgresql') && (self::$tableFieldCache[$tableName] != false)) { foreach (self::$tableFieldCache[$tableName] as $field) { if (strtolower($field->type) == 'timestamp without time zone') { if (stristr($field->Default, '\'::timestamp without time zone')) { list ($date, $junk) = explode('::', $field->Default, 2); $field->Default = trim($date, "'"); } } } } // Save the data for this table into the cache if ($useCache) { $cacheData = FOFPlatform::getInstance()->setCache('tablefields', json_encode(self::$tableFieldCache)); } } return self::$tableFieldCache[$tableName]; } public function getTableAlias() { return $this->_tableAlias; } public function setTableAlias($string) { $string = preg_replace('#[^A-Z0-9_]#i', '', $string); $this->_tableAlias = $string; } /** * Method to return the real name of a "special" column such as ordering, hits, published * etc etc. In this way you are free to follow your db naming convention and use the * built in Joomla functions. * * @param string $column Name of the "special" column (ie ordering, hits etc etc) * * @return string The string that identify the special */ public function getColumnAlias($column) { if (isset($this->_columnAlias[$column])) { $return = $this->_columnAlias[$column]; } else { $return = $column; } $return = preg_replace('#[^A-Z0-9_]#i', '', $return); return $return; } /** * Method to register a column alias for a "special" column. * * @param string $column The "special" column (ie ordering) * @param string $columnAlias The real column name (ie foo_ordering) * * @return void */ public function setColumnAlias($column, $columnAlias) { $column = strtolower($column); $column = preg_replace('#[^A-Z0-9_]#i', '', $column); $this->_columnAlias[$column] = $columnAlias; } /** * Get a JOIN query, used to join other tables * * @param boolean $asReference Return an object reference instead of a copy * * @return FOFDatabaseQuery Query used to join other tables */ public function getQueryJoin($asReference = false) { if ($asReference) { return $this->_queryJoin; } else { if ($this->_queryJoin) { return clone $this->_queryJoin; } else { return null; } } } /** * Sets the query with joins to other tables * * @param FOFDatabaseQuery $query The JOIN query to use * * @return void */ public function setQueryJoin($query) { $this->_queryJoin = $query; } /** * Extracts the fields from the join query * * @return array Fields contained in the join query */ protected function getQueryJoinFields() { $query = $this->getQueryJoin(); if (!$query) { return array(); } $tables = array(); $j_tables = array(); $j_fields = array(); // Get joined tables. Ignore FROM clause, since it should not be used (the starting point is the table "table") $joins = $query->join; foreach ($joins as $join) { $tables = array_merge($tables, $join->getElements()); } // Clean up table names foreach($tables as $table) { preg_match('#(.*)((\w)*(on|using))(.*)#i', $table, $matches); if($matches && isset($matches[1])) { // I always want the first part, no matter what $parts = explode(' ', $matches[1]); $t_table = $parts[0]; if($this->isQuoted($t_table)) { $t_table = substr($t_table, 1, strlen($t_table) - 2); } if(!in_array($t_table, $j_tables)) { $j_tables[] = $t_table; } } } // Do I have the current table inside the query join? Remove it (its fields are already ok) $find = array_search($this->getTableName(), $j_tables); if($find !== false) { unset($j_tables[$find]); } // Get table fields $fields = array(); foreach ($j_tables as $table) { $t_fields = $this->getTableFields($table); if ($t_fields) { $fields = array_merge($fields, $t_fields); } } // Remove any fields that aren't in the joined select $j_select = $query->select; if ($j_select && $j_select->getElements()) { $j_fields = $this->normalizeSelectFields($j_select->getElements()); } // I can intesect the keys $fields = array_intersect_key($fields, $j_fields); // Now I walk again the array to change the key of columns that have an alias foreach ($j_fields as $column => $alias) { if ($column != $alias) { $fields[$alias] = $fields[$column]; unset($fields[$column]); } } return $fields; } /** * Normalizes the fields, returning an associative array with all the fields. * Ie array('foobar as foo, bar') becomes array('foobar' => 'foo', 'bar' => 'bar') * * @param array $fields Array with column fields * * @return array Normalized array */ protected function normalizeSelectFields($fields) { $db = FOFPlatform::getInstance()->getDbo(); $return = array(); foreach ($fields as $field) { $t_fields = explode(',', $field); foreach ($t_fields as $t_field) { // Is there any alias? $parts = preg_split('#\sas\s#i', $t_field); // Do I have a table.column situation? Let's get the field name $tableField = explode('.', $parts[0]); if(isset($tableField[1])) { $column = trim($tableField[1]); } else { $column = trim($tableField[0]); } // Is this field quoted? If so, remove the quotes if($this->isQuoted($column)) { $column = substr($column, 1, strlen($column) - 2); } if(isset($parts[1])) { $alias = trim($parts[1]); // Is this field quoted? If so, remove the quotes if($this->isQuoted($alias)) { $alias = substr($alias, 1, strlen($alias) - 2); } } else { $alias = $column; } $return[$column] = $alias; } } return $return; } /** * Is the field quoted? * * @param string $column Column field * * @return bool Is the field quoted? */ protected function isQuoted($column) { // Empty string, un-quoted by definition if(!$column) { return false; } // I need some "magic". If the first char is not a letter, a number // an underscore or # (needed for table), then most likely the field is quoted preg_match_all('/^[a-z0-9_#]/i', $column, $matches); if(!$matches[0]) { return true; } return false; } /** * The event which runs before binding data to the table * * NOTE TO 3RD PARTY DEVELOPERS: * * When you override the following methods in your child classes, * be sure to call parent::method *AFTER* your code, otherwise the * plugin events do NOT get triggered * * Example: * protected function onBeforeBind(){ * // Your code here * return parent::onBeforeBind() && $your_result; * } * * Do not do it the other way around, e.g. return $your_result && parent::onBeforeBind() * Due to PHP short-circuit boolean evaluation the parent::onBeforeBind() * will not be called if $your_result is false. * * @param object|array &$from The data to bind * * @return boolean True on success */ protected function onBeforeBind(&$from) { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeBind', array(&$this, &$from)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeBind' . ucfirst($name), array(&$this, &$from)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after loading a record from the database * * @param boolean &$result Did the load succeeded? * * @return void */ protected function onAfterLoad(&$result) { // Call the behaviors $eventResult = $this->tableDispatcher->trigger('onAfterLoad', array(&$this, &$result)); if (in_array(false, $eventResult, true)) { // Behavior failed, return false $result = false; return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); FOFPlatform::getInstance()->runPlugins('onAfterLoad' . ucfirst($name), array(&$this, &$result)); } } /** * The event which runs before storing (saving) data to the database * * @param boolean $updateNulls Should nulls be saved as nulls (true) or just skipped over (false)? * * @return boolean True to allow saving */ protected function onBeforeStore($updateNulls) { // Do we have a "Created" set of fields? $created_on = $this->getColumnAlias('created_on'); $created_by = $this->getColumnAlias('created_by'); $modified_on = $this->getColumnAlias('modified_on'); $modified_by = $this->getColumnAlias('modified_by'); $locked_on = $this->getColumnAlias('locked_on'); $locked_by = $this->getColumnAlias('locked_by'); $title = $this->getColumnAlias('title'); $slug = $this->getColumnAlias('slug'); $hasCreatedOn = in_array($created_on, $this->getKnownFields()); $hasCreatedBy = in_array($created_by, $this->getKnownFields()); if ($hasCreatedOn && $hasCreatedBy) { $hasModifiedOn = in_array($modified_on, $this->getKnownFields()); $hasModifiedBy = in_array($modified_by, $this->getKnownFields()); $nullDate = $this->_db->getNullDate(); if (empty($this->$created_by) || ($this->$created_on == $nullDate) || empty($this->$created_on)) { $uid = FOFPlatform::getInstance()->getUser()->id; if ($uid) { $this->$created_by = FOFPlatform::getInstance()->getUser()->id; } $date = FOFPlatform::getInstance()->getDate('now', null, false); $this->$created_on = method_exists($date, 'toSql') ? $date->toSql() : $date->toMySQL(); } elseif ($hasModifiedOn && $hasModifiedBy) { $uid = FOFPlatform::getInstance()->getUser()->id; if ($uid) { $this->$modified_by = FOFPlatform::getInstance()->getUser()->id; } $date = FOFPlatform::getInstance()->getDate('now', null, false); $this->$modified_on = method_exists($date, 'toSql') ? $date->toSql() : $date->toMySQL(); } } // Do we have a set of title and slug fields? $hasTitle = in_array($title, $this->getKnownFields()); $hasSlug = in_array($slug, $this->getKnownFields()); if ($hasTitle && $hasSlug) { if (empty($this->$slug)) { // Create a slug from the title $this->$slug = FOFStringUtils::toSlug($this->$title); } else { // Filter the slug for invalid characters $this->$slug = FOFStringUtils::toSlug($this->$slug); } // Make sure we don't have a duplicate slug on this table $db = $this->getDbo(); $query = $db->getQuery(true) ->select($db->qn($slug)) ->from($this->_tbl) ->where($db->qn($slug) . ' = ' . $db->q($this->$slug)) ->where('NOT ' . $db->qn($this->_tbl_key) . ' = ' . $db->q($this->{$this->_tbl_key})); $db->setQuery($query); $existingItems = $db->loadAssocList(); $count = 0; $newSlug = $this->$slug; while (!empty($existingItems)) { $count++; $newSlug = $this->$slug . '-' . $count; $query = $db->getQuery(true) ->select($db->qn($slug)) ->from($this->_tbl) ->where($db->qn($slug) . ' = ' . $db->q($newSlug)) ->where('NOT '. $db->qn($this->_tbl_key) . ' = ' . $db->q($this->{$this->_tbl_key})); $db->setQuery($query); $existingItems = $db->loadAssocList(); } $this->$slug = $newSlug; } // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeStore', array(&$this, $updateNulls)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } // Execute onBeforeStore<tablename> events in loaded plugins if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeStore' . ucfirst($name), array(&$this, $updateNulls)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after binding data to the class * * @param object|array &$src The data to bind * * @return boolean True to allow binding without an error */ protected function onAfterBind(&$src) { // Call the behaviors $options = array( 'component' => $this->input->get('option'), 'view' => $this->input->get('view'), 'table_prefix' => $this->_tablePrefix ); $result = $this->tableDispatcher->trigger('onAfterBind', array(&$this, &$src, $options)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterBind' . ucfirst($name), array(&$this, &$src)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after storing (saving) data to the database * * @return boolean True to allow saving without an error */ protected function onAfterStore() { // Call the behaviors $result = $this->tableDispatcher->trigger('onAfterStore', array(&$this)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterStore' . ucfirst($name), array(&$this)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs before moving a record * * @param boolean $updateNulls Should nulls be saved as nulls (true) or just skipped over (false)? * * @return boolean True to allow moving */ protected function onBeforeMove($updateNulls) { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeMove', array(&$this, $updateNulls)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeMove' . ucfirst($name), array(&$this, $updateNulls)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after moving a record * * @return boolean True to allow moving without an error */ protected function onAfterMove() { // Call the behaviors $result = $this->tableDispatcher->trigger('onAfterMove', array(&$this)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterMove' . ucfirst($name), array(&$this)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs before reordering a table * * @param string $where The WHERE clause of the SQL query to run on reordering (record filter) * * @return boolean True to allow reordering */ protected function onBeforeReorder($where = '') { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeReorder', array(&$this, $where)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeReorder' . ucfirst($name), array(&$this, $where)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after reordering a table * * @return boolean True to allow the reordering to complete without an error */ protected function onAfterReorder() { // Call the behaviors $result = $this->tableDispatcher->trigger('onAfterReorder', array(&$this)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterReorder' . ucfirst($name), array(&$this)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs before deleting a record * * @param integer $oid The PK value of the record to delete * * @return boolean True to allow the deletion */ protected function onBeforeDelete($oid) { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeDelete', array(&$this, $oid)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeDelete' . ucfirst($name), array(&$this, $oid)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after deleting a record * * @param integer $oid The PK value of the record which was deleted * * @return boolean True to allow the deletion without errors */ protected function onAfterDelete($oid) { // Call the behaviors $result = $this->tableDispatcher->trigger('onAfterDelete', array(&$this, $oid)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterDelete' . ucfirst($name), array(&$this, $oid)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs before hitting a record * * @param integer $oid The PK value of the record to hit * @param boolean $log Should we log the hit? * * @return boolean True to allow the hit */ protected function onBeforeHit($oid, $log) { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeHit', array(&$this, $oid, $log)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeHit' . ucfirst($name), array(&$this, $oid, $log)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after hitting a record * * @param integer $oid The PK value of the record which was hit * * @return boolean True to allow the hitting without errors */ protected function onAfterHit($oid) { // Call the behaviors $result = $this->tableDispatcher->trigger('onAfterHit', array(&$this, $oid)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterHit' . ucfirst($name), array(&$this, $oid)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The even which runs before copying a record * * @param integer $oid The PK value of the record being copied * * @return boolean True to allow the copy to take place */ protected function onBeforeCopy($oid) { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeCopy', array(&$this, $oid)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeCopy' . ucfirst($name), array(&$this, $oid)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The even which runs after copying a record * * @param integer $oid The PK value of the record which was copied (not the new one) * * @return boolean True to allow the copy without errors */ protected function onAfterCopy($oid) { // Call the behaviors $result = $this->tableDispatcher->trigger('onAfterCopy', array(&$this, $oid)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterCopy' . ucfirst($name), array(&$this, $oid)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs before a record is (un)published * * @param integer|array &$cid The PK IDs of the records being (un)published * @param integer $publish 1 to publish, 0 to unpublish * * @return boolean True to allow the (un)publish to proceed */ protected function onBeforePublish(&$cid, $publish) { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforePublish', array(&$this, &$cid, $publish)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforePublish' . ucfirst($name), array(&$this, &$cid, $publish)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The event which runs after the object is reset to its default values. * * @return boolean True to allow the reset to complete without errors */ protected function onAfterReset() { // Call the behaviors $result = $this->tableDispatcher->trigger('onAfterReset', array(&$this)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onAfterReset' . ucfirst($name), array(&$this)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * The even which runs before the object is reset to its default values. * * @return boolean True to allow the reset to complete */ protected function onBeforeReset() { // Call the behaviors $result = $this->tableDispatcher->trigger('onBeforeReset', array(&$this)); if (in_array(false, $result, true)) { // Behavior failed, return false return false; } if ($this->_trigger_events) { $name = FOFInflector::pluralize($this->getKeyName()); $result = FOFPlatform::getInstance()->runPlugins('onBeforeReset' . ucfirst($name), array(&$this)); if (in_array(false, $result, true)) { return false; } else { return true; } } return true; } /** * Replace the input object of this table with the provided FOFInput object * * @param FOFInput $input The new input object * * @return void */ public function setInput(FOFInput $input) { $this->input = $input; } /** * Get the columns from database table. * * @return mixed An array of the field names, or false if an error occurs. * * @deprecated 2.1 */ public function getFields() { return $this->getTableFields(); } /** * Add a filesystem path where FOFTable should search for table class files. * You may either pass a string or an array of paths. * * @param mixed $path A filesystem path or array of filesystem paths to add. * * @return array An array of filesystem paths to find FOFTable classes in. */ public static function addIncludePath($path = null) { // If the internal paths have not been initialised, do so with the base table path. if (empty(self::$_includePaths)) { self::$_includePaths = array(__DIR__); } // Convert the passed path(s) to add to an array. settype($path, 'array'); // If we have new paths to add, do so. if (!empty($path) && !in_array($path, self::$_includePaths)) { // Check and add each individual new path. foreach ($path as $dir) { // Sanitize path. $dir = trim($dir); // Add to the front of the list so that custom paths are searched first. array_unshift(self::$_includePaths, $dir); } } return self::$_includePaths; } /** * Loads the asset table related to this table. * This will help tests, too, since we can mock this function. * * @return bool|JTableAsset False on failure, otherwise JTableAsset */ protected function getAsset() { $name = $this->_getAssetName(); // Do NOT touch JTable here -- we are loading the core asset table which is a JTable, not a FOFTable $asset = JTable::getInstance('Asset'); if (!$asset->loadByName($name)) { return false; } return $asset; } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @throws UnexpectedValueException * * @return string */ public function getAssetName() { $k = $this->_tbl_key; // If there is no assetKey defined, stop here, or we'll get a wrong name if(!$this->_assetKey || !$this->$k) { throw new UnexpectedValueException('Table must have an asset key defined and a value for the table id in order to track assets'); } return $this->_assetKey . '.' . (int) $this->$k; } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @throws UnexpectedValueException * * @return string */ public function getAssetKey() { return $this->_assetKey; } /** * Method to return the title to use for the asset table. In * tracking the assets a title is kept for each asset so that there is some * context available in a unified access manager. Usually this would just * return $this->title or $this->name or whatever is being used for the * primary name of the row. If this method is not overridden, the asset name is used. * * @return string The string to use as the title in the asset table. */ public function getAssetTitle() { return $this->getAssetName(); } /** * Method to get the parent asset under which to register this one. * By default, all assets are registered to the ROOT node with ID, * which will default to 1 if none exists. * The extended class can define a table and id to lookup. If the * asset does not exist it will be created. * * @param FOFTable $table A FOFTable object for the asset parent. * @param integer $id Id to look up * * @return integer */ public function getAssetParentId($table = null, $id = null) { // For simple cases, parent to the asset root. $assets = JTable::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo())); $rootId = $assets->getRootId(); if (!empty($rootId)) { return $rootId; } return 1; } /** * This method sets the asset key for the items of this table. Obviously, it * is only meant to be used when you have a table with an asset field. * * @param string $assetKey The name of the asset key to use * * @return void */ public function setAssetKey($assetKey) { $this->_assetKey = $assetKey; } /** * Method to get the database table name for the class. * * @return string The name of the database table being modeled. */ public function getTableName() { return $this->_tbl; } /** * Method to get the primary key field name for the table. * * @return string The name of the primary key for the table. */ public function getKeyName() { return $this->_tbl_key; } /** * Returns the identity value of this record * * @return mixed */ public function getId() { $key = $this->getKeyName(); return $this->$key; } /** * Method to get the FOFDatabaseDriver object. * * @return FOFDatabaseDriver The internal database driver object. */ public function getDbo() { return $this->_db; } /** * Method to set the FOFDatabaseDriver object. * * @param FOFDatabaseDriver $db A FOFDatabaseDriver object to be used by the table object. * * @return boolean True on success. */ public function setDBO($db) { $this->_db = $db; return true; } /** * Method to set rules for the record. * * @param mixed $input A JAccessRules object, JSON string, or array. * * @return void */ public function setRules($input) { if ($input instanceof JAccessRules) { $this->_rules = $input; } else { $this->_rules = new JAccessRules($input); } } /** * Method to get the rules for the record. * * @return JAccessRules object */ public function getRules() { return $this->_rules; } /** * Method to check if the record is treated as an ACL asset * * @return boolean [description] */ public function isAssetsTracked() { return $this->_trackAssets; } /** * Method to manually set this record as ACL asset or not. * We have to do this since the automatic check is made in the constructor, but here we can't set any alias. * So, even if you have an alias for `asset_id`, it wouldn't be reconized and assets won't be tracked. * * @param $state */ public function setAssetsTracked($state) { $state = (bool) $state; if($state) { JLoader::import('joomla.access.rules'); } $this->_trackAssets = $state; } /** * Method to provide a shortcut to binding, checking and storing a FOFTable * instance to the database table. The method will check a row in once the * data has been stored and if an ordering filter is present will attempt to * reorder the table rows based on the filter. The ordering filter is an instance * property name. The rows that will be reordered are those whose value matches * the FOFTable instance for the property specified. * * @param mixed $src An associative array or object to bind to the FOFTable instance. * @param string $orderingFilter Filter for the order updating * @param mixed $ignore An optional array or space separated list of properties * to ignore while binding. * * @return boolean True on success. */ public function save($src, $orderingFilter = '', $ignore = '') { // Attempt to bind the source to the instance. if (!$this->bind($src, $ignore)) { return false; } // Run any sanity checks on the instance and verify that it is ready for storage. if (!$this->check()) { return false; } // Attempt to store the properties to the database table. if (!$this->store()) { return false; } // Attempt to check the row in, just in case it was checked out. if (!$this->checkin()) { return false; } // If an ordering filter is set, attempt reorder the rows in the table based on the filter and value. if ($orderingFilter) { $filterValue = $this->$orderingFilter; $this->reorder($orderingFilter ? $this->_db->qn($orderingFilter) . ' = ' . $this->_db->q($filterValue) : ''); } // Set the error to empty and return true. $this->setError(''); return true; } /** * Method to get the next ordering value for a group of rows defined by an SQL WHERE clause. * This is useful for placing a new item last in a group of items in the table. * * @param string $where WHERE clause to use for selecting the MAX(ordering) for the table. * * @return mixed Boolean false an failure or the next ordering value as an integer. */ public function getNextOrder($where = '') { // If there is no ordering field set an error and return false. $ordering = $this->getColumnAlias('ordering'); if (!in_array($ordering, $this->getKnownFields())) { throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this))); } // Get the largest ordering value for a given where clause. $query = $this->_db->getQuery(true); $query->select('MAX('.$this->_db->qn($ordering).')'); $query->from($this->_tbl); if ($where) { $query->where($where); } $this->_db->setQuery($query); $max = (int) $this->_db->loadResult(); // Return the largest ordering value + 1. return ($max + 1); } /** * Method to lock the database table for writing. * * @return boolean True on success. * * @throws RuntimeException */ protected function _lock() { $this->_db->lockTable($this->_tbl); $this->_locked = true; return true; } /** * Method to unlock the database table for writing. * * @return boolean True on success. */ protected function _unlock() { $this->_db->unlockTables(); $this->_locked = false; return true; } public function setConfig(array $config) { $this->config = $config; } /** * Get the content type for ucm * * @return string The content type alias */ public function getContentType() { if ($this->contentType) { return $this->contentType; } /** * When tags was first introduced contentType variable didn't exist - so we guess one * This will fail if content history behvaiour is enabled. This code is deprecated * and will be removed in FOF 3.0 in favour of the content type class variable */ $component = $this->input->get('option'); $view = FOFInflector::singularize($this->input->get('view')); $alias = $component . '.' . $view; return $alias; } /** * Returns the table relations object of the current table, lazy-loading it if necessary * * @return FOFTableRelations */ public function getRelations() { if (is_null($this->_relations)) { $this->_relations = new FOFTableRelations($this); } return $this->_relations; } /** * Gets a reference to the configuration parameters provider for this table * * @return FOFConfigProvider */ public function getConfigProvider() { return $this->configProvider; } /** * Returns the configuration parameters provider's key for this table * * @return string */ public function getConfigProviderKey() { return $this->_configProviderKey; } /** * Check if a UCM content type exists for this resource, and * create it if it does not * * @param string $alias The content type alias (optional) * * @return null */ public function checkContentType($alias = null) { $contentType = new JTableContenttype($this->getDbo()); if (!$alias) { $alias = $this->getContentType(); } $aliasParts = explode('.', $alias); // Fetch the extension name $component = $aliasParts[0]; $component = JComponentHelper::getComponent($component); // Fetch the name using the menu item $query = $this->getDbo()->getQuery(true); $query->select('title')->from('#__menu')->where('component_id = ' . (int) $component->id); $this->getDbo()->setQuery($query); $component_name = JText::_($this->getDbo()->loadResult()); $name = $component_name . ' ' . ucfirst($aliasParts[1]); // Create a new content type for our resource if (!$contentType->load(array('type_alias' => $alias))) { $contentType->type_title = $name; $contentType->type_alias = $alias; $contentType->table = json_encode( array( 'special' => array( 'dbtable' => $this->getTableName(), 'key' => $this->getKeyName(), 'type' => $name, 'prefix' => $this->_tablePrefix, 'class' => 'FOFTable', 'config' => 'array()' ), 'common' => array( 'dbtable' => '#__ucm_content', 'key' => 'ucm_id', 'type' => 'CoreContent', 'prefix' => 'JTable', 'config' => 'array()' ) ) ); $contentType->field_mappings = json_encode( array( 'common' => array( 0 => array( "core_content_item_id" => $this->getKeyName(), "core_title" => $this->getUcmCoreAlias('title'), "core_state" => $this->getUcmCoreAlias('enabled'), "core_alias" => $this->getUcmCoreAlias('alias'), "core_created_time" => $this->getUcmCoreAlias('created_on'), "core_modified_time" => $this->getUcmCoreAlias('created_by'), "core_body" => $this->getUcmCoreAlias('body'), "core_hits" => $this->getUcmCoreAlias('hits'), "core_publish_up" => $this->getUcmCoreAlias('publish_up'), "core_publish_down" => $this->getUcmCoreAlias('publish_down'), "core_access" => $this->getUcmCoreAlias('access'), "core_params" => $this->getUcmCoreAlias('params'), "core_featured" => $this->getUcmCoreAlias('featured'), "core_metadata" => $this->getUcmCoreAlias('metadata'), "core_language" => $this->getUcmCoreAlias('language'), "core_images" => $this->getUcmCoreAlias('images'), "core_urls" => $this->getUcmCoreAlias('urls'), "core_version" => $this->getUcmCoreAlias('version'), "core_ordering" => $this->getUcmCoreAlias('ordering'), "core_metakey" => $this->getUcmCoreAlias('metakey'), "core_metadesc" => $this->getUcmCoreAlias('metadesc'), "core_catid" => $this->getUcmCoreAlias('cat_id'), "core_xreference" => $this->getUcmCoreAlias('xreference'), "asset_id" => $this->getUcmCoreAlias('asset_id') ) ), 'special' => array( 0 => array( ) ) ) ); $ignoreFields = array( $this->getUcmCoreAlias('modified_on', null), $this->getUcmCoreAlias('modified_by', null), $this->getUcmCoreAlias('locked_by', null), $this->getUcmCoreAlias('locked_on', null), $this->getUcmCoreAlias('hits', null), $this->getUcmCoreAlias('version', null) ); $contentType->content_history_options = json_encode( array( "ignoreChanges" => array_filter($ignoreFields, 'strlen') ) ); $contentType->router = ''; $contentType->store(); } } /** * Utility methods that fetches the column name for the field. * If it does not exists, returns a "null" string * * @param string $alias The alias for the column * @param string $null What to return if no column exists * * @return string The column name */ protected function getUcmCoreAlias($alias, $null = "null") { $alias = $this->getColumnAlias($alias); if (in_array($alias, $this->getKnownFields())) { return $alias; } return $null; } } PK Ma!]���k k hal/link.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage hal * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('FOF_INCLUDED') or die; /** * Implementation of the Hypertext Application Language link in PHP. * * @package FrameworkOnFramework * @since 2.1 */ class FOFHalLink { /** * For indicating the target URI. Corresponds with the ’Target IRI’ as * defined in Web Linking (RFC 5988). This attribute MAY contain a URI * Template (RFC6570) and in which case, SHOULD be complemented by an * additional templated attribtue on the link with a boolean value true. * * @var string */ protected $_href = ''; /** * This attribute SHOULD be present with a boolean value of true when the * href of the link contains a URI Template (RFC6570). * * @var boolean */ protected $_templated = false; /** * For distinguishing between Resource and Link elements that share the * same relation * * @var string */ protected $_name = null; /** * For indicating what the language of the result of dereferencing the link should be. * * @var string */ protected $_hreflang = null; /** * For labeling the destination of a link with a human-readable identifier. * * @var string */ protected $_title = null; /** * Public constructor of a FOFHalLink object * * @param string $href See $this->_href * @param boolean $templated See $this->_templated * @param string $name See $this->_name * @param string $hreflang See $this->_hreflang * @param string $title See $this->_title * * @throws RuntimeException If $href is empty */ public function __construct($href, $templated = false, $name = null, $hreflang = null, $title = null) { if (empty($href)) { throw new RuntimeException('A HAL link must always have a non-empty href'); } $this->_href = $href; $this->_templated = $templated; $this->_name = $name; $this->_hreflang = $hreflang; $this->_title = $title; } /** * Is this a valid link? Checks the existence of required fields, not their * values. * * @return boolean */ public function check() { return !empty($this->_href); } /** * Magic getter for the protected properties * * @param string $name The name of the property to retrieve, sans the underscore * * @return mixed Null will always be returned if the property doesn't exist */ public function __get($name) { $property = '_' . $name; if (property_exists($this, $property)) { return $this->$property; } else { return null; } } /** * Magic setter for the protected properties * * @param string $name The name of the property to set, sans the underscore * @param mixed $value The value of the property to set * * @return void */ public function __set($name, $value) { if (($name == 'href') && empty($value)) { return; } $property = '_' . $name; if (property_exists($this, $property)) { $this->$property = $value; } } } PK Ma!]��@ @ hal/render/json.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage hal * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('FOF_INCLUDED') or die; /** * Implements the HAL over JSON renderer * * @package FrameworkOnFramework * @since 2.1 */ class FOFHalRenderJson implements FOFHalRenderInterface { /** * When data is an array we'll output the list of data under this key * * @var string */ private $_dataKey = '_list'; /** * The document to render * * @var FOFHalDocument */ protected $_document; /** * Public constructor * * @param FOFHalDocument &$document The document to render */ public function __construct(&$document) { $this->_document = $document; } /** * Render a HAL document in JSON format * * @param array $options Rendering options. You can currently only set json_options (json_encode options) * * @return string The JSON representation of the HAL document */ public function render($options = array()) { if (isset($options['data_key'])) { $this->_dataKey = $options['data_key']; } if (isset($options['json_options'])) { $jsonOptions = $options['json_options']; } else { $jsonOptions = 0; } $serialiseThis = new stdClass; // Add links $collection = $this->_document->getLinks(); $serialiseThis->_links = new stdClass; foreach ($collection as $rel => $links) { if (!is_array($links)) { $serialiseThis->_links->$rel = $this->_getLink($links); } else { $serialiseThis->_links->$rel = array(); foreach ($links as $link) { array_push($serialiseThis->_links->$rel, $this->_getLink($link)); } } } // Add embedded documents $collection = $this->_document->getEmbedded(); if (!empty($collection)) { $serialiseThis->_embedded->$rel = new stdClass; foreach ($collection as $rel => $embeddeddocs) { if (!is_array($embeddeddocs)) { $embeddeddocs = array($embeddeddocs); } foreach ($embeddeddocs as $embedded) { $renderer = new FOFHalRenderJson($embedded); array_push($serialiseThis->_embedded->$rel, $renderer->render($options)); } } } // Add data $data = $this->_document->getData(); if (is_object($data)) { if ($data instanceof FOFTable) { $data = $data->getData(); } else { $data = (array) $data; } if (!empty($data)) { foreach ($data as $k => $v) { $serialiseThis->$k = $v; } } } elseif (is_array($data)) { $serialiseThis->{$this->_dataKey} = $data; } return json_encode($serialiseThis, $jsonOptions); } /** * Converts a FOFHalLink object into a stdClass object which will be used * for JSON serialisation * * @param FOFHalLink $link The link you want converted * * @return stdClass The converted link object */ protected function _getLink(FOFHalLink $link) { $ret = array( 'href' => $link->href ); if ($link->templated) { $ret['templated'] = 'true'; } if (!empty($link->name)) { $ret['name'] = $link->name; } if (!empty($link->hreflang)) { $ret['hreflang'] = $link->hreflang; } if (!empty($link->title)) { $ret['title'] = $link->title; } return (object) $ret; } } PK Ma!]�;��y y hal/render/interface.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage hal * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('FOF_INCLUDED') or die; /** * Interface for HAL document renderers * * @package FrameworkOnFramework * @since 2.1 */ interface FOFHalRenderInterface { /** * Render a HAL document into a representation suitable for consumption. * * @param array $options Renderer-specific options * * @return void */ public function render($options = array()); } PK Ma!]l{6� � hal/document.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage hal * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('FOF_INCLUDED') or die; /** * Implementation of the Hypertext Application Language document in PHP. It can * be used to provide hypermedia in a web service context. * * @package FrameworkOnFramework * @since 2.1 */ class FOFHalDocument { /** * The collection of links of this document * * @var FOFHalLinks */ private $_links = null; /** * The data (resource state or collection of resource state objects) of the * document. * * @var array */ private $_data = null; /** * Embedded documents. This is an array of FOFHalDocument instances. * * @var array */ private $_embedded = array(); /** * When $_data is an array we'll output the list of data under this key * (JSON) or tag (XML) * * @var string */ private $_dataKey = '_list'; /** * Public constructor * * @param mixed $data The data of the document (usually, the resource state) */ public function __construct($data = null) { $this->_data = $data; $this->_links = new FOFHalLinks; } /** * Add a link to the document * * @param string $rel The relation of the link to the document. * See RFC 5988 http://tools.ietf.org/html/rfc5988#section-6.2.2 A document MUST always have * a "self" link. * @param FOFHalLink $link The actual link object * @param boolean $overwrite When false and a link of $rel relation exists, an array of links is created. Otherwise the * existing link is overwriten with the new one * * @see FOFHalLinks::addLink * * @return boolean True if the link was added to the collection */ public function addLink($rel, FOFHalLink $link, $overwrite = true) { return $this->_links->addLink($rel, $link, $overwrite); } /** * Add links to the document * * @param string $rel The relation of the link to the document. See RFC 5988 * @param array $links An array of FOFHalLink objects * @param boolean $overwrite When false and a link of $rel relation exists, an array of * links is created. Otherwise the existing link is overwriten * with the new one * * @see FOFHalLinks::addLinks * * @return boolean */ public function addLinks($rel, array $links, $overwrite = true) { return $this->_links->addLinks($rel, $links, $overwrite); } /** * Add data to the document * * @param stdClass $data The data to add * @param boolean $overwrite Should I overwrite existing data? * * @return void */ public function addData($data, $overwrite = true) { if (is_array($data)) { $data = (object) $data; } if ($overwrite) { $this->_data = $data; } else { if (!is_array($this->_data)) { $this->_data = array($this->_data); } $this->_data[] = $data; } } /** * Add an embedded document * * @param string $rel The relation of the embedded document to its container document * @param FOFHalDocument $document The document to add * @param boolean $overwrite Should I overwrite existing data with the same relation? * * @return boolean */ public function addEmbedded($rel, FOFHalDocument $document, $overwrite = true) { if (!array_key_exists($rel, $this->_embedded) || !$overwrite) { $this->_embedded[$rel] = $document; } elseif (array_key_exists($rel, $this->_embedded) && !$overwrite) { if (!is_array($this->_embedded[$rel])) { $this->_embedded[$rel] = array($this->_embedded[$rel]); } $this->_embedded[$rel][] = $document; } else { return false; } } /** * Returns the collection of links of this document * * @param string $rel The relation of the links to fetch. Skip to get all links. * * @return array */ public function getLinks($rel = null) { return $this->_links->getLinks($rel); } /** * Returns the collection of embedded documents * * @param string $rel Optional; the relation to return the embedded documents for * * @return array|FOFHalDocument */ public function getEmbedded($rel = null) { if (empty($rel)) { return $this->_embedded; } elseif (isset($this->_embedded[$rel])) { return $this->_embedded[$rel]; } else { return array(); } } /** * Return the data attached to this document * * @return array|stdClass */ public function getData() { return $this->_data; } /** * Instantiate and call a suitable renderer class to render this document * into the specified format. * * @param string $format The format to render the document into, e.g. 'json' * * @return string The rendered document * * @throws RuntimeException If the format is unknown, i.e. there is no suitable renderer */ public function render($format = 'json') { $class_name = 'FOFHalRender' . ucfirst($format); if (!class_exists($class_name, true)) { throw new RuntimeException("Unsupported HAL Document format '$format'. Render aborted."); } $renderer = new $class_name($this); return $renderer->render( array( 'data_key' => $this->_dataKey ) ); } } PK Ma!]� �[s s hal/links.phpnu &1i� <?php /** * @package FrameworkOnFramework * @subpackage hal * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('FOF_INCLUDED') or die; /** * Implementation of the Hypertext Application Language links in PHP. This is * actually a collection of links. * * @package FrameworkOnFramework * @since 2.1 */ class FOFHalLinks { /** * The collection of links, sorted by relation * * @var array */ private $_links = array(); /** * Add a single link to the links collection * * @param string $rel The relation of the link to the document. See RFC 5988 * http://tools.ietf.org/html/rfc5988#section-6.2.2 A document * MUST always have a "self" link. * @param FOFHalLink $link The actual link object * @param boolean $overwrite When false and a link of $rel relation exists, an array of * links is created. Otherwise the existing link is overwriten * with the new one * * @return boolean True if the link was added to the collection */ public function addLink($rel, FOFHalLink $link, $overwrite = true) { if (!$link->check()) { return false; } if (!array_key_exists($rel, $this->_links) || $overwrite) { $this->_links[$rel] = $link; } elseif (array_key_exists($rel, $this->_links) && !$overwrite) { if (!is_array($this->_links[$rel])) { $this->_links[$rel] = array($this->_links[$rel]); } $this->_links[$rel][] = $link; } else { return false; } } /** * Add multiple links to the links collection * * @param string $rel The relation of the links to the document. See RFC 5988. * @param array $links An array of FOFHalLink objects * @param boolean $overwrite When false and a link of $rel relation exists, an array * of links is created. Otherwise the existing link is * overwriten with the new one * * @return boolean True if the link was added to the collection */ public function addLinks($rel, array $links, $overwrite = true) { if (empty($links)) { return false; } $localOverwrite = $overwrite; foreach ($links as $link) { if ($link instanceof FOFHalLink) { $this->addLink($rel, $link, $localOverwrite); } // After the first time we call this with overwrite on we have to // turn it off so that the other links are added to the set instead // of overwriting the first item that's already added. if ($localOverwrite) { $localOverwrite = false; } } } /** * Returns the collection of links * * @param string $rel Optional; the relation to return the links for * * @return array|FOFHalLink */ public function getLinks($rel = null) { if (empty($rel)) { return $this->_links; } elseif (isset($this->_links[$rel])) { return $this->_links[$rel]; } else { return array(); } } } PK Ma!]�h�q2"