| Current Path : /home/wuectly/www/03cbe/ |
| Current File : /home/wuectly/www/03cbe/includes.tar |
index.html 0000604 00000000037 15245362363 0006546 0 ustar 00 <!DOCTYPE html><title></title>
framework.php 0000604 00000005524 15245362363 0007265 0 ustar 00 <?php
/**
* @package Joomla.Administrator
*
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\Utilities\IpHelper;
// Joomla system checks.
@ini_set('magic_quotes_runtime', 0);
// System includes
require_once JPATH_LIBRARIES . '/import.legacy.php';
// Bootstrap the CMS libraries.
require_once JPATH_LIBRARIES . '/cms.php';
// Set system error handling
JError::setErrorHandling(E_NOTICE, 'message');
JError::setErrorHandling(E_WARNING, 'message');
JError::setErrorHandling(E_ERROR, 'message', array('JError', 'customErrorPage'));
$version = new JVersion;
// Installation check, and check on removal of the install directory.
if (!file_exists(JPATH_CONFIGURATION . '/configuration.php')
|| (filesize(JPATH_CONFIGURATION . '/configuration.php') < 10)
|| (file_exists(JPATH_INSTALLATION . '/index.php') && (false === $version->isInDevelopmentState())))
{
if (file_exists(JPATH_INSTALLATION . '/index.php'))
{
header('Location: ../installation/index.php');
exit();
}
else
{
echo 'No configuration file found and no installation code available. Exiting...';
exit;
}
}
// Pre-Load configuration. Don't remove the Output Buffering due to BOM issues, see JCode 26026
ob_start();
require_once JPATH_CONFIGURATION . '/configuration.php';
ob_end_clean();
// System configuration.
$config = new JConfig;
// Set the error_reporting
switch ($config->error_reporting)
{
case 'default':
case '-1':
break;
case 'none':
case '0':
error_reporting(0);
break;
case 'simple':
error_reporting(E_ERROR | E_WARNING | E_PARSE);
ini_set('display_errors', 1);
break;
case 'maximum':
error_reporting(E_ALL);
ini_set('display_errors', 1);
break;
case 'development':
error_reporting(-1);
ini_set('display_errors', 1);
break;
default:
error_reporting($config->error_reporting);
ini_set('display_errors', 1);
break;
}
define('JDEBUG', $config->debug);
// System profiler
if (JDEBUG)
{
// @deprecated 4.0 - The $_PROFILER global will be removed
$_PROFILER = JProfiler::getInstance('Application');
}
/**
* Correctly set the allowing of IP Overrides if behind a trusted proxy/load balancer.
*
* We need to do this as high up the stack as we can, as the default in \Joomla\Utilities\IpHelper is to
* $allowIpOverride = true which is the wrong default for a generic site NOT behind a trusted proxy/load balancer.
*/
if (property_exists($config, 'behind_loadbalancer') && $config->behind_loadbalancer == 1)
{
// If Joomla is configured to be behind a trusted proxy/load balancer, allow HTTP Headers to override the REMOTE_ADDR
IpHelper::setAllowIpOverrides(true);
}
else
{
// We disable the allowing of IP overriding using headers by default.
IpHelper::setAllowIpOverrides(false);
}
unset($config);
defines.php 0000604 00000002040 15245362363 0006673 0 ustar 00 <?php
/**
* @package Joomla.Administrator
*
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
// Global definitions
$parts = explode(DIRECTORY_SEPARATOR, JPATH_BASE);
array_pop($parts);
// Defines
define('JPATH_ROOT', implode(DIRECTORY_SEPARATOR, $parts));
define('JPATH_SITE', JPATH_ROOT);
define('JPATH_CONFIGURATION', JPATH_ROOT);
define('JPATH_ADMINISTRATOR', JPATH_ROOT . DIRECTORY_SEPARATOR . 'administrator');
define('JPATH_LIBRARIES', JPATH_ROOT . DIRECTORY_SEPARATOR . 'libraries');
define('JPATH_PLUGINS', JPATH_ROOT . DIRECTORY_SEPARATOR . 'plugins');
define('JPATH_INSTALLATION', JPATH_ROOT . DIRECTORY_SEPARATOR . 'installation');
define('JPATH_THEMES', JPATH_BASE . DIRECTORY_SEPARATOR . 'templates');
define('JPATH_CACHE', JPATH_BASE . DIRECTORY_SEPARATOR . 'cache');
define('JPATH_MANIFESTS', JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'manifests');
renderer/megamenurender.php 0000604 00000005606 15245530303 0012064 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage Document
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
use Joomla\CMS\Document\DocumentRenderer;
defined('JPATH_PLATFORM') or die;
/**
* JDocument Megamenu renderer
*/
class JDocumentRendererMegamenuRender extends DocumentRenderer
{
/**
* Render megamenu block, then push the output into megamenu renderer to display
*
* @param string $position The position of the modules to render
* @param array $params Associative array of values
* @param string $content Module content
*
* @return string The output of the script
*
* @since 11.1
*/
public function render($info = null, $params = array(), $content = null)
{
T3::import('menu/megamenu');
$t3app = T3::getApp();
//we will check from params
$menutype = empty($params['menutype']) ? (empty($params['name']) ? $t3app->getParam('mm_type', 'mainmenu') : $params['name']) : $params['menutype'];
$currentconfig = json_decode($t3app->getParam('mm_config', ''), true);
//force to array
if (!is_array($currentconfig)) {
$currentconfig = (array)$currentconfig;
}
//get user access levels
$viewLevels = JFactory::getUser()->getAuthorisedViewLevels();
$mmkey = $menutype;
$mmconfig = array();
if (!empty($currentconfig)) {
//find best fit configuration based on view level
$vlevels = array_merge($viewLevels);
if (is_array($vlevels) && in_array(3, $vlevels)) { //we assume, if a user is special, they should be registered also
$vlevels[] = 2;
}
$vlevels = array_unique($vlevels);
rsort($vlevels);
if (!is_array($vlevels)) $vlevels = array();
$vlevels[] = ''; // extend a blank, default key
// check if available configuration for language override
$langcode = JFactory::getDocument()->language;
$shortlangcode = substr($langcode, 0, 2);
$types = array($menutype . '-' . $langcode, $menutype . '-' . $shortlangcode, $menutype);
foreach ($types as $type) {
foreach ($vlevels as $vlevel) {
$key = $type . ($vlevel !== '' ? '-' . $vlevel : '');
if(isset($currentconfig[$key])) {
$mmkey = $key;
$menutype = $type;
break 2;
}
}
}
if (isset($currentconfig[$mmkey])) {
$mmconfig = $currentconfig[$mmkey];
if(!is_array($mmconfig)){
$mmconfig = array();
}
}
}
JFactory::getApplication()->triggerEvent('onT3Megamenu', array(&$menutype, &$mmconfig, &$viewLevels));
$mmconfig['access'] = $viewLevels;
$menu = new T3MenuMegamenu ($menutype, $mmconfig, $t3app->_tpl->params);
$buffer = $menu->render(true);
if (isset($params['return_result']) && $params['return_result']) {
return $buffer;
} else {
$t3app->setBuffer($buffer, 'megamenu', empty($params['name']) ? null : $params['name'], null);
return '';
}
}
}
renderer/pageclass.php 0000604 00000004223 15245530303 0011022 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage Document
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
use Joomla\CMS\Document\DocumentRenderer;
defined('JPATH_PLATFORM') or die;
/**
* JDocument Bodyclass renderer
*
* @package Joomla.Platform
* @subpackage Document
* @since 11.1
*/
class JDocumentRendererPageClass extends DocumentRenderer
{
/**
* Render body class of current page
*
* @param string $position The position of the modules to render
* @param array $params Associative array of values
* @param string $content Module content
*
* @return string The output of the script
*
* @since 11.1
*/
public function render($info, $params = array(), $content = null)
{
$input = JFactory::getApplication()->input;
$t3tpl = T3::getApp();
$pageclass = array();
if($input->getCmd('option', '')){
$pageclass[] = $input->getCmd('option', '');
}
if($input->getCmd('view', '')){
$pageclass[] = 'view-' . $input->getCmd('view', '');
}
if($input->getCmd('layout', '')){
$pageclass[] = 'layout-' . $input->getCmd('layout', '');
}
if($input->getCmd('task', '')){
$pageclass[] = 'task-' . $input->getCmd('task', '');
}
if($input->getCmd('Itemid', '')){
$pageclass[] = 'itemid-' . $input->getCmd('Itemid', '');
}
$menu = JFactory::getApplication()->getMenu();
if($menu){
$active = $menu->getActive();
$default = $menu->getDefault();
if ($active) {
if($default && $active->id == $default->id){
$pageclass[] = 'home';
}
if ($active->getParams() && $active->getParams()->get('pageclass_sfx')) {
$pageclass[] = $active->getParams()->get('pageclass_sfx');
}
}
}
$pageclass[] = 'j'.str_replace('.', '', (number_format((float)JVERSION, 1, '.', '')));
if(version_compare(JVERSION,'4','ge')){
$pageclass[] = 'j40';
}
$pageclass = array_unique(array_merge($pageclass, $t3tpl->getPageclass()));
JFactory::getApplication()->triggerEvent('onT3BodyClass', array(&$pageclass));
return implode(' ', $pageclass);
}
}
renderer/megamenu.php 0000604 00000001637 15245530303 0010664 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage Document
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* JDocument Megamenu renderer - this is a placeholder for menumenurender
*/
T3::import('renderer/megamenurender');
class JDocumentRendererMegamenu extends JDocumentRendererMegamenuRender
{
/**
* Render megamenu block
*
* @param string $position The position of the modules to render
* @param array $params Associative array of values
* @param string $content Module content
*
* @return string The output of the script
*
* @since 11.1
*/
public function render($info = null, $params = array(), $content = null)
{
$params['return_result'] = true;
return parent::render($info, $params, $content);
}
}
renderer/t3ajax.php 0000604 00000006437 15245530303 0010263 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage Document
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* JDocument Modules renderer
*
* @package Joomla.Platform
* @subpackage Document
* @since 11.1
*/
class JDocumentRendererT3Ajax extends JDocumentRenderer
{
/**
* Renders multiple modules script and returns the results as a string
*
* @param string $position The position of the modules to render
* @param array $params Associative array of values
* @param string $content Module content
*
* @return string The output of the script
*
* @since 11.1
*/
public function render($info, $params = array(), $content = null)
{
$input = JFactory::getApplication()->input;
$task = $input->getCmd('t3ajax', 'position');
$format = $input->getCmd('f', 'html');
if($task == 'position'){
if($format == 'html'){
return $this->htmlPosition($info, $params, $content);
} else if($format == 'json'){
return $this->jsonPosition($info, $params, $content);
}
} else if($task == 'module'){
if($format == 'html'){
return $this->htmlModule($info, $params, $content);
} else if($format == 'json'){
return $this->jsonModule($info, $params, $content);
}
}
return null;
}
protected function htmlPosition($info, $params = array(), $content = null)
{
$renderer = $this->_doc->loadRenderer('module');
$input = JFactory::getApplication()->input;
$position = $input->getCmd('p');
$buffer = '';
foreach (JModuleHelper::getModules($position) as $mod)
{
$buffer .= $renderer->render($mod, $params, $content);
}
return $buffer;
}
protected function jsonPosition($info, $params = array(), $content = null)
{
$result = array();
$result['markup'] = $this->htmlPosition($info, $params = array(), $content = null);
$result['stylesheets'] = $this->_doc->_styleSheets;
$result['styles'] = $this->_doc->_style;
$result['scripts'] = $this->_doc->_scripts;
$result['scriptinlines'] = $this->_doc->_script;
return json_encode($result);
}
protected function htmlModule($info, $params = array(), $content = null)
{
$input = JFactory::getApplication()->input;
$mid = $input->getInt('m');
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('m.id, m.title, m.module, m.position, m.content, m.showtitle, m.params');
$query->from('#__modules AS m');
$query->where('m.id = '. $mid);
$query->where('m.published = 1');
$db->setQuery($query);
$module = $db->loadObject();
$buffer = '';
//check in case the module is unpublish or deleted
if($module && $module->id){
$buffer = JModuleHelper::renderModule($module, array('style'=> $input->getCmd('style', 'T3Xhtml')));
}
return $buffer;
}
protected function jsonModule($info, $params = array(), $content = null)
{
$result = array();
$result['markup'] = $this->htmlModule($info, $params = array(), $content = null);
$result['stylesheets'] = $this->_doc->_styleSheets;
$result['styles'] = $this->_doc->_style;
$result['scripts'] = $this->_doc->_scripts;
$result['scriptinlines'] = $this->_doc->_script;
return json_encode($result);
}
}
renderer/t3bootstrap.php 0000604 00000002355 15245530303 0011350 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage Document
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
use Joomla\CMS\Document\DocumentRenderer;
defined('JPATH_PLATFORM') or die;
/**
* JDocument Megamenu renderer
*/
class JDocumentRendererT3Bootstrap extends DocumentRenderer
{
/**
* Render megamenu block
*
* @param string $position The position of the modules to render
* @param array $params Associative array of values
* @param string $content Module content
*
* @return string The output of the script
*
* @since 11.1
*/
public function render($info = null, $params = array(), $content = null)
{
T3::import('menu/t3bootstrap');
// import the renderer
$t3app = T3::getApp();
$menutype = empty($params['menutype']) ? $t3app->getParam('mm_type', 'mainmenu') : $params['menutype'];
if(version_compare(JVERSION, '3','lt')){
JDispatcher::getInstance()->trigger('onT3BSMenu', array(&$menutype));
}else{
JFactory::getApplication()->triggerEvent('onT3BSMenu', array(&$menutype));
}
$menu = new T3Bootstrap($menutype);
return $menu->render(true);
}
}
joomla25/html/bootstrap.php 0000604 00000073041 15245530303 0011667 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage HTML
*
* @copyright Copyright (C) 2005 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Utility class for Bootstrap elements.
*
* @package Joomla.Libraries
* @subpackage HTML
* @since 3.0
*/
abstract class JHtmlBootstrap
{
/**
* @var array Array containing information for loaded files
* @since 3.0
*/
protected static $loaded = array();
/**
* Add javascript support for the Bootstrap affix plugin
*
* @param string $selector Unique selector for the element to be affixed.
* @param array $params An array of options.
* Options for the affix plugin can be:
* - offset number|function|object Pixels to offset from screen when calculating position of scroll.
* If a single number is provided, the offset will be applied in both top
* and left directions. To listen for a single direction, or multiple
* unique offsets, just provide an object offset: { x: 10 }.
* Use a function when you need to dynamically provide an offset
* (useful for some responsive designs).
*
* @return void
*
* @since 3.1
*/
public static function affix($selector = 'affix', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (!isset(static::$loaded[__METHOD__][$sig]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['offset'] = isset($params['offset']) ? $params['offset'] : 10;
$options = self::getJSObject($opt);
// Attach the carousel to document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('#$selector').affix($options);
})(jQuery);"
);
// Set static array
static::$loaded[__METHOD__][$sig] = true;
}
return;
}
/**
* Add javascript support for Bootstrap alerts
*
* @param string $selector Common class for the alerts
*
* @return void
*
* @since 3.0
*/
public static function alert($selector = 'alert')
{
// Only load once
if (isset(static::$loaded[__METHOD__][$selector]))
{
return;
}
// Include Bootstrap framework
static::framework();
// Attach the alerts to the document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('.$selector').alert();
})(jQuery);"
);
static::$loaded[__METHOD__][$selector] = true;
return;
}
/**
* Add javascript support for Bootstrap buttons
*
* @param string $selector Common class for the buttons
*
* @return void
*
* @since 3.1
*/
public static function button($selector = 'button')
{
// Only load once
if (isset(static::$loaded[__METHOD__][$selector]))
{
return;
}
// Include Bootstrap framework
static::framework();
// Attach the alerts to the document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('.$selector').button();
})(jQuery);"
);
static::$loaded[__METHOD__][$selector] = true;
return;
}
/**
* Add javascript support for Bootstrap carousels
*
* @param string $selector Common class for the carousels.
* @param array $params An array of options for the modal.
* Options for the modal can be:
* - interval number The amount of time to delay between automatically cycling an item.
* If false, carousel will not automatically cycle.
* - pause string Pauses the cycling of the carousel on mouseenter and resumes the cycling
* of the carousel on mouseleave.
*
* @return void
*
* @since 3.0
*/
public static function carousel($selector = 'carousel', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (!isset(static::$loaded[__METHOD__][$sig]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['interval'] = isset($params['interval']) ? (int) $params['interval'] : 5000;
$opt['pause'] = isset($params['pause']) ? $params['pause'] : 'hover';
$options = self::getJSObject($opt);
// Attach the carousel to document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('.$selector').carousel($options);
})(jQuery);"
);
// Set static array
static::$loaded[__METHOD__][$sig] = true;
}
return;
}
/**
* Add javascript support for Bootstrap dropdowns
*
* @param string $selector Common class for the dropdowns
*
* @return void
*
* @since 3.0
*/
public static function dropdown($selector = 'dropdown-toggle')
{
// Only load once
if (isset(static::$loaded[__METHOD__][$selector]))
{
return;
}
// Include Bootstrap framework
static::framework();
// Attach the dropdown to the document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('.$selector').dropdown();
})(jQuery);"
);
static::$loaded[__METHOD__][$selector] = true;
return;
}
/**
* Method to load the Bootstrap JavaScript framework into the document head
*
* If debugging mode is on an uncompressed version of Bootstrap is included for easier debugging.
*
* @param mixed $debug Is debugging mode on? [optional]
*
* @return void
*
* @since 3.0
*/
public static function framework($debug = null)
{
return; // T3 already load framework
}
/**
* Add javascript support for Bootstrap modals
*
* @param string $selector The ID selector for the modal.
* @param array $params An array of options for the modal.
* Options for the modal can be:
* - backdrop boolean Includes a modal-backdrop element.
* - keyboard boolean Closes the modal when escape key is pressed.
* - show boolean Shows the modal when initialized.
* - remote string An optional remote URL to load
*
* @return void
*
* @since 3.0
*/
public static function modal($selector = 'modal', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (!isset(static::$loaded[__METHOD__][$sig]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['backdrop'] = isset($params['backdrop']) ? (boolean) $params['backdrop'] : true;
$opt['keyboard'] = isset($params['keyboard']) ? (boolean) $params['keyboard'] : true;
$opt['show'] = isset($params['show']) ? (boolean) $params['show'] : true;
$opt['remote'] = isset($params['remote']) ? $params['remote'] : '';
$options = self::getJSObject($opt);
// Attach the modal to document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('#$selector').modal($options);
})(jQuery);"
);
// Set static array
static::$loaded[__METHOD__][$sig] = true;
}
return;
}
/**
* Method to render a Bootstrap modal
*
* @param string $selector The ID selector for the modal.
* @param array $params An array of options for the modal.
* @param string $footer Optional markup for the modal footer
*
* @return string HTML markup for a modal
*
* @since 3.0
*/
public static function renderModal($selector = 'modal', $params = array(), $footer = '')
{
// Ensure the behavior is loaded
static::modal($selector, $params);
$html = "<div class=\"modal hide fade\" id=\"" . $selector . "\">\n";
$html .= "<div class=\"modal-header\">\n";
$html .= "<button type=\"button\" class=\"close\" data-dismiss=\"modal\">�</button>\n";
$html .= "<h3>" . $params['title'] . "</h3>\n";
$html .= "</div>\n";
$html .= "<div id=\"" . $selector . "-container\">\n";
$html .= "</div>\n";
$html .= "</div>\n";
$html .= "<script>";
$html .= "jQuery('#" . $selector . "').on('show', function () {\n";
$html .= "document.getElementById('" . $selector . "-container').innerHTML = '<div class=\"modal-body\"><iframe class=\"iframe\" src=\""
. $params['url'] . "\" height=\"" . $params['height'] . "\" width=\"" . $params['width'] . "\"></iframe></div>" . $footer . "';\n";
$html .= "});\n";
$html .= "</script>";
return $html;
}
/**
* Add javascript support for Bootstrap popovers
*
* Use element's Title as popover content
*
* @param string $selector Selector for the popover
* @param array $params An array of options for the popover.
* Options for the popover can be:
* animation boolean apply a css fade transition to the popover
* html boolean Insert HTML into the popover. If false, jQuery's text method will be used to insert
* content into the dom.
* placement string|function how to position the popover - top | bottom | left | right
* selector string If a selector is provided, popover objects will be delegated to the specified targets.
* trigger string how popover is triggered - hover | focus | manual
* title string|function default title value if `title` tag isn't present
* content string|function default content value if `data-content` attribute isn't present
* delay number|object delay showing and hiding the popover (ms) - does not apply to manual trigger type
* If a number is supplied, delay is applied to both hide/show
* Object structure is: delay: { show: 500, hide: 100 }
* container string|boolean Appends the popover to a specific element: { container: 'body' }
*
* @return void
*
* @since 3.0
*/
public static function popover($selector = '.hasPopover', $params = array())
{
// Only load once
if (isset(static::$loaded[__METHOD__][$selector]))
{
return;
}
// Include Bootstrap framework
static::framework();
$opt['animation'] = isset($params['animation']) ? $params['animation'] : null;
$opt['html'] = isset($params['html']) ? $params['html'] : true;
$opt['placement'] = isset($params['placement']) ? $params['placement'] : null;
$opt['selector'] = isset($params['selector']) ? $params['selector'] : null;
$opt['title'] = isset($params['title']) ? $params['title'] : null;
$opt['trigger'] = isset($params['trigger']) ? $params['trigger'] : 'hover focus';
$opt['content'] = isset($params['content']) ? $params['content'] : null;
$opt['delay'] = isset($params['delay']) ? $params['delay'] : null;
$opt['container'] = isset($params['container']) ? $params['container'] : 'body';
$options = self::getJSObject($opt);
// Attach the popover to the document
JFactory::getDocument()->addScriptDeclaration(
"jQuery(document).ready(function()
{
jQuery('" . $selector . "').popover(" . $options . ");
});"
);
static::$loaded[__METHOD__][$selector] = true;
return;
}
/**
* Add javascript support for Bootstrap ScrollSpy
*
* @param string $selector The ID selector for the ScrollSpy element.
* @param array $params An array of options for the ScrollSpy.
* Options for the modal can be:
* - offset number Pixels to offset from top when calculating position of scroll.
*
* @return void
*
* @since 3.0
*/
public static function scrollspy($selector = 'navbar', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (!isset(static::$loaded[__METHOD__][$sig]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['offset'] = isset($params['offset']) ? (int) $params['offset'] : 10;
$options = self::getJSObject($opt);
// Attach ScrollSpy to document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('#$selector').scrollspy($options);
})(jQuery);"
);
// Set static array
static::$loaded[__METHOD__][$sig] = true;
}
return;
}
/**
* Add javascript support for Bootstrap tooltips
*
* Add a title attribute to any element in the form
* title="title::text"
*
* @param string $selector The ID selector for the tooltip.
* @param array $params An array of options for the tooltip.
* Options for the tooltip can be:
* - animation boolean Apply a CSS fade transition to the tooltip
* - html boolean Insert HTML into the tooltip. If false, jQuery's text method will be used to insert
* content into the dom.
* - placement string|function How to position the tooltip - top | bottom | left | right
* - selector string If a selector is provided, tooltip objects will be delegated to the specified targets.
* - title string|function Default title value if `title` tag isn't present
* - trigger string How tooltip is triggered - hover | focus | manual
* - delay integer Delay showing and hiding the tooltip (ms) - does not apply to manual trigger type
* If a number is supplied, delay is applied to both hide/show
* Object structure is: delay: { show: 500, hide: 100 }
* - container string|boolean Appends the popover to a specific element: { container: 'body' }
*
* @return void
*
* @since 3.0
*/
public static function tooltip($selector = '.hasTooltip', $params = array())
{
if (!isset(static::$loaded[__METHOD__][$selector]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['animation'] = isset($params['animation']) ? (boolean) $params['animation'] : null;
$opt['html'] = isset($params['html']) ? (boolean) $params['html'] : true;
$opt['placement'] = isset($params['placement']) ? (string) $params['placement'] : null;
$opt['selector'] = isset($params['selector']) ? (string) $params['selector'] : null;
$opt['title'] = isset($params['title']) ? (string) $params['title'] : null;
$opt['trigger'] = isset($params['trigger']) ? (string) $params['trigger'] : null;
$opt['delay'] = isset($params['delay']) ? (int) $params['delay'] : null;
$opt['container'] = isset($params['container']) ? $params['container'] : 'body';
$opt['template'] = isset($params['template']) ? (string) $params['template'] : null;
$onShow = isset($params['onShow']) ? (string) $params['onShow'] : null;
$onShown = isset($params['onShown']) ? (string) $params['onShown'] : null;
$onHide = isset($params['onHide']) ? (string) $params['onHide'] : null;
$onHidden = isset($params['onHidden']) ? (string) $params['onHidden'] : null;
$options = self::getJSObject($opt);
// Build the script.
$script = array();
$script[] = "jQuery(document).ready(function(){";
$script[] = "\tjQuery('" . $selector . "').tooltip(" . $options . ");";
if ($onShow)
{
$script[] = "\tjQuery('" . $selector . "').on('show.bs.tooltip', " . $onShow . ");";
}
if ($onShown)
{
$script[] = "\tjQuery('" . $selector . "').on('shown.bs.tooltip', " . $onShown . ");";
}
if ($onHide)
{
$script[] = "\tjQuery('" . $selector . "').on('hide.bs.tooltip', " . $onHide . ");";
}
if ($onHidden)
{
$script[] = "\tjQuery('" . $selector . "').on('hidden.bs.tooltip', " . $onHidden . ");";
}
$script[] = "});";
// Attach tooltips to document
JFactory::getDocument()->addScriptDeclaration(implode("\n", $script));
// Set static array
static::$loaded[__METHOD__][$selector] = true;
}
return;
}
/**
* Add javascript support for Bootstrap typeahead
*
* @param string $selector The selector for the typeahead element.
* @param array $params An array of options for the typeahead element.
* Options for the tooltip can be:
* - source array, function The data source to query against. May be an array of strings or a function.
* The function is passed two arguments, the query value in the input field and the
* process callback. The function may be used synchronously by returning the data
* source directly or asynchronously via the process callback's single argument.
* - items number The max number of items to display in the dropdown.
* - minLength number The minimum character length needed before triggering autocomplete suggestions
* - matcher function The method used to determine if a query matches an item. Accepts a single argument,
* the item against which to test the query. Access the current query with this.query.
* Return a boolean true if query is a match.
* - sorter function Method used to sort autocomplete results. Accepts a single argument items and has
* the scope of the typeahead instance. Reference the current query with this.query.
* - updater function The method used to return selected item. Accepts a single argument, the item and
* has the scope of the typeahead instance.
* - highlighter function Method used to highlight autocomplete results. Accepts a single argument item and
* has the scope of the typeahead instance. Should return html.
*
* @return void
*
* @since 3.0
*/
public static function typeahead($selector = '.typeahead', $params = array())
{
if (!isset(static::$loaded[__METHOD__][$selector]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['source'] = isset($params['source']) ? $params['source'] : '[]';
$opt['items'] = isset($params['items']) ? (int) $params['items'] : 8;
$opt['minLength'] = isset($params['minLength']) ? (int) $params['minLength'] : 1;
$opt['matcher'] = isset($params['matcher']) ? (string) $params['matcher'] : null;
$opt['sorter'] = isset($params['sorter']) ? (string) $params['sorter'] : null;
$opt['updater'] = isset($params['updater']) ? (string) $params['updater'] : null;
$opt['highlighter'] = isset($params['highlighter']) ? (int) $params['highlighter'] : null;
$options = self::getJSObject($opt);
// Attach tooltips to document
JFactory::getDocument()->addScriptDeclaration(
"jQuery(document).ready(function()
{
jQuery('" . $selector . "').typeahead(" . $options . ");
});"
);
// Set static array
static::$loaded[__METHOD__][$selector] = true;
}
return;
}
/**
* Add javascript support for Bootstrap accordians and insert the accordian
*
* @param string $selector The ID selector for the tooltip.
* @param array $params An array of options for the tooltip.
* Options for the tooltip can be:
* - parent selector If selector then all collapsible elements under the specified parent will be closed when this
* collapsible item is shown. (similar to traditional accordion behavior)
* - toggle boolean Toggles the collapsible element on invocation
* - active string Sets the active slide during load
*
* @return string HTML for the accordian
*
* @since 3.0
*/
public static function startAccordion($selector = 'myAccordian', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (!isset(static::$loaded[__METHOD__][$sig]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['parent'] = isset($params['parent']) ? (boolean) $params['parent'] : false;
$opt['toggle'] = isset($params['toggle']) ? (boolean) $params['toggle'] : true;
$opt['active'] = isset($params['active']) ? (string) $params['active'] : '';
$options = self::getJSObject($opt);
// Attach accordion to document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('#$selector').collapse($options);
})(jQuery);"
);
// Set static array
static::$loaded[__METHOD__][$sig] = true;
static::$loaded[__METHOD__]['active'] = $opt['active'];
}
return '<div id="' . $selector . '" class="accordion">';
}
/**
* Close the current accordion
*
* @return string HTML to close the accordian
*
* @since 3.0
*/
public static function endAccordion()
{
return '</div>';
}
/**
* Begins the display of a new accordion slide.
*
* @param string $selector Identifier of the accordion group.
* @param string $text Text to display.
* @param string $id Identifier of the slide.
* @param string $class Class of the accordion group.
*
* @return string HTML to add the slide
*
* @since 3.0
*/
public static function addSlide($selector, $text, $id, $class = '')
{
$in = (static::$loaded['JHtmlBootstrap::startAccordion']['active'] == $id) ? ' in' : '';
$class = (!empty($class)) ? ' ' . $class : '';
$html = '<div class="accordion-group' . $class . '">'
. '<div class="accordion-heading">'
. '<strong><a href="#' . $id . '" data-parent="#' . $selector . '" data-toggle="collapse" class="accordion-toggle">'
. $text
. '</a></strong>'
. '</div>'
. '<div class="accordion-body collapse' . $in . '" id="' . $id . '">'
. '<div class="accordion-inner">';
return $html;
}
/**
* Close the current slide
*
* @return string HTML to close the slide
*
* @since 3.0
*/
public static function endSlide()
{
return '</div></div></div>';
}
/**
* Creates a tab pane
*
* @param string $selector The pane identifier.
* @param array $params The parameters for the pane
*
* @return string
*
* @since 3.1
*/
public static function startTabSet($selector = 'myTab', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (!isset(static::$loaded[__METHOD__][$sig]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['active'] = (isset($params['active']) && ($params['active'])) ? (string) $params['active'] : '';
// Attach tabs to document
JFactory::getDocument()
->addScriptDeclaration(JLayoutHelper::render('libraries.cms.html.bootstrap.starttabsetscript', array('selector' => $selector)));
// Set static array
static::$loaded[__METHOD__][$sig] = true;
static::$loaded[__METHOD__][$selector]['active'] = $opt['active'];
}
$html = JLayoutHelper::render('libraries.cms.html.bootstrap.starttabset', array('selector' => $selector));
return $html;
}
/**
* Close the current tab pane
*
* @return string HTML to close the pane
*
* @since 3.1
*/
public static function endTabSet()
{
$html = JLayoutHelper::render('libraries.cms.html.bootstrap.endtabset');
return $html;
}
/**
* Begins the display of a new tab content panel.
*
* @param string $selector Identifier of the panel.
* @param string $id The ID of the div element
* @param string $title The title text for the new UL tab
*
* @return string HTML to start a new panel
*
* @since 3.1
*/
public static function addTab($selector, $id, $title)
{
static $tabScriptLayout = null;
static $tabLayout = null;
$tabScriptLayout = is_null($tabScriptLayout) ? new JLayoutFile('libraries.cms.html.bootstrap.addtabscript') : $tabScriptLayout;
$tabLayout = is_null($tabLayout) ? new JLayoutFile('libraries.cms.html.bootstrap.addtab') : $tabLayout;
$active = (static::$loaded['JHtmlBootstrap::startTabSet'][$selector]['active'] == $id) ? ' active' : '';
// Inject tab into UL
JFactory::getDocument()
->addScriptDeclaration($tabScriptLayout->render(array('selector' => $selector,'id' => $id, 'active' => $active, 'title' => $title)));
$html = $tabLayout->render(array('id' => $id, 'active' => $active));
return $html;
}
/**
* Close the current tab content panel
*
* @return string HTML to close the pane
*
* @since 3.1
*/
public static function endTab()
{
$html = JLayoutHelper::render('libraries.cms.html.bootstrap.endtab');
return $html;
}
/**
* Creates a tab pane
*
* @param string $selector The pane identifier.
* @param array $params The parameters for the pane
*
* @return string
*
* @since 3.0
* @deprecated 4.0 Use JHtml::_('bootstrap.startTabSet') instead.
*/
public static function startPane($selector = 'myTab', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (!isset(static::$loaded['JHtmlBootstrap::startTabSet'][$sig]))
{
// Include Bootstrap framework
static::framework();
// Setup options object
$opt['active'] = isset($params['active']) ? (string) $params['active'] : '';
// Attach tooltips to document
JFactory::getDocument()->addScriptDeclaration(
"(function($){
$('#$selector a').click(function (e) {
e.preventDefault();
$(this).tab('show');
});
})(jQuery);"
);
// Set static array
static::$loaded['JHtmlBootstrap::startTabSet'][$sig] = true;
static::$loaded['JHtmlBootstrap::startTabSet'][$selector]['active'] = $opt['active'];
}
return '<div class="tab-content" id="' . $selector . 'Content">';
}
/**
* Close the current tab pane
*
* @return string HTML to close the pane
*
* @since 3.0
* @deprecated 4.0 Use JHtml::_('bootstrap.endTabSet') instead.
*/
public static function endPane()
{
return '</div>';
}
/**
* Begins the display of a new tab content panel.
*
* @param string $selector Identifier of the panel.
* @param string $id The ID of the div element
*
* @return string HTML to start a new panel
*
* @since 3.0
* @deprecated 4.0 Use JHtml::_('bootstrap.addTab') instead.
*/
public static function addPanel($selector, $id)
{
$active = (static::$loaded['JHtmlBootstrap::startTabSet'][$selector]['active'] == $id) ? ' active' : '';
return '<div id="' . $id . '" class="tab-pane' . $active . '">';
}
/**
* Close the current tab content panel
*
* @return string HTML to close the pane
*
* @since 3.0
* @deprecated 4.0 Use JHtml::_('bootstrap.endTab') instead.
*/
public static function endPanel()
{
return '</div>';
}
/**
* Loads CSS files needed by Bootstrap
*
* @param boolean $includeMainCss If true, main bootstrap.css files are loaded
* @param string $direction rtl or ltr direction. If empty, ltr is assumed
* @param array $attribs Optional array of attributes to be passed to JHtml::_('stylesheet')
*
* @return void
*
* @since 3.0
*/
public static function loadCss($includeMainCss = true, $direction = 'ltr', $attribs = array())
{
// Load Bootstrap main CSS
if ($includeMainCss)
{
JHtml::_('stylesheet', 'jui/bootstrap.min.css', $attribs, true);
JHtml::_('stylesheet', 'jui/bootstrap-responsive.min.css', $attribs, true);
JHtml::_('stylesheet', 'jui/bootstrap-extended.css', $attribs, true);
}
// Load Bootstrap RTL CSS
if ($direction === 'rtl')
{
JHtml::_('stylesheet', 'jui/bootstrap-rtl.css', $attribs, true);
}
}
/**
* Internal method to get a JavaScript object notation string from an array
*
* @param array $array The array to convert to JavaScript object notation
*
* @return string JavaScript object notation representation of the array
*
* @since 3.0
*/
public static function getJSObject(array $array = array())
{
$elements = array();
foreach ($array as $k => $v)
{
// Don't encode either of these types
if (is_null($v) || is_resource($v))
{
continue;
}
// Safely encode as a Javascript string
$key = json_encode((string) $k);
if (is_bool($v))
{
$elements[] = $key . ': ' . ($v ? 'true' : 'false');
}
elseif (is_numeric($v))
{
$elements[] = $key . ': ' . ($v + 0);
}
elseif (is_string($v))
{
if (strpos($v, '\\') === 0)
{
// Items such as functions and JSON objects are prefixed with \, strip the prefix and don't encode them
$elements[] = $key . ': ' . substr($v, 1);
}
else
{
// The safest way to insert a string
$elements[] = $key . ': ' . json_encode((string) $v);
}
}
else
{
$elements[] = $key . ': ' . static::getJSObject(is_object($v) ? get_object_vars($v) : $v);
}
}
return '{' . implode(',', $elements) . '}';
}
}
joomla25/html/string.php 0000604 00000021735 15245530303 0011163 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage HTML
*
* @copyright Copyright (C) 2005 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* HTML helper class for rendering manipulated strings.
*
* @package Joomla.Platform
* @subpackage HTML
* @since 1.6
*/
abstract class JHtmlString
{
/**
* Truncates text blocks over the specified character limit and closes
* all open HTML tags. The method will optionally not truncate an individual
* word, it will find the first space that is within the limit and
* truncate at that point. This method is UTF-8 safe.
*
* @param string $text The text to truncate.
* @param integer $length The maximum length of the text.
* @param boolean $noSplit Don't split a word if that is where the cutoff occurs (default: true).
* @param boolean $allowHtml Allow HTML tags in the output, and close any open tags (default: true).
*
* @return string The truncated text.
*
* @since 1.6
*/
public static function truncate($text, $length = 0, $noSplit = true, $allowHtml = true)
{
// Assume a lone open tag is invalid HTML.
if ($length == 1 && substr($text, 0, 1) == '<')
{
return '...';
}
// Check if HTML tags are allowed.
if (!$allowHtml)
{
// Deal with spacing issues in the input.
$text = str_replace('>', '> ', $text);
$text = str_replace(array(' ', ' '), ' ', $text);
$text = JString::trim(preg_replace('#\s+#mui', ' ', $text));
// Strip the tags from the input and decode entities.
$text = strip_tags($text);
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// Remove remaining extra spaces.
$text = str_replace(' ', ' ', $text);
$text = JString::trim(preg_replace('#\s+#mui', ' ', $text));
}
// Whether or not allowing HTML, truncate the item text if it is too long.
if ($length > 0 && JString::strlen($text) > $length)
{
$tmp = trim(JString::substr($text, 0, $length));
if (substr($tmp, 0, 1) == '<' && strpos($tmp, '>') === false)
{
return '...';
}
// $noSplit true means that we do not allow splitting of words.
if ($noSplit)
{
// Find the position of the last space within the allowed length.
$offset = JString::strrpos($tmp, ' ');
$tmp = JString::substr($tmp, 0, $offset + 1);
// If there are no spaces and the string is longer than the maximum
// we need to just use the ellipsis. In that case we are done.
if ($offset === false && strlen($text) > $length)
{
return '...';
}
if (JString::strlen($tmp) > $length - 3)
{
$tmp = trim(JString::substr($tmp, 0, JString::strrpos($tmp, ' ')));
}
}
if ($allowHtml)
{
// Put all opened tags into an array
preg_match_all("#<([a-z][a-z0-9]*)\b.*?(?!/)>#i", $tmp, $result);
$openedTags = $result[1];
// Some tags self close so they do not need a separate close tag.
$openedTags = array_diff($openedTags, array("img", "hr", "br"));
$openedTags = array_values($openedTags);
// Put all closed tags into an array
preg_match_all("#</([a-z]+)>#iU", $tmp, $result);
$closedTags = $result[1];
$numOpened = count($openedTags);
// All tags are closed so trim the text and finish.
if (count($closedTags) == $numOpened)
{
return trim($tmp) . '...';
}
// Closing tags need to be in the reverse order of opening tags.
$openedTags = array_reverse($openedTags);
// Close tags
for ($i = 0; $i < $numOpened; $i++)
{
if (!in_array($openedTags[$i], $closedTags))
{
$tmp .= "</" . $openedTags[$i] . ">";
}
else
{
unset($closedTags[array_search($openedTags[$i], $closedTags)]);
}
}
}
if ($tmp === false || strlen($text) > strlen($tmp))
{
$text = trim($tmp) . '...';
}
}
// Clean up any internal spaces created by the processing.
$text = str_replace(' </', '</', $text);
$text = str_replace(' ...', '...', $text);
return $text;
}
/**
* Method to extend the truncate method to more complex situations
*
* The goal is to get the proper length plain text string with as much of
* the html intact as possible with all tags properly closed.
*
* @param string $html The content of the introtext to be truncated
* @param integer $maxLength The maximum number of characters to render
* @param boolean $noSplit Don't split a word if that is where the cutoff occurs (default: true).
*
* @return string The truncated string. If the string is truncated an ellipsis
* (...) will be appended.
*
* @note If a maximum length of 3 or less is selected and the text has more than
* that number of characters an ellipsis will be displayed.
* This method will not create valid HTML from malformed HTML.
*
* @since 3.1
*/
public static function truncateComplex($html, $maxLength = 0, $noSplit = true)
{
// Start with some basic rules.
$baseLength = strlen($html);
// If the original HTML string is shorter than the $maxLength do nothing and return that.
if ($baseLength <= $maxLength || $maxLength == 0)
{
return $html;
}
// Take care of short simple cases.
if ($maxLength <= 3 && substr($html, 0, 1) != '<' && strpos(substr($html, 0, $maxLength - 1), '<') === false && $baseLength > $maxLength)
{
return '...';
}
// Deal with maximum length of 1 where the string starts with a tag.
if ($maxLength == 1 && substr($html, 0, 1) == '<')
{
$endTagPos = strlen(strstr($html, '>', true));
$tag = substr($html, 1, $endTagPos);
$l = $endTagPos + 1;
if ($noSplit)
{
return substr($html, 0, $l) . '</' . $tag . '...';
}
// TODO: $character doesn't seem to be used...
$character = substr(strip_tags($html), 0, 1);
return substr($html, 0, $l) . '</' . $tag . '...';
}
// First get the truncated plain text string. This is the rendered text we want to end up with.
$ptString = JHtml::_('string.truncate', $html, $maxLength, $noSplit, $allowHtml = false);
// It's all HTML, just return it.
if (strlen($ptString) == 0)
{
return $html;
}
// If the plain text is shorter than the max length the variable will not end in ...
// In that case we use the whole string.
if (substr($ptString, -3) != '...')
{
return $html;
}
// Regular truncate gives us the ellipsis but we want to go back for text and tags.
if ($ptString == '...')
{
$stripped = substr(strip_tags($html), 0, $maxLength);
$ptString = JHtml::_('string.truncate', $stripped, $maxLength, $noSplit, $allowHtml = false);
}
// We need to trim the ellipsis that truncate adds.
$ptString = rtrim($ptString, '.');
// Now deal with more complex truncation.
while ($maxLength <= $baseLength)
{
// Get the truncated string assuming HTML is allowed.
$htmlString = JHtml::_('string.truncate', $html, $maxLength, $noSplit, $allowHtml = true);
if ($htmlString == '...' && strlen($ptString) + 3 > $maxLength)
{
return $htmlString;
}
$htmlString = rtrim($htmlString, '.');
// Now get the plain text from the HTML string and trim it.
$htmlStringToPtString = JHtml::_('string.truncate', $htmlString, $maxLength, $noSplit, $allowHtml = false);
$htmlStringToPtString = rtrim($htmlStringToPtString, '.');
// If the new plain text string matches the original plain text string we are done.
if ($ptString == $htmlStringToPtString)
{
return $htmlString . '...';
}
// Get the number of HTML tag characters in the first $maxLength characters
$diffLength = strlen($ptString) - strlen($htmlStringToPtString);
if ($diffLength <= 0)
{
return $htmlString . '...';
}
// Set new $maxlength that adjusts for the HTML tags
$maxLength += $diffLength;
}
}
/**
* Abridges text strings over the specified character limit. The
* behavior will insert an ellipsis into the text replacing a section
* of variable size to ensure the string does not exceed the defined
* maximum length. This method is UTF-8 safe.
*
* For example, it transforms "Really long title" to "Really...title".
*
* Note that this method does not scan for HTML tags so will potentially break them.
*
* @param string $text The text to abridge.
* @param integer $length The maximum length of the text (default is 50).
* @param integer $intro The maximum length of the intro text (default is 30).
*
* @return string The abridged text.
*
* @since 1.6
*/
public static function abridge($text, $length = 50, $intro = 30)
{
// Abridge the item text if it is too long.
if (JString::strlen($text) > $length)
{
// Determine the remaining text length.
$remainder = $length - ($intro + 3);
// Extract the beginning and ending text sections.
$beg = JString::substr($text, 0, $intro);
$end = JString::substr($text, JString::strlen($text) - $remainder);
// Build the resulting string.
$text = $beg . '...' . $end;
}
return $text;
}
}
joomla25/html/jquery.php 0000604 00000005717 15245530303 0011176 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage HTML
*
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Utility class for jQuery JavaScript behaviors
*
* @since 3.0
*/
abstract class JHtmlJquery
{
/**
* @var array Array containing information for loaded files
* @since 3.0
*/
protected static $loaded = array();
/**
* Method to load the jQuery JavaScript framework into the document head
*
* If debugging mode is on an uncompressed version of jQuery is included for easier debugging.
*
* @param boolean $noConflict True to load jQuery in noConflict mode [optional]
* @param mixed $debug Is debugging mode on? [optional]
* @param boolean $migrate True to enable the jQuery Migrate plugin
*
* @return void
*
* @since 3.0
*/
public static function framework($noConflict = true, $debug = null, $migrate = true)
{
// Only load once
if (!empty(static::$loaded[__METHOD__]))
{
return;
}
// If no debugging value is set, use the configuration setting
if ($debug === null)
{
$config = JFactory::getConfig();
$debug = (boolean) $config->get('debug');
}
JHtml::_('script', 'jui/jquery.min.js', false, true, false, false, $debug);
// Check if we are loading in noConflict
if ($noConflict)
{
JHtml::_('script', 'jui/jquery-noconflict.js', false, true, false, false, false);
}
// Check if we are loading Migrate
if ($migrate)
{
JHtml::_('script', 'jui/jquery-migrate.min.js', false, true, false, false, $debug);
}
static::$loaded[__METHOD__] = true;
return;
}
/**
* Method to load the jQuery UI JavaScript framework into the document head
*
* If debugging mode is on an uncompressed version of jQuery UI is included for easier debugging.
*
* @param array $components The jQuery UI components to load [optional]
* @param mixed $debug Is debugging mode on? [optional]
*
* @return void
*
* @since 3.0
*/
public static function ui(array $components = array('core'), $debug = null)
{
// Set an array containing the supported jQuery UI components handled by this method
$supported = array('core', 'sortable');
// Include jQuery
static::framework();
// If no debugging value is set, use the configuration setting
if ($debug === null)
{
$config = JFactory::getConfig();
$debug = (boolean) $config->get('debug');
}
// Load each of the requested components
foreach ($components as $component)
{
// Only attempt to load the component if it's supported in core and hasn't already been loaded
if (in_array($component, $supported) && empty(static::$loaded[__METHOD__][$component]))
{
JHtml::_('script', 'jui/jquery.ui.' . $component . '.min.js', false, true, false, false, $debug);
static::$loaded[__METHOD__][$component] = true;
}
}
return;
}
}
joomla25/layout/file.php 0000604 00000033350 15245530303 0011141 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage Layout
*
* @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('JPATH_PLATFORM') or die;
/**
* Base class for rendering a display layout
* loaded from from a layout file
*
* @see https://docs.joomla.org/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.0
*/
class JLayoutFile extends JLayoutBase
{
/**
* Cached layout paths
*
* @var array
* @since 3.5
*/
protected static $cache = array();
/**
* Dot separated path to the layout file, relative to base path
*
* @var string
* @since 3.0
*/
protected $layoutId = '';
/**
* Base path to use when loading layout files
*
* @var string
* @since 3.0
*/
protected $basePath = null;
/**
* Full path to actual layout files, after possible template override check
*
* @var string
* @since 3.0.3
*/
protected $fullPath = null;
/**
* Paths to search for layouts
*
* @var array
* @since 3.2
*/
protected $includePaths = array();
/**
* Method to instantiate the file-based layout.
*
* @param string $layoutId Dot separated path to the layout file, relative to base path
* @param string $basePath Base path to use when loading layout files
* @param mixed $options Optional custom options to load. Registry or array format [@since 3.2]
*
* @since 3.0
*/
public function __construct($layoutId, $basePath = null, $options = null)
{
// Initialise / Load options
$this->setOptions($options);
// Main properties
$this->setLayoutId($layoutId);
$this->basePath = $basePath;
// Init Enviroment
$this->setComponent($this->options->get('component', 'auto'));
$this->setClient($this->options->get('client', 'auto'));
}
/**
* Method to render the layout.
*
* @param array $displayData Array of properties available for use inside the layout file to build the displayed output
*
* @return string The necessary HTML to display the layout
*
* @since 3.0
*/
public function render($displayData = array())
{
$this->clearDebugMessages();
// Inherit base output from parent class
$layoutOutput = '';
// Automatically merge any previously data set if $displayData is an array
if (is_array($displayData))
{
$displayData = array_merge($this->data, $displayData);
}
// Check possible overrides, and build the full path to layout file
$path = $this->getPath();
if ($this->isDebugEnabled())
{
echo "<pre>" . $this->renderDebugMessages() . "</pre>";
}
// Nothing to show
if (empty($path))
{
return $layoutOutput;
}
ob_start();
include $path;
$layoutOutput .= ob_get_contents();
ob_end_clean();
return $layoutOutput;
}
/**
* Method to finds the full real file path, checking possible overrides
*
* @return string The full path to the layout file
*
* @since 3.0
*/
protected function getPath()
{
JLoader::import('joomla.filesystem.path');
$layoutId = $this->getLayoutId();
$includePaths = $this->getIncludePaths();
$suffixes = $this->getSuffixes();
$this->addDebugMessage('<strong>Layout:</strong> ' . $this->layoutId);
if (!$layoutId)
{
$this->addDebugMessage('<strong>There is no active layout</strong>');
return null;
}
if (!$includePaths)
{
$this->addDebugMessage('<strong>There are no folders to search for layouts:</strong> ' . $layoutId);
return null;
}
$hash = md5(
json_encode(
array(
'paths' => $includePaths,
'suffixes' => $suffixes
)
)
);
if (!empty(static::$cache[$layoutId][$hash]))
{
$this->addDebugMessage('<strong>Cached path:</strong> ' . static::$cache[$layoutId][$hash]);
return static::$cache[$layoutId][$hash];
}
$this->addDebugMessage('<strong>Include Paths:</strong> ' . print_r($includePaths, true));
// Search for suffixed versions. Example: tags.j31.php
if ($suffixes)
{
$this->addDebugMessage('<strong>Suffixes:</strong> ' . print_r($suffixes, true));
foreach ($suffixes as $suffix)
{
$rawPath = str_replace('.', '/', $this->layoutId) . '.' . $suffix . '.php';
$this->addDebugMessage('<strong>Searching layout for:</strong> ' . $rawPath);
if ($foundLayout = JPath::find($this->includePaths, $rawPath))
{
$this->addDebugMessage('<strong>Found layout:</strong> ' . $this->fullPath);
static::$cache[$layoutId][$hash] = $foundLayout;
return static::$cache[$layoutId][$hash];
}
}
}
// Standard version
$rawPath = str_replace('.', '/', $this->layoutId) . '.php';
$this->addDebugMessage('<strong>Searching layout for:</strong> ' . $rawPath);
$foundLayout = JPath::find($this->includePaths, $rawPath);
if (!$foundLayout)
{
$this->addDebugMessage('<strong>Unable to find layout: </strong> ' . $layoutId);
return null;
}
$this->addDebugMessage('<strong>Found layout:</strong> ' . $foundLayout);
static::$cache[$layoutId][$hash] = $foundLayout;
return static::$cache[$layoutId][$hash];
}
/**
* Add one path to include in layout search. Proxy of addIncludePaths()
*
* @param string $path The path to search for layouts
*
* @return self
*
* @since 3.2
*/
public function addIncludePath($path)
{
$this->addIncludePaths($path);
return $this;
}
/**
* Add one or more paths to include in layout search
*
* @param string $paths The path or array of paths to search for layouts
*
* @return self
*
* @since 3.2
*/
public function addIncludePaths($paths)
{
if (empty($paths))
{
return $this;
}
$includePaths = $this->getIncludePaths();
if (is_array($paths))
{
$includePaths = array_unique(array_merge($paths, $includePaths));
}
else
{
array_unshift($includePaths, $paths);
}
$this->setIncludePaths($includePaths);
return $this;
}
/**
* Clear the include paths
*
* @return self
*
* @since 3.5
*/
public function clearIncludePaths()
{
$this->includePaths = array();
return $this;
}
/**
* Get the active include paths
*
* @return array
*
* @since 3.5
*/
public function getIncludePaths()
{
if (empty($this->includePaths))
{
$this->includePaths = $this->getDefaultIncludePaths();
}
return $this->includePaths;
}
/**
* Get the active layout id
*
* @return string
*
* @since 3.5
*/
public function getLayoutId()
{
return $this->layoutId;
}
/**
* Get the active suffixes
*
* @return array
*
* @since 3.5
*/
public function getSuffixes()
{
return $this->getOptions()->get('suffixes', array());
}
/**
* Load the automatically generated language suffixes.
* Example: array('es-ES', 'es', 'ltr')
*
* @return self
*
* @since 3.5
*/
public function loadLanguageSuffixes()
{
$lang = JFactory::getLanguage();
$langTag = $lang->getTag();
$langParts = explode('-', $langTag);
$suffixes = array($langTag, $langParts[0]);
$suffixes[] = $lang->isRTL() ? 'rtl' : 'ltr';
$this->setSuffixes($suffixes);
return $this;
}
/**
* Load the automatically generated version suffixes.
* Example: array('j311', 'j31', 'j3')
*
* @return self
*
* @since 3.5
*/
public function loadVersionSuffixes()
{
$cmsVersion = new JVersion;
// Example j311
$fullVersion = 'j' . str_replace('.', '', $cmsVersion->getShortVersion());
// Create suffixes like array('j311', 'j31', 'j3')
$suffixes = array(
$fullVersion,
substr($fullVersion, 0, 3),
substr($fullVersion, 0, 2),
);
$this->setSuffixes(array_unique($suffixes));
return $this;
}
/**
* Remove one path from the layout search
*
* @param string $path The path to remove from the layout search
*
* @return self
*
* @since 3.2
*/
public function removeIncludePath($path)
{
$this->removeIncludePaths($path);
return $this;
}
/**
* Remove one or more paths to exclude in layout search
*
* @param string $paths The path or array of paths to remove for the layout search
*
* @return self
*
* @since 3.2
*/
public function removeIncludePaths($paths)
{
if (!empty($paths))
{
$paths = (array) $paths;
$this->includePaths = array_diff($this->includePaths, $paths);
}
return $this;
}
/**
* Validate that the active component is valid
*
* @param string $option URL Option of the component. Example: com_content
*
* @return boolean
*
* @since 3.2
*/
protected function validComponent($option = null)
{
// By default we will validate the active component
$component = ($option !== null) ? $option : $this->options->get('component', null);
// Valid option format
if (!empty($component) && substr_count($component, 'com_'))
{
// Latest check: component exists and is enabled
return JComponentHelper::isEnabled($component);
}
return false;
}
/**
* Method to change the component where search for layouts
*
* @param string $option URL Option of the component. Example: com_content
*
* @return mixed Component option string | null for none
*
* @since 3.2
*/
public function setComponent($option)
{
$component = null;
switch ((string) $option)
{
case 'none':
$component = null;
break;
case 'auto':
$component = JApplicationHelper::getComponentName();
break;
default:
$component = $option;
break;
}
// Extra checks
if (!$this->validComponent($component))
{
$component = null;
}
$this->options->set('component', $component);
// Refresh include paths
$this->clearIncludePaths();
}
/**
* Function to initialise the application client
*
* @param mixed $client Frontend: 'site' or 0 | Backend: 'admin' or 1
*
* @return void
*
* @since 3.2
*/
public function setClient($client)
{
// Force string conversion to avoid unexpected states
switch ((string) $client)
{
case 'site':
case '0':
$client = 0;
break;
case 'admin':
case '1':
$client = 1;
break;
default:
$client = (int) T3::isAdmin();
break;
}
$this->options->set('client', $client);
// Refresh include paths
$this->clearIncludePaths();
}
/**
* Change the layout
*
* @param string $layoutId Layout to render
*
* @return self
*
* @since 3.2
*
* @deprecated 3.5 Use setLayoutId()
*/
public function setLayout($layoutId)
{
// Log usage of deprecated function
JLog::add(__METHOD__ . '() is deprecated, use JLayoutFile::setLayoutId() instead.', JLog::WARNING, 'deprecated');
return $this->setLayoutId($layoutId);
}
/**
* Set the active layout id
*
* @param string $layoutId Layout identifier
*
* @return self
*
* @since 3.5
*/
public function setLayoutId($layoutId)
{
$this->layoutId = $layoutId;
$this->fullPath = null;
return $this;
}
/**
* Refresh the list of include paths
*
* @return self
*
* @since 3.2
*
* @deprecated 3.5 Use JLayoutFile::clearIncludePaths()
*/
protected function refreshIncludePaths()
{
// Log usage of deprecated function
JLog::add(__METHOD__ . '() is deprecated, use JLayoutFile::clearIncludePaths() instead.', JLog::WARNING, 'deprecated');
$this->clearIncludePaths();
return $this;
}
/**
* Get the default array of include paths
*
* @return array
*
* @since 3.5
*/
public function getDefaultIncludePaths()
{
// Reset includePaths
$paths = array();
// (1 - highest priority) Received a custom high priority path
if (!is_null($this->basePath))
{
$paths[] = rtrim($this->basePath, DIRECTORY_SEPARATOR);
}
// Component layouts & overrides if exist
$component = $this->options->get('component', null);
if (!empty($component))
{
// (2) Component template overrides path
$paths[] = JPATH_THEMES . '/' . JFactory::getApplication()->getTemplate() . '/html/layouts/' . $component;
// (3) Component path
if ($this->options->get('client') == 0)
{
$paths[] = JPATH_SITE . '/components/' . $component . '/layouts';
}
else
{
$paths[] = JPATH_ADMINISTRATOR . '/components/' . $component . '/layouts';
}
}
// (4.1) - user custom layout overridden
if (!defined('T3_LOCAL_DISABLED')) $paths[] = T3_LOCAL_PATH . '/html/layouts';
// (4) Standard Joomla! layouts overriden
$paths[] = JPATH_THEMES . '/' . JFactory::getApplication()->getTemplate() . '/html/layouts';
// (5.1) - T3 base layout overridden
$paths[] = T3_PATH . '/html/layouts';
// (5 - lower priority) Frontend base layouts
$paths[] = JPATH_ROOT . '/layouts';
return $paths;
}
/**
* Set the include paths to search for layouts
*
* @param array $paths Array with paths to search in
*
* @return self
*
* @since 3.5
*/
public function setIncludePaths($paths)
{
$this->includePaths = (array) $paths;
return $this;
}
/**
* Set suffixes to search layouts
*
* @param mixed $suffixes String with a single suffix or 'auto' | 'none' or array of suffixes
*
* @return self
*
* @since 3.5
*/
public function setSuffixes(array $suffixes)
{
$this->options->set('suffixes', $suffixes);
return $this;
}
/**
* Render a layout with the same include paths & options
*
* @param object $layoutId Object which properties are used inside the layout file to build displayed output
* @param mixed $displayData Data to be rendered
*
* @return string The necessary HTML to display the layout
*
* @since 3.2
*/
public function sublayout($layoutId, $displayData)
{
// Sublayouts are searched in a subfolder with the name of the current layout
if (!empty($this->layoutId))
{
$layoutId = $this->layoutId . '.' . $layoutId;
}
$sublayout = new static($layoutId, $this->basePath, $this->options);
$sublayout->includePaths = $this->includePaths;
return $sublayout->render($displayData);
}
}
joomla25/layout/base.php 0000604 00000012316 15245530303 0011133 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage Layout
*
* @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('JPATH_PLATFORM') or die;
use Joomla\Registry\Registry;
/**
* Base class for rendering a display layout
*
* @see https://docs.joomla.org/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.0
*/
class JLayoutBase implements JLayout
{
/**
* Options object
*
* @var Registry
* @since 3.2
*/
protected $options = null;
/**
* Data for the layout
*
* @var array
* @since 3.5
*/
protected $data = array();
/**
* Debug information messages
*
* @var array
* @since 3.2
*/
protected $debugMessages = array();
/**
* Set the options
*
* @param array|Registry $options Array / Registry object with the options to load
*
* @return JLayoutBase Instance of $this to allow chaining.
*
* @since 3.2
*/
public function setOptions($options = null)
{
// Received Registry
if ($options instanceof Registry)
{
$this->options = $options;
}
// Received array
elseif (is_array($options))
{
$this->options = new Registry($options);
}
else
{
$this->options = new Registry;
}
return $this;
}
/**
* Get the options
*
* @return Registry Object with the options
*
* @since 3.2
*/
public function getOptions()
{
// Always return a Registry instance
if (!($this->options instanceof Registry))
{
$this->resetOptions();
}
return $this->options;
}
/**
* Function to empty all the options
*
* @return JLayoutBase Instance of $this to allow chaining.
*
* @since 3.2
*/
public function resetOptions()
{
return $this->setOptions(null);
}
/**
* Method to escape output.
*
* @param string $output The output to escape.
*
* @return string The escaped output.
*
* @since 3.0
*/
public function escape($output)
{
return htmlspecialchars($output, ENT_COMPAT, 'UTF-8');
}
/**
* Get the debug messages array
*
* @return array
*
* @since 3.2
*/
public function getDebugMessages()
{
return $this->debugMessages;
}
/**
* Method to render the layout.
*
* @param array $displayData Array of properties available for use inside the layout file to build the displayed output
*
* @return string The necessary HTML to display the layout
*
* @since 3.0
*/
public function render($displayData)
{
// Automatically merge any previously data set if $displayData is an array
if (is_array($displayData))
{
$displayData = array_merge($this->data, $displayData);
}
return '';
}
/**
* Render the list of debug messages
*
* @return string Output text/HTML code
*
* @since 3.2
*/
public function renderDebugMessages()
{
return implode($this->debugMessages, "\n");
}
/**
* Add a debug message to the debug messages array
*
* @param string $message Message to save
*
* @return self
*
* @since 3.2
*/
public function addDebugMessage($message)
{
$this->debugMessages[] = $message;
return $this;
}
/**
* Clear the debug messages array
*
* @return self
*
* @since 3.5
*/
public function clearDebugMessages()
{
$this->debugMessages = array();
return $this;
}
/**
* Render a layout with debug info
*
* @param mixed $data Data passed to the layout
*
* @return string
*
* @since 3.5
*/
public function debug($data = array())
{
$this->setDebug(true);
$output = $this->render($data);
$this->setDebug(false);
return $output;
}
/**
* Method to get the value from the data array
*
* @param string $key Key to search for in the data array
* @param mixed $defaultValue Default value to return if the key is not set
*
* @return mixed Value from the data array | defaultValue if doesn't exist
*
* @since 3.5
*/
public function get($key, $defaultValue = null)
{
return isset($this->data[$key]) ? $this->data[$key] : $defaultValue;
}
/**
* Get the data being rendered
*
* @return array
*
* @since 3.5
*/
public function getData()
{
return $this->data;
}
/**
* Check if debug mode is enabled
*
* @return boolean
*
* @since 3.5
*/
public function isDebugEnabled()
{
return $this->getOptions()->get('debug', false) === true;
}
/**
* Method to set a value in the data array. Example: $layout->set('items', $items);
*
* @param string $key Key for the data array
* @param mixed $value Value to assign to the key
*
* @return self
*
* @since 3.5
*/
public function set($key, $value)
{
$this->data[(string) $key] = $value;
return $this;
}
/**
* Set the the data passed the layout
*
* @param array $data Array with the data for the layout
*
* @return self
*
* @since 3.5
*/
public function setData(array $data)
{
$this->data = $data;
return $this;
}
/**
* Change the debug mode
*
* @param boolean $debug Enable / Disable debug
*
* @return self
*
* @since 3.5
*/
public function setDebug($debug)
{
$this->options->set('debug', (boolean) $debug);
return $this;
}
}
joomla25/layout/helper.php 0000604 00000005022 15245530303 0011474 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage Layout
*
* @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('JPATH_PLATFORM') or die;
/**
* Helper to render a JLayout object, storing a base path
*
* @see https://docs.joomla.org/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.1
*/
class JLayoutHelper
{
/**
* A default base path that will be used if none is provided when calling the render method.
* Note that JLayoutFile itself will defaults to JPATH_ROOT . '/layouts' if no basePath is supplied at all
*
* @var string
* @since 3.1
*/
public static $defaultBasePath = '';
/**
* Method to render a layout with debug info
*
* @param string $layoutFile Dot separated path to the layout file, relative to base path
* @param object $displayData Object which properties are used inside the layout file to build displayed output
* @param string $basePath Base path to use when loading layout files
* @param mixed $options Optional custom options to load. Registry or array format
*
* @return string
*
* @since 3.5
*/
public static function debug($layoutFile, $displayData = null, $basePath = '', $options = null)
{
$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;
// Make sure we send null to JLayoutFile if no path set
$basePath = empty($basePath) ? null : $basePath;
$layout = new JLayoutFile($layoutFile, $basePath, $options);
$renderedLayout = $layout->debug($displayData);
return $renderedLayout;
}
/**
* Method to render the layout.
*
* @param string $layoutFile Dot separated path to the layout file, relative to base path
* @param object $displayData Object which properties are used inside the layout file to build displayed output
* @param string $basePath Base path to use when loading layout files
* @param mixed $options Optional custom options to load. Registry or array format
*
* @return string
*
* @since 3.1
*/
public static function render($layoutFile, $displayData = null, $basePath = '', $options = null)
{
$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;
// Make sure we send null to JLayoutFile if no path set
$basePath = empty($basePath) ? null : $basePath;
$layout = new JLayoutFile($layoutFile, $basePath, $options);
$renderedLayout = $layout->render($displayData);
return $renderedLayout;
}
}
joomla25/layout/layout.php 0000604 00000001640 15245530303 0011534 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage Layout
*
* @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('JPATH_PLATFORM') or die;
/**
* Interface to handle display layout
*
* @see https://docs.joomla.org/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.0
*/
interface JLayout
{
/**
* Method to escape output.
*
* @param string $output The output to escape.
*
* @return string The escaped output.
*
* @since 3.0
*/
public function escape($output);
/**
* Method to render the layout.
*
* @param array $displayData Array of properties available for use inside the layout file to build the displayed output
*
* @return string The rendered layout.
*
* @since 3.0
*/
public function render($displayData);
}
joomla25/layout/index.html 0000604 00000000037 15245530303 0011502 0 ustar 00 <!DOCTYPE html><title></title>
joomla25/modulehelper.php 0000604 00000042363 15245530303 0011376 0 ustar 00 <?php
/**
* @package Advanced Module Manager
* @version 3.3.0a
*
* @author Peter van Westen <peter@nonumber.nl>
* @link http://www.nonumber.nl
* @copyright Copyright � 2011 All Rights Reserved
* Brandon IT Consulting (http://www.metamodpro.com)
* NoNumber (http://www.nonumber.nl)
* JoomlArt (http://www.joomlart.com)
* @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
*/
/**
* BASE ON JOOMLA CORE FILE:
* /libraries/joomla/application/module/helper.php
*/
/**
* @package Joomla.Platform
* @subpackage Application
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or defined('JPATH_BASE') or die;
jimport('joomla.application.component.helper');
/**
* Module helper class
*
* @package Joomla.Platform
* @subpackage Application
* @since 11.1
*/
abstract class JModuleHelper
{
/**
* Get module by name (real, eg 'Breadcrumbs' or folder, eg 'mod_breadcrumbs')
*
* @param string $name The name of the module
* @param string $title The title of the module, optional
*
* @return object The Module object
*
* @since 11.1
*/
public static function &getModule($name, $title = null)
{
$result = null;
$modules =& JModuleHelper::_load();
$total = count($modules);
for ($i = 0; $i < $total; $i++)
{
// Match the name of the module
if ($modules[$i]->name == $name || $modules[$i]->module == $name)
{
// Match the title if we're looking for a specific instance of the module
if (!$title || $modules[$i]->title == $title)
{
// Found it
$result = &$modules[$i];
break; // Found it
}
}
}
// If we didn't find it, and the name is mod_something, create a dummy object
if (is_null($result) && substr($name, 0, 4) == 'mod_')
{
$result = new stdClass;
$result->id = 0;
$result->title = '';
$result->module = $name;
$result->position = '';
$result->content = '';
$result->showtitle = 0;
$result->control = '';
$result->params = '';
$result->user = 0;
}
return $result;
}
/**
* Get modules by position
*
* @param string $position The position of the module
*
* @return array An array of module objects
*
* @since 11.1
*/
public static function &getModules($position)
{
$position = strtolower($position);
$result = array();
$modules =& JModuleHelper::_load();
$total = count($modules);
for ($i = 0; $i < $total; $i++)
{
if ($modules[$i]->position == $position)
{
$result[] = &$modules[$i];
}
}
if (count($result) == 0)
{
if (JRequest::getBool('tp') && JComponentHelper::getParams('com_templates')->get('template_positions_display'))
{
$result[0] = JModuleHelper::getModule('mod_' . $position);
$result[0]->title = $position;
$result[0]->content = $position;
$result[0]->position = $position;
}
}
return $result;
}
/**
* Checks if a module is enabled
*
* @param string $module The module name
*
* @return boolean
*
* @since 11.1
*/
public static function isEnabled($module)
{
$result = JModuleHelper::getModule($module);
return !is_null($result);
}
/**
* Render the module.
*
* @param object $module A module object.
* @param array $attribs An array of attributes for the module (probably from the XML).
*
* @return string The HTML content of the module output.
*
* @since 11.1
*/
public static function renderModule($module, $attribs = array())
{
static $chrome;
if (constant('JDEBUG'))
{
JProfiler::getInstance('Application')->mark('beforeRenderModule ' . $module->module . ' (' . $module->title . ')');
}
$app = JFactory::getApplication();
// Record the scope.
$scope = $app->scope;
// Set scope to component name
$app->scope = $module->module;
// Get module parameters
$params = new JRegistry;
$params->loadString($module->params);
// Get module path
$module->module = preg_replace('/[^A-Z0-9_\.-]/i', '', $module->module);
$path = JPATH_BASE . '/modules/' . $module->module . '/' . $module->module . '.php';
// Load the module
// $module->user is a check for 1.0 custom modules and is deprecated refactoring
if (empty($module->user) && file_exists($path))
{
$lang = JFactory::getLanguage();
// 1.5 or Core then 1.6 3PD
$lang->load($module->module, JPATH_BASE, null, false, false) ||
$lang->load($module->module, dirname($path), null, false, false) ||
$lang->load($module->module, JPATH_BASE, $lang->getDefault(), false, false) ||
$lang->load($module->module, dirname($path), $lang->getDefault(), false, false);
$content = '';
ob_start();
include $path;
$module->content = ob_get_contents() . $content;
ob_end_clean();
}
// Load the module chrome functions
if (!$chrome)
{
$chrome = array();
}
include_once JPATH_THEMES . '/system/html/modules.php';
$chromePath = JPATH_THEMES . '/' . $app->getTemplate() . '/html/modules.php';
if (!isset($chrome[$chromePath]))
{
if (file_exists($chromePath))
{
include_once $chromePath;
}
$chrome[$chromePath] = true;
}
// Make sure a style is set
if (!isset($attribs['style']))
{
$attribs['style'] = 'none';
}
// Dynamically add outline style
if (JRequest::getBool('tp') && JComponentHelper::getParams('com_templates')->get('template_positions_display'))
{
$attribs['style'] .= ' outline';
}
// Do 3rd party stuff to manipulate module content
// onRenderModule is allowed to alter the $module, $attribs
// and may return a boolean.
// true=remove, any other value = keep.
// $result holds an array of booleans, 1 from each plugin.
// we ditch the module if any of them = true.
$result = $app->triggerEvent( 'onRenderModule', array( &$module, &$attribs ) );
if (!is_array($result)) {
$result = array($result);
}
if ( array_search( true, $result, true ) !== false )
{
return '';
}
foreach (explode(' ', $attribs['style']) as $style)
{
$chromeMethod = 'modChrome_' . $style;
// Apply chrome and render module
if (function_exists($chromeMethod))
{
$module->style = $attribs['style'];
ob_start();
$chromeMethod($module, $params, $attribs);
$module->content = ob_get_contents();
ob_end_clean();
}
}
//revert the scope
$app->scope = $scope;
if (constant('JDEBUG'))
{
JProfiler::getInstance('Application')->mark('afterRenderModule ' . $module->module . ' (' . $module->title . ')');
}
return $module->content;
}
/**
* Get the path to a layout for a module
*
* @param string $module The name of the module
* @param string $layout The name of the module layout. If alternative layout, in the form template:filename.
*
* @return string The path to the module layout
*
* @since 11.1
*/
public static function getLayoutPath($module, $layout = 'default')
{
$template = JFactory::getApplication()->getTemplate();
$defaultLayout = $layout;
if (strpos($layout, ':') !== false)
{
// Get the template and file name from the string
$temp = explode(':', $layout);
$template = ($temp[0] == '_') ? $template : $temp[0];
$layout = $temp[1];
$defaultLayout = ($temp[1]) ? $temp[1] : 'default';
}
// Build the template and base path for the layout
$tPath = JPATH_THEMES . '/' . $template . '/html/' . $module . '/' . $layout . '.php';
$bPath = JPATH_BASE . '/modules/' . $module . '/tmpl/' . $defaultLayout . '.php';
$dPath = JPATH_BASE . '/modules/' . $module . '/tmpl/default.php';
// Do 3rd party stuff to detect layout path for the module
// onGetLayoutPath should return the path to the $layout of $module or false
// $results holds an array of results returned from plugins, 1 from each plugin.
// if a path to the $layout is found and it is a file, return that path
$app = JFactory::getApplication();
$result = $app->triggerEvent( 'onGetLayoutPath', array( $module, $layout ) );
if (is_array($result))
{
foreach ($result as $path)
{
if ($path !== false && is_file ($path))
{
return $path;
}
}
}
// If the template has a layout override use it
if (file_exists($tPath))
{
return $tPath;
}
elseif (file_exists($bPath))
{
return $bPath;
}
else
{
return $dPath;
}
}
/**
* Load published modules.
*
* @return array
*
* @since 11.1
*/
protected static function &_load()
{
static $clean;
if (isset($clean))
{
return $clean;
}
$Itemid = JRequest::getInt('Itemid');
$app = JFactory::getApplication();
$user = JFactory::getUser();
$groups = implode(',', $user->getAuthorisedViewLevels());
$lang = JFactory::getLanguage()->getTag();
$clientId = (int) $app->getClientId();
/*
$cache = JFactory::getCache('com_modules', '');
$cacheid = md5(serialize(array($Itemid, $groups, $clientId, $lang)));
if (!($clean = $cache->get($cacheid)))
{
*/
$db = JFactory::getDbo();
$query = new stdClass;
$query->select = array();
$query->from = array();
$query->join = array();
$query->where = array();
$query->order = array();
$query->select[] = 'm.published, m.id, m.title, m.module, m.position, m.content, m.showtitle, m.params, mm.menuid';
$query->from[] = '#__modules AS m';
$query->join[] = '#__modules_menu AS mm ON mm.moduleid = m.id';
$query->where[] = 'm.published = 1';
$query->join[] = '#__extensions AS e ON e.element = m.module AND e.client_id = m.client_id';
$query->where[] = 'e.enabled = 1';
$date = JFactory::getDate();
$now = $date->toSql();
$nullDate = $db->getNullDate();
$query->where[] = '(m.publish_up = ' . $db->q($nullDate) . ' OR m.publish_up <= ' . $db->q($now) . ')';
$query->where[] = '(m.publish_down = ' . $db->q($nullDate) . ' OR m.publish_down >= ' . $db->q($now) . ')';
$query->where[] = 'm.access IN ('.$groups.')';
$query->where[] = 'm.client_id = ' . $clientId;
$query->where[] = '(mm.menuid = ' . (int) $Itemid . ' OR mm.menuid <= 0)';
// Filter by language
if ($app->isClient('site') && $app->getLanguageFilter())
{
$query->where[] = 'm.language IN (' . $db->q($lang) . ',' . $db->q('*') . ')';
}
$query->order[] = 'm.position, m.ordering';
// Do 3rd party stuff to change query
$app->triggerEvent( 'onCreateModuleQuery', array( &$query ) );
$q = $db->getQuery(true);
// convert array object to query object
foreach ( $query as $type => $strings )
{
foreach ( $strings as $string )
{
if ( $type == 'join' )
{
$q->{$type}( 'LEFT', $string );
}
else
{
$q->{$type}( $string );
}
}
}
// Set the query
$db->setQuery($q);
$modules = $db->loadObjectList();
$clean = array();
if ($db->getErrorNum())
{
JError::raiseWarning(500, JText::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $db->getErrorMsg()));
return $clean;
}
// Apply negative selections and eliminate duplicates
$negId = $Itemid ? -(int) $Itemid : false;
$dupes = array();
for ($i = 0, $n = count($modules); $i < $n; $i++)
{
$module = &$modules[$i];
// The module is excluded if there is an explicit prohibition
$negHit = ($negId === (int) $module->menuid);
if (isset($dupes[$module->id]))
{
// If this item has been excluded, keep the duplicate flag set,
// but remove any item from the cleaned array.
if ($negHit)
{
unset($clean[$module->id]);
}
continue;
}
$dupes[$module->id] = true;
// Only accept modules without explicit exclusions.
if (!$negHit)
{
// Determine if this is a 1.0 style custom module (no mod_ prefix)
// This should be eliminated when the class is refactored.
// $module->user is deprecated.
$file = $module->module;
$custom = substr($file, 0, 4) == 'mod_' ? 0 : 1;
$module->user = $custom;
// 1.0 style custom module name is given by the title field, otherwise strip off "mod_"
$module->name = $custom ? $module->module : substr($file, 4);
$module->style = null;
$module->position = strtolower($module->position);
$clean[$module->id] = $module;
}
}
unset($dupes);
// Do 3rd party stuff to manipulate module array.
// Any plugins using this architecture may make alterations to the referenced $modules array.
// To remove items you can do unset($modules[n]) or $modules[n]->published = false.
// "onPrepareModuleList" may alter or add $modules, and does not need to return anything.
// This should be used for module addition/deletion that the user would expect to happen at an
// early stage.
$app->triggerEvent( 'onPrepareModuleList', array( &$clean ) );
// "onAlterModuleList" may alter or add $modules, and does not need to return anything.
$app->triggerEvent( 'onAlterModuleList', array( &$clean ) );
// "onPostProcessModuleList" allows a plugin to perform actions like parameter changes
// on the completed list of modules and is guaranteed to occur *after*
// the earlier plugins.
$app->triggerEvent( 'onPostProcessModuleList', array( &$clean ) );
// Remove any that were marked as disabled during the preceding steps
foreach ( $clean as $id => $module )
{
if ( !isset( $module->published ) || $module->published == 0 )
{
unset( $clean[$id] );
}
}
// Return to simple indexing that matches the query order.
$clean = array_values($clean);
/*
$cache->store($clean, $cacheid);
}
*/
return $clean;
}
/**
* Module cache helper
*
* Caching modes:
* To be set in XML:
* 'static' One cache file for all pages with the same module parameters
* 'oldstatic' 1.5 definition of module caching, one cache file for all pages
* with the same module id and user aid,
* 'itemid' Changes on itemid change, to be called from inside the module:
* 'safeuri' Id created from $cacheparams->modeparams array,
* 'id' Module sets own cache id's
*
* @param object $module Module object
* @param object $moduleparams Module parameters
* @param object $cacheparams Module cache parameters - id or url parameters, depending on the module cache mode
*
* @return string
*
* @since 11.1
*
* @link JFilterInput::clean()
*/
public static function moduleCache($module, $moduleparams, $cacheparams)
{
if (!isset($cacheparams->modeparams))
{
$cacheparams->modeparams = null;
}
// Add module ID to fix problem of cache for modules with the same type
if ($cacheparams->modeparams && is_string($cacheparams->modeparams)) {
$cacheparams->modeparams .= ":".$module->id;
}
if (!isset($cacheparams->cachegroup))
{
$cacheparams->cachegroup = $module->module;
}
$user = JFactory::getUser();
$cache = JFactory::getCache($cacheparams->cachegroup, 'callback');
$conf = JFactory::getConfig();
// Turn cache off for internal callers if parameters are set to off and for all logged in users
if ($moduleparams->get('owncache', null) === '0' || $conf->get('caching') == 0 || $user->get('id'))
{
$cache->setCaching(false);
}
// module cache is set in seconds, global cache in minutes, setLifeTime works in minutes
$cache->setLifeTime($moduleparams->get('cache_time', $conf->get('cachetime') * 60) / 60);
$wrkaroundoptions = array('nopathway' => 1, 'nohead' => 0, 'nomodules' => 1, 'modulemode' => 1, 'mergehead' => 1);
$wrkarounds = true;
$view_levels = md5(serialize($user->getAuthorisedViewLevels()));
switch ($cacheparams->cachemode)
{
case 'id':
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$cacheparams->modeparams,
$wrkarounds,
$wrkaroundoptions
);
break;
case 'safeuri':
$secureid = null;
if (is_array($cacheparams->modeparams))
{
$uri = JRequest::get();
$safeuri = new stdClass;
foreach ($cacheparams->modeparams as $key => $value)
{
// Use int filter for id/catid to clean out spamy slugs
if (isset($uri[$key]))
{
$safeuri->$key = JRequest::_cleanVar($uri[$key], 0, $value);
}
}
}
$secureid = md5(serialize(array($safeuri, $cacheparams->method, $moduleparams)));
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$module->id . $view_levels . $secureid,
$wrkarounds,
$wrkaroundoptions
);
break;
case 'static':
$ret = $cache->get(
array($cacheparams->class,
$cacheparams->method),
$cacheparams->methodparams,
$module->module . md5(serialize($cacheparams->methodparams)),
$wrkarounds,
$wrkaroundoptions
);
break;
case 'oldstatic': // provided for backward compatibility, not really usefull
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$module->id . $view_levels,
$wrkarounds,
$wrkaroundoptions
);
break;
case 'itemid':
default:
$ret = $cache->get(
array($cacheparams->class, $cacheparams->method),
$cacheparams->methodparams,
$module->id . $view_levels . JRequest::getVar('Itemid', null, 'default', 'INT'),
$wrkarounds,
$wrkaroundoptions
);
break;
}
return $ret;
}
}
joomla25/view.php 0000604 00000044321 15245530303 0007657 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage Application
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Base class for a Joomla View
*
* Class holding methods for displaying presentation data.
*
* @package Joomla.Platform
* @subpackage Application
* @since 11.1
*/
class JView extends JObject
{
/**
* The name of the view
*
* @var array
*/
protected $_name = null;
/**
* Registered models
*
* @var array
*/
protected $_models = array();
/**
* The base path of the view
*
* @var string
*/
protected $_basePath = null;
/**
* The default model
*
* @var string
*/
protected $_defaultModel = null;
/**
* Layout name
*
* @var string
*/
protected $_layout = 'default';
/**
* Layout extension
*
* @var string
*/
protected $_layoutExt = 'php';
/**
* Layout template
*
* @var string
*/
protected $_layoutTemplate = '_';
/**
* The set of search directories for resources (templates)
*
* @var array
*/
protected $_path = array('template' => array(), 'helper' => array());
/**
* The name of the default template source file.
*
* @var string
*/
protected $_template = null;
/**
* The output of the template script.
*
* @var string
*/
protected $_output = null;
/**
* Callback for escaping.
*
* @var string
*/
protected $_escape = 'htmlspecialchars';
/**
* Charset to use in escaping mechanisms; defaults to urf8 (UTF-8)
*
* @var string
*/
protected $_charset = 'UTF-8';
/**
* Constructor
*
* @param array $config A named configuration array for object construction.<br/>
* name: the name (optional) of the view (defaults to the view class name suffix).<br/>
* charset: the character set to use for display<br/>
* escape: the name (optional) of the function to use for escaping strings<br/>
* base_path: the parent path (optional) of the views directory (defaults to the component folder)<br/>
* template_plath: the path (optional) of the layout directory (defaults to base_path + /views/ + view name<br/>
* helper_path: the path (optional) of the helper files (defaults to base_path + /helpers/)<br/>
* layout: the layout (optional) to use to display the view<br/>
*
* @since 11.1
*/
public function __construct($config = array())
{
// Set the view name
if (empty($this->_name))
{
if (array_key_exists('name', $config))
{
$this->_name = $config['name'];
}
else
{
$this->_name = $this->getName();
}
}
// Set the charset (used by the variable escaping functions)
if (array_key_exists('charset', $config))
{
$this->_charset = $config['charset'];
}
// User-defined escaping callback
if (array_key_exists('escape', $config))
{
$this->setEscape($config['escape']);
}
// Set a base path for use by the view
if (array_key_exists('base_path', $config))
{
$this->_basePath = $config['base_path'];
}
else
{
$this->_basePath = JPATH_COMPONENT;
}
// Set the default template search path
if (array_key_exists('template_path', $config))
{
// User-defined dirs
$this->_setPath('template', $config['template_path']);
}
else
{
$this->_setPath('template', $this->_basePath . '/views/' . $this->getName() . '/tmpl');
}
// Set the default helper search path
if (array_key_exists('helper_path', $config))
{
// User-defined dirs
$this->_setPath('helper', $config['helper_path']);
}
else
{
$this->_setPath('helper', $this->_basePath . '/helpers');
}
// Set the layout
if (array_key_exists('layout', $config))
{
$this->setLayout($config['layout']);
}
else
{
$this->setLayout('default');
}
$this->baseurl = JURI::base(true);
}
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise a JError object.
*
* @see fetch()
* @since 11.1
*/
public function display($tpl = null)
{
$result = $this->loadTemplate($tpl);
if ($result instanceof Exception)
{
return $result;
}
echo $result;
}
/**
* Assigns variables to the view script via differing strategies.
*
* This method is overloaded; you can assign all the properties of
* an object, an associative array, or a single value by name.
*
* You are not allowed to set variables that begin with an underscore;
* these are either private properties for JView or private variables
* within the template script itself.
*
* <code>
* $view = new JView;
*
* // Assign directly
* $view->var1 = 'something';
* $view->var2 = 'else';
*
* // Assign by name and value
* $view->assign('var1', 'something');
* $view->assign('var2', 'else');
*
* // Assign by assoc-array
* $ary = array('var1' => 'something', 'var2' => 'else');
* $view->assign($obj);
*
* // Assign by object
* $obj = new stdClass;
* $obj->var1 = 'something';
* $obj->var2 = 'else';
* $view->assign($obj);
*
* </code>
*
* @return boolean True on success, false on failure.
*/
public function assign()
{
// Get the arguments; there may be 1 or 2.
$arg0 = @func_get_arg(0);
$arg1 = @func_get_arg(1);
// Assign by object
if (is_object($arg0))
{
// Assign public properties
foreach (get_object_vars($arg0) as $key => $val)
{
if (substr($key, 0, 1) != '_')
{
$this->$key = $val;
}
}
return true;
}
// Assign by associative array
if (is_array($arg0))
{
foreach ($arg0 as $key => $val)
{
if (substr($key, 0, 1) != '_')
{
$this->$key = $val;
}
}
return true;
}
// Assign by string name and mixed value.
// We use array_key_exists() instead of isset() because isset()
// fails if the value is set to null.
if (is_string($arg0) && substr($arg0, 0, 1) != '_' && func_num_args() > 1)
{
$this->$arg0 = $arg1;
return true;
}
// $arg0 was not object, array, or string.
return false;
}
/**
* Assign variable for the view (by reference).
*
* You are not allowed to set variables that begin with an underscore;
* these are either private properties for JView or private variables
* within the template script itself.
*
* <code>
* $view = new JView;
*
* // Assign by name and value
* $view->assignRef('var1', $ref);
*
* // Assign directly
* $view->ref = &$var1;
* </code>
*
* @param string $key The name for the reference in the view.
* @param mixed &$val The referenced variable.
*
* @return boolean True on success, false on failure.
*
* @since 11.1
*/
public function assignRef($key, &$val)
{
if (is_string($key) && substr($key, 0, 1) != '_')
{
$this->$key = &$val;
return true;
}
return false;
}
/**
* Escapes a value for output in a view script.
*
* If escaping mechanism is either htmlspecialchars or htmlentities, uses
* {@link $_encoding} setting.
*
* @param mixed $var The output to escape.
*
* @return mixed The escaped value.
*
* @since 11.1
*/
public function escape($var)
{
if (in_array($this->_escape, array('htmlspecialchars', 'htmlentities')))
{
return call_user_func($this->_escape, $var, ENT_COMPAT, $this->_charset);
}
return call_user_func($this->_escape, $var);
}
/**
* Method to get data from a registered model or a property of the view
*
* @param string $property The name of the method to call on the model or the property to get
* @param string $default The name of the model to reference or the default value [optional]
*
* @return mixed The return value of the method
*
* @since 11.1
*/
public function get($property, $default = null)
{
// If $model is null we use the default model
if (is_null($default))
{
$model = $this->_defaultModel;
}
else
{
$model = strtolower($default);
}
// First check to make sure the model requested exists
if (isset($this->_models[$model]))
{
// Model exists, let's build the method name
$method = 'get' . ucfirst($property);
// Does the method exist?
if (method_exists($this->_models[$model], $method))
{
// The method exists, let's call it and return what we get
$result = $this->_models[$model]->$method();
return $result;
}
}
// Degrade to JObject::get
$result = parent::get($property, $default);
return $result;
}
/**
* Method to get the model object
*
* @param string $name The name of the model (optional)
*
* @return mixed JModel object
*
* @since 11.1
*/
public function getModel($name = null)
{
if ($name === null)
{
$name = $this->_defaultModel;
}
return $this->_models[strtolower($name)];
}
/**
* Get the layout.
*
* @return string The layout name
*/
public function getLayout()
{
return $this->_layout;
}
/**
* Get the layout template.
*
* @return string The layout template name
*/
public function getLayoutTemplate()
{
return $this->_layoutTemplate;
}
/**
* Method to get the view name
*
* The model name by default parsed using the classname, or it can be set
* by passing a $config['name'] in the class constructor
*
* @return string The name of the model
*
* @since 11.1
*/
public function getName()
{
if (empty($this->_name))
{
$r = null;
if (!preg_match('/View((view)*(.*(view)?.*))$/i', get_class($this), $r))
{
JError::raiseError(500, JText::_('JLIB_APPLICATION_ERROR_VIEW_GET_NAME'));
}
if (strpos($r[3], "view"))
{
JError::raiseWarning('SOME_ERROR_CODE', JText::_('JLIB_APPLICATION_ERROR_VIEW_GET_NAME_SUBSTRING'));
}
$this->_name = strtolower($r[3]);
}
return $this->_name;
}
/**
* Method to add a model to the view. We support a multiple model single
* view system by which models are referenced by classname. A caveat to the
* classname referencing is that any classname prepended by JModel will be
* referenced by the name without JModel, eg. JModelCategory is just
* Category.
*
* @param JModel &$model The model to add to the view.
* @param boolean $default Is this the default model?
*
* @return object The added model.
*
* @since 11.1
*/
public function setModel(&$model, $default = false)
{
$name = strtolower($model->getName());
$this->_models[$name] = &$model;
if ($default)
{
$this->_defaultModel = $name;
}
return $model;
}
/**
* Sets the layout name to use
*
* @param string $layout The layout name or a string in format <template>:<layout file>
*
* @return string Previous value.
*
* @since 11.1
*/
public function setLayout($layout)
{
$previous = $this->_layout;
if (strpos($layout, ':') === false)
{
$this->_layout = $layout;
}
else
{
// Convert parameter to array based on :
$temp = explode(':', $layout);
$this->_layout = $temp[1];
// Set layout template
$this->_layoutTemplate = $temp[0];
}
return $previous;
}
/**
* Allows a different extension for the layout files to be used
*
* @param string $value The extension.
*
* @return string Previous value
*
* @since 11.1
*/
public function setLayoutExt($value)
{
$previous = $this->_layoutExt;
if ($value = preg_replace('#[^A-Za-z0-9]#', '', trim($value)))
{
$this->_layoutExt = $value;
}
return $previous;
}
/**
* Sets the _escape() callback.
*
* @param mixed $spec The callback for _escape() to use.
*
* @return void
*
* @since 11.1
*/
public function setEscape($spec)
{
$this->_escape = $spec;
}
/**
* Adds to the stack of view script paths in LIFO order.
*
* @param mixed $path A directory path or an array of paths.
*
* @return void
*
* @since 11.1
*/
public function addTemplatePath($path)
{
$this->_addPath('template', $path);
}
/**
* Adds to the stack of helper script paths in LIFO order.
*
* @param mixed $path A directory path or an array of paths.
*
* @return void
*
* @since 11.1
*/
public function addHelperPath($path)
{
$this->_addPath('helper', $path);
}
/**
* Load a template file -- first look in the templates folder for an override
*
* @param string $tpl The name of the template source file; automatically searches the template paths and compiles as needed.
*
* @return string The output of the the template script.
*
* @since 11.1
*/
public function loadTemplate($tpl = null)
{
// Clear prior output
$this->_output = null;
$template = JFactory::getApplication()->getTemplate();
$layout = $this->getLayout();
$layoutTemplate = $this->getLayoutTemplate();
// Create the template file name based on the layout
$file = isset($tpl) ? $layout . '_' . $tpl : $layout;
// Clean the file name
$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $file);
$tpl = isset($tpl) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $tpl) : $tpl;
// Load the language file for the template
$lang = JFactory::getLanguage();
$lang->load('tpl_' . $template, JPATH_BASE, null, false, false)
|| $lang->load('tpl_' . $template, JPATH_THEMES . "/$template", null, false, false)
|| $lang->load('tpl_' . $template, JPATH_BASE, $lang->getDefault(), false, false)
|| $lang->load('tpl_' . $template, JPATH_THEMES . "/$template", $lang->getDefault(), false, false);
// Change the template folder if alternative layout is in different template
if (isset($layoutTemplate) && $layoutTemplate != '_' && $layoutTemplate != $template)
{
$this->_path['template'] = str_replace($template, $layoutTemplate, $this->_path['template']);
}
// Load the template script
jimport('joomla.filesystem.path');
$filetofind = $this->_createFileName('template', array('name' => $file));
$this->_template = JPath::find($this->_path['template'], $filetofind);
// If alternate layout can't be found, fall back to default layout
if ($this->_template == false)
{
$filetofind = $this->_createFileName('', array('name' => 'default' . (isset($tpl) ? '_' . $tpl : $tpl)));
$this->_template = JPath::find($this->_path['template'], $filetofind);
}
if ($this->_template != false)
{
// Unset so as not to introduce into template scope
unset($tpl);
unset($file);
// Never allow a 'this' property
if (isset($this->this))
{
unset($this->this);
}
// Start capturing output into a buffer
ob_start();
// Include the requested template filename in the local scope
// (this will execute the view logic).
include $this->_template;
// Done with the requested template; get the buffer and
// clear it.
$this->_output = ob_get_contents();
ob_end_clean();
return $this->_output;
}
else
{
return JError::raiseError(500, JText::sprintf('JLIB_APPLICATION_ERROR_LAYOUTFILE_NOT_FOUND', $file));
}
}
/**
* Load a helper file
*
* @param string $hlp The name of the helper source file automatically searches the helper paths and compiles as needed.
*
* @return void
*
* @since 11.1
*/
public function loadHelper($hlp = null)
{
// Clean the file name
$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $hlp);
// Load the template script
jimport('joomla.filesystem.path');
$helper = JPath::find($this->_path['helper'], $this->_createFileName('helper', array('name' => $file)));
if ($helper != false)
{
// Include the requested template filename in the local scope
include_once $helper;
}
}
/**
* Sets an entire array of search paths for templates or resources.
*
* @param string $type The type of path to set, typically 'template'.
* @param mixed $path The new search path, or an array of search paths. If null or false, resets to the current directory only.
*
* @return void
*
* @since 11.1
*/
protected function _setPath($type, $path)
{
$component = JApplicationHelper::getComponentName();
$app = JFactory::getApplication();
// Clear out the prior search dirs
$this->_path[$type] = array();
// Actually add the user-specified directories
$this->_addPath($type, $path);
// Always add the fallback directories as last resort
switch (strtolower($type))
{
case 'template':
// Set the alternative template search dir
if (isset($app))
{
$component = preg_replace('/[^A-Z0-9_\.-]/i', '', $component);
//if it is T3 template, update search path for template
$this->_addPath('template', T3_PATH . '/html/' . $component . '/' . $this->getName());
$fallback = JPATH_THEMES . '/' . $app->getTemplate() . '/html/' . $component . '/' . $this->getName();
$this->_addPath('template', $fallback);
//search path for user custom folder
if (!defined('T3_LOCAL_DISABLED')) $this->_addPath('template', T3_LOCAL_PATH . '/html/' . $component . '/' . $this->getName());
}
break;
}
}
/**
* Adds to the search path for templates and resources.
*
* @param string $type The type of path to add.
* @param mixed $path The directory or stream, or an array of either, to search.
*
* @return void
*
* @since 11.1
*/
protected function _addPath($type, $path)
{
// Just force to array
settype($path, 'array');
// Loop through the path directories
foreach ($path as $dir)
{
// no surrounding spaces allowed!
$dir = trim($dir);
// Add trailing separators as needed
if (substr($dir, -1) != DIRECTORY_SEPARATOR)
{
// Directory
$dir .= DIRECTORY_SEPARATOR;
}
// Add to the top of the search dirs
array_unshift($this->_path[$type], $dir);
}
}
/**
* Create the filename for a resource
*
* @param string $type The resource type to create the filename for
* @param array $parts An associative array of filename information
*
* @return string The filename
*
* @since 11.1
*/
protected function _createFileName($type, $parts = array())
{
$filename = '';
switch ($type)
{
case 'template':
$filename = strtolower($parts['name']) . '.' . $this->_layoutExt;
break;
default:
$filename = strtolower($parts['name']) . '.php';
break;
}
return $filename;
}
}
joomla25/pagination.php 0000604 00000047375 15245530303 0011052 0 ustar 00 <?php
/**
* @package Joomla.Platform
* @subpackage HTML
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Pagination Class. Provides a common interface for content pagination for the
* Joomla! Platform.
*
* @package Joomla.Platform
* @subpackage HTML
* @since 11.1
*/
class JPagination extends JObject
{
/**
* @var integer The record number to start displaying from.
* @since 11.1
*/
public $limitstart = null;
/**
* @var integer Number of rows to display per page.
* @since 11.1
*/
public $limit = null;
/**
* @var integer Total number of rows.
* @since 11.1
*/
public $total = null;
/**
* @var integer Prefix used for request variables.
* @since 11.1
*/
public $prefix = null;
/**
* @var boolean View all flag
* @since 11.1
*/
protected $_viewall = false;
/**
* Additional URL parameters to be added to the pagination URLs generated by the class. These
* may be useful for filters and extra values when dealing with lists and GET requests.
*
* @var array
* @since 11.1
*/
protected $_additionalUrlParams = array();
/**
* Constructor.
*
* @param integer $total The total number of items.
* @param integer $limitstart The offset of the item to start at.
* @param integer $limit The number of items to display per page.
* @param string $prefix The prefix used for request variables.
*
* @since 11.1
*/
public function __construct($total, $limitstart, $limit, $prefix = '')
{
// Value/type checking.
$this->total = (int) $total;
$this->limitstart = (int) max($limitstart, 0);
$this->limit = (int) max($limit, 0);
$this->prefix = $prefix;
if ($this->limit > $this->total)
{
$this->limitstart = 0;
}
if (!$this->limit)
{
$this->limit = $total;
$this->limitstart = 0;
}
/*
* If limitstart is greater than total (i.e. we are asked to display records that don't exist)
* then set limitstart to display the last natural page of results
*/
if ($this->limitstart > $this->total - $this->limit)
{
$this->limitstart = max(0, (int) (ceil($this->total / $this->limit) - 1) * $this->limit);
}
// Set the total pages and current page values.
if ($this->limit > 0)
{
$this->set('pages.total', ceil($this->total / $this->limit));
$this->set('pages.current', ceil(($this->limitstart + 1) / $this->limit));
}
// Set the pagination iteration loop values.
$displayedPages = 10;
$this->set('pages.start', $this->get('pages.current') - ($displayedPages / 2));
if ($this->get('pages.start') < 1)
{
$this->set('pages.start', 1);
}
if (($this->get('pages.start') + $displayedPages) > $this->get('pages.total'))
{
$this->set('pages.stop', $this->get('pages.total'));
if ($this->get('pages.total') < $displayedPages)
{
$this->set('pages.start', 1);
}
else
{
$this->set('pages.start', $this->get('pages.total') - $displayedPages + 1);
}
}
else
{
$this->set('pages.stop', ($this->get('pages.start') + $displayedPages - 1));
}
// If we are viewing all records set the view all flag to true.
if ($limit == 0)
{
$this->_viewall = true;
}
}
/**
* Method to set an additional URL parameter to be added to all pagination class generated
* links.
*
* @param string $key The name of the URL parameter for which to set a value.
* @param mixed $value The value to set for the URL parameter.
*
* @return mixed The old value for the parameter.
*
* @since 11.1
*/
public function setAdditionalUrlParam($key, $value)
{
// Get the old value to return and set the new one for the URL parameter.
$result = isset($this->_additionalUrlParams[$key]) ? $this->_additionalUrlParams[$key] : null;
// If the passed parameter value is null unset the parameter, otherwise set it to the given value.
if ($value === null)
{
unset($this->_additionalUrlParams[$key]);
}
else
{
$this->_additionalUrlParams[$key] = $value;
}
return $result;
}
/**
* Method to get an additional URL parameter (if it exists) to be added to
* all pagination class generated links.
*
* @param string $key The name of the URL parameter for which to get the value.
*
* @return mixed The value if it exists or null if it does not.
*
* @since 11.1
*/
public function getAdditionalUrlParam($key)
{
$result = isset($this->_additionalUrlParams[$key]) ? $this->_additionalUrlParams[$key] : null;
return $result;
}
/**
* Return the rationalised offset for a row with a given index.
*
* @param integer $index The row index
*
* @return integer Rationalised offset for a row with a given index.
*
* @since 11.1
*/
public function getRowOffset($index)
{
return $index + 1 + $this->limitstart;
}
/**
* Return the pagination data object, only creating it if it doesn't already exist.
*
* @return object Pagination data object.
*
* @since 11.1
*/
public function getData()
{
static $data;
if (!is_object($data))
{
$data = $this->_buildDataObject();
}
return $data;
}
/**
* Create and return the pagination pages counter string, ie. Page 2 of 4.
*
* @return string Pagination pages counter string.
*
* @since 11.1
*/
public function getPagesCounter()
{
// Initialise variables.
$html = null;
if ($this->get('pages.total') > 1)
{
$html .= JText::sprintf('JLIB_HTML_PAGE_CURRENT_OF_TOTAL', $this->get('pages.current'), $this->get('pages.total'));
}
return $html;
}
/**
* Create and return the pagination result set counter string, e.g. Results 1-10 of 42
*
* @return string Pagination result set counter string.
*
* @since 11.1
*/
public function getResultsCounter()
{
// Initialise variables.
$html = null;
$fromResult = $this->limitstart + 1;
// If the limit is reached before the end of the list.
if ($this->limitstart + $this->limit < $this->total)
{
$toResult = $this->limitstart + $this->limit;
}
else
{
$toResult = $this->total;
}
// If there are results found.
if ($this->total > 0)
{
$msg = JText::sprintf('JLIB_HTML_RESULTS_OF', $fromResult, $toResult, $this->total);
$html .= "\n" . $msg;
}
else
{
$html .= "\n" . JText::_('JLIB_HTML_NO_RECORDS_FOUND');
}
return $html;
}
/**
* Create and return the pagination page list string, ie. Previous, Next, 1 2 3 ... x.
*
* @return string Pagination page list string.
*
* @since 11.1
*/
public function getPagesLinks()
{
$app = JFactory::getApplication();
// Build the page navigation list.
$data = $this->_buildDataObject();
$list = array();
$list['prefix'] = $this->prefix;
$itemOverride = false;
$listOverride = false;
// T3: detect if chrome pagination.php in template or in plugin
$chromePath = T3Path::getPath ('html/pagination.php');
// $chromePath = JPATH_THEMES . '/' . $app->getTemplate() . '/html/pagination.php';
if (file_exists($chromePath))
{
include_once $chromePath;
if (function_exists('pagination_item_active') && function_exists('pagination_item_inactive'))
{
$itemOverride = true;
}
if (function_exists('pagination_list_render'))
{
$listOverride = true;
}
}
// Build the select list
if ($data->all->base !== null)
{
$list['all']['active'] = true;
$list['all']['data'] = ($itemOverride) ? pagination_item_active($data->all) : $this->_item_active($data->all);
}
else
{
$list['all']['active'] = false;
$list['all']['data'] = ($itemOverride) ? pagination_item_inactive($data->all) : $this->_item_inactive($data->all);
}
if ($data->start->base !== null)
{
$list['start']['active'] = true;
$list['start']['data'] = ($itemOverride) ? pagination_item_active($data->start) : $this->_item_active($data->start);
}
else
{
$list['start']['active'] = false;
$list['start']['data'] = ($itemOverride) ? pagination_item_inactive($data->start) : $this->_item_inactive($data->start);
}
if ($data->previous->base !== null)
{
$list['previous']['active'] = true;
$list['previous']['data'] = ($itemOverride) ? pagination_item_active($data->previous) : $this->_item_active($data->previous);
}
else
{
$list['previous']['active'] = false;
$list['previous']['data'] = ($itemOverride) ? pagination_item_inactive($data->previous) : $this->_item_inactive($data->previous);
}
$list['pages'] = array(); //make sure it exists
foreach ($data->pages as $i => $page)
{
if ($page->base !== null)
{
$list['pages'][$i]['active'] = true;
$list['pages'][$i]['data'] = ($itemOverride) ? pagination_item_active($page) : $this->_item_active($page);
}
else
{
$list['pages'][$i]['active'] = false;
$list['pages'][$i]['data'] = ($itemOverride) ? pagination_item_inactive($page) : $this->_item_inactive($page);
}
}
if ($data->next->base !== null)
{
$list['next']['active'] = true;
$list['next']['data'] = ($itemOverride) ? pagination_item_active($data->next) : $this->_item_active($data->next);
}
else
{
$list['next']['active'] = false;
$list['next']['data'] = ($itemOverride) ? pagination_item_inactive($data->next) : $this->_item_inactive($data->next);
}
if ($data->end->base !== null)
{
$list['end']['active'] = true;
$list['end']['data'] = ($itemOverride) ? pagination_item_active($data->end) : $this->_item_active($data->end);
}
else
{
$list['end']['active'] = false;
$list['end']['data'] = ($itemOverride) ? pagination_item_inactive($data->end) : $this->_item_inactive($data->end);
}
if ($this->total > $this->limit)
{
return ($listOverride) ? pagination_list_render($list) : $this->_list_render($list);
}
else
{
return '';
}
}
/**
* Return the pagination footer.
*
* @return string Pagination footer.
*
* @since 11.1
*/
public function getListFooter()
{
$app = JFactory::getApplication();
$list = array();
$list['prefix'] = $this->prefix;
$list['limit'] = $this->limit;
$list['limitstart'] = $this->limitstart;
$list['total'] = $this->total;
$list['limitfield'] = $this->getLimitBox();
$list['pagescounter'] = $this->getPagesCounter();
$list['pageslinks'] = $this->getPagesLinks();
// T3: detect if chrome pagination.php in template or in plugin
$chromePath = T3Path::getPath ('html/pagination.php');
// $chromePath = JPATH_THEMES . '/' . $app->getTemplate() . '/html/pagination.php';
if (file_exists($chromePath))
{
include_once $chromePath;
if (function_exists('pagination_list_footer'))
{
return pagination_list_footer($list);
}
}
return $this->_list_footer($list);
}
/**
* Creates a dropdown box for selecting how many records to show per page.
*
* @return string The HTML for the limit # input box.
*
* @since 11.1
*/
public function getLimitBox()
{
$app = JFactory::getApplication();
// Initialise variables.
$limits = array();
// Make the option list.
for ($i = 5; $i <= 30; $i += 5)
{
$limits[] = JHtml::_('select.option', "$i");
}
$limits[] = JHtml::_('select.option', '50', JText::_('J50'));
$limits[] = JHtml::_('select.option', '100', JText::_('J100'));
$limits[] = JHtml::_('select.option', '0', JText::_('JALL'));
$selected = $this->_viewall ? 0 : $this->limit;
// Build the select list.
if (T3::isAdmin())
{
$html = JHtml::_(
'select.genericlist',
$limits,
$this->prefix . 'limit',
'class="inputbox" size="1" onchange="Joomla.submitform();"',
'value',
'text',
$selected
);
}
else
{
$html = JHtml::_(
'select.genericlist',
$limits,
$this->prefix . 'limit',
'class="inputbox" size="1" onchange="this.form.submit()"',
'value',
'text',
$selected
);
}
return $html;
}
/**
* Return the icon to move an item UP.
*
* @param integer $i The row index.
* @param boolean $condition True to show the icon.
* @param string $task The task to fire.
* @param string $alt The image alternative text string.
* @param boolean $enabled An optional setting for access control on the action.
* @param string $checkbox An optional prefix for checkboxes.
*
* @return string Either the icon to move an item up or a space.
*
* @since 11.1
*/
public function orderUpIcon($i, $condition = true, $task = 'orderup', $alt = 'JLIB_HTML_MOVE_UP', $enabled = true, $checkbox = 'cb')
{
if (($i > 0 || ($i + $this->limitstart > 0)) && $condition)
{
return JHtml::_('jgrid.orderUp', $i, $task, '', $alt, $enabled, $checkbox);
}
else
{
return ' ';
}
}
/**
* Return the icon to move an item DOWN.
*
* @param integer $i The row index.
* @param integer $n The number of items in the list.
* @param boolean $condition True to show the icon.
* @param string $task The task to fire.
* @param string $alt The image alternative text string.
* @param boolean $enabled An optional setting for access control on the action.
* @param string $checkbox An optional prefix for checkboxes.
*
* @return string Either the icon to move an item down or a space.
*
* @since 11.1
*/
public function orderDownIcon($i, $n, $condition = true, $task = 'orderdown', $alt = 'JLIB_HTML_MOVE_DOWN', $enabled = true, $checkbox = 'cb')
{
if (($i < $n - 1 || $i + $this->limitstart < $this->total - 1) && $condition)
{
return JHtml::_('jgrid.orderDown', $i, $task, '', $alt, $enabled, $checkbox);
}
else
{
return ' ';
}
}
/**
* Create the HTML for a list footer
*
* @param array $list Pagination list data structure.
*
* @return string HTML for a list footer
*
* @since 11.1
*/
protected function _list_footer($list)
{
$html = "<div class=\"list-footer\">\n";
$html .= "\n<div class=\"limit\">" . JText::_('JGLOBAL_DISPLAY_NUM') . $list['limitfield'] . "</div>";
$html .= $list['pageslinks'];
$html .= "\n<div class=\"counter\">" . $list['pagescounter'] . "</div>";
$html .= "\n<input type=\"hidden\" name=\"" . $list['prefix'] . "limitstart\" value=\"" . $list['limitstart'] . "\" />";
$html .= "\n</div>";
return $html;
}
/**
* Create the html for a list footer
*
* @param array $list Pagination list data structure.
*
* @return string HTML for a list start, previous, next,end
*
* @since 11.1
*/
protected function _list_render($list)
{
// Reverse output rendering for right-to-left display.
$html = '<ul>';
$html .= '<li class="pagination-start">' . $list['start']['data'] . '</li>';
$html .= '<li class="pagination-prev">' . $list['previous']['data'] . '</li>';
foreach ($list['pages'] as $page)
{
$html .= '<li>' . $page['data'] . '</li>';
}
$html .= '<li class="pagination-next">' . $list['next']['data'] . '</li>';
$html .= '<li class="pagination-end">' . $list['end']['data'] . '</li>';
$html .= '</ul>';
return $html;
}
/**
* Method to create an active pagination link to the item
*
* @param JPaginationObject &$item The object with which to make an active link.
*
* @return string HTML link
*
* @since 11.1
*/
protected function _item_active(&$item)
{
$app = JFactory::getApplication();
if (T3::isAdmin())
{
if ($item->base > 0)
{
return "<a title=\"" . $item->text . "\" onclick=\"document.adminForm." . $this->prefix . "limitstart.value=" . $item->base
. "; Joomla.submitform();return false;\">" . $item->text . "</a>";
}
else
{
return "<a title=\"" . $item->text . "\" onclick=\"document.adminForm." . $this->prefix
. "limitstart.value=0; Joomla.submitform();return false;\">" . $item->text . "</a>";
}
}
else
{
return "<a title=\"" . $item->text . "\" href=\"" . $item->link . "\" class=\"pagenav\">" . $item->text . "</a>";
}
}
/**
* Method to create an inactive pagination string
*
* @param object &$item The item to be processed
*
* @return string
*
* @since 11.1
*/
protected function _item_inactive(&$item)
{
$app = JFactory::getApplication();
if (T3::isAdmin())
{
return "<span>" . $item->text . "</span>";
}
else
{
return "<span class=\"pagenav\">" . $item->text . "</span>";
}
}
/**
* Create and return the pagination data object.
*
* @return object Pagination data object.
*
* @since 11.1
*/
protected function _buildDataObject()
{
// Initialise variables.
$data = new stdClass;
// Build the additional URL parameters string.
$params = '';
if (!empty($this->_additionalUrlParams))
{
foreach ($this->_additionalUrlParams as $key => $value)
{
$params .= '&' . $key . '=' . $value;
}
}
$data->all = new JPaginationObject(JText::_('JLIB_HTML_VIEW_ALL'), $this->prefix);
if (!$this->_viewall)
{
$data->all->base = '0';
$data->all->link = JRoute::_($params . '&' . $this->prefix . 'limitstart=');
}
// Set the start and previous data objects.
$data->start = new JPaginationObject(JText::_('JLIB_HTML_START'), $this->prefix);
$data->previous = new JPaginationObject(JText::_('JPREV'), $this->prefix);
if ($this->get('pages.current') > 1)
{
$page = ($this->get('pages.current') - 2) * $this->limit;
// Set the empty for removal from route
//$page = $page == 0 ? '' : $page;
$data->start->base = '0';
$data->start->link = JRoute::_($params . '&' . $this->prefix . 'limitstart=0' . '&limit=' . $this->limit);
$data->previous->base = $page;
$data->previous->link = JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $page . '&limit=' . $this->limit);
}
// Set the next and end data objects.
$data->next = new JPaginationObject(JText::_('JNEXT'), $this->prefix);
$data->end = new JPaginationObject(JText::_('JLIB_HTML_END'), $this->prefix);
if ($this->get('pages.current') < $this->get('pages.total'))
{
$next = $this->get('pages.current') * $this->limit;
$end = ($this->get('pages.total') - 1) * $this->limit;
$data->next->base = $next;
$data->next->link = JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $next . '&limit=' . $this->limit);
$data->end->base = $end;
$data->end->link = JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $end . '&limit=' . $this->limit);
}
$data->pages = array();
$stop = $this->get('pages.stop');
for ($i = $this->get('pages.start'); $i <= $stop; $i++)
{
$offset = ($i - 1) * $this->limit;
// Set the empty for removal from route
//$offset = $offset == 0 ? '' : $offset;
$data->pages[$i] = new JPaginationObject($i, $this->prefix);
if ($i != $this->get('pages.current') || $this->_viewall)
{
$data->pages[$i]->base = $offset;
$data->pages[$i]->link = JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $offset . '&limit=' . $this->limit);
}
}
return $data;
}
}
/**
* Pagination object representing a particular item in the pagination lists.
*
* @package Joomla.Platform
* @subpackage HTML
* @since 11.1
*/
class JPaginationObject extends JObject
{
/**
* @var string The link text.
* @since 11.1
*/
public $text;
/**
* @var integer The number of rows as a base offset.
* @since 11.1
*/
public $base;
/**
* @var string The link URL.
* @since 11.1
*/
public $link;
/**
* @var integer The prefix used for request variables.
* @since 11.1
*/
public $prefix;
/**
* Class constructor.
*
* @param string $text The link text.
* @param integer $prefix The prefix used for request variables.
* @param integer $base The number of rows as a base offset.
* @param string $link The link URL.
*
* @since 11.1
*/
public function __construct($text, $prefix = '', $base = null, $link = null)
{
$this->text = $text;
$this->prefix = $prefix;
$this->base = $base;
$this->link = $link;
}
}
joomla4/html/bootstrap.php 0000604 00000004443 15245530303 0011604 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage HTML
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
use Joomla\CMS\HTML\Helpers\Bootstrap as JBootstrap;
defined('JPATH_PLATFORM') or die;
/**
* Utility class for JavaScript behaviors
*
* @since 1.5
*/
abstract class T3HtmlBootstrap extends JBootstrap
{
/**
* Method to render a Bootstrap modal
*
* @param string $selector The ID selector for the modal.
* @param array $params An array of options for the modal.
* Options for the modal can be:
* - title string The modal title
* - backdrop mixed A boolean select if a modal-backdrop element should be included (default = true)
* The string 'static' includes a backdrop which doesn't close the modal on click.
* - keyboard boolean Closes the modal when escape key is pressed (default = true)
* - closeButton boolean Display modal close button (default = true)
* - animation boolean Fade in from the top of the page (default = true)
* - footer string Optional markup for the modal footer
* - url string URL of a resource to be inserted as an `<iframe>` inside the modal body
* - height string height of the `<iframe>` containing the remote resource
* - width string width of the `<iframe>` containing the remote resource
* @param string $body Markup for the modal body. Appended after the `<iframe>` if the URL option is set
*
* @return string HTML markup for a modal
*
* @since 3.0
*/
public static function renderModal($selector = 'modal', $params = array(), $body = '') :string
{
$method = parent::class . '::' . __FUNCTION__;
// Force to rerender in admin
if (!empty(static::$loaded[$method][$selector]))
{
static::$loaded[$method][$selector] = false;
}
return parent::renderModal($selector, $params, $body);
}
} joomla4/html/behavior.php 0000604 00000014666 15245530303 0011376 0 ustar 00 <?php
/**
* @package Joomla.Libraries
* @subpackage HTML
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
use Joomla\CMS\HTML\Helpers\Behavior;
defined('JPATH_PLATFORM') or die;
/**
* Utility class for JavaScript behaviors
*
* @since 1.5
*/
abstract class T3HtmlBehavior extends Behavior
{
/**
* Add unobtrusive JavaScript support for a hover tooltips.
*
* Add a title attribute to any element in the form
* title="title::text"
*
* Uses the core Tips class in MooTools.
*
* @param string $selector The class selector for the tooltip.
* @param array $params An array of options for the tooltip.
* Options for the tooltip can be:
* - maxTitleChars integer The maximum number of characters in the tooltip title (defaults to 50).
* - offsets object The distance of your tooltip from the mouse (defaults to {'x': 16, 'y': 16}).
* - showDelay integer The millisecond delay the show event is fired (defaults to 100).
* - hideDelay integer The millisecond delay the hide hide is fired (defaults to 100).
* - className string The className your tooltip container will get.
* - fixed boolean If set to true, the toolTip will not follow the mouse.
* - onShow function The default function for the show event, passes the tip element
* and the currently hovered element.
* - onHide function The default function for the hide event, passes the currently
* hovered element.
*
* @return void
*
* @since 1.5
*/
public static function tooltip($selector = '.hasTip', $params = array())
{
$sig = md5(serialize(array($selector, $params)));
if (isset(static::$loaded[__METHOD__][$sig]))
{
return;
}
// Include MooTools framework
static::framework(true);
// Setup options object
$opt['maxTitleChars'] = isset($params['maxTitleChars']) && $params['maxTitleChars'] ? (int) $params['maxTitleChars'] : 50;
// Offsets needs an array in the format: array('x'=>20, 'y'=>30)
$opt['offset'] = isset($params['offset']) && is_array($params['offset']) ? $params['offset'] : null;
$opt['showDelay'] = isset($params['showDelay']) ? (int) $params['showDelay'] : null;
$opt['hideDelay'] = isset($params['hideDelay']) ? (int) $params['hideDelay'] : null;
$opt['className'] = isset($params['className']) ? $params['className'] : null;
$opt['fixed'] = isset($params['fixed']) && $params['fixed'];
$opt['onShow'] = isset($params['onShow']) ? '\\' . $params['onShow'] : null;
$opt['onHide'] = isset($params['onHide']) ? '\\' . $params['onHide'] : null;
$options = json_encode($opt);
// Include jQuery
JHtml::_('jquery.framework');
// Attach tooltips to document
JFactory::getDocument()->addScriptDeclaration(
"jQuery(function($) {
$('$selector').each(function() {
var title = $(this).attr('title');
if (title) {
var parts = title.split('::', 2);
var mtelement = document.getElementById(this);
if(mtelement){
mtelement.store('tip:title', parts[0]);
mtelement.store('tip:text', parts[1]);
}
}
});
// var JTooltips = new Tips($('$selector').get(), $options);
});"
);
// Set static array
static::$loaded[__METHOD__][$sig] = true;
return;
}
/**
* Add unobtrusive JavaScript support for form validation.
*
* To enable form validation the form tag must have class="form-validate".
* Each field that needs to be validated needs to have class="validate".
* Additional handlers can be added to the handler for username, password,
* numeric and email. To use these add class="validate-email" and so on.
*
* @return void
*
* @since 1.5
*
* @Deprecated 3.4 Use formvalidator instead
*/
public static function formvalidation()
{
// Include MooTools framework
static::framework();
// Load the new jQuery code
static::formvalidator();
}
/**
* Method to load the MooTools framework into the document head
*
* If debugging mode is on an uncompressed version of MooTools is included for easier debugging.
*
* @param boolean $extras Flag to determine whether to load MooTools More in addition to Core
* @param mixed $debug Is debugging mode on? [optional]
*
* @return void
*
* @since 1.6
* @deprecated 4.0 Update scripts to jquery
*/
public static function framework($extras = false, $debug = null)
{
$type = $extras ? 'more' : 'core';
// Only load once
if (!empty(static::$loaded[__METHOD__][$type]))
{
return;
}
JLog::add('JHtmlBehavior::framework is deprecated. Update to jquery scripts.', JLog::WARNING, 'deprecated');
// If no debugging value is set, use the configuration setting
if ($debug === null)
{
$debug = JDEBUG;
}
if ($type !== 'core' && empty(static::$loaded[__METHOD__]['core']))
{
static::framework(false, $debug);
}
JHtml::_('script', 'system/mootools-' . $type . '.js', array('version' => 'auto', 'relative' => true, 'detectDebug' => $debug));
// Keep loading core.js for BC reasons
static::core();
static::$loaded[__METHOD__][$type] = true;
return;
}
/**
* Add unobtrusive JavaScript support for image captions.
*
* @param string $selector The selector for which a caption behaviour is to be applied.
*
* @return void
*
* @since 1.5
*
* @Deprecated 4.0 Use native HTML figure tags.
*/
public static function caption($selector = 'img.caption')
{
JLog::add('JHtmlBehavior::caption is deprecated. Use native HTML figure tags.', JLog::WARNING, 'deprecated');
// Only load once
if (isset(static::$loaded[__METHOD__][$selector]))
{
return;
}
// Include jQuery
JHtml::_('jquery.framework');
if (!is_file(JPATH_ROOT . 'media/system/js/caption.js')){
return;
}
JHtml::_('script', 'system/js/caption.js', array('version' => 'auto', 'relative' => true));
// Attach caption to document
JFactory::getDocument()->addScriptDeclaration(
"jQuery(window).on('load', function() {
new JCaption('" . $selector . "');
});"
);
// Set static array
static::$loaded[__METHOD__][$selector] = true;
}
}
joomla4/ModuleHelper.php 0000604 00000021576 15245530303 0011216 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Helper;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Profiler\Profiler;
use Joomla\Registry\Registry;
// Make alias of original FileLayout
\T3::makeAlias(JPATH_LIBRARIES . '/src/Helper/ModuleHelper.php', 'ModuleHelper', '_ModuleHelper');
/**
* Module helper class
*
* @since 1.5
*/
abstract class ModuleHelper extends _ModuleHelper
{
/**
* Render the module.
*
* @param object $module A module object.
* @param array $attribs An array of attributes for the module (probably from the XML).
*
* @return string The HTML content of the module output.
*
* @since 1.5
*/
public static function renderModule($module, $attribs = array())
{
$app = Factory::getApplication();
// Check that $module is a valid module object
if (!\is_object($module) || !isset($module->module) || !isset($module->params))
{
if (JDEBUG)
{
Log::addLogger(array('text_file' => 'jmodulehelper.log.php'), Log::ALL, array('modulehelper'));
$app->getLogger()->debug(
__METHOD__ . '() - The $module parameter should be a module object.',
array('category' => 'modulehelper')
);
}
return '';
}
// Get module parameters
$params = new Registry($module->params);
// Render the module content
static::renderRawModule($module, $params, $attribs);
// Return early if only the content is required
if (!empty($attribs['contentOnly']))
{
return $module->content;
}
if (JDEBUG)
{
Profiler::getInstance('Application')->mark('beforeRenderModule ' . $module->module . ' (' . $module->title . ')');
}
// Record the scope.
$scope = $app->scope;
// Set scope to component name
$app->scope = $module->module;
// Get the template
$template = $app->getTemplate();
// Check if the current module has a style param to override template module style
$paramsChromeStyle = $params->get('style');
$basePath = '';
if ($paramsChromeStyle)
{
$paramsChromeStyle = explode('-', $paramsChromeStyle, 2);
$ChromeStyleTemplate = strtolower($paramsChromeStyle[0]);
$attribs['style'] = $paramsChromeStyle[1];
// Only set $basePath if the specified template isn't the current or system one.
if ($ChromeStyleTemplate !== $template && $ChromeStyleTemplate !== 'system')
{
$basePath = JPATH_THEMES . '/' . $ChromeStyleTemplate . '/html/layouts';
}
}
if(version_compare(JVERSION, '4.0', 'lt')){
include_once JPATH_THEMES . '/system/html/modules.php';
}
$chromePath = JPATH_THEMES . '/' . $template . '/html/modules.php';
if (!isset($chrome[$chromePath]))
{
if (file_exists($chromePath))
{
// load module style on template
include_once $chromePath;
}else{
if($app->isClient('site')){
// load module style function for use on some module of JA
$chromePath = \T3Path::getPath('html/modules.php');
include_once $chromePath;
}
}
$chrome[$chromePath] = true;
}
// Make sure a style is set
if (!isset($attribs['style']))
{
$attribs['style'] = 'none';
}
// Dynamically add outline style
if ($app->input->getBool('tp') && ComponentHelper::getParams('com_templates')->get('template_positions_display'))
{
$attribs['style'] .= ' outline';
}
$module->style = $attribs['style'];
// If the $module is nulled it will return an empty content, otherwise it will render the module normally.
$app->triggerEvent('onRenderModule', array(&$module, &$attribs));
if ($module === null || !isset($module->content))
{
return '';
}
$displayData = array(
'module' => $module,
'params' => $params,
'attribs' => $attribs,
);
foreach (explode(' ', $attribs['style']) as $style)
{
if ($moduleContent = LayoutHelper::render('chromes.' . $style, $displayData, $basePath))
{
$module->content = $moduleContent;
}
}
foreach (explode(' ', $attribs['style']) as $style)
{
$chromeMethod = 'modChrome_' . $style;
$chromeStylePath = \T3Path::getPath('html/layouts/chromes/'.$style.'.php');
// Apply chrome and render module
if (function_exists($chromeMethod) && !file_exists($chromeStylePath))
{
$module->style = $attribs['style'];
ob_start();
$chromeMethod($module, $params, $attribs);
$module->content = ob_get_contents();
ob_end_clean();
}
}
// Revert the scope
$app->scope = $scope;
$app->triggerEvent('onAfterRenderModule', array(&$module, &$attribs));
if (JDEBUG)
{
Profiler::getInstance('Application')->mark('afterRenderModule ' . $module->module . ' (' . $module->title . ')');
}
return $module->content;
}
/**
* Render the module content.
*
* @param object $module A module object
* @param Registry $params A module parameters
* @param array $attribs An array of attributes for the module (probably from the XML).
*
* @return string
*
* @since 4.0.0
*/
public static function renderRawModule($module, Registry $params, $attribs = array())
{
if (!empty($module->contentRendered))
{
return $module->content;
}
if (JDEBUG)
{
Profiler::getInstance('Application')->mark('beforeRenderModule ' . $module->module . ' (' . $module->title . ')');
}
$app = Factory::getApplication();
// Record the scope.
$scope = $app->scope;
// Set scope to component name
$app->scope = $module->module;
if(version_compare(JVERSION, '4.0', 'ge')){
$dispatcher = $app->bootModule($module->module, $app->getName())->getDispatcher($module, $app);
// Check if we have a dispatcher
if ($dispatcher)
{
ob_start();
$dispatcher->dispatch();
$module->content = ob_get_clean();
}
}else{
// Get module path
$module->module = preg_replace('/[^A-Z0-9_\.-]/i', '', $module->module);
$path = JPATH_BASE . '/modules/' . $module->module . '/' . $module->module . '.php';
// Load the module
if (file_exists($path))
{
$lang = Factory::getLanguage();
$coreLanguageDirectory = JPATH_BASE;
$extensionLanguageDirectory = dirname($path);
$langPaths = $lang->getPaths();
// Only load the module's language file if it hasn't been already
if (!$langPaths || (!isset($langPaths[$coreLanguageDirectory]) && !isset($langPaths[$extensionLanguageDirectory])))
{
// 1.5 or Core then 1.6 3PD
$lang->load($module->module, $coreLanguageDirectory, null, false, true) ||
$lang->load($module->module, $extensionLanguageDirectory, null, false, true);
}
$content = '';
ob_start();
include $path;
$module->content = ob_get_contents() . $content;
ob_end_clean();
}
}
// Add the flag that the module content has been rendered
$module->contentRendered = true;
// Revert the scope
$app->scope = $scope;
if (JDEBUG)
{
Profiler::getInstance('Application')->mark('afterRenderRawModule ' . $module->module . ' (' . $module->title . ')');
}
return $module->content;
}
/**
* Get the path to a layout for a module
*
* @param string $module The name of the module
* @param string $layout The name of the module layout. If alternative layout, in the form template:filename.
*
* @return string The path to the module layout
*
* @since 1.5
*/
public static function getLayoutPath($module, $layout = 'default')
{
$template = Factory::getApplication()->getTemplate();
$defaultLayout = $layout;
if (strpos($layout, ':') !== false)
{
// Get the template and file name from the string
$temp = explode(':', $layout);
$template = $temp[0] === '_' ? $template : $temp[0];
$layout = $temp[1];
$defaultLayout = $temp[1] ?: 'default';
}
// T3
// Do 3rd party stuff to detect layout path for the module
// onGetLayoutPath should return the path to the $layout of $module or false
// $results holds an array of results returned from plugins, 1 from each plugin.
// if a path to the $layout is found and it is a file, return that path
$app = Factory::getApplication();
$result = $app->triggerEvent( 'onGetLayoutPath', array( $module, $layout ) );
if (is_array($result))
{
foreach ($result as $path)
{
if ($path !== false && is_file ($path))
{
return $path;
}
}
}
// /T3
// Build the template and base path for the layout
$tPath = JPATH_THEMES . '/' . $template . '/html/' . $module . '/' . $layout . '.php';
$bPath = JPATH_BASE . '/modules/' . $module . '/tmpl/' . $defaultLayout . '.php';
$dPath = JPATH_BASE . '/modules/' . $module . '/tmpl/default.php';
// If the template has a layout override use it
if (file_exists($tPath))
{
return $tPath;
}
if (file_exists($bPath))
{
return $bPath;
}
return $dPath;
}
}
joomla4/Pagination.php 0000604 00000013615 15245530303 0010715 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Pagination;
defined('JPATH_PLATFORM') or die;
// Make alias of original FileLayout
\T3::makeAlias(JPATH_LIBRARIES . '/src/Pagination/Pagination.php', 'Pagination', '_Pagination');
/**
* Pagination Class. Provides a common interface for content pagination for the Joomla! CMS.
*
* @since 1.5
*/
class Pagination extends _Pagination
{
/**
* Create and return the pagination page list string, ie. Previous, Next, 1 2 3 ... x.
*
* @return string Pagination page list string.
*
* @since 1.5
*/
public function getPagesLinks()
{
// Build the page navigation list.
$data = $this->_buildDataObject();
$list = array();
$list['prefix'] = $this->prefix;
$itemOverride = false;
$listOverride = false;
// $chromePath = JPATH_THEMES . '/' . $this->app->getTemplate() . '/html/pagination.php';
// T3: detect if chrome pagination.php in template or in plugin
$chromePath = \T3Path::getPath ('html/pagination.php');
if (file_exists($chromePath))
{
include_once $chromePath;
/*
* @deprecated 4.0 Item rendering should use a layout
*/
if (function_exists('pagination_item_active') && function_exists('pagination_item_inactive'))
{
\JLog::add(
'pagination_item_active and pagination_item_inactive are deprecated. Use the layout joomla.pagination.link instead.',
\JLog::WARNING,
'deprecated'
);
$itemOverride = true;
}
/*
* @deprecated 4.0 The list rendering is now a layout.
* @see Pagination::_list_render()
*/
if (function_exists('pagination_list_render'))
{
\JLog::add('pagination_list_render is deprecated. Use the layout joomla.pagination.list instead.', \JLog::WARNING, 'deprecated');
$listOverride = true;
}
}
// Build the select list
if ($data->all->base !== null)
{
$list['all']['active'] = true;
$list['all']['data'] = $itemOverride ? pagination_item_active($data->all) : $this->_item_active($data->all);
}
else
{
$list['all']['active'] = false;
$list['all']['data'] = $itemOverride ? pagination_item_inactive($data->all) : $this->_item_inactive($data->all);
}
if ($data->start->base !== null)
{
$list['start']['active'] = true;
$list['start']['data'] = $itemOverride ? pagination_item_active($data->start) : $this->_item_active($data->start);
}
else
{
$list['start']['active'] = false;
$list['start']['data'] = $itemOverride ? pagination_item_inactive($data->start) : $this->_item_inactive($data->start);
}
if ($data->previous->base !== null)
{
$list['previous']['active'] = true;
$list['previous']['data'] = $itemOverride ? pagination_item_active($data->previous) : $this->_item_active($data->previous);
}
else
{
$list['previous']['active'] = false;
$list['previous']['data'] = $itemOverride ? pagination_item_inactive($data->previous) : $this->_item_inactive($data->previous);
}
// Make sure it exists
$list['pages'] = array();
foreach ($data->pages as $i => $page)
{
if ($page->base !== null)
{
$list['pages'][$i]['active'] = true;
$list['pages'][$i]['data'] = $itemOverride ? pagination_item_active($page) : $this->_item_active($page);
}
else
{
$list['pages'][$i]['active'] = false;
$list['pages'][$i]['data'] = $itemOverride ? pagination_item_inactive($page) : $this->_item_inactive($page);
}
}
if ($data->next->base !== null)
{
$list['next']['active'] = true;
$list['next']['data'] = $itemOverride ? pagination_item_active($data->next) : $this->_item_active($data->next);
}
else
{
$list['next']['active'] = false;
$list['next']['data'] = $itemOverride ? pagination_item_inactive($data->next) : $this->_item_inactive($data->next);
}
if ($data->end->base !== null)
{
$list['end']['active'] = true;
$list['end']['data'] = $itemOverride ? pagination_item_active($data->end) : $this->_item_active($data->end);
}
else
{
$list['end']['active'] = false;
$list['end']['data'] = $itemOverride ? pagination_item_inactive($data->end) : $this->_item_inactive($data->end);
}
if ($this->total > $this->limit)
{
return $listOverride ? pagination_list_render($list) : $this->_list_render($list);
}
else
{
return '';
}
}
/**
* Return the pagination footer.
*
* @return string Pagination footer.
*
* @since 1.5
*/
public function getListFooter()
{
// Keep B/C for overrides done with chromes
// $chromePath = JPATH_THEMES . '/' . $this->app->getTemplate() . '/html/pagination.php';
// T3: detect if chrome pagination.php in template or in plugin
$chromePath = \T3Path::getPath ('html/pagination.php');
if (file_exists($chromePath))
{
include_once $chromePath;
if (function_exists('pagination_list_footer'))
{
\JLog::add('pagination_list_footer is deprecated. Use the layout joomla.pagination.links instead.', \JLog::WARNING, 'deprecated');
$list = array(
'prefix' => $this->prefix,
'limit' => $this->limit,
'limitstart' => $this->limitstart,
'total' => $this->total,
'limitfield' => $this->getLimitBox(),
'pagescounter' => $this->getPagesCounter(),
'pageslinks' => $this->getPagesLinks(),
);
return pagination_list_footer($list);
}
}
return $this->getPaginationLinks();
}
/**
* Compatible with J3
*/
public function get($property, $default = null)
{
\JLog::add('Pagination::get() is deprecated. Access the properties directly.', \JLog::WARNING, 'deprecated');
if (strpos($property, '.'))
{
$prop = explode('.', $property);
$prop[1] = ucfirst($prop[1]);
$property = implode($prop);
}
if (isset($this->$property))
{
return $this->$property;
}
return $default;
}
}
joomla4/FileLayout.php 0000604 00000004722 15245530303 0010700 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Layout;
use Joomla\CMS\Factory;
defined('JPATH_PLATFORM') or die;
// Make alias of original FileLayout
\T3::makeAlias(JPATH_LIBRARIES . '/src/Layout/FileLayout.php', 'FileLayout', '_FileLayout');
/**
* Base class for rendering a display layout
* loaded from from a layout file
*
* @link https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
* @since 3.0
*/
class FileLayout extends _FileLayout
{
/**
* Get the default array of include paths
*
* @return array
*
* @since 3.5
*/
public function getDefaultIncludePaths()
{
// Get the template
$template = Factory::getApplication()->getTemplate(true);
// Reset includePaths
$paths = array();
// (1 - highest priority) Received a custom high priority path
if ($this->basePath !== null)
{
$paths[] = rtrim($this->basePath, DIRECTORY_SEPARATOR);
}
// Component layouts & overrides if exist
$component = $this->options->get('component', null);
if (!empty($component))
{
// (2) Component template overrides path
$paths[] = JPATH_THEMES . '/' . $template->template . '/html/layouts/' . $component;
if (!empty($template->parent))
{
// (2.a) Component template overrides path for an inherited template using the parent
$paths[] = JPATH_THEMES . '/' . $template->parent . '/html/layouts/' . $component;
}
// (3) Component path
if ($this->options->get('client') == 0)
{
$paths[] = JPATH_SITE . '/components/' . $component . '/layouts';
}
else
{
$paths[] = JPATH_ADMINISTRATOR . '/components/' . $component . '/layouts';
}
}
// T3 - (4.1) - user custom layout overridden
if (!defined('T3_LOCAL_DISABLED')) $paths[] = T3_LOCAL_PATH . '/html/layouts';
// (4) Standard Joomla! layouts overridden
$paths[] = JPATH_THEMES . '/' . $template->template . '/html/layouts';
if (!empty($template->parent))
{
// (4.a) Component template overrides path for an inherited template using the parent
$paths[] = JPATH_THEMES . '/' . $template->parent . '/html/layouts';
}
// T3 - (5.1) - T3 base layout overridden
$paths[] = T3_PATH . '/html/layouts';
// (5 - lower priority) Frontend base layouts
$paths[] = JPATH_ROOT . '/layouts';
return $paths;
}
}
joomla4/HtmlView.php 0000604 00000004222 15245530303 0010355 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\MVC\View;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\Path;
defined('JPATH_PLATFORM') or die;
// Make alias of original FileLayout
\T3::makeAlias(JPATH_LIBRARIES . '/src/MVC/View/HtmlView.php', 'HtmlView', '_JHtmlView');
/**
* Base class for a Joomla View
*
* Class holding methods for displaying presentation data.
*
* @since 2.5.5
*/
class HtmlView extends _JHtmlView
{
/**
* Sets an entire array of search paths for templates or resources.
*
* @param string $type The type of path to set, typically 'template'.
* @param mixed $path The new search path, or an array of search paths. If null or false, resets to the current directory only.
*
* @return void
*
* @since 3.0
*/
protected function _setPath($type, $path)
{
$component = ApplicationHelper::getComponentName();
$app = Factory::getApplication();
// Clear out the prior search dirs
$this->_path[$type] = array();
// Actually add the user-specified directories
$this->_addPath($type, $path);
// Always add the fallback directories as last resort
switch (strtolower($type))
{
case 'template':
// Set the alternative template search dir
if (isset($app))
{
$component = preg_replace('/[^A-Z0-9_\.-]/i', '', $component);
//if it is T3 template, update search path for template
$this->_addPath('template', T3_PATH . '/html/' . $component . '/' . $this->getName());
if (\T3::isAdmin()) $this->_addPath('template', T3_ADMIN_PATH . '/admin/html/' . $component . '/' . $this->getName());
$fallback = JPATH_THEMES . '/' . $app->getTemplate() . '/html/' . $component . '/' . $this->getName();
$this->_addPath('template', $fallback);
//search path for user custom folder
if (!defined('T3_LOCAL_DISABLED')) $this->_addPath('template', T3_LOCAL_PATH . '/html/' . $component . '/' . $this->getName());
}
break;
}
}
}
minify/csscompressor.php 0000604 00000020101 15245530303 0011443 0 ustar 00 <?php
/**
* Class Minify_CSS_Compressor
* @package Minify
*/
/**
* Compress CSS
*
* This is a heavy regex-based removal of whitespace, unnecessary
* comments and tokens, and some CSS value minimization, where practical.
* Many steps have been taken to avoid breaking comment-based hacks,
* including the ie5/mac filter (and its inversion), but expect tricky
* hacks involving comment tokens in 'content' value strings to break
* minimization badly. A test suite is available.
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
class Minify_CSS_Compressor {
/**
* Minify a CSS string
*
* @param string $css
*
* @param array $options (currently ignored)
*
* @return string
*/
public static function process($css, $options = array())
{
$obj = new Minify_CSS_Compressor($options);
return $obj->_process($css);
}
/**
* @var array
*/
protected $_options = null;
/**
* Are we "in" a hack? I.e. are some browsers targetted until the next comment?
*
* @var bool
*/
protected $_inHack = false;
/**
* Constructor
*
* @param array $options (currently ignored)
*/
private function __construct($options) {
$this->_options = $options;
}
/**
* Minify a CSS string
*
* @param string $css
*
* @return string
*/
protected function _process($css)
{
$css = str_replace("\r\n", "\n", $css);
// preserve empty comment after '>'
// http://www.webdevout.net/css-hacks#in_css-selectors
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
// preserve empty comment between property and value
// http://css-discuss.incutio.com/?page=BoxModelHack
$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
// apply callback to all valid comments (and strip out surrounding ws
$css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
,array($this, '_commentCB'), $css);
// remove ws around { } and last semicolon in declaration block
$css = preg_replace('/\\s*{\\s*/', '{', $css);
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
// remove ws surrounding semicolons
$css = preg_replace('/\\s*;\\s*/', ';', $css);
// remove ws around urls
$css = preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
\\s*
\\) # )
/x', 'url($1)', $css);
// remove ws between rules and colons
$css = preg_replace('/
\\s*
([{;]) # 1 = beginning of block or rule separator
\\s*
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
\\s*
:
\\s*
(\\b|[#\'"-]) # 3 = first character of a value
/x', '$1$2:$3', $css);
// remove ws in selectors
$css = preg_replace_callback('/
(?: # non-capture
\\s*
[^~>+,\\s]+ # selector part
\\s*
[,>+~] # combinators
)+
\\s*
[^~>+,\\s]+ # selector part
{ # open declaration block
/x'
,array($this, '_selectorsCB'), $css);
// minimize hex colors
$css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
, '$1#$2$3$4$5', $css);
// remove spaces between font families
$css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
,array($this, '_fontFamilyCB'), $css);
$css = preg_replace('/@import\\s+url/', '@import url', $css);
// replace any ws involving newlines with a single newline
$css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
// separate common descendent selectors w/ newlines (to limit line lengths)
$css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
// Use newline after 1st numeric value (to limit line lengths).
$css = preg_replace('/
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
\\s+
/x'
,"$1\n", $css);
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
return trim($css);
}
/**
* Replace what looks like a set of selectors
*
* @param array $m regex matches
*
* @return string
*/
protected function _selectorsCB($m)
{
// remove ws around the combinators
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
}
/**
* Process a comment and return a replacement
*
* @param array $m regex matches
*
* @return string
*/
protected function _commentCB($m)
{
$hasSurroundingWs = (trim($m[0]) !== $m[1]);
$m = $m[1];
// $m is the comment content w/o the surrounding tokens,
// but the return value will replace the entire comment.
if ($m === 'keep') {
return '/**/';
}
if ($m === '" "') {
// component of http://tantek.com/CSS/Examples/midpass.html
return '/*" "*/';
}
if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
// component of http://tantek.com/CSS/Examples/midpass.html
return '/*";}}/* */';
}
if ($this->_inHack) {
// inversion: feeding only to one browser
if (preg_match('@
^/ # comment started like /*/
\\s*
(\\S[\\s\\S]+?) # has at least some non-ws content
\\s*
/\\* # ends like /*/ or /**/
@x', $m, $n)) {
// end hack mode after this comment, but preserve the hack and comment content
$this->_inHack = false;
return "/*/{$n[1]}/**/";
}
}
if (substr($m, -1) === '\\') { // comment ends like \*/
// begin hack mode and preserve hack
$this->_inHack = true;
return '/*\\*/';
}
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
// begin hack mode and preserve hack
$this->_inHack = true;
return '/*/*/';
}
if ($this->_inHack) {
// a regular comment ends hack mode but should be preserved
$this->_inHack = false;
return '/**/';
}
// Issue 107: if there's any surrounding whitespace, it may be important, so
// replace the comment with a single space
return $hasSurroundingWs // remove all other comments
? ' '
: '';
}
/**
* Process a font-family listing and return a replacement
*
* @param array $m regex matches
*
* @return string
*/
protected function _fontFamilyCB($m)
{
// Issue 210: must not eliminate WS between words in unquoted families
$pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$out = 'font-family:';
while (null !== ($piece = array_shift($pieces))) {
if ($piece[0] !== '"' && $piece[0] !== "'") {
$piece = preg_replace('/\\s+/', ' ', $piece);
$piece = preg_replace('/\\s?,\\s?/', ',', $piece);
}
$out .= $piece;
}
return $out . $m[2];
}
}
minify/closurecompiler.php 0000604 00000011327 15245530303 0011757 0 ustar 00 <?php
/**
* Class Minify_JS_ClosureCompiler
* @package Minify
*/
/**
* Minify Javascript using Google's Closure Compiler API
*
* @link http://code.google.com/closure/compiler/
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*
* @todo can use a stream wrapper to unit test this?
*/
class Minify_JS_ClosureCompiler {
const URL = 'http://closure-compiler.appspot.com/compile';
/**
* Minify Javascript code via HTTP request to the Closure Compiler API
*
* @param string $js input code
* @param array $options unused at this point
* @return string
*/
public static function minify($js, array $options = array())
{
$obj = new self($options);
return $obj->min($js);
}
/**
*
* @param array $options
*
* fallbackFunc : default array($this, 'fallback');
*/
public function __construct(array $options = array())
{
$this->_fallbackFunc = isset($options['fallbackMinifier'])
? $options['fallbackMinifier']
: array($this, '_fallback');
}
public function min($js)
{
$postBody = $this->_buildPostBody($js);
$bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($postBody, '8bit')
: strlen($postBody);
if ($bytes > 200000) {
//T3 Framework
//instead of throwing error, we use fall back option
if (is_callable($this->_fallbackFunc)) {
$response = "/*\n(Using fallback minifier)\n*/\n";
$response .= call_user_func($this->_fallbackFunc, $js);
} else {
throw new Minify_JS_ClosureCompiler_Exception(
'POST content larger than 200000 bytes'
);
}
}
$response = $this->_getResponse($postBody);
if (preg_match('/^Error\(\d\d?\):/', $response)) {
if (is_callable($this->_fallbackFunc)) {
$response = "/* Received errors from Closure Compiler API:\n$response"
. "\n(Using fallback minifier)\n*/\n";
$response .= call_user_func($this->_fallbackFunc, $js);
} else {
throw new Minify_JS_ClosureCompiler_Exception($response);
}
}
if ($response === '') {
$errors = $this->_getResponse($this->_buildPostBody($js, true));
throw new Minify_JS_ClosureCompiler_Exception($errors);
}
return $response;
}
protected $_fallbackFunc = null;
protected function _getResponse($postBody)
{
$allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
if ($allowUrlFopen) {
$contents = file_get_contents(self::URL, false, stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n",
'content' => $postBody,
'max_redirects' => 0,
'timeout' => 15,
)
)));
} elseif (defined('CURLOPT_POST')) {
$ch = curl_init(self::URL);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
$contents = curl_exec($ch);
curl_close($ch);
} else {
throw new Minify_JS_ClosureCompiler_Exception(
"Could not make HTTP request: allow_url_open is false and cURL not available"
);
}
if (false === $contents) {
throw new Minify_JS_ClosureCompiler_Exception(
"No HTTP response from server"
);
}
return trim($contents);
}
protected function _buildPostBody($js, $returnErrors = false)
{
return http_build_query(array(
'js_code' => $js,
'output_info' => ($returnErrors ? 'errors' : 'compiled_code'),
'output_format' => 'text',
'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
), null, '&');
}
/**
* Default fallback function if CC API fails
* @param string $js
* @return string
*/
protected function _fallback($js)
{
//T3 Framework
T3::import('minify/jsmin');
return JSMin::minify($js);
}
}
class Minify_JS_ClosureCompiler_Exception extends Exception {}
minify/jsmin.php 0000604 00000036261 15245530303 0007674 0 ustar 00 <?php
/**
* JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
*
* <code>
* $minifiedJs = JSMin::minify($js);
* </code>
*
* This is a modified port of jsmin.c. Improvements:
*
* Does not choke on some regexp literals containing quote characters. E.g. /'/
*
* Spaces are preserved after some add/sub operators, so they are not mistakenly
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b
*
* Preserves multi-line comments that begin with /*!
*
* PHP 5 or higher is required.
*
* Permission is hereby granted to use this version of the library under the
* same terms as jsmin.c, which has the following license:
*
* --
* Copyright (c) 2002 Douglas Crockford (www.crockford.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* The Software shall be used for Good, not Evil.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* --
*
* @package JSMin
* @author Ryan Grove <ryan@wonko.com> (PHP port)
* @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
* @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
* @license http://opensource.org/licenses/mit-license.php MIT License
* @link http://code.google.com/p/jsmin-php/
*/
class JSMin {
const ORD_LF = 10;
const ORD_SPACE = 32;
const ACTION_KEEP_A = 1;
const ACTION_DELETE_A = 2;
const ACTION_DELETE_A_B = 3;
protected $a = "\n";
protected $b = '';
protected $input = '';
protected $inputIndex = 0;
protected $inputLength = 0;
protected $lookAhead = null;
protected $output = '';
protected $lastByteOut = '';
protected $keptComment = '';
/**
* Minify Javascript.
*
* @param string $js Javascript to be minified
*
* @return string
*/
public static function minify($js)
{
$jsmin = new JSMin($js);
return $jsmin->min();
}
/**
* @param string $input
*/
public function __construct($input)
{
$this->input = $input;
}
/**
* Perform minification, return result
*
* @return string
*/
public function min()
{
if ($this->output !== '') { // min already run
return $this->output;
}
$mbIntEnc = null;
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
$mbIntEnc = mb_internal_encoding();
mb_internal_encoding('8bit');
}
$this->input = str_replace("\r\n", "\n", $this->input);
$this->inputLength = strlen($this->input);
$this->action(self::ACTION_DELETE_A_B);
while ($this->a !== null) {
// determine next command
$command = self::ACTION_KEEP_A; // default
if ($this->a === ' ') {
if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
&& ($this->b === $this->lastByteOut)) {
// Don't delete this space. If we do, the addition/subtraction
// could be parsed as a post-increment
} elseif (! $this->isAlphaNum($this->b)) {
$command = self::ACTION_DELETE_A;
}
} elseif ($this->a === "\n") {
if ($this->b === ' ') {
$command = self::ACTION_DELETE_A_B;
// in case of mbstring.func_overload & 2, must check for null b,
// otherwise mb_strpos will give WARNING
} elseif ($this->b === null
|| (false === strpos('{[(+-!~', $this->b)
&& ! $this->isAlphaNum($this->b))) {
$command = self::ACTION_DELETE_A;
}
} elseif (! $this->isAlphaNum($this->a)) {
if ($this->b === ' '
|| ($this->b === "\n"
&& (false === strpos('}])+-"\'', $this->a)))) {
$command = self::ACTION_DELETE_A_B;
}
}
$this->action($command);
}
$this->output = trim($this->output);
if ($mbIntEnc !== null) {
mb_internal_encoding($mbIntEnc);
}
return $this->output;
}
/**
* ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
* ACTION_DELETE_A = Copy B to A. Get the next B.
* ACTION_DELETE_A_B = Get the next B.
*
* @param int $command
* @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException
*/
protected function action($command)
{
// make sure we don't compress "a + ++b" to "a+++b", etc.
if ($command === self::ACTION_DELETE_A_B
&& $this->b === ' '
&& ($this->a === '+' || $this->a === '-')) {
// Note: we're at an addition/substraction operator; the inputIndex
// will certainly be a valid index
if ($this->input[$this->inputIndex] === $this->a) {
// This is "+ +" or "- -". Don't delete the space.
$command = self::ACTION_KEEP_A;
}
}
switch ($command) {
case self::ACTION_KEEP_A: // 1
$this->output .= $this->a;
if ($this->keptComment) {
$this->output = rtrim($this->output, "\n");
$this->output .= $this->keptComment;
$this->keptComment = '';
}
$this->lastByteOut = $this->a;
// fallthrough intentional
case self::ACTION_DELETE_A: // 2
$this->a = $this->b;
if ($this->a === "'" || $this->a === '"') { // string literal
$str = $this->a; // in case needed for exception
for(;;) {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
$this->a = $this->get();
if ($this->a === $this->b) { // end quote
break;
}
if ($this->isEOF($this->a)) {
throw new JSMin_UnterminatedStringException(
"JSMin: Unterminated String at byte {$this->inputIndex}: {$str}");
}
$str .= $this->a;
if ($this->a === '\\') {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
$this->a = $this->get();
$str .= $this->a;
}
}
}
// fallthrough intentional
case self::ACTION_DELETE_A_B: // 3
$this->b = $this->next();
if ($this->b === '/' && $this->isRegexpLiteral()) {
$this->output .= $this->a . $this->b;
$pattern = '/'; // keep entire pattern in case we need to report it in the exception
for(;;) {
$this->a = $this->get();
$pattern .= $this->a;
if ($this->a === '[') {
for(;;) {
$this->output .= $this->a;
$this->a = $this->get();
$pattern .= $this->a;
if ($this->a === ']') {
break;
}
if ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
$pattern .= $this->a;
}
if ($this->isEOF($this->a)) {
throw new JSMin_UnterminatedRegExpException(
"JSMin: Unterminated set in RegExp at byte "
. $this->inputIndex .": {$pattern}");
}
}
}
if ($this->a === '/') { // end pattern
break; // while (true)
} elseif ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
$pattern .= $this->a;
} elseif ($this->isEOF($this->a)) {
throw new JSMin_UnterminatedRegExpException(
"JSMin: Unterminated RegExp at byte {$this->inputIndex}: {$pattern}");
}
$this->output .= $this->a;
$this->lastByteOut = $this->a;
}
$this->b = $this->next();
}
// end case ACTION_DELETE_A_B
}
}
/**
* @return bool
*/
protected function isRegexpLiteral()
{
if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
// we obviously aren't dividing
return true;
}
if ($this->a === ' ' || $this->a === "\n") {
$length = strlen($this->output);
if ($length < 2) { // weird edge case
return true;
}
// you can't divide a keyword
if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) {
if ($this->output === $m[0]) { // odd but could happen
return true;
}
// make sure it's a keyword, not end of an identifier
$charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1);
if (! $this->isAlphaNum($charBeforeKeyword)) {
return true;
}
}
}
return false;
}
/**
* Return the next character from stdin. Watch out for lookahead. If the character is a control character,
* translate it to a space or linefeed.
*
* @return string
*/
protected function get()
{
$c = $this->lookAhead;
$this->lookAhead = null;
if ($c === null) {
// getc(stdin)
if ($this->inputIndex < $this->inputLength) {
$c = $this->input[$this->inputIndex];
$this->inputIndex += 1;
} else {
$c = null;
}
}
if ($c === null || ord($c) >= self::ORD_SPACE || $c === "\n") {
return $c;
}
if ($c === "\r") {
return "\n";
}
return ' ';
}
/**
* Does $a indicate end of input?
*
* @param string $a
* @return bool
*/
protected function isEOF($a)
{
return ord($a) <= self::ORD_LF;
}
/**
* Get next char (without getting it). If is ctrl character, translate to a space or newline.
*
* @return string
*/
protected function peek()
{
$this->lookAhead = $this->get();
return $this->lookAhead;
}
/**
* Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
*
* @param string $c
*
* @return bool
*/
protected function isAlphaNum($c)
{
return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126);
}
/**
* Consume a single line comment from input (possibly retaining it)
*/
protected function consumeSingleLineComment()
{
$comment = '';
while (true) {
$get = $this->get();
$comment .= $get;
if (ord($get) <= self::ORD_LF) { // end of line reached
// if IE conditional comment
if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
$this->keptComment .= "/{$comment}";
}
return;
}
}
}
/**
* Consume a multiple line comment from input (possibly retaining it)
*
* @throws JSMin_UnterminatedCommentException
*/
protected function consumeMultipleLineComment()
{
$this->get();
$comment = '';
for(;;) {
$get = $this->get();
if ($get === '*') {
if ($this->peek() === '/') { // end of comment reached
$this->get();
if (0 === strpos($comment, '!')) {
// preserved by YUI Compressor
if (!$this->keptComment) {
// don't prepend a newline if two comments right after one another
$this->keptComment = "\n";
}
$this->keptComment .= "/*!" . substr($comment, 1) . "*/\n";
} else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
// IE conditional
$this->keptComment .= "/*{$comment}*/";
}
return;
}
} elseif ($get === null) {
throw new JSMin_UnterminatedCommentException(
"JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}");
}
$comment .= $get;
}
}
/**
* Get the next character, skipping over comments. Some comments may be preserved.
*
* @return string
*/
protected function next()
{
$get = $this->get();
if ($get === '/') {
switch ($this->peek()) {
case '/':
$this->consumeSingleLineComment();
$get = "\n";
break;
case '*':
$this->consumeMultipleLineComment();
$get = ' ';
break;
}
}
return $get;
}
}
class JSMin_UnterminatedStringException extends Exception {}
class JSMin_UnterminatedCommentException extends Exception {}
class JSMin_UnterminatedRegExpException extends Exception {}
jacssjanus/csslex.php 0000604 00000007446 15245530303 0010731 0 ustar 00 <?php
class CSSLEX {
private $csslex = array();
function __construct () {
$csslex = array();
$csslex['keyword'] = '(?:\@(?:import|page|media|charset))';
# nl \n|\r\n|\r|\f ; a newline
$csslex['newline'] = '\n|\r\n|\r|\f';
# h [0-9a-f] ; a hexadecimal digit
$csslex['hex'] = '[0-9a-f]';
# nonascii [\200-\377]
$csslex['non_ascii'] = '[\200-\377]';
# unicode \\{h}{1,6}(\r\n|[ \t\r\n\f])?
$csslex['unicode'] = '(?:(?:\\' . $csslex['hex'] . '{1,6})(?:\r\n|[ \t\r\n\f])?)';
# escape {unicode}|\\[^\r\n\f0-9a-f]
$csslex['escape'] = '(?:' . $csslex['unicode'] . '|\\[^\r\n\f0-9a-f])';
# nmstart [_a-z]|{nonascii}|{escape}
$csslex['nmstart'] = '(?:[_a-z]|' . $csslex['non_ascii'] . '|' . $csslex['escape'] . ')';
# nmchar [_a-z0-9-]|{nonascii}|{escape}
$csslex['nmchar'] = '(?:[_a-z0-9-]|' . $csslex['non_ascii'] . '|' . $csslex['escape'] . ')';
# ident -?{nmstart}{nmchar}*
$csslex['ident'] = '-?' . $csslex['nmstart'] . $csslex['nmchar'] . '*';
# name {nmchar}+
$csslex['name'] = $csslex['nmchar'] . '+';
# hash
$csslex['hash'] = '#' . $csslex['name'];
# string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\" ; "string"
$csslex['string1'] = '"(?:[^\"\\]|\\.)*"';
# string2 \'([^\n\r\f\\']|\\{nl}|{escape})*\' ; 'string'
$csslex['string2'] = "'(?:[^\'\\]|\\.)*'";
# string {string1}|{string2}
$csslex['string'] = '(?:' . $csslex['string1'] . '|' . $csslex['string2'] . ')';
# num [0-9]+|[0-9]*"."[0-9]+
$csslex['num'] = '(?:[0-9]*\.[0-9]+|[0-9]+)';
# s [ \t\r\n\f]
$csslex['space'] = '[ \t\r\n\f]';
# w {s}*
$csslex['whitespace'] = '(?:' . $csslex['space'] . '*)';
# url special chars
$csslex['url_special_chars'] = '[!#$%&*-~]';
# url chars ({url_special_chars}|{nonascii}|{escape})*
$csslex['url_chars'] = sprintf('(?:%s|%s|%s)*', $csslex['url_special_chars'], $csslex['non_ascii'], $csslex['escape']);
# url
$csslex['url'] = sprintf('url\(%s(%s|%s)%s\)', $csslex['whitespace'], $csslex['string'], $csslex['url_chars'], $csslex['whitespace']);
# comments
# see http://www.w3.org/tr/css21/grammar.html
$csslex['comment'] = '\/\*[^\*]*\*+([^\/\*][^\*]*\*+)*\/';
# {e}{m} {return ems;}
# {e}{x} {return exs;}
# {p}{x} {return length;}
# {c}{m} {return length;}
# {m}{m} {return length;}
# {i}{n} {return length;}
# {p}{t} {return length;}
# {p}{c} {return length;}
# {d}{e}{g} {return angle;}
# {r}{a}{d} {return angle;}
# {g}{r}{a}{d} {return angle;}
# {m}{s} {return time;}
# {s} {return time;}
# {h}{z} {return freq;}
# {k}{h}{z} {return freq;}
# % {return percentage;}
$csslex['unit'] = '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)';
# {num}{unit|ident} {return number;}
$csslex['quantity'] = sprintf('%s(?:%s%s|%s)?', $csslex['num'], $csslex['whitespace'], $csslex['unit'], $csslex['ident']);
# "<!--" {return cdo;}
# "-->" {return cdc;}
# "~=" {return includes;}
# "|=" {return dashmatch;}
# {w}"{" {return lbrace;}
# {w}"+" {return plus;}
# {w}">" {return greater;}
# {w}"," {return comma;}
$csslex['punc'] = '<!--|-->|~=|\|=|[\{\+>,:;]';
$this->csslex = $csslex;
}
function __get ($name) {
return isset($this->csslex[$name]) ? $this->csslex[$name] : null;
}
}
jacssjanus/test.php 0000604 00000036647 15245530303 0010414 0 ustar 00 <style type="text/css">
.pass {color: green;}
.fail {color: red;}
table.result {
font-family: verdana,arial,sans-serif;
font-size:11px;
color:#333333;
border-width: 1px;
border-color: #666666;
border-collapse: collapse;
}
table.result td {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #666666;
background-color: #ffffff;
width: 400px;
}
table.result tr td:first-child {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #666666;
background-color: #dedede;
width: 50px;
}
</style>
<?php
require_once 'ja.cssjanus.php';
$test = '';
$testcase = '';
$shouldbe = '';
$swap_ltr_rtl_in_url = False;
$swap_left_right_in_url = False;
function test () {
global $test, $testcase, $shouldbe, $swap_ltr_rtl_in_url, $swap_left_right_in_url;
$input = implode ("\n", $testcase);
$expect = implode ("\n", $shouldbe);
$output = JACSSJanus::transform ($input, $swap_ltr_rtl_in_url, $swap_left_right_in_url);
$pass = ($output == $expect);
$result = $pass ? '<span class="pass">pass</span>' : '<span class="fail">fail</span>';
?>
<h2 class="<?php echo $pass ? 'pass':'fail' ?>"><?php echo $test ?></h2>
<table class="result">
<tr><td>Input</td><td><?php echo str_replace("\n", "<br />\n", $input) ?></td></tr>
<tr><td>Expect</td><td><?php echo str_replace("\n", "<br />\n", $expect) ?></td></tr>
<tr><td>Output</td><td><?php echo str_replace("\n", "<br />\n", $output) ?></td></tr>
<tr><td>Result</td><td><?php echo $result ?></td></tr>
</table>
<br /><br />
<?php
}
$test = 'testPreserveComments';
$testcase = array('/* left /* right */left: 10px');
$shouldbe = array('/* left /* right */right: 10px');
test();
$testcase = array('/*left*//*left*/left: 10px');
$shouldbe = array('/*left*//*left*/right: 10px');
test();
$testcase = array('/* Going right is cool */\n#test {left: 10px}');
$shouldbe = array('/* Going right is cool */\n#test {right: 10px}');
$testcase = array('/* padding-right 1 2 3 4 */\n#test {left: 10px}\n/*right*/');
$shouldbe = array('/* padding-right 1 2 3 4 */\n#test {right: 10px}\n/*right*/');
test();
$testcase = array('/** Two line comment\n * left\n \*/\n#test {left: 10px}');
$shouldbe = array('/** Two line comment\n * left\n \*/\n#test {right: 10px}');
test();
$test = 'testPositionAbsoluteOrRelativeValues';
$testcase = array('left: 10px');
$shouldbe = array('right: 10px');
test();
$test = 'testFourNotation';
$testcase = array('padding: .25em 15px 0pt 0ex');
$shouldbe = array('padding: .25em 0ex 0pt 15px');
test();
$testcase = array('margin: 1px -4px 3px 2px');
$shouldbe = array('margin: 1px 2px 3px -4px');
test();
$testcase = array('padding:0 15px .25em 0');
$shouldbe = array('padding:0 0 .25em 15px');
test();
$testcase = array('padding: 1px 4.1grad 3px 2%');
$shouldbe = array('padding: 1px 2% 3px 4.1grad');
test();
$testcase = array('padding: 1px 2px 3px auto');
$shouldbe = array('padding: 1px auto 3px 2px');
test();
$testcase = array('padding: 1px inherit 3px auto');
$shouldbe = array('padding: 1px auto 3px inherit');
test();
# not really four notation
$testcase = array('#settings td p strong');
$shouldbe = $testcase;
test();
$test = 'testThreeNotation';
$testcase = array('margin: 1em 0 .25em');
$shouldbe = array('margin: 1em 0 .25em');
test();
$testcase = array('margin:-1.5em 0 -.75em');
$shouldbe = array('margin:-1.5em 0 -.75em');
test();
$test = 'testTwoNotation';
$testcase = array('padding: 1px 2px');
$shouldbe = array('padding: 1px 2px');
test();
$test = 'testOneNotation';
$testcase = array('padding: 1px');
$shouldbe = array('padding: 1px');
test();
$test = 'testDirection';
# we don't want direction to be changed other than in body
$testcase = array('direction: ltr');
$shouldbe = array('direction: ltr');
test();
# we don't want direction to be changed other than in body
$testcase = array('direction: rtl');
$shouldbe = array('direction: rtl');
test();
# we don't want direction to be changed other than in body
$testcase = array('input { direction: ltr }');
$shouldbe = array('input { direction: ltr }');
test();
$testcase = array('body { direction: ltr }');
$shouldbe = array('body { direction: rtl }');
test();
$testcase = array('body { padding: 10px; direction: ltr; }');
$shouldbe = array('body { padding: 10px; direction: rtl; }');
test();
$testcase = array('body { direction: ltr } .myClass { direction: ltr }');
$shouldbe = array('body { direction: rtl } .myClass { direction: ltr }');
test();
$testcase = array('body{\n direction: ltr\n}');
$shouldbe = array('body{\n direction: rtl\n}');
test();
$test = 'testDoubleDash';
$testcase = array('border-left-color: red');
$shouldbe = array('border-right-color: red');
test();
$testcase = array('border-right-color: red');
$shouldbe = array('border-left-color: red');
test();
# This is for compatibility strength, in reality CSS has no properties
# that are currently like this.
$test = 'testCSSProperty';
$testcase = array('alright: 10px');
$shouldbe = array('alright: 10px');
test();
$testcase = array('alleft: 10px');
$shouldbe = array('alleft: 10px');
test();
$test = 'testFloat';
$testcase = array('float: right');
$shouldbe = array('float: left');
test();
$testcase = array('float: left');
$shouldbe = array('float: right');
test();
$test = 'testUrlWithFlagOff';
$swap_ltr_rtl_in_url = False;
$swap_left_right_in_url = False;
$testcase = array('background: url(/foo/bar-left.png)');
$shouldbe = array('background: url(/foo/bar-left.png)');
test();
$testcase = array('background: url(/foo/left-bar.png)');
$shouldbe = array('background: url(/foo/left-bar.png)');
test();
$testcase = array('url("http://www.blogger.com/img/triangle_ltr.gif")');
$shouldbe = array('url("http://www.blogger.com/img/triangle_ltr.gif")');
test();
$testcase = array("url('http://www.blogger.com/img/triangle_ltr.gif')");
$shouldbe = array("url('http://www.blogger.com/img/triangle_ltr.gif')");
test();
$testcase = array("url('http://www.blogger.com/img/triangle_ltr.gif' )");
$shouldbe = array("url('http://www.blogger.com/img/triangle_ltr.gif' )");
test();
$testcase = array('background: url(/foo/bar.left.png)');
$shouldbe = array('background: url(/foo/bar.left.png)');
test();
$testcase = array('background: url(/foo/bar-rtl.png)');
$shouldbe = array('background: url(/foo/bar-rtl.png)');
test();
$testcase = array('background: url(/foo/bar-rtl.png); left: 10px');
$shouldbe = array('background: url(/foo/bar-rtl.png); right: 10px');
test();
$testcase = array('background: url(/foo/bar-right.png); direction: ltr');
$shouldbe = array('background: url(/foo/bar-right.png); direction: ltr');
test();
$testcase = array('background: url(/foo/bar-rtl_right.png);',
'left:10px; direction: ltr');
$shouldbe = array('background: url(/foo/bar-rtl_right.png);',
'right:10px; direction: ltr');
test();
$test = 'testUrlWithFlagOn';
$swap_ltr_rtl_in_url = True;
$swap_left_right_in_url = True;
$testcase = array('background: url(/foo/bar-left.png)');
$shouldbe = array('background: url(/foo/bar-right.png)');
test();
$testcase = array('background: url(/foo/left-bar.png)');
$shouldbe = array('background: url(/foo/right-bar.png)');
test();
$testcase = array('url("http://www.blogger.com/img/triangle_ltr.gif")');
$shouldbe = array('url("http://www.blogger.com/img/triangle_rtl.gif")');
test();
$testcase = array("url('http://www.blogger.com/img/triangle_ltr.gif')");
$shouldbe = array("url('http://www.blogger.com/img/triangle_rtl.gif')");
test();
$testcase = array("url('http://www.blogger.com/img/triangle_ltr.gif' )");
$shouldbe = array("url('http://www.blogger.com/img/triangle_rtl.gif' )");
test();
$testcase = array('background: url(/foo/bar.left.png)');
$shouldbe = array('background: url(/foo/bar.right.png)');
test();
$testcase = array('background: url(/foo/bright.png)');
$shouldbe = array('background: url(/foo/bright.png)');
test();
$testcase = array('background: url(/foo/bar-rtl.png)');
$shouldbe = array('background: url(/foo/bar-ltr.png)');
test();
$testcase = array('background: url(/foo/bar-rtl.png); left: 10px');
$shouldbe = array('background: url(/foo/bar-ltr.png); right: 10px');
test();
$testcase = array('background: url(/foo/bar-right.png); direction: ltr');
$shouldbe = array('background: url(/foo/bar-left.png); direction: ltr');
test();
$testcase = array('background: url(/foo/bar-rtl_right.png);',
'left:10px; direction: ltr');
$shouldbe = array('background: url(/foo/bar-ltr_left.png);',
'right:10px; direction: ltr');
test();
$test = 'testPadding';
$testcase = array('padding-right: bar');
$shouldbe = array('padding-left: bar');
test();
$testcase = array('padding-left: bar');
$shouldbe = array('padding-right: bar');
test();
$test = 'testMargin';
$testcase = array('margin-left: bar');
$shouldbe = array('margin-right: bar');
test();
$testcase = array('margin-right: bar');
$shouldbe = array('margin-left: bar');
test();
$test = 'testBorder';
$testcase = array('border-left: bar');
$shouldbe = array('border-right: bar');
test();
$testcase = array('border-right: bar');
$shouldbe = array('border-left: bar');
test();
$test = 'testCursor';
$testcase = array('cursor: e-resize');
$shouldbe = array('cursor: w-resize');
test();
$testcase = array('cursor: w-resize');
$shouldbe = array('cursor: e-resize');
test();
$testcase = array('cursor: se-resize');
$shouldbe = array('cursor: sw-resize');
test();
$testcase = array('cursor: sw-resize');
$shouldbe = array('cursor: se-resize');
test();
$testcase = array('cursor: ne-resize');
$shouldbe = array('cursor: nw-resize');
test();
$testcase = array('cursor: nw-resize');
$shouldbe = array('cursor: ne-resize');
test();
$test = 'testBGPosition';
$testcase = array('background: url(/foo/bar.png) top left');
$shouldbe = array('background: url(/foo/bar.png) top right');
test();
$testcase = array('background: url(/foo/bar.png) top right');
$shouldbe = array('background: url(/foo/bar.png) top left');
test();
$testcase = array('background-position: top left');
$shouldbe = array('background-position: top right');
test();
$testcase = array('background-position: top right');
$shouldbe = array('background-position: top left');
test();
$test = 'testBGPositionPercentage';
$testcase = array('background-position: 100% 40%');
$shouldbe = array('background-position: 0% 40%');
test();
$testcase = array('background-position: 0% 40%');
$shouldbe = array('background-position: 100% 40%');
test();
$testcase = array('background-position: 23% 0');
$shouldbe = array('background-position: 77% 0');
test();
$testcase = array('background-position: 23% auto');
$shouldbe = array('background-position: 77% auto');
test();
$testcase = array('background-position-x: 23%');
$shouldbe = array('background-position-x: 77%');
test();
$testcase = array('background-position-y: 23%');
$shouldbe = array('background-position-y: 23%');
test();
$testcase = array('background:url(../foo-bar_baz.2008.gif) no-repeat 75% 50%');
$shouldbe = array('background:url(../foo-bar_baz.2008.gif) no-repeat 25% 50%');
test();
$testcase = array('.test { background: 10% 20% } .test2 { background: 40% 30% }');
$shouldbe = array('.test { background: 90% 20% } .test2 { background: 60% 30% }');
test();
$testcase = array('.test { background: 0% 20% } .test2 { background: 40% 30% }');
$shouldbe = array('.test { background: 100% 20% } .test2 { background: 60% 30% }');
test();
$test = 'testDirectionalClassnames';
/*
"""Makes sure we don't unnecessarily destroy classnames with tokens in them.
Despite the fact that that is a bad classname in CSS, we don't want to
break anybody.
"""
*/
$testcase = array('.column-left { float: left }');
$shouldbe = array('.column-left { float: right }');
test();
$testcase = array('#bright-light { float: left }');
$shouldbe = array('#bright-light { float: right }');
test();
$testcase = array('a.left:hover { float: left }');
$shouldbe = array('a.left:hover { float: right }');
test();
#tests newlines
$testcase = array("#bright-left,\n.test-me { float: left }");
$shouldbe = array("#bright-left,\n.test-me { float: right }");
test();
#tests newlines
$testcase = array("#bright-left,", '.test-me { float: left }');
$shouldbe = array("#bright-left,", '.test-me { float: right }');
test();
#tests multiple names and commas
$testcase = array('div.leftpill, div.leftpillon {margin-right: 0 !important}');
$shouldbe = array('div.leftpill, div.leftpillon {margin-left: 0 !important}');
test();
$testcase = array('div.left > span.right+span.left { float: left }');
$shouldbe = array('div.left > span.right+span.left { float: right }');
test();
$testcase = array('.thisclass .left .myclass {background:#fff;}');
$shouldbe = array('.thisclass .left .myclass {background:#fff;}');
test();
$testcase = array('.thisclass .left .myclass #myid {background:#fff;}');
$shouldbe = array('.thisclass .left .myclass #myid {background:#fff;}');
test();
$test = 'testLongLineWithMultipleDefs';
$testcase = array('body{direction:rtl;float:right}
.b2{direction:ltr;float:right}');
$shouldbe = array('body{direction:ltr;float:left}
.b2{direction:ltr;float:left}');
test();
$test = 'testNoFlip';
# """Tests the /* @noflip */ annotation on classnames."""
$testcase = array('/* @noflip */ div { float: left; }');
$shouldbe = array('/* @noflip */ div { float: left; }');
test();
$testcase = array('/* @noflip */ div, .notme { float: left; }');
$shouldbe = array('/* @noflip */ div, .notme { float: left; }');
test();
$testcase = array('/* @noflip */ div { float: left; } div { float: left; }');
$shouldbe = array('/* @noflip */ div { float: left; } div { float: right; }');
test();
$testcase = array('/* @noflip */\ndiv { float: left; }\ndiv { float: left; }');
$shouldbe = array('/* @noflip */\ndiv { float: left; }\ndiv { float: right; }');
test();
# Test @noflip on single rules within classes
$testcase = array('div { float: left; /* @noflip */ float: left; }');
$shouldbe = array('div { float: right; /* @noflip */ float: left; }');
test();
$testcase = array('div\n{ float: left;\n/* @noflip */\n float: left;\n }');
$shouldbe = array('div\n{ float: right;\n/* @noflip */\n float: left;\n }');
test();
$testcase = array('div\n{ float: left;\n/* @noflip */\n text-align: left\n }');
$shouldbe = array('div\n{ float: right;\n/* @noflip */\n text-align: left\n }');
test();
$testcase = array('div\n{ /* @noflip */\ntext-align: left;\nfloat: left\n }');
$shouldbe = array('div\n{ /* @noflip */\ntext-align: left;\nfloat: right\n }');
test();
$testcase = array('/* @noflip */div{float:left;text-align:left;}div{float:left}');
$shouldbe = array('/* @noflip */div{float:left;text-align:left;}div{float:right}');
test();
$testcase = array('/* @noflip */','div{float:left;text-align:left;}a{foo:left}');
$shouldbe = array('/* @noflip */','div{float:left;text-align:left;}a{foo:right}');
test();
$test = 'testBorderRadiusNotation';
$testcase = array('border-radius: .25em 15px 0pt 0ex');
$shouldbe = array('border-radius: 15px .25em 0ex 0pt');
test();
$testcase = array('border-radius: 10px 15px 0px');
$shouldbe = array('border-radius: 15px 10px 15px 0px');
test();
$testcase = array('border-radius: 7px 8px');
$shouldbe = array('border-radius: 8px 7px');
test();
$testcase = array('border-radius: 5px');
$shouldbe = array('border-radius: 5px');
test();
$test = 'testGradientNotation';
$testcase = array('background-image: -moz-linear-gradient(#326cc1, #234e8c)');
$shouldbe = array('background-image: -moz-linear-gradient(#326cc1, #234e8c)');
test();
$testcase = array('background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, from(#666666), to(#ffffff))');
$shouldbe = array('background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, from(#666666), to(#ffffff))');
test();
jacssjanus/ja.cssjanus.php 0000604 00000065040 15245530303 0011644 0 ustar 00 <?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
/**
* This is a rewrite & update version of PHP port of CSSJanus, a utility that transforms CSS style sheets
* written for LTR to RTL.
*
* The original Python version of CSSJanus is Copyright 2008 by Google Inc. and
* is distributed under the Apache license.
*
* The original PHP version of CSSJanus: https://doc.wikimedia.org/mediawiki-core/master/php/html/CSSJanus_8php.html.
*
* Original code: http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus.py
* License of original code: http://code.google.com/p/cssjanus/source/browse/trunk/LICENSE
* @author Khanh Le
*
*/
require_once 'csslex.php';
class JACSSJanus {
// Patterns defined as null are built dynamically by buildPatterns()
private static $patterns = array(
);
public static function getPatterns () {
self::buildPatterns();
return self::$patterns;
}
/**
* Build patterns we can't define above because they depend on other patterns.
*/
private static function buildPatterns() {
//increase the backtrack limit
@ini_set('pcre.backtrack_limit', '2M');
if ( isset( self::$patterns['token_delimiter'] ) ) {
// Patterns have already been built
return;
}
$csslex = new CSSLEX;
$patterns =& self::$patterns;
$patterns['token_delimiter'] = '`';
$patterns['tmp_token'] = sprintf('%sTMP%s', $patterns['token_delimiter'], $patterns['token_delimiter']);
$patterns['token_lines'] = sprintf('%sj%s', $patterns['token_delimiter'], $patterns['token_delimiter']);
# global constant text strings for css value matches.
$patterns['ltr'] = 'ltr';
$patterns['rtl'] = 'rtl';
$patterns['left'] = 'left';
$patterns['right'] = 'right';
# this is a lookbehind match to ensure that we don't replace instances
# of our string token (left, rtl, etc...) if there's a letter in front of it.
# specifically, this prevents replacements like 'background: url(bright.png)'.
$patterns['lookbehind_not_letter'] = '(?<![a-za-z])';
# this is a lookahead match to make sure we don't replace left and right
# in actual classnames, so that we don't break the html/css dependencies.
# read literally, it says ignore cases where the word left, for instance, is
# directly followed by valid classname characters and a curly brace.
# ex: .column-left {float: left} will become .column-left {float: right}
$patterns['lookahead_not_open_brace'] = sprintf('(?!(?:%s|%s|%s|#|\:|\.|\,|\+|]|=|>)*?(\,|{))',
$csslex->nmchar, $patterns['token_lines'], $csslex->space);
# these two lookaheads are to test whether or not we are within a
# background: url(here) situation.
# ref: http://www.w3.org/tr/css21/syndata.html#uri
$patterns['valid_after_uri_chars'] = sprintf("[\'\"]?%s", $csslex->whitespace);
$patterns['lookahead_not_closing_paren'] = sprintf("(?!%s?%s\))", $csslex->url_chars,
$patterns['valid_after_uri_chars']);
$patterns['lookahead_for_closing_paren'] = sprintf("(?=%s?%s\))", $csslex->url_chars,
$patterns['valid_after_uri_chars']);
# compile a regex to swap left and right values in 4 part notations.
# we need to match negatives and decimal numeric values.
# the case of border-radius is extra complex, so we handle it separately below.
# ex. 'margin: .25em -2px 3px 0' becomes 'margin: .25em 0 3px -2px'.
$patterns['possibly_negative_quantity'] = sprintf('((?:-?%s)|(?:inherit|auto))', $csslex->quantity);
$patterns['possibly_negative_quantity_space'] = sprintf('%s%s%s', $patterns['possibly_negative_quantity'],
$csslex->space,
$csslex->whitespace);
$patterns['four_notation_quantity_re'] = sprintf('/%s%s%s%s/i',
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity']
);
$patterns['color'] = sprintf('(%s|%s)', $csslex->name, $csslex->hash);
$patterns['color_space'] = sprintf('%s%s', $patterns['color'], $csslex->space);
$patterns['four_notation_color_re'] = sprintf('/(-color%s:%s)%s%s%s(%s)/i',
$csslex->whitespace,
$csslex->whitespace,
$patterns['color_space'],
$patterns['color_space'],
$patterns['color_space'],
$patterns['color']
);
# border-radius is very different from usual 4 part notation: abcd should
# change to badc (while it would be adcb in normal 4 part notation), abc
# should change to babc, and ab should change to ba
$patterns['border_radius_re'] = sprintf('/((?:%s)?)border-radius(%s:%s)'
.'(?:%s)?(?:%s)?(?:%s)?(?:%s)'
.'(?:%s\/%s(?:%s)?(?:%s)?(?:%s)?(?:%s))?/i',$csslex->ident,
$csslex->whitespace,
$csslex->whitespace,
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity'],
$csslex->whitespace,
$csslex->whitespace,
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity_space'],
$patterns['possibly_negative_quantity']
);
# compile the cursor resize regexes
$patterns['cursor_east_re'] = '/' . $patterns['lookbehind_not_letter'] . '([ns]?)e-resize/';
$patterns['cursor_west_re'] = '/' . $patterns['lookbehind_not_letter'] . '([ns]?)w-resize/';
# matches the condition where we need to replace the horizontal component
# of a background-position value when expressed in horizontal percentage.
# had to make two regexes because in the case of position-x there is only
# one quantity, and otherwise we don't want to match and change cases with only
# one quantity.
$patterns['bg_horizontal_percentage_re'] = sprintf('/background(-position)?(%s:%s)'
.'([^%%]*?)(%s)%%'
.'(%s(?:%s|top|center|bottom))/',
$csslex->whitespace,
$csslex->whitespace,
$csslex->num,
$csslex->whitespace,
$patterns['possibly_negative_quantity']
);
$patterns['bg_horizontal_percentage_x_re'] = sprintf('/background-position-x(%s:%s)(%s)%%/', $csslex->whitespace,
$csslex->whitespace,
$csslex->num);
# non-percentage units used for css lengths
$patterns['length_unit'] = '(?:em|ex|px|cm|mm|in|pt|pc)';
# to make sure the lone 0 is not just starting a number (like "02") or a percentage like ("0 %");
$patterns['lookahead_end_of_zero'] = sprintf('(?![0-9]|%s%%)', $csslex->whitespace);
# a length with a unit specified. matches "0" too, as it's a length, not a percentage.
$patterns['length'] = sprintf('(?:-?%s(?:%s%s)|0+%s)', $csslex->num,
$csslex->whitespace,
$patterns['length_unit'],
$patterns['lookahead_end_of_zero']);
# zero length. used in the replacement functions.
$patterns['zero_length'] = sprintf('/(?:-?0+(?:%s%s)|0+%s)$/', $csslex->whitespace,
$patterns['length_unit'],
$patterns['lookahead_end_of_zero']);
# matches background, background-position, and background-position-x
# properties when using a css length for its horizontal positioning.
$patterns['bg_horizontal_length_re'] = sprintf('/background(-position)?(%s:%s)'
.'((?:.+?%s+)??)(%s)'
.'((?:%s+)(?:%s|top|center|bottom))/', $csslex->whitespace,
$csslex->whitespace,
$csslex->space,
$patterns['length'],
$csslex->space,
$patterns['possibly_negative_quantity']);
$patterns['bg_horizontal_length_x_re'] = sprintf('/background-position-x(%s:%s)(%s)/', $csslex->whitespace,
$csslex->whitespace,
$patterns['length']);
# matches the opening of a body selector.
$patterns['body_selector'] = sprintf('body%s{%s', $csslex->whitespace, $csslex->whitespace);
# matches anything up until the closing of a selector.
$patterns['chars_within_selector'] = '[^\}]*?';
# matches the direction property in a selector.
$patterns['direction_re'] = sprintf('direction%s:%s', $csslex->whitespace, $csslex->whitespace);
# these allow us to swap "ltr" with "rtl" and vice versa only within the
# body selector and on the same line.
$patterns['body_direction_ltr_re'] = sprintf('/(%s)(%s)(%s)(ltr)/i',
$patterns['body_selector'],
$patterns['chars_within_selector'],
$patterns['direction_re']
);
$patterns['body_direction_rtl_re'] = sprintf('/(%s)(%s)(%s)(rtl)/i',
$patterns['body_selector'],
$patterns['chars_within_selector'],
$patterns['direction_re']
);
# allows us to swap "direction:ltr" with "direction:rtl" and
# vice versa anywhere in a line.
$patterns['direction_ltr_re'] = sprintf('/%s(ltr)/', $patterns['direction_re']);
$patterns['direction_rtl_re'] = sprintf('/%s(rtl)/', $patterns['direction_re']);
# we want to be able to switch left with right and vice versa anywhere
# we encounter left/right strings, except inside the background:url(). the next
# two regexes are for that purpose. we have alternate in_url versions of the
# regexes compiled in case the user passes the flag that they do
# actually want to have left and right swapped inside of background:urls.
$patterns['left_re'] = sprintf('/%s((?:top|bottom)?)(%s)%s%s/i', $patterns['lookbehind_not_letter'],
$patterns['left'],
$patterns['lookahead_not_closing_paren'],
$patterns['lookahead_not_open_brace']
);
$patterns['right_re'] = sprintf('/%s((?:top|bottom)?)(%s)%s%s/i', $patterns['lookbehind_not_letter'],
$patterns['right'],
$patterns['lookahead_not_closing_paren'],
$patterns['lookahead_not_open_brace']);
$patterns['left_in_url_re'] = sprintf('/%s(%s)%s/i', $patterns['lookbehind_not_letter'],
$patterns['left'],
$patterns['lookahead_for_closing_paren']);
$patterns['right_in_url_re'] = sprintf('/%s(%s)%s/i', $patterns['lookbehind_not_letter'],
$patterns['right'],
$patterns['lookahead_for_closing_paren']);
$patterns['ltr_in_url_re'] = sprintf('/%s(%s)%s/i', $patterns['lookbehind_not_letter'],
$patterns['ltr'],
$patterns['lookahead_for_closing_paren']);
$patterns['rtl_in_url_re'] = sprintf('/%s(%s)%s/i', $patterns['lookbehind_not_letter'],
$patterns['rtl'],
$patterns['lookahead_for_closing_paren']);
$patterns['comment_re'] = sprintf('/(%s)/i', $csslex->comment);
$patterns['noflip_token'] = '\@noflip';
# the noflip_token inside of a comment. for now, this requires that comments
# be in the input, which means users of a css compiler would have to run
# this script first if they want this functionality.
$patterns['noflip_annotation'] = sprintf('\/\*%s%s%s\*\/', $csslex->whitespace,
$patterns['noflip_token'],
$csslex->whitespace);
# after a noflip_annotation, and within a class selector, we want to be able
# to set aside a single rule not to be flipped. we can do this by matching
# our noflip annotation and then using a lookahead to make sure there is not
# an opening brace before the match.
$patterns['noflip_single_re'] = sprintf('/(%s%s[^;}]+;?)/i', $patterns['noflip_annotation'],
$patterns['lookahead_not_open_brace']);
# after a noflip_annotation, we want to grab anything up until the next } which
# means the entire following class block. this will prevent all of its
# declarations from being flipped.
$patterns['noflip_class_re'] = sprintf('/(%s%s})/i', $patterns['noflip_annotation'],
$patterns['chars_within_selector']);
# border-radis properties and their values
$patterns['border_radius_tokenizer_re'] = sprintf('/((?:%s)?border-radius%s:[^;}]+;?)/i', $csslex->ident,
$csslex->whitespace);
$patterns['gradient_re'] = sprintf('/%s[\.-]gradient%s\(/i', $csslex->ident, $csslex->whitespace);
}
/**
* Transform an LTR stylesheet to RTL
* @param $css String: stylesheet to transform
* @param $swapLtrRtlInURL Boolean: If true, swap 'ltr' and 'rtl' in URLs
* @param $swapLeftRightInURL Boolean: If true, swap 'left' and 'right' in URLs
* @return Transformed stylesheet
*/
public static function transform( $css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false ) {
self::buildPatterns();
// We wrap tokens in ` , not ~ like the original implementation does.
// This was done because ` is not a legal character in CSS and can only
// occur in URLs, where we escape it to %60 before inserting our tokens.
$css = str_replace( self::$patterns['token_delimiter'], '%60', $css );
// Tokenize single line rules with /* @noflip */
$noFlipSingle = new CSSJanus_Tokenizer( self::$patterns['noflip_single_re'], '`NOFLIP_SINGLE`' );
$css = $noFlipSingle->tokenize( $css );
// Tokenize class rules with /* @noflip */
$noFlipClass = new CSSJanus_Tokenizer( self::$patterns['noflip_class_re'], '`NOFLIP_CLASS`' );
$css = $noFlipClass->tokenize( $css );
// Tokenize comments
$comments = new CSSJanus_Tokenizer( self::$patterns['comment_re'], '`C`' );
$css = $comments->tokenize( $css );
# Tokenize gradients since we don't want to mirror the values inside
//$comments = new CSSJanus_Tokenizer( self::$patterns['comment_re']GradientMatcher(), '`GRADIENT`' );
//$css = $comments->tokenize( $css );
// LTR->RTL fixes start here
$css = self::FixBodyDirectionLtrAndRtl( $css );
if ( $swapLtrRtlInURL ) {
$css = self::fixLtrRtlInURL( $css );
}
if ( $swapLeftRightInURL ) {
$css = self::fixLeftRightInURL( $css );
}
$css = self::fixLeftAndRight( $css );
$css = self::fixCursorProperties( $css );
$css = self::fixBorderRadius( $css );
# Since FourPartNotation conflicts with BorderRadius, we tokenize border-radius properties here.
$border_radius_tokenizer = new CSSJanus_Tokenizer( self::$patterns['border_radius_tokenizer_re'], '`BORDER_RADIUS`' );
$css = $border_radius_tokenizer->tokenize( $css );
$css = self::fixFourPartNotation( $css );
$css = $border_radius_tokenizer->detokenize( $css );
$css = self::fixBackgroundPosition( $css );
// Detokenize stuff we tokenized before
$css = $comments->detokenize( $css );
$css = $noFlipClass->detokenize( $css );
$css = $noFlipSingle->detokenize( $css );
return $css;
}
/**
* Replaces ltr with rtl and vice versa ONLY in the body direction.
*
*/
private static function FixBodyDirectionLtrAndRtl( $css ) {
$css = preg_replace( self::$patterns['body_direction_ltr_re'], '\1\2\3' . self::$patterns['tmp_token'], $css );
$css = preg_replace( self::$patterns['body_direction_rtl_re'], '\1\2\3' . self::$patterns['ltr'], $css );
$css = str_replace( self::$patterns['tmp_token'], self::$patterns['rtl'], $css );
return $css;
}
/**
* Flip rules like left: , padding-right: , etc.
*/
private static function fixLeftAndRight( $css ) {
$css = preg_replace( self::$patterns['left_re'], '\1' . self::$patterns['tmp_token'], $css );
$css = preg_replace( self::$patterns['right_re'], '\1' . self::$patterns['left'], $css );
$css = str_replace( self::$patterns['tmp_token'], self::$patterns['right'], $css );
return $css;
}
/**
* Replace 'left' with 'right' and vice versa in background URLs
*/
private static function fixleftrightinurl( $css ) {
$css = preg_replace( self::$patterns['left_in_url_re'], self::$patterns['tmp_token'], $css );
$css = preg_replace( self::$patterns['right_in_url_re'], self::$patterns['left'], $css );
$css = str_replace( self::$patterns['tmp_token'], self::$patterns['right'], $css );
return $css;
}
/**
* replace 'ltr' with 'rtl' and vice versa in background urls
*/
private static function fixltrrtlinurl( $css ) {
$css = preg_replace( self::$patterns['ltr_in_url_re'], self::$patterns['tmp_token'], $css );
$css = preg_replace( self::$patterns['rtl_in_url_re'], self::$patterns['ltr'], $css );
$css = str_replace( self::$patterns['tmp_token'], self::$patterns['rtl'], $css );
return $css;
}
/**
* flip east and west in rules like cursor: nw-resize;
*/
private static function fixcursorproperties( $css ) {
$css = preg_replace( self::$patterns['cursor_east_re'], '\1' . self::$patterns['tmp_token'], $css );
$css = preg_replace( self::$patterns['cursor_west_re'], '\1e-resize', $css );
$css = str_replace( self::$patterns['tmp_token'], 'w-resize', $css );
return $css;
}
/**
* Fixes border-radius and its browser-specific variants.
*/
private static function fixBorderRadius( $css ) {
//echo self::$patterns['border_radius_re']; die();
$css = preg_replace_callback(self::$patterns['border_radius_re'], array( 'self', 'reorderBorderRadius' ), $css );
return $css;
}
/**
* Fixes border-radius and its browser-specific variants.
*/
private static function reorderBorderRadius( $matches ) {
$first_group = self::reorderBorderRadiusPart(array_slice ($matches, 3, 4));
$second_group = self::reorderBorderRadiusPart(array_slice ($matches, 7));
if ($second_group == '')
return sprintf('%sborder-radius%s%s', $matches[1], $matches[2], $first_group);
else
return sprintf('%sborder-radius%s%s / %s', $matches[1], $matches[2], $first_group, $second_group);
}
/**
* Fixes border-radius and its browser-specific variants.
*/
private static function reorderBorderRadiusPart( $ps ) {
# Remove any piece which may be 'None'
$part = array();
foreach ($ps as $p) {
if ($p != '') $part[] = $p;
}
if (count($part) == 4) {
return sprintf('%s %s %s %s', $part[1], $part[0], $part[3], $part[2]);
} elseif (count($part) == 3) {
return sprintf('%s %s %s %s', $part[1], $part[0], $part[1], $part[2]);
} elseif (count($part) == 2) {
return sprintf('%s %s', $part[1], $part[0]);
} elseif (count($part) == 1) {
return $part[0];
} elseif (count($part) == 0) {
return '';
} else {
return null;
}
}
/**
* Swap the second and fourth parts in four-part notation rules like
* padding: 1px 2px 3px 4px;
*
* Unlike the original implementation, this function doesn't suffer from
* the bug where whitespace is not preserved when flipping four-part rules
* and four-part color rules with multiple whitespace characters between
* colors are not recognized.
* See http://code.google.com/p/cssjanus/issues/detail?id=16
*/
private static function fixFourPartNotation( $css ) {
$css = preg_replace( self::$patterns['four_notation_quantity_re'], '\1 \4 \3 \2', $css );
$css = preg_replace( self::$patterns['four_notation_color_re'], '\1\2 \5 \4 \3', $css );
return $css;
}
/**
* Flip horizontal background percentages.
*/
private static function fixBackgroundPosition( $css ) {
$css = preg_replace_callback( self::$patterns['bg_horizontal_percentage_re'],
array( 'self', 'calculateNewBackgroundPosition' ), $css );
$css = preg_replace_callback( self::$patterns['bg_horizontal_percentage_x_re'],
array( 'self', 'calculateNewBackgroundPositionX' ), $css );
$css = preg_replace_callback( self::$patterns['bg_horizontal_length_re'],
array( 'self', 'calculateNewBackgroundLengthPosition' ), $css );
$css = preg_replace_callback( self::$patterns['bg_horizontal_length_x_re'],
array( 'self', 'calculateNewBackgroundLengthPositionX' ), $css );
return $css;
}
/**
* Callback for calculateNewBackgroundPosition()
*/
private static function calculateNewBackgroundPosition( $matches ) {
# The flipped value is the offset from 100%
$new_x = 100-intval($matches[4]);
# Since m.group(1) may very well be None type and we need a string..
if ($matches[1]){
$position_string = $matches[1];
} else {
$position_string = '';
}
return sprintf('background%s%s%s%s%%%s', $position_string, $matches[2], $matches[3], $new_x, $matches[5]);
}
/**
* Callback for calculateNewBackgroundPosition()
*/
private static function calculateNewBackgroundPositionX( $matches ) {
# The flipped value is the offset from 100%
$new_x = 100-intval($matches[2]);
return sprintf('background-position-x%s%s%%', $matches[1], $new_x);
}
/**
* Fixes horizontal background-position lengths.
* Return: A string with the horizontal background position set to 100%, if zero.
*/
private static function calculateNewBackgroundLengthPosition( $matches ) {
# return original if error
if ($matches[4]) {
return $matches[0];
}
# Since m.group(1) may very well be None type and we need a string..
if ($matches[1]){
$position_string = $matches[1];
} else {
$position_string = '';
}
return sprintf('background%s%s%s100%%%s', $position_string, $matches[2], $matches[3], $matches[5]);
}
/**
* Fixes background-position-x lengths
* Return: A string with the background-position-x set to 100%, if zero.
*/
private static function calculateNewBackgroundLengthPositionX( $matches ) {
# return original if error
if ($matches[2]) {
return $matches[0];
}
return sprintf('background-position-x%s100%%', $matches[1]);
}
}
/**
* Utility class used by CSSJanus that tokenizes and untokenizes things we want
* to protect from being janused.
* @author Roan Kattouw
*/
class CSSJanus_Tokenizer {
private $regex, $token;
private $originals;
/**
* Constructor
* @param $regex string Regular expression whose matches to replace by a token.
* @param $token string Token
*/
public function __construct( $regex, $token ) {
$this->regex = $regex;
$this->token = $token;
$this->originals = array();
}
/**
* Replace all occurrences of $regex in $str with a token and remember
* the original strings.
* @param $str String to tokenize
* @return string Tokenized string
*/
public function tokenize( $str ) {
return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str );
}
private function tokenizeCallback( $matches ) {
$this->originals[] = $matches[0];
return $this->token;
}
/**
* Replace tokens with their originals. If multiple strings were tokenized, it's important they be
* detokenized in exactly the SAME ORDER.
* @param $str String: previously run through tokenize()
* @return string Original string
*/
public function detokenize( $str ) {
// PHP has no function to replace only the first occurrence or to
// replace occurrences of the same string with different values,
// so we use preg_replace_callback() even though we don't really need a regex
return preg_replace_callback( '/' . preg_quote( $this->token, '/' ) . '/',
array( $this, 'detokenizeCallback' ), $str );
}
private function detokenizeCallback( $matches ) {
$retval = current( $this->originals );
next( $this->originals );
return $retval;
}
} lessphp/legacy.less.php 0000604 00000002240 15245530303 0011136 0 ustar 00 <?php
/**
*------------------------------------------------------------------------------
* @package T3 Framework for Joomla!
*------------------------------------------------------------------------------
* @copyright Copyright (C) 2004-2013 JoomlArt.com. All Rights Reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
* @authors JoomlArt, JoomlaBamboo, (contribute to this project at github
* & Google group to become co-author)
* @Google group: https://groups.google.com/forum/#!forum/t3fw
* @Link: http://t3-framework.org
*------------------------------------------------------------------------------
*/
// No direct access
defined('_JEXEC') or die();
if (!class_exists('lessc_formatter_compressed', false))
T3::import('lessphp/lessc.inc');
/**
* T3LessCompiler class compile less
*
* @package T3
*/
class T3LessCompiler
{
public static function compile ($source, $importdirs) {
// call Less to compile
$parser = new lessc();
$parser->setImportDir(array_keys($importdirs));
$parser->setPreserveComments(true);
$output = $parser->compile($source);
return $output;
}
}
lessphp/less/cache.php 0000604 00000011113 15245530303 0010735 0 ustar 00 <?php
require_once( dirname(__FILE__).'/version.php');
/**
* Utility for handling the generation and caching of css files
*
* @package Less
* @subpackage cache
*
*/
class Less_Cache{
public static $cache_dir = false; // directory less.php can use for storing data
/**
* Save and reuse the results of compiled less files.
* The first call to Get() will generate css and save it.
* Subsequent calls to Get() with the same arguments will return the same css filename
*
* @param array $less_files Array of .less files to compile
* @param array $parser_options Array of compiler options
* @param boolean $use_cache Set to false to regenerate the css file
* @return string Name of the css file
*/
public static function Get( $less_files, $parser_options = array(), $use_cache = true ){
//check $cache_dir
if( isset($parser_options['cache_dir']) ){
Less_Cache::$cache_dir = $parser_options['cache_dir'];
}
if( empty(Less_Cache::$cache_dir) ){
throw new Exception('cache_dir not set');
}
self::CheckCacheDir();
// generate name for compiled css file
$less_files = (array)$less_files;
$hash = md5(json_encode($less_files));
$list_file = Less_Cache::$cache_dir.'lessphp_'.$hash.'.list';
if( $use_cache === true ){
// check cached content
if( file_exists($list_file) ){
$list = explode("\n",file_get_contents($list_file));
$compiled_name = self::CompiledName($list);
$compiled_file = Less_Cache::$cache_dir.$compiled_name;
if( file_exists($compiled_file) ){
@touch($list_file);
@touch($compiled_file);
return $compiled_name;
}
}
}
$compiled = self::Cache( $less_files, $parser_options );
if( !$compiled ){
return false;
}
//save the file list
$cache = implode("\n",$less_files);
file_put_contents( $list_file, $cache );
//save the css
$compiled_name = self::CompiledName( $less_files );
file_put_contents( Less_Cache::$cache_dir.$compiled_name, $compiled );
//clean up
self::CleanCache();
return $compiled_name;
}
/**
* Force the compiler to regenerate the cached css file
*
* @param array $less_files Array of .less files to compile
* @param array $parser_options Array of compiler options
* @return string Name of the css file
*/
public static function Regen( $less_files, $parser_options = array() ){
return self::Get( $less_files, $parser_options, false );
}
public static function Cache( &$less_files, $parser_options = array() ){
// get less.php if it exists
$file = dirname(__FILE__) . '/Less.php';
if( file_exists($file) && !class_exists('Less_Parser') ){
require_once($file);
}
$parser_options['cache_dir'] = Less_Cache::$cache_dir;
$parser = new Less_Parser($parser_options);
// combine files
foreach($less_files as $file_path => $uri_or_less ){
//treat as less markup if there are newline characters
if( strpos($uri_or_less,"\n") !== false ){
$parser->Parse( $uri_or_less );
continue;
}
$parser->ParseFile( $file_path, $uri_or_less );
}
$compiled = $parser->getCss();
$less_files = $parser->allParsedFiles();
return $compiled;
}
private static function CompiledName( $files ){
//save the file list
$temp = array(Less_Version::cache_version);
foreach($files as $file){
$temp[] = filemtime($file)."\t".filesize($file)."\t".$file;
}
return 'lessphp_'.sha1(json_encode($temp)).'.css';
}
public static function SetCacheDir( $dir ){
Less_Cache::$cache_dir = $dir;
}
public static function CheckCacheDir(){
Less_Cache::$cache_dir = str_replace('\\','/',Less_Cache::$cache_dir);
Less_Cache::$cache_dir = rtrim(Less_Cache::$cache_dir,'/').'/';
if( !file_exists(Less_Cache::$cache_dir) ){
if( !mkdir(Less_Cache::$cache_dir) ){
throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.Less_Cache::$cache_dir);
}
}elseif( !is_dir(Less_Cache::$cache_dir) ){
throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.Less_Cache::$cache_dir);
}elseif( !is_writable(Less_Cache::$cache_dir) ){
throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.Less_Cache::$cache_dir);
}
}
public static function CleanCache(){
static $clean = false;
if( $clean ){
return;
}
$files = scandir(Less_Cache::$cache_dir);
if( $files ){
$check_time = time() - 604800;
foreach($files as $file){
if( strpos($file,'lessphp_') !== 0 ){
continue;
}
$full_path = Less_Cache::$cache_dir.'/'.$file;
if( filemtime($full_path) > $check_time ){
continue;
}
unlink($full_path);
}
}
$clean = true;
}
} lessphp/less/version.php 0000604 00000000510 15245530303 0011356 0 ustar 00 <?php
/**
* Release numbers
*
* @package Less
* @subpackage version
*/
class Less_Version{
const version = '1.7.0.1'; // The current build number of less.php
const less_version = '1.7'; // The less.js version that this build should be compatible with
const cache_version = '170'; // The parser cache version
} lessphp/less/less.php 0000604 00000731674 15245530303 0010665 0 ustar 00 <?php
require_once( dirname(__FILE__).'/cache.php');
/**
* Class for parsing and compiling less files into css
*
* @package Less
* @subpackage parser
*
*/
class Less_Parser{
/**
* Default parser options
*/
public static $default_options = array(
'compress' => false, // option - whether to compress
'strictUnits' => false, // whether units need to evaluate correctly
'strictMath' => false, // whether math has to be within parenthesis
'relativeUrls' => true, // option - whether to adjust URL's to be relative
'urlArgs' => array(), // whether to add args into url tokens
'numPrecision' => 8,
'import_dirs' => array(),
'import_callback' => null,
'cache_dir' => null,
'cache_method' => 'php', //false, 'serialize', 'php', 'var_export';
'sourceMap' => false, // whether to output a source map
'sourceMapBasepath' => null,
'sourceMapWriteTo' => null,
'sourceMapURL' => null,
'plugins' => array(),
);
public static $options = array();
private $input; // Less input string
private $input_len; // input string length
private $pos; // current index in `input`
private $saveStack = array(); // holds state for backtracking
private $furthest;
/**
* @var Less_Environment
*/
private $env;
private $rules = array();
private static $imports = array();
public static $has_extends = false;
public static $next_id = 0;
/**
* Filename to contents of all parsed the files
*
* @var array
*/
public static $contentsMap = array();
public $parensInOp;
/**
* @param Less_Environment|array|null $env
*/
public function __construct( $env = null ){
// Top parser on an import tree must be sure there is one "env"
// which will then be passed around by reference.
if( $env instanceof Less_Environment ){
$this->env = $env;
}else{
$this->SetOptions(Less_Parser::$default_options);
$this->Reset( $env );
}
}
/**
* Reset the parser state completely
*
*/
public function Reset( $options = null ){
$this->rules = array();
self::$imports = array();
self::$has_extends = false;
self::$imports = array();
self::$contentsMap = array();
$this->env = new Less_Environment($options);
$this->env->Init();
//set new options
if( is_array($options) ){
$this->SetOptions(Less_Parser::$default_options);
$this->SetOptions($options);
}
}
/**
* Set one or more compiler options
* options: import_dirs, cache_dir, cache_method
*
*/
public function SetOptions( $options ){
foreach($options as $option => $value){
$this->SetOption($option,$value);
}
}
/**
* Set one compiler option
*
*/
public function SetOption($option,$value){
switch($option){
case 'import_dirs':
$this->SetImportDirs($value);
return;
case 'cache_dir':
if( is_string($value) ){
Less_Cache::SetCacheDir($value);
Less_Cache::CheckCacheDir();
}
return;
}
Less_Parser::$options[$option] = $value;
}
/**
* Get the current css buffer
*
* @return string
*/
public function getCss(){
$precision = ini_get('precision');
@ini_set('precision',16);
$locale = setlocale(LC_NUMERIC, 0);
setlocale(LC_NUMERIC, "C");
$root = new Less_Tree_Ruleset(array(), $this->rules );
$root->root = true;
$root->firstRoot = true;
$this->PreVisitors($root);
self::$has_extends = false;
$evaldRoot = $root->compile($this->env);
$this->PostVisitors($evaldRoot);
if( Less_Parser::$options['sourceMap'] ){
$generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
// will also save file
// FIXME: should happen somewhere else?
$css = $generator->generateCSS();
}else{
$css = $evaldRoot->toCSS();
}
if( Less_Parser::$options['compress'] ){
$css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css);
}
//reset php settings
@ini_set('precision',$precision);
setlocale(LC_NUMERIC, $locale);
return $css;
}
/**
* Run pre-compile visitors
*
*/
private function PreVisitors($root){
if( Less_Parser::$options['plugins'] ){
foreach(Less_Parser::$options['plugins'] as $plugin){
if( !empty($plugin->isPreEvalVisitor) ){
$plugin->run($root);
}
}
}
}
/**
* Run post-compile visitors
*
*/
private function PostVisitors($evaldRoot){
$visitors = array();
$visitors[] = new Less_Visitor_joinSelector();
if( self::$has_extends ){
$visitors[] = new Less_Visitor_processExtends();
}
$visitors[] = new Less_Visitor_toCSS();
if( Less_Parser::$options['plugins'] ){
foreach(Less_Parser::$options['plugins'] as $plugin){
if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){
continue;
}
if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){
array_unshift( $visitors, $plugin);
}else{
$visitors[] = $plugin;
}
}
}
for($i = 0; $i < count($visitors); $i++ ){
$visitors[$i]->run($evaldRoot);
}
}
/**
* Parse a Less string into css
*
* @param string $str The string to convert
* @param string $uri_root The url of the file
* @return Less_Tree_Ruleset|Less_Parser
*/
public function parse( $str, $file_uri = null ){
if( !$file_uri ){
$uri_root = '';
$filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
}else{
$file_uri = self::WinPath($file_uri);
$filename = basename($file_uri);
$uri_root = dirname($file_uri);
}
$previousFileInfo = $this->env->currentFileInfo;
$uri_root = self::WinPath($uri_root);
$this->SetFileInfo($filename, $uri_root);
$this->input = $str;
$this->_parse();
if( $previousFileInfo ){
$this->env->currentFileInfo = $previousFileInfo;
}
return $this;
}
/**
* Parse a Less string from a given file
*
* @throws Less_Exception_Parser
* @param string $filename The file to parse
* @param string $uri_root The url of the file
* @param bool $returnRoot Indicates whether the return value should be a css string a root node
* @return Less_Tree_Ruleset|Less_Parser
*/
public function parseFile( $filename, $uri_root = '', $returnRoot = false){
if( !file_exists($filename) ){
$this->Error(sprintf('File `%s` not found.', $filename));
}
// fix uri_root?
// Instead of The mixture of file path for the first argument and directory path for the second argument has bee
if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){
$uri_root = dirname($uri_root);
}
$previousFileInfo = $this->env->currentFileInfo;
$filename = self::WinPath($filename);
$uri_root = self::WinPath($uri_root);
$this->SetFileInfo($filename, $uri_root);
self::AddParsedFile($filename);
if( $returnRoot ){
$rules = $this->GetRules( $filename );
$return = new Less_Tree_Ruleset(array(), $rules );
}else{
$this->_parse( $filename );
$return = $this;
}
if( $previousFileInfo ){
$this->env->currentFileInfo = $previousFileInfo;
}
return $return;
}
/**
* Allows a user to set variables values
* @param array $vars
* @return Less_Parser
*/
public function ModifyVars( $vars ){
$this->input = $this->serializeVars( $vars );
$this->_parse();
return $this;
}
/**
* @param string $filename
*/
public function SetFileInfo( $filename, $uri_root = ''){
$filename = Less_Environment::normalizePath($filename);
$dirname = preg_replace('/[^\/\\\\]*$/','',$filename);
if( !empty($uri_root) ){
$uri_root = rtrim($uri_root,'/').'/';
}
$currentFileInfo = array();
//entry info
if( isset($this->env->currentFileInfo) ){
$currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
$currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
$currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
}else{
$currentFileInfo['entryPath'] = $dirname;
$currentFileInfo['entryUri'] = $uri_root;
$currentFileInfo['rootpath'] = $dirname;
}
$currentFileInfo['currentDirectory'] = $dirname;
$currentFileInfo['currentUri'] = $uri_root.basename($filename);
$currentFileInfo['filename'] = $filename;
$currentFileInfo['uri_root'] = $uri_root;
//inherit reference
if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){
$currentFileInfo['reference'] = true;
}
$this->env->currentFileInfo = $currentFileInfo;
}
/**
* @deprecated 1.5.1.2
*
*/
public function SetCacheDir( $dir ){
if( !file_exists($dir) ){
if( mkdir($dir) ){
return true;
}
throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir);
}elseif( !is_dir($dir) ){
throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir);
}elseif( !is_writable($dir) ){
throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir);
}else{
$dir = self::WinPath($dir);
Less_Cache::$cache_dir = rtrim($dir,'/').'/';
return true;
}
}
/**
* Set a list of directories or callbacks the parser should use for determining import paths
*
* @param array $dirs
*/
public function SetImportDirs( $dirs ){
Less_Parser::$options['import_dirs'] = array();
foreach($dirs as $path => $uri_root){
$path = self::WinPath($path);
if( !empty($path) ){
$path = rtrim($path,'/').'/';
}
if ( !is_callable($uri_root) ){
$uri_root = self::WinPath($uri_root);
if( !empty($uri_root) ){
$uri_root = rtrim($uri_root,'/').'/';
}
}
Less_Parser::$options['import_dirs'][$path] = $uri_root;
}
}
/**
* @param string $file_path
*/
private function _parse( $file_path = null ){
$this->rules = array_merge($this->rules, $this->GetRules( $file_path ));
}
/**
* Return the results of parsePrimary for $file_path
* Use cache and save cached results if possible
*
* @param string|null $file_path
*/
private function GetRules( $file_path ){
$this->SetInput($file_path);
$cache_file = $this->CacheFile( $file_path );
if( $cache_file && file_exists($cache_file) ){
switch(Less_Parser::$options['cache_method']){
// Using serialize
// Faster but uses more memory
case 'serialize':
$cache = unserialize(file_get_contents($cache_file));
if( $cache ){
touch($cache_file);
$this->UnsetInput();
return $cache;
}
break;
// Using generated php code
case 'var_export':
case 'php':
$this->UnsetInput();
return include($cache_file);
}
}
$rules = $this->parsePrimary();
if( $this->pos < $this->input_len ){
throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo);
}
$this->UnsetInput();
//save the cache
if( $cache_file ){
//msg('write cache file');
switch(Less_Parser::$options['cache_method']){
case 'serialize':
file_put_contents( $cache_file, serialize($rules) );
break;
case 'php':
file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' );
break;
case 'var_export':
//Requires __set_state()
file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' );
break;
}
Less_Cache::CleanCache();
}
return $rules;
}
/**
* Set up the input buffer
*
*/
public function SetInput( $file_path ){
if( $file_path ){
$this->input = file_get_contents( $file_path );
}
$this->pos = $this->furthest = 0;
// Remove potential UTF Byte Order Mark
$this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input);
$this->input_len = strlen($this->input);
if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){
$uri = $this->env->currentFileInfo['currentUri'];
Less_Parser::$contentsMap[$uri] = $this->input;
}
}
/**
* Free up some memory
*
*/
public function UnsetInput(){
unset($this->input, $this->pos, $this->input_len, $this->furthest);
$this->saveStack = array();
}
public function CacheFile( $file_path ){
if( $file_path && Less_Parser::$options['cache_method'] && Less_Cache::$cache_dir ){
$env = get_object_vars($this->env);
unset($env['frames']);
$parts = array();
$parts[] = $file_path;
$parts[] = filesize( $file_path );
$parts[] = filemtime( $file_path );
$parts[] = $env;
$parts[] = Less_Version::cache_version;
$parts[] = Less_Parser::$options['cache_method'];
return Less_Cache::$cache_dir.'lessphp_'.base_convert( sha1(json_encode($parts) ), 16, 36).'.lesscache';
}
}
static function AddParsedFile($file){
self::$imports[] = $file;
}
static function AllParsedFiles(){
return self::$imports;
}
/**
* @param string $file
*/
static function FileParsed($file){
return in_array($file,self::$imports);
}
function save() {
$this->saveStack[] = $this->pos;
}
private function restore() {
$this->pos = array_pop($this->saveStack);
}
private function forget(){
array_pop($this->saveStack);
}
private function isWhitespace($offset = 0) {
return preg_match('/\s/',$this->input[ $this->pos + $offset]);
}
/**
* Parse from a token, regexp or string, and move forward if match
*
* @param array $toks
* @return array
*/
private function match($toks){
// The match is confirmed, add the match length to `this::pos`,
// and consume any extra white-space characters (' ' || '\n')
// which come after that. The reason for this is that LeSS's
// grammar is mostly white-space insensitive.
//
foreach($toks as $tok){
$char = $tok[0];
if( $char === '/' ){
$match = $this->MatchReg($tok);
if( $match ){
return count($match) === 1 ? $match[0] : $match;
}
}elseif( $char === '#' ){
$match = $this->MatchChar($tok[1]);
}else{
// Non-terminal, match using a function call
$match = $this->$tok();
}
if( $match ){
return $match;
}
}
}
/**
* @param string[] $toks
*
* @return string
*/
private function MatchFuncs($toks){
foreach($toks as $tok){
$match = $this->$tok();
if( $match ){
return $match;
}
}
}
// Match a single character in the input,
private function MatchChar($tok){
if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){
$this->skipWhitespace(1);
return $tok;
}
}
// Match a regexp from the current start point
private function MatchReg($tok){
if( preg_match($tok, $this->input, $match, 0, $this->pos) ){
$this->skipWhitespace(strlen($match[0]));
return $match;
}
return [];
}
/**
* Same as match(), but don't change the state of the parser,
* just return the match.
*
* @param string $tok
* @return integer
*/
public function PeekReg($tok){
return preg_match($tok, $this->input, $match, 0, $this->pos);
}
/**
* @param string $tok
*/
public function PeekChar($tok){
//return ($this->input[$this->pos] === $tok );
return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok );
}
/**
* @param integer $length
*/
public function skipWhitespace($length){
$this->pos += $length;
for(; $this->pos < $this->input_len; $this->pos++ ){
$c = $this->input[$this->pos];
if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){
break;
}
}
}
/**
* @param string $tok
* @param string|null $msg
*/
public function expect($tok, $msg = NULL) {
$result = $this->match( array($tok) );
if (!$result) {
$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
} else {
return $result;
}
}
/**
* @param string $tok
*/
public function expectChar($tok, $msg = null ){
$result = $this->MatchChar($tok);
if( !$result ){
$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
}else{
return $result;
}
}
//
// Here in, the parsing rules/functions
//
// The basic structure of the syntax tree generated is as follows:
//
// Ruleset -> Rule -> Value -> Expression -> Entity
//
// Here's some LESS code:
//
// .class {
// color: #fff;
// border: 1px solid #000;
// width: @w + 4px;
// > .child {...}
// }
//
// And here's what the parse tree might look like:
//
// Ruleset (Selector '.class', [
// Rule ("color", Value ([Expression [Color #fff]]))
// Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
// Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
// Ruleset (Selector [Element '>', '.child'], [...])
// ])
//
// In general, most rules will try to parse a token with the `$()` function, and if the return
// value is truly, will return a new node, of the relevant type. Sometimes, we need to check
// first, before parsing, that's when we use `peek()`.
//
//
// The `primary` rule is the *entry* and *exit* point of the parser.
// The rules here can appear at any level of the parse tree.
//
// The recursive nature of the grammar is an interplay between the `block`
// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
// as represented by this simplified grammar:
//
// primary → (ruleset | rule)+
// ruleset → selector+ block
// block → '{' primary '}'
//
// Only at one point is the primary rule not called from the
// block rule: at the root level.
//
private function parsePrimary(){
$root = array();
while( true ){
if( $this->pos >= $this->input_len ){
break;
}
$node = $this->parseExtend(true);
if( $node ){
$root = array_merge($root,$node);
continue;
}
//$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective'));
if( $node ){
$root[] = $node;
}elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){
break;
}
if( $this->PeekChar('}') ){
break;
}
}
return $root;
}
// We create a Comment node for CSS comments `/* */`,
// but keep the LeSS comments `//` silent, by just skipping
// over them.
private function parseComment(){
if( $this->input[$this->pos] !== '/' ){
return;
}
if( $this->input[$this->pos+1] === '/' ){
$match = $this->MatchReg('/\\G\/\/.*/');
return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo));
}
//$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
$comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors
if( $comment ){
return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo));
}
}
private function parseComments(){
$comments = array();
while( $this->pos < $this->input_len ){
$comment = $this->parseComment();
if( !$comment ){
break;
}
$comments[] = $comment;
}
return $comments;
}
//
// A string, which supports escaping " and '
//
// "milky way" 'he\'s the one!'
//
private function parseEntitiesQuoted() {
$j = $this->pos;
$e = false;
$index = $this->pos;
if( $this->input[$this->pos] === '~' ){
$j++;
$e = true; // Escaped strings
}
if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){
return;
}
if ($e) {
$this->MatchChar('~');
}
$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/');
if( $str ){
$result = $str[0][0] == '"' ? $str[1] : $str[2];
return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) );
}
return;
}
//
// A catch-all word, such as:
//
// black border-collapse
//
private function parseEntitiesKeyword(){
//$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
$k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/');
if( $k ){
$k = $k[0];
$color = $this->fromKeyword($k);
if( $color ){
return $color;
}
return $this->NewObj1('Less_Tree_Keyword',$k);
}
}
// duplicate of Less_Tree_Color::FromKeyword
private function FromKeyword( $keyword ){
$keyword = strtolower($keyword);
if( Less_Colors::hasOwnProperty($keyword) ){
// detect named color
return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1));
}
if( $keyword === 'transparent' ){
return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true));
}
}
//
// A function call
//
// rgb(255, 0, 255)
//
// We also try to catch IE's `alpha()`, but let the `alpha` parser
// deal with the details.
//
// The arguments are parsed with the `entities.arguments` parser.
//
private function parseEntitiesCall(){
$index = $this->pos;
if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){
return;
}
$name = $name[1];
$nameLC = strtolower($name);
if ($nameLC === 'url') {
return null;
}
$this->pos += strlen($name);
if( $nameLC === 'alpha' ){
$alpha_ret = $this->parseAlpha();
if( $alpha_ret ){
return $alpha_ret;
}
}
$this->MatchChar('('); // Parse the '(' and consume whitespace.
$args = $this->parseEntitiesArguments();
if( !$this->MatchChar(')') ){
return;
}
if ($name) {
return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) );
}
}
/**
* Parse a list of arguments
*
* @return array
*/
private function parseEntitiesArguments(){
$args = array();
while( true ){
$arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') );
if( !$arg ){
break;
}
$args[] = $arg;
if( !$this->MatchChar(',') ){
break;
}
}
return $args;
}
private function parseEntitiesLiteral(){
return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') );
}
// Assignments are argument entities for calls.
// They are present in ie filter properties as shown below.
//
// filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
//
private function parseEntitiesAssignment() {
$key = $this->MatchReg('/\\G\w+(?=\s?=)/');
if( !$key ){
return;
}
if( !$this->MatchChar('=') ){
return;
}
$value = $this->parseEntity();
if( $value ){
return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value));
}
}
//
// Parse url() tokens
//
// We use a specific rule for urls, because they don't really behave like
// standard function calls. The difference is that the argument doesn't have
// to be enclosed within a string, so it can't be parsed as an Expression.
//
private function parseEntitiesUrl(){
if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){
return;
}
$value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') );
if( !$value ){
$value = '';
}
$this->expectChar(')');
if( isset($value->value) || $value instanceof Less_Tree_Variable ){
return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo));
}
return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) );
}
//
// A Variable entity, such as `@fink`, in
//
// width: @fink + 2px
//
// We use a different parser for variable definitions,
// see `parsers.variable`.
//
private function parseEntitiesVariable(){
$index = $this->pos;
if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) {
return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo));
}
}
// A variable entity useing the protective {} e.g. @{var}
private function parseEntitiesVariableCurly() {
$index = $this->pos;
if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){
return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo));
}
}
//
// A Hexadecimal color
//
// #4F3C2F
//
// `rgb` and `hsl` colors are parsed through the `entities.call` parser.
//
private function parseEntitiesColor(){
if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) {
return $this->NewObj1('Less_Tree_Color',$rgb[1]);
}
}
//
// A Dimension, that is, a number and a unit
//
// 0.5em 95%
//
private function parseEntitiesDimension(){
$c = @ord($this->input[$this->pos]);
//Is the first char of the dimension 0-9, '.', '+' or '-'
if (($c > 57 || $c < 43) || $c === 47 || $c == 44){
return;
}
$value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/');
if( $value ){
if( isset($value[2]) ){
return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2]));
}
return $this->NewObj1('Less_Tree_Dimension',$value[1]);
}
}
//
// A unicode descriptor, as is used in unicode-range
//
// U+0?? or U+00A1-00A9
//
function parseUnicodeDescriptor() {
$ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/');
if( $ud ){
return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]);
}
}
//
// JavaScript code to be evaluated
//
// `window.location.href`
//
private function parseEntitiesJavascript(){
$e = false;
$j = $this->pos;
if( $this->input[$j] === '~' ){
$j++;
$e = true;
}
if( $this->input[$j] !== '`' ){
return;
}
if( $e ){
$this->MatchChar('~');
}
$str = $this->MatchReg('/\\G`([^`]*)`/');
if( $str ){
return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e));
}
}
//
// The variable part of a variable definition. Used in the `rule` parser
//
// @fink:
//
private function parseVariable(){
if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) {
return $name[1];
}
}
//
// The variable part of a variable definition. Used in the `rule` parser
//
// @fink();
//
private function parseRulesetCall(){
if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){
return $this->NewObj1('Less_Tree_RulesetCall', $name[1] );
}
}
//
// extend syntax - used to extend selectors
//
function parseExtend($isRule = false){
$index = $this->pos;
$extendList = array();
if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; }
do{
$option = null;
$elements = array();
while( true ){
$option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/');
if( $option ){ break; }
$e = $this->parseElement();
if( !$e ){ break; }
$elements[] = $e;
}
if( $option ){
$option = $option[1];
}
$extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index ));
}while( $this->MatchChar(",") );
$this->expect('/\\G\)/');
if( $isRule ){
$this->expect('/\\G;/');
}
return $extendList;
}
//
// A Mixin call, with an optional argument list
//
// #mixins > .square(#fff);
// .rounded(4px, black);
// .button;
//
// The `while` loop is there because mixins can be
// namespaced, but we only support the child and descendant
// selector for now.
//
private function parseMixinCall(){
$char = $this->input[$this->pos];
if( $char !== '.' && $char !== '#' ){
return;
}
$index = $this->pos;
$this->save(); // stop us absorbing part of an invalid selector
$elements = $this->parseMixinCallElements();
if( $elements ){
if( $this->MatchChar('(') ){
$returned = $this->parseMixinArgs(true);
$args = $returned['args'];
$this->expectChar(')');
}else{
$args = array();
}
$important = $this->parseImportant();
if( $this->parseEnd() ){
$this->forget();
return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important));
}
}
$this->restore();
}
private function parseMixinCallElements(){
$elements = array();
$c = null;
while( true ){
$elemIndex = $this->pos;
$e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/');
if( !$e ){
break;
}
$elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo));
$c = $this->MatchChar('>');
}
return $elements;
}
/**
* @param boolean $isCall
*/
private function parseMixinArgs( $isCall ){
$expressions = array();
$argsSemiColon = array();
$isSemiColonSeperated = null;
$argsComma = array();
$expressionContainsNamed = null;
$name = null;
$returner = array('args'=>array(), 'variadic'=> false);
$this->save();
while( true ){
if( $isCall ){
$arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
} else {
$this->parseComments();
if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){
$returner['variadic'] = true;
if( $this->MatchChar(";") && !$isSemiColonSeperated ){
$isSemiColonSeperated = true;
}
if( $isSemiColonSeperated ){
$argsSemiColon[] = array('variadic'=>true);
}else{
$argsComma[] = array('variadic'=>true);
}
break;
}
$arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') );
}
if( !$arg ){
break;
}
$nameLoop = null;
if( $arg instanceof Less_Tree_Expression ){
$arg->throwAwayComments();
}
$value = $arg;
$val = null;
if( $isCall ){
// Variable
if( property_exists($arg,'value') && count($arg->value) == 1 ){
$val = $arg->value[0];
}
} else {
$val = $arg;
}
if( $val instanceof Less_Tree_Variable ){
if( $this->MatchChar(':') ){
if( $expressions ){
if( $isSemiColonSeperated ){
$this->Error('Cannot mix ; and , as delimiter types');
}
$expressionContainsNamed = true;
}
// we do not support setting a ruleset as a default variable - it doesn't make sense
// However if we do want to add it, there is nothing blocking it, just don't error
// and remove isCall dependency below
$value = null;
if( $isCall ){
$value = $this->parseDetachedRuleset();
}
if( !$value ){
$value = $this->parseExpression();
}
if( !$value ){
if( $isCall ){
$this->Error('could not understand value for named argument');
} else {
$this->restore();
$returner['args'] = array();
return $returner;
}
}
$nameLoop = ($name = $val->name);
}elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){
$returner['variadic'] = true;
if( $this->MatchChar(";") && !$isSemiColonSeperated ){
$isSemiColonSeperated = true;
}
if( $isSemiColonSeperated ){
$argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true);
}else{
$argsComma[] = array('name'=> $arg->name, 'variadic' => true);
}
break;
}elseif( !$isCall ){
$name = $nameLoop = $val->name;
$value = null;
}
}
if( $value ){
$expressions[] = $value;
}
$argsComma[] = array('name'=>$nameLoop, 'value'=>$value );
if( $this->MatchChar(',') ){
continue;
}
if( $this->MatchChar(';') || $isSemiColonSeperated ){
if( $expressionContainsNamed ){
$this->Error('Cannot mix ; and , as delimiter types');
}
$isSemiColonSeperated = true;
if( count($expressions) > 1 ){
$value = $this->NewObj1('Less_Tree_Value', $expressions);
}
$argsSemiColon[] = array('name'=>$name, 'value'=>$value );
$name = null;
$expressions = array();
$expressionContainsNamed = false;
}
}
$this->forget();
$returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma);
return $returner;
}
//
// A Mixin definition, with a list of parameters
//
// .rounded (@radius: 2px, @color) {
// ...
// }
//
// Until we have a finer grained state-machine, we have to
// do a look-ahead, to make sure we don't have a mixin call.
// See the `rule` function for more information.
//
// We start by matching `.rounded (`, and then proceed on to
// the argument list, which has optional default values.
// We store the parameters in `params`, with a `value` key,
// if there is a value, such as in the case of `@radius`.
//
// Once we've got our params list, and a closing `)`, we parse
// the `{...}` block.
//
private function parseMixinDefinition(){
$cond = null;
$char = $this->input[$this->pos];
if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){
return;
}
$this->save();
$match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/');
if( $match ){
$name = $match[1];
$argInfo = $this->parseMixinArgs( false );
$params = $argInfo['args'];
$variadic = $argInfo['variadic'];
// .mixincall("@{a}");
// looks a bit like a mixin definition..
// also
// .mixincall(@a: {rule: set;});
// so we have to be nice and restore
if( !$this->MatchChar(')') ){
$this->furthest = $this->pos;
$this->restore();
return;
}
$this->parseComments();
if ($this->MatchReg('/\\Gwhen/')) { // Guard
$cond = $this->expect('parseConditions', 'Expected conditions');
}
$ruleset = $this->parseBlock();
if( is_array($ruleset) ){
$this->forget();
return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic));
}
$this->restore();
}else{
$this->forget();
}
}
//
// Entities are the smallest recognized token,
// and can be found inside a rule's value.
//
private function parseEntity(){
return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') );
}
//
// A Rule terminator. Note that we use `peek()` to check for '}',
// because the `block` rule will be expecting it, but we still need to make sure
// it's there, if ';' was ommitted.
//
private function parseEnd(){
return $this->MatchChar(';') || $this->PeekChar('}');
}
//
// IE's alpha function
//
// alpha(opacity=88)
//
private function parseAlpha(){
if ( ! $this->MatchReg('/\\G\(opacity=/i')) {
return;
}
$value = $this->MatchReg('/\\G[0-9]+/');
if( $value ){
$value = $value[0];
}else{
$value = $this->parseEntitiesVariable();
if( !$value ){
return;
}
}
$this->expectChar(')');
return $this->NewObj1('Less_Tree_Alpha',$value);
}
//
// A Selector Element
//
// div
// + h1
// #socks
// input[type="text"]
//
// Elements are the building blocks for Selectors,
// they are made out of a `Combinator` (see combinator rule),
// and an element name, such as a tag a class, or `*`.
//
private function parseElement(){
$c = $this->parseCombinator();
$index = $this->pos;
$e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
'#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') );
if( is_null($e) ){
$this->save();
if( $this->MatchChar('(') ){
if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){
$e = $this->NewObj1('Less_Tree_Paren',$v);
$this->forget();
}else{
$this->restore();
}
}else{
$this->forget();
}
}
if( !is_null($e) ){
return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo));
}
}
//
// Combinators combine elements together, in a Selector.
//
// Because our parser isn't white-space sensitive, special care
// has to be taken, when parsing the descendant combinator, ` `,
// as it's an empty space. We have to check the previous character
// in the input, to see if it's a ` ` character.
//
private function parseCombinator(){
$c = $this->input[$this->pos];
if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){
$this->pos++;
if( $this->input[$this->pos] === '^' ){
$c = '^^';
$this->pos++;
}
$this->skipWhitespace(0);
return $c;
}
if( $this->pos > 0 && $this->isWhitespace(-1) ){
return ' ';
}
}
//
// A CSS selector (see selector below)
// with less extensions e.g. the ability to extend and guard
//
private function parseLessSelector(){
return $this->parseSelector(true);
}
//
// A CSS Selector
//
// .class > div + h1
// li a:hover
//
// Selectors are made out of one or more Elements, see above.
//
private function parseSelector( $isLess = false ){
$elements = array();
$extendList = array();
$condition = null;
$when = false;
$extend = false;
$e = null;
$c = null;
$index = $this->pos;
while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){
if( $when ){
$condition = $this->expect('parseConditions', 'expected condition');
}elseif( $condition ){
//error("CSS guard can only be used at the end of selector");
}elseif( $extend ){
$extendList = array_merge($extendList,$extend);
}else{
//if( count($extendList) ){
//error("Extend can only be used at the end of selector");
//}
$c = $this->input[ $this->pos ];
$elements[] = $e;
$e = null;
}
if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; }
}
if( $elements ){
return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo));
}
if( $extendList ) {
$this->Error('Extend must be used to extend a selector, it cannot be used on its own');
}
}
private function parseTag(){
return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*');
}
private function parseAttribute(){
$val = null;
if( !$this->MatchChar('[') ){
return;
}
$key = $this->parseEntitiesVariableCurly();
if( !$key ){
$key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/');
}
$op = $this->MatchReg('/\\G[|~*$^]?=/');
if( $op ){
$val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') );
}
$this->expectChar(']');
// kiendt- fix check empty option
$_op = !empty($op[0]) ? $op[0] : 0;
return $this->NewObj3('Less_Tree_Attribute',array( $key, $_op, $val));
}
//
// The `block` rule is used by `ruleset` and `mixin.definition`.
// It's a wrapper around the `primary` rule, with added `{}`.
//
private function parseBlock(){
if( $this->MatchChar('{') ){
$content = $this->parsePrimary();
if( $this->MatchChar('}') ){
return $content;
}
}
}
private function parseBlockRuleset(){
$block = $this->parseBlock();
if( $block ){
$block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block));
}
return $block;
}
private function parseDetachedRuleset(){
$blockRuleset = $this->parseBlockRuleset();
if( $blockRuleset ){
return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset);
}
}
//
// div, .class, body > p {...}
//
private function parseRuleset(){
$selectors = array();
$this->save();
while( true ){
$s = $this->parseLessSelector();
if( !$s ){
break;
}
$selectors[] = $s;
$this->parseComments();
if( $s->condition && count($selectors) > 1 ){
$this->Error('Guards are only currently allowed on a single selector.');
}
if( !$this->MatchChar(',') ){
break;
}
if( $s->condition ){
$this->Error('Guards are only currently allowed on a single selector.');
}
$this->parseComments();
}
if( $selectors ){
$rules = $this->parseBlock();
if( is_array($rules) ){
$this->forget();
return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports
}
}
// Backtrack
$this->furthest = $this->pos;
$this->restore();
}
/**
* Custom less.php parse function for finding simple name-value css pairs
* ex: width:100px;
*
*/
private function parseNameValue(){
$index = $this->pos;
$this->save();
//$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/');
if( $match ){
if( $match[4] == '}' ){
$this->pos = $index + strlen($match[0])-1;
}
if( $match[3] ){
$match[2] .= ' !important';
}
return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo));
}
$this->restore();
}
private function parseRule( $tryAnonymous = null ){
$merge = false;
$startOfRule = $this->pos;
$c = $this->input[$this->pos];
if( $c === '.' || $c === '#' || $c === '&' ){
return;
}
$this->save();
$name = $this->MatchFuncs( array('parseVariable','parseRuleProperty'));
if( $name ){
$isVariable = is_string($name);
$value = null;
if( $isVariable ){
$value = $this->parseDetachedRuleset();
}
$important = null;
if( !$value ){
// prefer to try to parse first if its a variable or we are compressing
// but always fallback on the other one
//if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){
$value = $this->MatchFuncs( array('parseValue','parseAnonymousValue'));
}else{
$value = $this->MatchFuncs( array('parseAnonymousValue','parseValue'));
}
$important = $this->parseImportant();
// a name returned by this.ruleProperty() is always an array of the form:
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
// where each item is a tree.Keyword or tree.Variable
if( !$isVariable && is_array($name) ){
$nm = array_pop($name);
if( $nm->value ){
$merge = $nm->value;
}
}
}
if( $value && $this->parseEnd() ){
$this->forget();
return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo));
}else{
$this->furthest = $this->pos;
$this->restore();
if( $value && !$tryAnonymous ){
return $this->parseRule(true);
}
}
}else{
$this->forget();
}
}
function parseAnonymousValue(){
if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){
$this->pos += strlen($match[1]);
return $this->NewObj1('Less_Tree_Anonymous',$match[1]);
}
}
//
// An @import directive
//
// @import "lib";
//
// Depending on our environment, importing is done differently:
// In the browser, it's an XHR request, in Node, it would be a
// file-system operation. The function used for importing is
// stored in `import`, which we pass to the Import constructor.
//
private function parseImport(){
$this->save();
$dir = $this->MatchReg('/\\G@import?\s+/');
if( $dir ){
$options = $this->parseImportOptions();
$path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl'));
if( $path ){
$features = $this->parseMediaFeatures();
if( $this->MatchChar(';') ){
if( $features ){
$features = $this->NewObj1('Less_Tree_Value',$features);
}
$this->forget();
return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo));
}
}
}
$this->restore();
}
private function parseImportOptions(){
$options = array();
// list of options, surrounded by parens
if( !$this->MatchChar('(') ){
return $options;
}
do{
$optionName = $this->parseImportOption();
if( $optionName ){
$value = true;
switch( $optionName ){
case "css":
$optionName = "less";
$value = false;
break;
case "once":
$optionName = "multiple";
$value = false;
break;
}
$options[$optionName] = $value;
if( !$this->MatchChar(',') ){ break; }
}
}while( $optionName );
$this->expectChar(')');
return $options;
}
private function parseImportOption(){
$opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference)/');
if( $opt ){
return $opt[1];
}
}
private function parseMediaFeature() {
$nodes = array();
do{
$e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable'));
if( $e ){
$nodes[] = $e;
} elseif ($this->MatchChar('(')) {
$p = $this->parseProperty();
$e = $this->parseValue();
if ($this->MatchChar(')')) {
if ($p && $e) {
$r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true));
$nodes[] = $this->NewObj1('Less_Tree_Paren',$r);
} elseif ($e) {
$nodes[] = $this->NewObj1('Less_Tree_Paren',$e);
} else {
return null;
}
} else
return null;
}
} while ($e);
if ($nodes) {
return $this->NewObj1('Less_Tree_Expression',$nodes);
}
}
private function parseMediaFeatures() {
$features = array();
do{
$e = $this->parseMediaFeature();
if( $e ){
$features[] = $e;
if (!$this->MatchChar(',')) break;
}else{
$e = $this->parseEntitiesVariable();
if( $e ){
$features[] = $e;
if (!$this->MatchChar(',')) break;
}
}
} while ($e);
return $features ? $features : null;
}
private function parseMedia() {
if( $this->MatchReg('/\\G@media/') ){
$features = $this->parseMediaFeatures();
$rules = $this->parseBlock();
if( is_array($rules) ){
return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo));
}
}
}
//
// A CSS Directive
//
// @charset "utf-8";
//
private function parseDirective(){
if( !$this->PeekChar('@') ){
return;
}
$rules = null;
$index = $this->pos;
$hasBlock = true;
$hasIdentifier = false;
$hasExpression = false;
$hasUnknown = false;
$value = $this->MatchFuncs(array('parseImport','parseMedia'));
if( $value ){
return $value;
}
$this->save();
$name = $this->MatchReg('/\\G@[a-z-]+/');
if( !$name ) return;
$name = $name[0];
$nonVendorSpecificName = $name;
$pos = strpos($name,'-', 2);
if( $name[1] == '-' && $pos > 0 ){
$nonVendorSpecificName = "@" . substr($name, $pos + 1);
}
switch( $nonVendorSpecificName ){
/*
case "@font-face":
case "@viewport":
case "@top-left":
case "@top-left-corner":
case "@top-center":
case "@top-right":
case "@top-right-corner":
case "@bottom-left":
case "@bottom-left-corner":
case "@bottom-center":
case "@bottom-right":
case "@bottom-right-corner":
case "@left-top":
case "@left-middle":
case "@left-bottom":
case "@right-top":
case "@right-middle":
case "@right-bottom":
hasBlock = true;
break;
*/
case "@charset":
$hasIdentifier = true;
$hasBlock = false;
break;
case "@namespace":
$hasExpression = true;
$hasBlock = false;
break;
case "@keyframes":
$hasIdentifier = true;
break;
case "@host":
case "@page":
case "@document":
case "@supports":
$hasUnknown = true;
break;
}
if( $hasIdentifier ){
$value = $this->parseEntity();
if( !$value ){
$this->error("expected " . $name . " identifier");
}
} else if( $hasExpression ){
$value = $this->parseExpression();
if( !$value ){
$this->error("expected " . $name. " expression");
}
} else if ($hasUnknown) {
$value = $this->MatchReg('/\\G[^{;]+/');
if( $value ){
$value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0]));
}
}
if( $hasBlock ){
$rules = $this->parseBlockRuleset();
}
if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) {
$this->forget();
return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo));
}
$this->restore();
}
//
// A Value is a comma-delimited list of Expressions
//
// font-family: Baskerville, Georgia, serif;
//
// In a Rule, a Value represents everything after the `:`,
// and before the `;`.
//
private function parseValue(){
$expressions = array();
do{
$e = $this->parseExpression();
if( $e ){
$expressions[] = $e;
if (! $this->MatchChar(',')) {
break;
}
}
}while($e);
if( $expressions ){
return $this->NewObj1('Less_Tree_Value',$expressions);
}
}
private function parseImportant (){
if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){
return ' !important';
}
}
private function parseSub (){
if( $this->MatchChar('(') ){
$a = $this->parseAddition();
if( $a ){
$this->expectChar(')');
return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached
}
}
}
/**
* Parses multiplication operation
*
* @return Less_Tree_Operation|null
*/
function parseMultiplication(){
$return = $m = $this->parseOperand();
if( $return ){
while( true ){
$isSpaced = $this->isWhitespace( -1 );
if( $this->PeekReg('/\\G\/[*\/]/') ){
break;
}
$op = $this->MatchChar('/');
if( !$op ){
$op = $this->MatchChar('*');
if( !$op ){
break;
}
}
$a = $this->parseOperand();
if(!$a) { break; }
$m->parensInOp = true;
$a->parensInOp = true;
$return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) );
}
}
return $return;
}
/**
* Parses an addition operation
*
* @return Less_Tree_Operation|null
*/
private function parseAddition (){
$return = $m = $this->parseMultiplication();
if( $return ){
while( true ){
$isSpaced = $this->isWhitespace( -1 );
$op = $this->MatchReg('/\\G[-+]\s+/');
if( $op ){
$op = $op[0];
}else{
if( !$isSpaced ){
$op = $this->match(array('#+','#-'));
}
if( !$op ){
break;
}
}
$a = $this->parseMultiplication();
if( !$a ){
break;
}
$m->parensInOp = true;
$a->parensInOp = true;
$return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced));
}
}
return $return;
}
/**
* Parses the conditions
*
* @return Less_Tree_Condition|null
*/
private function parseConditions() {
$index = $this->pos;
$return = $a = $this->parseCondition();
if( $a ){
while( true ){
if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') || !$this->MatchChar(',') ){
break;
}
$b = $this->parseCondition();
if( !$b ){
break;
}
$return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index));
}
return $return;
}
}
private function parseCondition() {
$index = $this->pos;
$negate = false;
$c = null;
if ($this->MatchReg('/\\Gnot/')) $negate = true;
$this->expectChar('(');
$a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
if( $a ){
$op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/');
if( $op ){
$b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
if( $b ){
$c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate));
} else {
$this->Error('Unexpected expression');
}
} else {
$k = $this->NewObj1('Less_Tree_Keyword','true');
$c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate));
}
$this->expectChar(')');
return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c;
}
}
/**
* An operand is anything that can be part of an operation,
* such as a Color, or a Variable
*
*/
private function parseOperand (){
$negate = false;
$offset = $this->pos+1;
if( $offset >= $this->input_len ){
return;
}
$char = $this->input[$offset];
if( $char === '@' || $char === '(' ){
$negate = $this->MatchChar('-');
}
$o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall'));
if( $negate ){
$o->parensInOp = true;
$o = $this->NewObj1('Less_Tree_Negative',$o);
}
return $o;
}
/**
* Expressions either represent mathematical operations,
* or white-space delimited Entities.
*
* 1px solid black
* @var * 2
*
* @return Less_Tree_Expression|null
*/
private function parseExpression (){
$entities = array();
do{
$e = $this->MatchFuncs(array('parseAddition','parseEntity'));
if( $e ){
$entities[] = $e;
// operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
if( !$this->PeekReg('/\\G\/[\/*]/') ){
$delim = $this->MatchChar('/');
if( $delim ){
$entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim);
}
}
}
}while($e);
if( $entities ){
return $this->NewObj1('Less_Tree_Expression',$entities);
}
}
/**
* Parse a property
* eg: 'min-width', 'orientation', etc
*
* @return string
*/
private function parseProperty (){
$name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/');
if( $name ){
return $name[1];
}
}
/**
* Parse a rule property
* eg: 'color', 'width', 'height', etc
*
* @return string
*/
private function parseRuleProperty(){
$offset = $this->pos;
$name = array();
$index = array();
$length = 0;
$this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name );
while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // !
if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){
// at last, we have the complete match now. move forward,
// convert name particles to tree objects and return:
$this->skipWhitespace($length);
if( $name[0] === '' ){
array_shift($name);
array_shift($index);
}
foreach($name as $k => $s ){
if( !$s || $s[0] !== '@' ){
$name[$k] = $this->NewObj1('Less_Tree_Keyword',$s);
}else{
$name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo));
}
}
return $name;
}
}
private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ){
preg_match($re, $this->input, $a, 0, $offset);
if( $a ){
$index[] = $this->pos + $length;
$length += strlen($a[0]);
$offset += strlen($a[0]);
$name[] = $a[1];
return true;
}
}
public function serializeVars( $vars ){
$s = '';
foreach($vars as $name => $value){
$s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';');
}
return $s;
}
/**
* Some versions of php have trouble with method_exists($a,$b) if $a is not an object
*
* @param string $b
*/
public static function is_method($a,$b){
return is_object($a) && method_exists($a,$b);
}
/**
* Round numbers similarly to javascript
* eg: 1.499999 to 1 instead of 2
*
*/
public static function round($i, $precision = 0){
$precision = pow(10,$precision);
$i = $i*$precision;
$ceil = ceil($i);
$floor = floor($i);
if( ($ceil - $i) <= ($i - $floor) ){
return $ceil/$precision;
}else{
return $floor/$precision;
}
}
/**
* Create Less_Tree_* objects and optionally generate a cache string
*
* @return mixed
*/
public function NewObj0($class){
$obj = new $class();
if( Less_Cache::$cache_dir ){
$obj->cache_string = ' new '.$class.'()';
}
return $obj;
}
public function NewObj1($class, $arg){
$obj = new $class( $arg );
if( Less_Cache::$cache_dir ){
$obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')';
}
return $obj;
}
public function NewObj2($class, $args){
$obj = new $class( $args[0], $args[1] );
if( Less_Cache::$cache_dir ){
$this->ObjCache( $obj, $class, $args);
}
return $obj;
}
public function NewObj3($class, $args){
$obj = new $class( $args[0], $args[1], $args[2] );
if( Less_Cache::$cache_dir ){
$this->ObjCache( $obj, $class, $args);
}
return $obj;
}
public function NewObj4($class, $args){
$obj = new $class( $args[0], $args[1], $args[2], $args[3] );
if( Less_Cache::$cache_dir ){
$this->ObjCache( $obj, $class, $args);
}
return $obj;
}
public function NewObj5($class, $args){
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
if( Less_Cache::$cache_dir ){
$this->ObjCache( $obj, $class, $args);
}
return $obj;
}
public function NewObj6($class, $args){
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
if( Less_Cache::$cache_dir ){
$this->ObjCache( $obj, $class, $args);
}
return $obj;
}
public function NewObj7($class, $args){
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
if( Less_Cache::$cache_dir ){
$this->ObjCache( $obj, $class, $args);
}
return $obj;
}
//caching
public function ObjCache($obj, $class, $args=array()){
$obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')';
}
public function ArgCache($args){
return implode(',',array_map( array('Less_Parser','ArgString'),$args));
}
/**
* Convert an argument to a string for use in the parser cache
*
* @return string
*/
public static function ArgString($arg){
$type = gettype($arg);
if( $type === 'object'){
$string = $arg->cache_string;
unset($arg->cache_string);
return $string;
}elseif( $type === 'array' ){
$string = ' Array(';
foreach($arg as $k => $a){
$string .= var_export($k,true).' => '.self::ArgString($a).',';
}
return $string . ')';
}
return var_export($arg,true);
}
public function Error($msg){
throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo);
}
public static function WinPath($path){
return str_replace('\\', '/', $path);
}
}
/**
* Utility for css colors
*
* @package Less
* @subpackage color
*/
class Less_Colors {
public static $colors = array(
'aliceblue'=>'#f0f8ff',
'antiquewhite'=>'#faebd7',
'aqua'=>'#00ffff',
'aquamarine'=>'#7fffd4',
'azure'=>'#f0ffff',
'beige'=>'#f5f5dc',
'bisque'=>'#ffe4c4',
'black'=>'#000000',
'blanchedalmond'=>'#ffebcd',
'blue'=>'#0000ff',
'blueviolet'=>'#8a2be2',
'brown'=>'#a52a2a',
'burlywood'=>'#deb887',
'cadetblue'=>'#5f9ea0',
'chartreuse'=>'#7fff00',
'chocolate'=>'#d2691e',
'coral'=>'#ff7f50',
'cornflowerblue'=>'#6495ed',
'cornsilk'=>'#fff8dc',
'crimson'=>'#dc143c',
'cyan'=>'#00ffff',
'darkblue'=>'#00008b',
'darkcyan'=>'#008b8b',
'darkgoldenrod'=>'#b8860b',
'darkgray'=>'#a9a9a9',
'darkgrey'=>'#a9a9a9',
'darkgreen'=>'#006400',
'darkkhaki'=>'#bdb76b',
'darkmagenta'=>'#8b008b',
'darkolivegreen'=>'#556b2f',
'darkorange'=>'#ff8c00',
'darkorchid'=>'#9932cc',
'darkred'=>'#8b0000',
'darksalmon'=>'#e9967a',
'darkseagreen'=>'#8fbc8f',
'darkslateblue'=>'#483d8b',
'darkslategray'=>'#2f4f4f',
'darkslategrey'=>'#2f4f4f',
'darkturquoise'=>'#00ced1',
'darkviolet'=>'#9400d3',
'deeppink'=>'#ff1493',
'deepskyblue'=>'#00bfff',
'dimgray'=>'#696969',
'dimgrey'=>'#696969',
'dodgerblue'=>'#1e90ff',
'firebrick'=>'#b22222',
'floralwhite'=>'#fffaf0',
'forestgreen'=>'#228b22',
'fuchsia'=>'#ff00ff',
'gainsboro'=>'#dcdcdc',
'ghostwhite'=>'#f8f8ff',
'gold'=>'#ffd700',
'goldenrod'=>'#daa520',
'gray'=>'#808080',
'grey'=>'#808080',
'green'=>'#008000',
'greenyellow'=>'#adff2f',
'honeydew'=>'#f0fff0',
'hotpink'=>'#ff69b4',
'indianred'=>'#cd5c5c',
'indigo'=>'#4b0082',
'ivory'=>'#fffff0',
'khaki'=>'#f0e68c',
'lavender'=>'#e6e6fa',
'lavenderblush'=>'#fff0f5',
'lawngreen'=>'#7cfc00',
'lemonchiffon'=>'#fffacd',
'lightblue'=>'#add8e6',
'lightcoral'=>'#f08080',
'lightcyan'=>'#e0ffff',
'lightgoldenrodyellow'=>'#fafad2',
'lightgray'=>'#d3d3d3',
'lightgrey'=>'#d3d3d3',
'lightgreen'=>'#90ee90',
'lightpink'=>'#ffb6c1',
'lightsalmon'=>'#ffa07a',
'lightseagreen'=>'#20b2aa',
'lightskyblue'=>'#87cefa',
'lightslategray'=>'#778899',
'lightslategrey'=>'#778899',
'lightsteelblue'=>'#b0c4de',
'lightyellow'=>'#ffffe0',
'lime'=>'#00ff00',
'limegreen'=>'#32cd32',
'linen'=>'#faf0e6',
'magenta'=>'#ff00ff',
'maroon'=>'#800000',
'mediumaquamarine'=>'#66cdaa',
'mediumblue'=>'#0000cd',
'mediumorchid'=>'#ba55d3',
'mediumpurple'=>'#9370d8',
'mediumseagreen'=>'#3cb371',
'mediumslateblue'=>'#7b68ee',
'mediumspringgreen'=>'#00fa9a',
'mediumturquoise'=>'#48d1cc',
'mediumvioletred'=>'#c71585',
'midnightblue'=>'#191970',
'mintcream'=>'#f5fffa',
'mistyrose'=>'#ffe4e1',
'moccasin'=>'#ffe4b5',
'navajowhite'=>'#ffdead',
'navy'=>'#000080',
'oldlace'=>'#fdf5e6',
'olive'=>'#808000',
'olivedrab'=>'#6b8e23',
'orange'=>'#ffa500',
'orangered'=>'#ff4500',
'orchid'=>'#da70d6',
'palegoldenrod'=>'#eee8aa',
'palegreen'=>'#98fb98',
'paleturquoise'=>'#afeeee',
'palevioletred'=>'#d87093',
'papayawhip'=>'#ffefd5',
'peachpuff'=>'#ffdab9',
'peru'=>'#cd853f',
'pink'=>'#ffc0cb',
'plum'=>'#dda0dd',
'powderblue'=>'#b0e0e6',
'purple'=>'#800080',
'red'=>'#ff0000',
'rosybrown'=>'#bc8f8f',
'royalblue'=>'#4169e1',
'saddlebrown'=>'#8b4513',
'salmon'=>'#fa8072',
'sandybrown'=>'#f4a460',
'seagreen'=>'#2e8b57',
'seashell'=>'#fff5ee',
'sienna'=>'#a0522d',
'silver'=>'#c0c0c0',
'skyblue'=>'#87ceeb',
'slateblue'=>'#6a5acd',
'slategray'=>'#708090',
'slategrey'=>'#708090',
'snow'=>'#fffafa',
'springgreen'=>'#00ff7f',
'steelblue'=>'#4682b4',
'tan'=>'#d2b48c',
'teal'=>'#008080',
'thistle'=>'#d8bfd8',
'tomato'=>'#ff6347',
'turquoise'=>'#40e0d0',
'violet'=>'#ee82ee',
'wheat'=>'#f5deb3',
'white'=>'#ffffff',
'whitesmoke'=>'#f5f5f5',
'yellow'=>'#ffff00',
'yellowgreen'=>'#9acd32'
);
public static function hasOwnProperty($color) {
return isset(self::$colors[$color]);
}
public static function color($color) {
return self::$colors[$color];
}
}
/**
* Environment
*
* @package Less
* @subpackage environment
*/
class Less_Environment{
//public $paths = array(); // option - unmodified - paths to search for imports on
//public static $files = array(); // list of files that have been imported, used for import-once
//public $rootpath; // option - rootpath to append to URL's
//public static $strictImports = null; // option -
//public $insecure; // option - whether to allow imports from insecure ssl hosts
//public $processImports; // option - whether to process imports. if false then imports will not be imported
//public $javascriptEnabled; // option - whether JavaScript is enabled. if undefined, defaults to true
//public $useFileCache; // browser only - whether to use the per file session cache
public $currentFileInfo; // information about the current file - for error reporting and importing and making urls relative etc.
public $importMultiple = false; // whether we are currently importing multiple copies
/**
* @var array
*/
public $frames = array();
/**
* @var array
*/
public $mediaBlocks = array();
/**
* @var array
*/
public $mediaPath = array();
public static $parensStack = 0;
public static $tabLevel = 0;
public static $lastRule = false;
public static $_outputMap;
public static $mixin_stack = 0;
public function Init(){
self::$parensStack = 0;
self::$tabLevel = 0;
self::$lastRule = false;
self::$mixin_stack = 0;
if( Less_Parser::$options['compress'] ){
Less_Environment::$_outputMap = array(
',' => ',',
': ' => ':',
'' => '',
' ' => ' ',
':' => ' :',
'+' => '+',
'~' => '~',
'>' => '>',
'|' => '|',
'^' => '^',
'^^' => '^^'
);
}else{
Less_Environment::$_outputMap = array(
',' => ', ',
': ' => ': ',
'' => '',
' ' => ' ',
':' => ' :',
'+' => ' + ',
'~' => ' ~ ',
'>' => ' > ',
'|' => '|',
'^' => ' ^ ',
'^^' => ' ^^ '
);
}
}
public function copyEvalEnv($frames = array() ){
$new_env = new Less_Environment();
$new_env->frames = $frames;
return $new_env;
}
public static function isMathOn(){
return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack;
}
public static function isPathRelative($path){
return !preg_match('/^(?:[a-z-]+:|\/)/',$path);
}
/**
* Canonicalize a path by resolving references to '/./', '/../'
* Does not remove leading "../"
* @param string path or url
* @return string Canonicalized path
*
*/
static function normalizePath($path){
$segments = explode('/',$path);
$segments = array_reverse($segments);
$path = array();
$path_len = 0;
while( $segments ){
$segment = array_pop($segments);
switch( $segment ) {
case '.':
break;
case '..':
if( !$path_len || ( $path[$path_len-1] === '..') ){
$path[] = $segment;
$path_len++;
}else{
array_pop($path);
$path_len--;
}
break;
default:
$path[] = $segment;
$path_len++;
break;
}
}
return implode('/',$path);
}
public function unshiftFrame($frame){
array_unshift($this->frames, $frame);
}
public function shiftFrame(){
return array_shift($this->frames);
}
}
/**
* Builtin functions
*
* @package Less
* @subpackage function
* @see http://lesscss.org/functions/
*/
class Less_Functions{
public $env;
public $currentFileInfo;
function __construct($env, $currentFileInfo = null ){
$this->env = $env;
$this->currentFileInfo = $currentFileInfo;
}
/**
* @param string $op
*/
static public function operate( $op, $a, $b ){
switch ($op) {
case '+': return $a + $b;
case '-': return $a - $b;
case '*': return $a * $b;
case '/': return $a / $b;
}
}
static public function clamp($val, $max = 1){
return min( max($val, 0), $max);
}
static function fround( $value ){
if( $value === 0 ){
return $value;
}
if( Less_Parser::$options['numPrecision'] ){
$p = pow(10, Less_Parser::$options['numPrecision']);
return round( $value * $p) / $p;
}
return $value;
}
static public function number($n){
if ($n instanceof Less_Tree_Dimension) {
return floatval( $n->unit->is('%') ? $n->value / 100 : $n->value);
} else if (is_numeric($n)) {
return $n;
} else {
throw new Less_Exception_Compiler("color functions take numbers as parameters");
}
}
static public function scaled($n, $size = 255 ){
if( $n instanceof Less_Tree_Dimension && $n->unit->is('%') ){
return (float)$n->value * $size / 100;
} else {
return Less_Functions::number($n);
}
}
public function rgb ($r, $g, $b){
return $this->rgba($r, $g, $b, 1.0);
}
public function rgba($r, $g, $b, $a){
$rgb = array($r, $g, $b);
$rgb = array_map(array('Less_Functions','scaled'),$rgb);
$a = self::number($a);
return new Less_Tree_Color($rgb, $a);
}
public function hsl($h, $s, $l){
return $this->hsla($h, $s, $l, 1.0);
}
public function hsla($h, $s, $l, $a){
$h = fmod(self::number($h), 360) / 360; // Classic % operator will change float to int
$s = self::clamp(self::number($s));
$l = self::clamp(self::number($l));
$a = self::clamp(self::number($a));
$m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
$m1 = $l * 2 - $m2;
return $this->rgba( self::hsla_hue($h + 1/3, $m1, $m2) * 255,
self::hsla_hue($h, $m1, $m2) * 255,
self::hsla_hue($h - 1/3, $m1, $m2) * 255,
$a);
}
/**
* @param double $h
*/
function hsla_hue($h, $m1, $m2){
$h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h);
if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
else if ($h * 2 < 1) return $m2;
else if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
else return $m1;
}
public function hsv($h, $s, $v) {
return $this->hsva($h, $s, $v, 1.0);
}
/**
* @param double $a
*/
public function hsva($h, $s, $v, $a) {
$h = ((Less_Functions::number($h) % 360) / 360 ) * 360;
$s = Less_Functions::number($s);
$v = Less_Functions::number($v);
$a = Less_Functions::number($a);
$i = floor(($h / 60) % 6);
$f = ($h / 60) - $i;
$vs = array( $v,
$v * (1 - $s),
$v * (1 - $f * $s),
$v * (1 - (1 - $f) * $s));
$perm = array(array(0, 3, 1),
array(2, 0, 1),
array(1, 0, 3),
array(1, 2, 0),
array(3, 1, 0),
array(0, 1, 2));
return $this->rgba($vs[$perm[$i][0]] * 255,
$vs[$perm[$i][1]] * 255,
$vs[$perm[$i][2]] * 255,
$a);
}
public function hue($color){
$c = $color->toHSL();
return new Less_Tree_Dimension(Less_Parser::round($c['h']));
}
public function saturation($color){
$c = $color->toHSL();
return new Less_Tree_Dimension(Less_Parser::round($c['s'] * 100), '%');
}
public function lightness($color){
$c = $color->toHSL();
return new Less_Tree_Dimension(Less_Parser::round($c['l'] * 100), '%');
}
public function hsvhue( $color ){
$hsv = $color->toHSV();
return new Less_Tree_Dimension( Less_Parser::round($hsv['h']) );
}
public function hsvsaturation( $color ){
$hsv = $color->toHSV();
return new Less_Tree_Dimension( Less_Parser::round($hsv['s'] * 100), '%' );
}
public function hsvvalue( $color ){
$hsv = $color->toHSV();
return new Less_Tree_Dimension( Less_Parser::round($hsv['v'] * 100), '%' );
}
public function red($color) {
return new Less_Tree_Dimension( $color->rgb[0] );
}
public function green($color) {
return new Less_Tree_Dimension( $color->rgb[1] );
}
public function blue($color) {
return new Less_Tree_Dimension( $color->rgb[2] );
}
public function alpha($color){
$c = $color->toHSL();
return new Less_Tree_Dimension($c['a']);
}
public function luma ($color) {
return new Less_Tree_Dimension(Less_Parser::round( $color->luma() * $color->alpha * 100), '%');
}
public function luminance( $color ){
$luminance =
(0.2126 * $color->rgb[0] / 255)
+ (0.7152 * $color->rgb[1] / 255)
+ (0.0722 * $color->rgb[2] / 255);
return new Less_Tree_Dimension(Less_Parser::round( $luminance * $color->alpha * 100), '%');
}
public function saturate($color, $amount = null){
// filter: saturate(3.2);
// should be kept as is, so check for color
if( !property_exists($color,'rgb') ){
return null;
}
$hsl = $color->toHSL();
$hsl['s'] += $amount->value / 100;
$hsl['s'] = self::clamp($hsl['s']);
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
/**
* @param Less_Tree_Dimension $amount
*/
public function desaturate($color, $amount){
$hsl = $color->toHSL();
$hsl['s'] -= $amount->value / 100;
$hsl['s'] = self::clamp($hsl['s']);
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
public function lighten($color, $amount){
$hsl = $color->toHSL();
$hsl['l'] += $amount->value / 100;
$hsl['l'] = self::clamp($hsl['l']);
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
public function darken($color, $amount){
if( $color instanceof Less_Tree_Color ){
$hsl = $color->toHSL();
$hsl['l'] -= $amount->value / 100;
$hsl['l'] = self::clamp($hsl['l']);
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
Less_Functions::Expected('color',$color);
}
public function fadein($color, $amount){
$hsl = $color->toHSL();
$hsl['a'] += $amount->value / 100;
$hsl['a'] = self::clamp($hsl['a']);
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
public function fadeout($color, $amount){
$hsl = $color->toHSL();
$hsl['a'] -= $amount->value / 100;
$hsl['a'] = self::clamp($hsl['a']);
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
public function fade($color, $amount){
$hsl = $color->toHSL();
$hsl['a'] = $amount->value / 100;
$hsl['a'] = self::clamp($hsl['a']);
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
public function spin($color, $amount){
$hsl = $color->toHSL();
$hue = fmod($hsl['h'] + $amount->value, 360);
$hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
}
//
// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
// http://sass-lang.com
//
/**
* @param Less_Tree_Color $color1
*/
public function mix($color1, $color2, $weight = null){
if (!$weight) {
$weight = new Less_Tree_Dimension('50', '%');
}
$p = $weight->value / 100.0;
$w = $p * 2 - 1;
$hsl1 = $color1->toHSL();
$hsl2 = $color2->toHSL();
$a = $hsl1['a'] - $hsl2['a'];
$w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2;
$w2 = 1 - $w1;
$rgb = array($color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
$color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
$color1->rgb[2] * $w1 + $color2->rgb[2] * $w2);
$alpha = $color1->alpha * $p + $color2->alpha * (1 - $p);
return new Less_Tree_Color($rgb, $alpha);
}
public function greyscale($color){
return $this->desaturate($color, new Less_Tree_Dimension(100));
}
public function contrast( $color, $dark = null, $light = null, $threshold = null){
// filter: contrast(3.2);
// should be kept as is, so check for color
if( !property_exists($color,'rgb') ){
return null;
}
if( !$light ){
$light = $this->rgba(255, 255, 255, 1.0);
}
if( !$dark ){
$dark = $this->rgba(0, 0, 0, 1.0);
}
//Figure out which is actually light and dark!
if( $dark->luma() > $light->luma() ){
$t = $light;
$light = $dark;
$dark = $t;
}
if( !$threshold ){
$threshold = 0.43;
} else {
$threshold = Less_Functions::number($threshold);
}
if( $color->luma() < $threshold ){
return $light;
} else {
return $dark;
}
}
public function e ($str){
if( is_string($str) ){
return new Less_Tree_Anonymous($str);
}
return new Less_Tree_Anonymous($str instanceof Less_Tree_JavaScript ? $str->expression : $str->value);
}
public function escape ($str){
$revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'",'%3F'=>'?','%26'=>'&','%2C'=>',','%2F'=>'/','%40'=>'@','%2B'=>'+','%24'=>'$');
return new Less_Tree_Anonymous(strtr(rawurlencode($str->value), $revert));
}
/**
* todo: This function will need some additional work to make it work the same as less.js
*
*/
public function replace( $string, $pattern, $replacement, $flags = null ){
$result = $string->value;
$expr = '/'.str_replace('/','\\/',$pattern->value).'/';
if( $flags && $flags->value){
$expr .= self::replace_flags($flags->value);
}
$result = preg_replace($expr,$replacement->value,$result);
if( property_exists($string,'quote') ){
return new Less_Tree_Quoted( $string->quote, $result, $string->escaped);
}
return new Less_Tree_Quoted( '', $result );
}
public static function replace_flags($flags){
$flags = str_split($flags,1);
$new_flags = '';
foreach($flags as $flag){
switch($flag){
case 'e':
case 'g':
break;
default:
$new_flags .= $flag;
break;
}
}
return $new_flags;
}
public function _percent(){
$string = func_get_arg(0);
$args = func_get_args();
array_shift($args);
$result = $string->value;
foreach($args as $arg){
if( preg_match('/%[sda]/i',$result, $token) ){
$token = $token[0];
$value = stristr($token, 's') ? $arg->value : $arg->toCSS();
$value = preg_match('/[A-Z]$/', $token) ? urlencode($value) : $value;
$result = preg_replace('/%[sda]/i',$value, $result, 1);
}
}
$result = str_replace('%%', '%', $result);
return new Less_Tree_Quoted( $string->quote , $result, $string->escaped);
}
public function unit( $val, $unit = null) {
if( !($val instanceof Less_Tree_Dimension) ){
throw new Less_Exception_Compiler('The first argument to unit must be a number' . ($val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.') );
}
if( $unit ){
if( $unit instanceof Less_Tree_Keyword ){
$unit = $unit->value;
} else {
$unit = $unit->toCSS();
}
} else {
$unit = "";
}
return new Less_Tree_Dimension($val->value, $unit );
}
public function convert($val, $unit){
return $val->convertTo($unit->value);
}
public function round($n, $f = false) {
$fraction = 0;
if( $f !== false ){
$fraction = $f->value;
}
return $this->_math('Less_Parser::round',null, $n, $fraction);
}
public function pi(){
return new Less_Tree_Dimension(M_PI);
}
public function mod($a, $b) {
return new Less_Tree_Dimension( $a->value % $b->value, $a->unit);
}
public function pow($x, $y) {
if( is_numeric($x) && is_numeric($y) ){
$x = new Less_Tree_Dimension($x);
$y = new Less_Tree_Dimension($y);
}elseif( !($x instanceof Less_Tree_Dimension) || !($y instanceof Less_Tree_Dimension) ){
throw new Less_Exception_Compiler('Arguments must be numbers');
}
return new Less_Tree_Dimension( pow($x->value, $y->value), $x->unit );
}
// var mathFunctions = [{name:"ce ...
public function ceil( $n ){ return $this->_math('ceil', null, $n); }
public function floor( $n ){ return $this->_math('floor', null, $n); }
public function sqrt( $n ){ return $this->_math('sqrt', null, $n); }
public function abs( $n ){ return $this->_math('abs', null, $n); }
public function tan( $n ){ return $this->_math('tan', '', $n); }
public function sin( $n ){ return $this->_math('sin', '', $n); }
public function cos( $n ){ return $this->_math('cos', '', $n); }
public function atan( $n ){ return $this->_math('atan', 'rad', $n); }
public function asin( $n ){ return $this->_math('asin', 'rad', $n); }
public function acos( $n ){ return $this->_math('acos', 'rad', $n); }
private function _math() {
$args = func_get_args();
$fn = array_shift($args);
$unit = array_shift($args);
if ($args[0] instanceof Less_Tree_Dimension) {
if( $unit === null ){
$unit = $args[0]->unit;
}else{
$args[0] = $args[0]->unify();
}
$args[0] = (float)$args[0]->value;
return new Less_Tree_Dimension( call_user_func_array($fn, $args), $unit);
} else if (is_numeric($args[0])) {
return call_user_func_array($fn,$args);
} else {
throw new Less_Exception_Compiler("math functions take numbers as parameters");
}
}
/**
* @param boolean $isMin
*/
function _minmax( $isMin, $args ){
$arg_count = count($args);
if( $arg_count < 1 ){
throw new Less_Exception_Compiler( 'one or more arguments required');
}
$j = null;
$unitClone = null;
$unitStatic = null;
$order = array(); // elems only contains original argument values.
$values = array(); // key is the unit.toString() for unified tree.Dimension values,
// value is the index into the order array.
for( $i = 0; $i < $arg_count; $i++ ){
$current = $args[$i];
if( !($current instanceof Less_Tree_Dimension) ){
if( is_array($args[$i]->value) ){
$args[] = $args[$i]->value;
}
continue;
}
if( $current->unit->toString() === '' && !$unitClone ){
$temp = new Less_Tree_Dimension($current->value, $unitClone);
$currentUnified = $temp->unify();
}else{
$currentUnified = $current->unify();
}
if( $currentUnified->unit->toString() === "" && !$unitStatic ){
$unit = $unitStatic;
}else{
$unit = $currentUnified->unit->toString();
}
if( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ){
$unitStatic = $unit;
}
if( $unit != '' && !$unitClone ){
$unitClone = $current->unit->toString();
}
if( isset($values['']) && $unit !== '' && $unit === $unitStatic ){
$j = $values[''];
}elseif( isset($values[$unit]) ){
$j = $values[$unit];
}else{
if( $unitStatic && $unit !== $unitStatic ){
throw new Less_Exception_Compiler( 'incompatible types');
}
$values[$unit] = count($order);
$order[] = $current;
continue;
}
if( $order[$j]->unit->toString() === "" && $unitClone ){
$temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone);
$referenceUnified = $temp->unifiy();
}else{
$referenceUnified = $order[$j]->unify();
}
if( ($isMin && $currentUnified->value < $referenceUnified->value) || (!$isMin && $currentUnified->value > $referenceUnified->value) ){
$order[$j] = $current;
}
}
if( count($order) == 1 ){
return $order[0];
}
$args = array();
foreach($order as $a){
$args[] = $a->toCSS($this->env);
}
return new Less_Tree_Anonymous( ($isMin?'min(':'max(') . implode(Less_Environment::$_outputMap[','],$args).')');
}
public function min(){
$args = func_get_args();
return $this->_minmax( true, $args );
}
public function max(){
$args = func_get_args();
return $this->_minmax( false, $args );
}
public function getunit($n){
return new Less_Tree_Anonymous($n->unit);
}
public function argb($color) {
return new Less_Tree_Anonymous($color->toARGB());
}
public function percentage($n) {
return new Less_Tree_Dimension($n->value * 100, '%');
}
public function color($n) {
if( $n instanceof Less_Tree_Quoted ){
$colorCandidate = $n->value;
$returnColor = Less_Tree_Color::fromKeyword($colorCandidate);
if( $returnColor ){
return $returnColor;
}
if( preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/',$colorCandidate) ){
return new Less_Tree_Color(substr($colorCandidate, 1));
}
throw new Less_Exception_Compiler("argument must be a color keyword or 3/6 digit hex e.g. #FFF");
} else {
throw new Less_Exception_Compiler("argument must be a string");
}
}
public function iscolor($n) {
return $this->_isa($n, 'Less_Tree_Color');
}
public function isnumber($n) {
return $this->_isa($n, 'Less_Tree_Dimension');
}
public function isstring($n) {
return $this->_isa($n, 'Less_Tree_Quoted');
}
public function iskeyword($n) {
return $this->_isa($n, 'Less_Tree_Keyword');
}
public function isurl($n) {
return $this->_isa($n, 'Less_Tree_Url');
}
public function ispixel($n) {
return $this->isunit($n, 'px');
}
public function ispercentage($n) {
return $this->isunit($n, '%');
}
public function isem($n) {
return $this->isunit($n, 'em');
}
/**
* @param string $unit
*/
public function isunit( $n, $unit ){
return ($n instanceof Less_Tree_Dimension) && $n->unit->is( ( property_exists($unit,'value') ? $unit->value : $unit) ) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
}
/**
* @param string $type
*/
private function _isa($n, $type) {
return is_a($n, $type) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
}
public function tint($color, $amount) {
return $this->mix( $this->rgb(255,255,255), $color, $amount);
}
public function shade($color, $amount) {
return $this->mix($this->rgb(0, 0, 0), $color, $amount);
}
public function extract($values, $index ){
$index = (int)$index->value - 1; // (1-based index)
// handle non-array values as an array of length 1
// return 'undefined' if index is invalid
if( property_exists($values,'value') && is_array($values->value) ){
if( isset($values->value[$index]) ){
return $values->value[$index];
}
return null;
}elseif( (int)$index === 0 ){
return $values;
}
return null;
}
function length($values){
$n = (property_exists($values,'value') && is_array($values->value)) ? count($values->value) : 1;
return new Less_Tree_Dimension($n);
}
function datauri($mimetypeNode, $filePathNode = null ) {
$filePath = ( $filePathNode ? $filePathNode->value : null );
$mimetype = $mimetypeNode->value;
$args = 2;
if( !$filePath ){
$filePath = $mimetype;
$args = 1;
}
$filePath = str_replace('\\','/',$filePath);
if( Less_Environment::isPathRelative($filePath) ){
if( Less_Parser::$options['relativeUrls'] ){
$temp = $this->currentFileInfo['currentDirectory'];
} else {
$temp = $this->currentFileInfo['entryPath'];
}
if( !empty($temp) ){
$filePath = Less_Environment::normalizePath(rtrim($temp,'/').'/'.$filePath);
}
}
// detect the mimetype if not given
if( $args < 2 ){
/* incomplete
$mime = require('mime');
mimetype = mime.lookup(path);
// use base 64 unless it's an ASCII or UTF-8 format
var charset = mime.charsets.lookup(mimetype);
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
if (useBase64) mimetype += ';base64';
*/
$mimetype = Less_Mime::lookup($filePath);
$charset = Less_Mime::charsets_lookup($mimetype);
$useBase64 = !in_array($charset,array('US-ASCII', 'UTF-8'));
if( $useBase64 ){ $mimetype .= ';base64'; }
}else{
$useBase64 = preg_match('/;base64$/',$mimetype);
}
if( file_exists($filePath) ){
$buf = @file_get_contents($filePath);
}else{
$buf = false;
}
// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
// and the --ieCompat flag is enabled, return a normal url() instead.
$DATA_URI_MAX_KB = 32;
$fileSizeInKB = round( strlen($buf) / 1024 );
if( $fileSizeInKB >= $DATA_URI_MAX_KB ){
$url = new Less_Tree_Url( ($filePathNode ? $filePathNode : $mimetypeNode), $this->currentFileInfo);
return $url->compile($this);
}
if( $buf ){
$buf = $useBase64 ? base64_encode($buf) : rawurlencode($buf);
$filePath = '"data:' . $mimetype . ',' . $buf . '"';
}
return new Less_Tree_Url( new Less_Tree_Anonymous($filePath) );
}
//svg-gradient
function svggradient( $direction ){
$throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
$arguments = func_get_args();
if( count($arguments) < 3 ){
throw new Less_Exception_Compiler( $throw_message );
}
$stops = array_slice($arguments,1);
$gradientType = 'linear';
$rectangleDimension = 'x="0" y="0" width="1" height="1"';
$useBase64 = true;
$directionValue = $direction->toCSS();
switch( $directionValue ){
case "to bottom":
$gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
break;
case "to right":
$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
break;
case "to bottom right":
$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
break;
case "to top right":
$gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
break;
case "ellipse":
case "ellipse at center":
$gradientType = "radial";
$gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
$rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
break;
default:
throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
}
$returner = '<?xml version="1.0" ?>' .
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
'<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
for( $i = 0; $i < count($stops); $i++ ){
if( is_object($stops[$i]) && property_exists($stops[$i],'value') ){
$color = $stops[$i]->value[0];
$position = $stops[$i]->value[1];
}else{
$color = $stops[$i];
$position = null;
}
if( !($color instanceof Less_Tree_Color) || (!(($i === 0 || $i+1 === count($stops)) && $position === null) && !($position instanceof Less_Tree_Dimension)) ){
throw new Less_Exception_Compiler( $throw_message );
}
if( $position ){
$positionValue = $position->toCSS();
}elseif( $i === 0 ){
$positionValue = '0%';
}else{
$positionValue = '100%';
}
$alpha = $color->alpha;
$returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ($alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '') . '/>';
}
$returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
if( $useBase64 ){
$returner = "'data:image/svg+xml;base64,".base64_encode($returner)."'";
}else{
$returner = "'data:image/svg+xml,".$returner."'";
}
return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
}
/**
* @param string $type
*/
private static function Expected( $type, $arg ){
$debug = debug_backtrace();
array_shift($debug);
$last = array_shift($debug);
$last = array_intersect_key($last,array('function'=>'','class'=>'','line'=>''));
$message = 'Object of type '.get_class($arg).' passed to darken function. Expecting `'.$type.'`. '.$arg->toCSS().'. '.print_r($last,true);
throw new Less_Exception_Compiler($message);
}
/**
* Php version of javascript's `encodeURIComponent` function
*
* @param string $string The string to encode
* @return string The encoded string
*/
public static function encodeURIComponent($string){
$revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')');
return strtr(rawurlencode($string), $revert);
}
// Color Blending
// ref: http://www.w3.org/TR/compositing-1
public function colorBlend( $mode, $color1, $color2 ){
$ab = $color1->alpha; // backdrop
$as = $color2->alpha; // source
$r = array(); // result
$ar = $as + $ab * (1 - $as);
for( $i = 0; $i < 3; $i++ ){
$cb = $color1->rgb[$i] / 255;
$cs = $color2->rgb[$i] / 255;
$cr = call_user_func( $mode, $cb, $cs );
if( $ar ){
$cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar;
}
$r[$i] = $cr * 255;
}
return new Less_Tree_Color($r, $ar);
}
public function multiply($color1, $color2 ){
return $this->colorBlend( array($this,'colorBlendMultiply'), $color1, $color2 );
}
private function colorBlendMultiply($cb, $cs){
return $cb * $cs;
}
public function screen($color1, $color2 ){
return $this->colorBlend( array($this,'colorBlendScreen'), $color1, $color2 );
}
private function colorBlendScreen( $cb, $cs){
return $cb + $cs - $cb * $cs;
}
public function overlay($color1, $color2){
return $this->colorBlend( array($this,'colorBlendOverlay'), $color1, $color2 );
}
private function colorBlendOverlay($cb, $cs ){
$cb *= 2;
return ($cb <= 1)
? $this->colorBlendMultiply($cb, $cs)
: $this->colorBlendScreen($cb - 1, $cs);
}
public function softlight($color1, $color2){
return $this->colorBlend( array($this,'colorBlendSoftlight'), $color1, $color2 );
}
private function colorBlendSoftlight($cb, $cs ){
$d = 1;
$e = $cb;
if( $cs > 0.5 ){
$e = 1;
$d = ($cb > 0.25) ? sqrt($cb)
: ((16 * $cb - 12) * $cb + 4) * $cb;
}
return $cb - (1 - 2 * $cs) * $e * ($d - $cb);
}
public function hardlight($color1, $color2){
return $this->colorBlend( array($this,'colorBlendHardlight'), $color1, $color2 );
}
private function colorBlendHardlight( $cb, $cs ){
return $this->colorBlendOverlay($cs, $cb);
}
public function difference($color1, $color2) {
return $this->colorBlend( array($this,'colorBlendDifference'), $color1, $color2 );
}
private function colorBlendDifference( $cb, $cs ){
return abs($cb - $cs);
}
public function exclusion( $color1, $color2 ){
return $this->colorBlend( array($this,'colorBlendExclusion'), $color1, $color2 );
}
private function colorBlendExclusion( $cb, $cs ){
return $cb + $cs - 2 * $cb * $cs;
}
public function average($color1, $color2){
return $this->colorBlend( array($this,'colorBlendAverage'), $color1, $color2 );
}
// non-w3c functions:
function colorBlendAverage($cb, $cs ){
return ($cb + $cs) / 2;
}
public function negation($color1, $color2 ){
return $this->colorBlend( array($this,'colorBlendNegation'), $color1, $color2 );
}
function colorBlendNegation($cb, $cs){
return 1 - abs($cb + $cs - 1);
}
// ~ End of Color Blending
}
/**
* Mime lookup
*
* @package Less
* @subpackage node
*/
class Less_Mime{
// this map is intentionally incomplete
// if you want more, install 'mime' dep
static $_types = array(
'.htm' => 'text/html',
'.html'=> 'text/html',
'.gif' => 'image/gif',
'.jpg' => 'image/jpeg',
'.jpeg'=> 'image/jpeg',
'.png' => 'image/png'
);
static function lookup( $filepath ){
$parts = explode('.',$filepath);
$ext = '.'.strtolower(array_pop($parts));
if( !isset(self::$_types[$ext]) ){
return null;
}
return self::$_types[$ext];
}
static function charsets_lookup( $type = null ){
// assumes all text types are UTF-8
return $type && preg_match('/^text\//',$type) ? 'UTF-8' : '';
}
}
/**
* Tree
*
* @package Less
* @subpackage tree
*/
class Less_Tree{
public $cache_string;
public function toCSS(){
$output = new Less_Output();
$this->genCSS($output);
return $output->toString();
}
/**
* Generate CSS by adding it to the output object
*
* @param Less_Output $output The output
* @return void
*/
public function genCSS($output){}
/**
* @param Less_Tree_Ruleset[] $rules
*/
public static function outputRuleset( $output, $rules ){
$ruleCnt = count($rules);
Less_Environment::$tabLevel++;
// Compressed
if( Less_Parser::$options['compress'] ){
$output->add('{');
for( $i = 0; $i < $ruleCnt; $i++ ){
$rules[$i]->genCSS( $output );
}
$output->add( '}' );
Less_Environment::$tabLevel--;
return;
}
// Non-compressed
$tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 );
$tabRuleStr = $tabSetStr.' ';
$output->add( " {" );
for($i = 0; $i < $ruleCnt; $i++ ){
$output->add( $tabRuleStr );
$rules[$i]->genCSS( $output );
}
Less_Environment::$tabLevel--;
$output->add( $tabSetStr.'}' );
}
public function accept($visitor){}
public static function ReferencedArray($rules){
foreach($rules as $rule){
if( method_exists($rule, 'markReferenced') ){
$rule->markReferenced();
}
}
}
/**
* Requires php 5.3+
*/
public static function __set_state($args){
$class = get_called_class();
$obj = new $class(null,null,null,null);
foreach($args as $key => $val){
$obj->$key = $val;
}
return $obj;
}
}
/**
* Parser output
*
* @package Less
* @subpackage output
*/
class Less_Output{
/**
* Output holder
*
* @var string
*/
protected $strs = array();
/**
* Adds a chunk to the stack
*
* @param string $chunk The chunk to output
* @param Less_FileInfo $fileInfo The file information
* @param integer $index The index
* @param mixed $mapLines
*/
public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
$this->strs[] = $chunk;
}
/**
* Is the output empty?
*
* @return boolean
*/
public function isEmpty(){
return count($this->strs) === 0;
}
/**
* Converts the output to string
*
* @return string
*/
public function toString(){
return implode('',$this->strs);
}
}
/**
* Visitor
*
* @package Less
* @subpackage visitor
*/
class Less_Visitor{
var $methods = array();
var $_visitFnCache = array();
function __construct(){
$this->_visitFnCache = get_class_methods(get_class($this));
$this->_visitFnCache = array_flip($this->_visitFnCache);
}
function visitObj( $node ){
$funcName = 'visit'.$node->type;
if( isset($this->_visitFnCache[$funcName]) ){
$visitDeeper = true;
$this->$funcName( $node, $visitDeeper );
if( $visitDeeper ){
$node->accept($this);
}
$funcName = $funcName . "Out";
if( isset($this->_visitFnCache[$funcName]) ){
$this->$funcName( $node );
}
}else{
$node->accept($this);
}
return $node;
}
function visitArray( $nodes ){
array_map( array($this,'visitObj'), $nodes);
return $nodes;
}
}
/**
* Replacing Visitor
*
* @package Less
* @subpackage visitor
*/
class Less_VisitorReplacing extends Less_Visitor{
function visitObj( $node ){
$funcName = 'visit'.$node->type;
if( isset($this->_visitFnCache[$funcName]) ){
$visitDeeper = true;
$node = $this->$funcName( $node, $visitDeeper );
if( $node ){
if( $visitDeeper && is_object($node) ){
$node->accept($this);
}
$funcName = $funcName . "Out";
if( isset($this->_visitFnCache[$funcName]) ){
$this->$funcName( $node );
}
}
}else{
$node->accept($this);
}
return $node;
}
function visitArray( $nodes ){
$newNodes = array();
foreach($nodes as $node){
$evald = $this->visitObj($node);
if( $evald ){
if( is_array($evald) ){
self::flatten($evald,$newNodes);
}else{
$newNodes[] = $evald;
}
}
}
return $newNodes;
}
function flatten( $arr, &$out ){
foreach($arr as $item){
if( !is_array($item) ){
$out[] = $item;
continue;
}
foreach($item as $nestedItem){
if( is_array($nestedItem) ){
self::flatten( $nestedItem, $out);
}else{
$out[] = $nestedItem;
}
}
}
return $out;
}
}
/**
* Configurable
*
* @package Less
* @subpackage Core
*/
abstract class Less_Configurable {
/**
* Array of options
*
* @var array
*/
protected $options = array();
/**
* Array of default options
*
* @var array
*/
protected $defaultOptions = array();
/**
* Set options
*
* If $options is an object it will be converted into an array by called
* it's toArray method.
*
* @throws Exception
* @param array|object $options
*
*/
public function setOptions($options){
$options = array_intersect_key($options,$this->defaultOptions);
$this->options = array_merge($this->defaultOptions, $this->options, $options);
}
/**
* Get an option value by name
*
* If the option is empty or not set a NULL value will be returned.
*
* @param string $name
* @param mixed $default Default value if confiuration of $name is not present
* @return mixed
*/
public function getOption($name, $default = null){
if(isset($this->options[$name])){
return $this->options[$name];
}
return $default;
}
/**
* Set an option
*
* @param string $name
* @param mixed $value
*/
public function setOption($name, $value){
$this->options[$name] = $value;
}
}
/**
* Alpha
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Alpha extends Less_Tree{
public $value;
public $type = 'Alpha';
public function __construct($val){
$this->value = $val;
}
//function accept( $visitor ){
// $this->value = $visitor->visit( $this->value );
//}
public function compile($env){
if( is_object($this->value) ){
$this->value = $this->value->compile($env);
}
return $this;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
$output->add( "alpha(opacity=" );
if( is_string($this->value) ){
$output->add( $this->value );
}else{
$this->value->genCSS( $output);
}
$output->add( ')' );
}
public function toCSS(){
return "alpha(opacity=" . (is_string($this->value) ? $this->value : $this->value->toCSS()) . ")";
}
}
/**
* Anonymous
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Anonymous extends Less_Tree{
public $value;
public $quote;
public $index;
public $mapLines;
public $currentFileInfo;
public $type = 'Anonymous';
/**
* @param integer $index
* @param boolean $mapLines
*/
public function __construct($value, $index = null, $currentFileInfo = null, $mapLines = null ){
$this->value = $value;
$this->index = $index;
$this->mapLines = $mapLines;
$this->currentFileInfo = $currentFileInfo;
}
public function compile(){
return new Less_Tree_Anonymous($this->value, $this->index, $this->currentFileInfo, $this->mapLines);
}
function compare($x){
if( !is_object($x) ){
return -1;
}
$left = $this->toCSS();
$right = $x->toCSS();
if( $left === $right ){
return 0;
}
return $left < $right ? -1 : 1;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
$output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
}
public function toCSS(){
return $this->value;
}
}
/**
* Assignment
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Assignment extends Less_Tree{
public $key;
public $value;
public $type = 'Assignment';
function __construct($key, $val) {
$this->key = $key;
$this->value = $val;
}
function accept( $visitor ){
$this->value = $visitor->visitObj( $this->value );
}
public function compile($env) {
return new Less_Tree_Assignment( $this->key, $this->value->compile($env));
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
$output->add( $this->key . '=' );
$this->value->genCSS( $output );
}
public function toCss(){
return $this->key . '=' . $this->value->toCSS();
}
}
/**
* Attribute
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Attribute extends Less_Tree{
public $key;
public $op;
public $value;
public $type = 'Attribute';
function __construct($key, $op, $value){
$this->key = $key;
$this->op = $op;
$this->value = $value;
}
function compile($env){
$key_obj = is_object($this->key);
$val_obj = is_object($this->value);
if( !$key_obj && !$val_obj ){
return $this;
}
return new Less_Tree_Attribute(
$key_obj ? $this->key->compile($env) : $this->key ,
$this->op,
$val_obj ? $this->value->compile($env) : $this->value);
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$output->add( $this->toCSS() );
}
function toCSS(){
$value = $this->key;
if( $this->op ){
$value .= $this->op;
$value .= (is_object($this->value) ? $this->value->toCSS() : $this->value);
}
return '[' . $value . ']';
}
}
/**
* Call
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Call extends Less_Tree{
public $value;
var $name;
var $args;
var $index;
var $currentFileInfo;
public $type = 'Call';
public $parensInOp;
public function __construct($name, $args, $index, $currentFileInfo = null ){
$this->name = $name;
$this->args = $args;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
function accept( $visitor ){
$this->args = $visitor->visitArray( $this->args );
}
//
// When evaluating a function call,
// we either find the function in `tree.functions` [1],
// in which case we call it, passing the evaluated arguments,
// or we simply print it out as it appeared originally [2].
//
// The *functions.js* file contains the built-in functions.
//
// The reason why we evaluate the arguments, is in the case where
// we try to pass a variable to a function, like: `saturate(@color)`.
// The function should receive the value, not the variable.
//
public function compile($env=null){
$args = array();
foreach($this->args as $a){
$args[] = $a->compile($env);
}
$nameLC = strtolower($this->name);
switch($nameLC){
case '%':
$nameLC = '_percent';
break;
case 'get-unit':
$nameLC = 'getunit';
break;
case 'data-uri':
$nameLC = 'datauri';
break;
case 'svg-gradient':
$nameLC = 'svggradient';
break;
}
$result = null;
if( $nameLC === 'default' ){
$result = Less_Tree_DefaultFunc::compile();
}else{
if( method_exists('Less_Functions',$nameLC) ){ // 1.
try {
$func = new Less_Functions($env, $this->currentFileInfo);
$result = call_user_func_array( array($func,$nameLC),$args);
} catch (Exception $e) {
throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index);
}
}
}
if( $result !== null ){
return $result;
}
return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
$output->add( $this->name . '(', $this->currentFileInfo, $this->index );
$args_len = count($this->args);
for($i = 0; $i < $args_len; $i++ ){
$this->args[$i]->genCSS( $output );
if( $i + 1 < $args_len ){
$output->add( ', ' );
}
}
$output->add( ')' );
}
//public function toCSS(){
// return $this->compile()->toCSS();
//}
}
/**
* Color
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Color extends Less_Tree{
public $rgb;
public $alpha;
public $isTransparentKeyword;
public $type = 'Color';
public function __construct($rgb, $a = 1, $isTransparentKeyword = null ){
if( $isTransparentKeyword ){
$this->rgb = $rgb;
$this->alpha = $a;
$this->isTransparentKeyword = true;
return;
}
$this->rgb = array();
if( is_array($rgb) ){
$this->rgb = $rgb;
}else if( strlen($rgb) == 6 ){
foreach(str_split($rgb, 2) as $c){
$this->rgb[] = hexdec($c);
}
}else{
foreach(str_split($rgb, 1) as $c){
$this->rgb[] = hexdec($c.$c);
}
}
$this->alpha = is_numeric($a) ? $a : 1;
}
public function compile(){
return $this;
}
public function luma(){
$r = $this->rgb[0] / 255;
$g = $this->rgb[1] / 255;
$b = $this->rgb[2] / 255;
$r = ($r <= 0.03928) ? $r / 12.92 : pow((($r + 0.055) / 1.055), 2.4);
$g = ($g <= 0.03928) ? $g / 12.92 : pow((($g + 0.055) / 1.055), 2.4);
$b = ($b <= 0.03928) ? $b / 12.92 : pow((($b + 0.055) / 1.055), 2.4);
return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
$output->add( $this->toCSS() );
}
public function toCSS( $doNotCompress = false ){
$compress = Less_Parser::$options['compress'] && !$doNotCompress;
$alpha = Less_Functions::fround( $this->alpha );
//
// If we have some transparency, the only way to represent it
// is via `rgba`. Otherwise, we use the hex representation,
// which has better compatibility with older browsers.
// Values are capped between `0` and `255`, rounded and zero-padded.
//
if( $alpha < 1 ){
if( $alpha === 0 && isset($this->isTransparentKeyword) && $this->isTransparentKeyword ){
return 'transparent';
}
$values = array();
foreach($this->rgb as $c){
$values[] = Less_Functions::clamp( round($c), 255);
}
$values[] = $alpha;
$glue = ($compress ? ',' : ', ');
return "rgba(" . implode($glue, $values) . ")";
}else{
$color = $this->toRGB();
if( $compress ){
// Convert color to short format
if( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6]) {
$color = '#'.$color[1] . $color[3] . $color[5];
}
}
return $color;
}
}
//
// Operations have to be done per-channel, if not,
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
//
/**
* @param string $op
*/
public function operate( $op, $other) {
$rgb = array();
$alpha = $this->alpha * (1 - $other->alpha) + $other->alpha;
for ($c = 0; $c < 3; $c++) {
$rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c]);
}
return new Less_Tree_Color($rgb, $alpha);
}
public function toRGB(){
return $this->toHex($this->rgb);
}
public function toHSL(){
$r = $this->rgb[0] / 255;
$g = $this->rgb[1] / 255;
$b = $this->rgb[2] / 255;
$a = $this->alpha;
$max = max($r, $g, $b);
$min = min($r, $g, $b);
$l = ($max + $min) / 2;
$d = $max - $min;
$h = $s = 0;
if( $max !== $min ){
$s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
switch ($max) {
case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
case $g: $h = ($b - $r) / $d + 2; break;
case $b: $h = ($r - $g) / $d + 4; break;
}
$h /= 6;
}
return array('h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
}
//Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
function toHSV() {
$r = $this->rgb[0] / 255;
$g = $this->rgb[1] / 255;
$b = $this->rgb[2] / 255;
$a = $this->alpha;
$max = max($r, $g, $b);
$min = min($r, $g, $b);
$v = $max;
$d = $max - $min;
if ($max === 0) {
$s = 0;
} else {
$s = $d / $max;
}
$h = 0;
if( $max !== $min ){
switch($max){
case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
case $g: $h = ($b - $r) / $d + 2; break;
case $b: $h = ($r - $g) / $d + 4; break;
}
$h /= 6;
}
return array('h'=> $h * 360, 's'=> $s, 'v'=> $v, 'a' => $a );
}
public function toARGB(){
$argb = array_merge( (array) Less_Parser::round($this->alpha * 255), $this->rgb);
return $this->toHex( $argb );
}
public function compare($x){
if( !property_exists( $x, 'rgb' ) ){
return -1;
}
return ($x->rgb[0] === $this->rgb[0] &&
$x->rgb[1] === $this->rgb[1] &&
$x->rgb[2] === $this->rgb[2] &&
$x->alpha === $this->alpha) ? 0 : -1;
}
function toHex( $v ){
$ret = '#';
foreach($v as $c){
$c = Less_Functions::clamp( Less_Parser::round($c), 255);
if( $c < 16 ){
$ret .= '0';
}
$ret .= dechex($c);
}
return $ret;
}
/**
* @param string $keyword
*/
public static function fromKeyword( $keyword ){
$keyword = strtolower($keyword);
if( Less_Colors::hasOwnProperty($keyword) ){
// detect named color
return new Less_Tree_Color(substr(Less_Colors::color($keyword), 1));
}
if( $keyword === 'transparent' ){
return new Less_Tree_Color( array(0, 0, 0), 0, true);
}
}
}
/**
* Comment
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Comment extends Less_Tree{
public $value;
public $silent;
public $isReferenced;
public $currentFileInfo;
public $type = 'Comment';
public function __construct($value, $silent, $index = null, $currentFileInfo = null ){
$this->value = $value;
$this->silent = !! $silent;
$this->currentFileInfo = $currentFileInfo;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
//if( $this->debugInfo ){
//$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
//}
$output->add( trim($this->value) );//TODO shouldn't need to trim, we shouldn't grab the \n
}
public function toCSS(){
return Less_Parser::$options['compress'] ? '' : $this->value;
}
public function isSilent(){
$isReference = ($this->currentFileInfo && isset($this->currentFileInfo['reference']) && (!isset($this->isReferenced) || !$this->isReferenced) );
$isCompressed = Less_Parser::$options['compress'] && !preg_match('/^\/\*!/', $this->value);
return $this->silent || $isReference || $isCompressed;
}
public function compile(){
return $this;
}
public function markReferenced(){
$this->isReferenced = true;
}
}
/**
* Condition
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Condition extends Less_Tree{
public $op;
public $lvalue;
public $rvalue;
public $index;
public $negate;
public $type = 'Condition';
public function __construct($op, $l, $r, $i = 0, $negate = false) {
$this->op = trim($op);
$this->lvalue = $l;
$this->rvalue = $r;
$this->index = $i;
$this->negate = $negate;
}
public function accept($visitor){
$this->lvalue = $visitor->visitObj( $this->lvalue );
$this->rvalue = $visitor->visitObj( $this->rvalue );
}
public function compile($env) {
$a = $this->lvalue->compile($env);
$b = $this->rvalue->compile($env);
switch( $this->op ){
case 'and':
$result = $a && $b;
break;
case 'or':
$result = $a || $b;
break;
default:
if( Less_Parser::is_method($a, 'compare') ){
$result = $a->compare($b);
}elseif( Less_Parser::is_method($b, 'compare') ){
$result = $b->compare($a);
}else{
throw new Less_Exception_Compiler('Unable to perform comparison', null, $this->index);
}
switch ($result) {
case -1:
$result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
break;
case 0:
$result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
break;
case 1:
$result = $this->op === '>' || $this->op === '>=';
break;
}
break;
}
return $this->negate ? !$result : $result;
}
}
/**
* DefaultFunc
*
* @package Less
* @subpackage tree
*/
class Less_Tree_DefaultFunc{
static $error_;
static $value_;
static function compile(){
if( self::$error_ ){
throw Exception(self::$error_);
}
if( self::$value_ !== null ){
return self::$value_ ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
}
}
static function value( $v ){
self::$value_ = $v;
}
static function error( $e ){
self::$error_ = $e;
}
static function reset(){
self::$value_ = self::$error_ = null;
}
}
/**
* DetachedRuleset
*
* @package Less
* @subpackage tree
*/
class Less_Tree_DetachedRuleset extends Less_Tree{
public $ruleset;
public $frames;
public $type = 'DetachedRuleset';
function __construct( $ruleset, $frames = null ){
$this->ruleset = $ruleset;
$this->frames = $frames;
}
function accept($visitor) {
$this->ruleset = $visitor->visitObj($this->ruleset);
}
function compile($env){
if( $this->frames ){
$frames = $this->frames;
}else{
$frames = $env->frames;
}
return new Less_Tree_DetachedRuleset($this->ruleset, $frames);
}
function callEval($env) {
if( $this->frames ){
return $this->ruleset->compile( $env->copyEvalEnv( array_merge($this->frames,$env->frames) ) );
}
return $this->ruleset->compile( $env );
}
}
/**
* Dimension
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Dimension extends Less_Tree{
public $value;
public $unit;
public $type = 'Dimension';
public $parensInOp;
public function __construct($value, $unit = null){
$this->value = floatval($value);
if( $unit && ($unit instanceof Less_Tree_Unit) ){
$this->unit = $unit;
}elseif( $unit ){
$this->unit = new Less_Tree_Unit( array($unit) );
}else{
$this->unit = new Less_Tree_Unit( );
}
}
function accept( $visitor ){
$this->unit = $visitor->visitObj( $this->unit );
}
public function compile(){
return $this;
}
public function toColor() {
return new Less_Tree_Color(array($this->value, $this->value, $this->value));
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
if( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ){
throw new Less_Exception_Compiler("Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString());
}
$value = Less_Functions::fround( $this->value );
$strValue = (string)$value;
if( $value !== 0 && $value < 0.000001 && $value > -0.000001 ){
// would be output 1e-6 etc.
$strValue = number_format($strValue,10);
$strValue = preg_replace('/\.?0+$/','', $strValue);
}
if( Less_Parser::$options['compress'] ){
// Zero values doesn't need a unit
if( $value === 0 && $this->unit->isLength() ){
$output->add( $strValue );
return $strValue;
}
// Float values doesn't need a leading zero
if( $value > 0 && $value < 1 && $strValue[0] === '0' ){
$strValue = substr($strValue,1);
}
}
$output->add( $strValue );
$this->unit->genCSS( $output );
}
public function __toString(){
return $this->toCSS();
}
// In an operation between two Dimensions,
// we default to the first Dimension's unit,
// so `1px + 2em` will yield `3px`.
/**
* @param string $op
*/
public function operate( $op, $other){
$value = Less_Functions::operate( $op, $this->value, $other->value);
$unit = clone $this->unit;
if( $op === '+' || $op === '-' ){
if( !$unit->numerator && !$unit->denominator ){
$unit->numerator = $other->unit->numerator;
$unit->denominator = $other->unit->denominator;
}elseif( !$other->unit->numerator && !$other->unit->denominator ){
// do nothing
}else{
$other = $other->convertTo( $this->unit->usedUnits());
if( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ){
throw new Less_Exception_Compiler("Incompatible units. Change the units or use the unit function. Bad units: '".$unit->toString() . "' and ".$other->unit->toString()+"'.");
}
$value = Less_Functions::operate( $op, $this->value, $other->value);
}
}elseif( $op === '*' ){
$unit->numerator = array_merge($unit->numerator, $other->unit->numerator);
$unit->denominator = array_merge($unit->denominator, $other->unit->denominator);
sort($unit->numerator);
sort($unit->denominator);
$unit->cancel();
}elseif( $op === '/' ){
$unit->numerator = array_merge($unit->numerator, $other->unit->denominator);
$unit->denominator = array_merge($unit->denominator, $other->unit->numerator);
sort($unit->numerator);
sort($unit->denominator);
$unit->cancel();
}
return new Less_Tree_Dimension( $value, $unit);
}
public function compare($other) {
if ($other instanceof Less_Tree_Dimension) {
if( $this->unit->isEmpty() || $other->unit->isEmpty() ){
$a = $this;
$b = $other;
} else {
$a = $this->unify();
$b = $other->unify();
if( $a->unit->compare($b->unit) !== 0 ){
return -1;
}
}
$aValue = $a->value;
$bValue = $b->value;
if ($bValue > $aValue) {
return -1;
} elseif ($bValue < $aValue) {
return 1;
} else {
return 0;
}
} else {
return -1;
}
}
function unify() {
return $this->convertTo(array('length'=> 'px', 'duration'=> 's', 'angle' => 'rad' ));
}
function convertTo($conversions) {
$value = $this->value;
$unit = clone $this->unit;
if( is_string($conversions) ){
$derivedConversions = array();
foreach( Less_Tree_UnitConversions::$groups as $i ){
if( isset(Less_Tree_UnitConversions::${$i}[$conversions]) ){
$derivedConversions = array( $i => $conversions);
}
}
$conversions = $derivedConversions;
}
foreach($conversions as $groupName => $targetUnit){
$group = Less_Tree_UnitConversions::${$groupName};
//numerator
foreach($unit->numerator as $i => $atomicUnit){
$atomicUnit = $unit->numerator[$i];
if( !isset($group[$atomicUnit]) ){
continue;
}
$value = $value * ($group[$atomicUnit] / $group[$targetUnit]);
$unit->numerator[$i] = $targetUnit;
}
//denominator
foreach($unit->denominator as $i => $atomicUnit){
$atomicUnit = $unit->denominator[$i];
if( !isset($group[$atomicUnit]) ){
continue;
}
$value = $value / ($group[$atomicUnit] / $group[$targetUnit]);
$unit->denominator[$i] = $targetUnit;
}
}
$unit->cancel();
return new Less_Tree_Dimension( $value, $unit);
}
}
/**
* Directive
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Directive extends Less_Tree{
public $name;
public $value;
public $rules;
public $index;
public $isReferenced;
public $currentFileInfo;
public $debugInfo;
public $type = 'Directive';
public $allExtends;
public function __construct($name, $value, $rules, $index = null, $currentFileInfo = null, $debugInfo = null ){
$this->name = $name;
$this->value = $value;
if( $rules ){
$this->rules = $rules;
$this->rules->allowImports = true;
}
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
$this->debugInfo = $debugInfo;
}
function accept( $visitor ){
if( $this->rules ){
$this->rules = $visitor->visitObj( $this->rules );
}
if( $this->value ){
$this->value = $visitor->visitObj( $this->value );
}
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$value = $this->value;
$rules = $this->rules;
$output->add( $this->name, $this->currentFileInfo, $this->index );
if( $this->value ){
$output->add(' ');
$this->value->genCSS($output);
}
if( $this->rules ){
Less_Tree::outputRuleset( $output, array($this->rules));
} else {
$output->add(';');
}
}
public function compile($env){
$value = $this->value;
$rules = $this->rules;
if( $value ){
$value = $value->compile($env);
}
if( $rules ){
$rules = $rules->compile($env);
$rules->root = true;
}
return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
}
public function variable($name){
if( $this->rules ){
return $this->rules->variable($name);
}
}
public function find($selector){
if( $this->rules ){
return $this->rules->find($selector, $this);
}
}
//rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
public function markReferenced(){
$this->isReferenced = true;
if( $this->rules ){
Less_Tree::ReferencedArray($this->rules->rules);
}
}
}
/**
* Element
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Element extends Less_Tree{
public $combinator = '';
public $value = '';
public $index;
public $currentFileInfo;
public $type = 'Element';
public $value_is_object = false;
public function __construct($combinator, $value, $index = null, $currentFileInfo = null ){
$this->value = $value;
$this->value_is_object = is_object($value);
if( $combinator ){
$this->combinator = $combinator;
}
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
function accept( $visitor ){
if( $this->value_is_object ){ //object or string
$this->value = $visitor->visitObj( $this->value );
}
}
public function compile($env){
if( Less_Environment::$mixin_stack ){
return new Less_Tree_Element($this->combinator, ($this->value_is_object ? $this->value->compile($env) : $this->value), $this->index, $this->currentFileInfo );
}
if( $this->value_is_object ){
$this->value = $this->value->compile($env);
}
return $this;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
$output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
}
public function toCSS(){
if( $this->value_is_object ){
$value = $this->value->toCSS();
}else{
$value = $this->value;
}
if( $value === '' && $this->combinator && $this->combinator === '&' ){
return '';
}
return Less_Environment::$_outputMap[$this->combinator] . $value;
}
}
/**
* Expression
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Expression extends Less_Tree{
public $value = array();
public $parens = false;
public $parensInOp = false;
public $type = 'Expression';
public function __construct( $value, $parens = null ){
$this->value = $value;
$this->parens = $parens;
}
function accept( $visitor ){
$this->value = $visitor->visitArray( $this->value );
}
public function compile($env) {
$doubleParen = false;
if( $this->parens && !$this->parensInOp ){
Less_Environment::$parensStack++;
}
$returnValue = null;
if( $this->value ){
$count = count($this->value);
if( $count > 1 ){
$ret = array();
foreach($this->value as $e){
$ret[] = $e->compile($env);
}
$returnValue = new Less_Tree_Expression($ret);
}else{
if( ($this->value[0] instanceof Less_Tree_Expression) && $this->value[0]->parens && !$this->value[0]->parensInOp ){
$doubleParen = true;
}
$returnValue = $this->value[0]->compile($env);
}
} else {
$returnValue = $this;
}
if( $this->parens ){
if( !$this->parensInOp ){
Less_Environment::$parensStack--;
}elseif( !Less_Environment::isMathOn() && !$doubleParen ){
$returnValue = new Less_Tree_Paren($returnValue);
}
}
return $returnValue;
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$val_len = count($this->value);
for( $i = 0; $i < $val_len; $i++ ){
$this->value[$i]->genCSS( $output );
if( $i + 1 < $val_len ){
$output->add( ' ' );
}
}
}
function throwAwayComments() {
if( is_array($this->value) ){
$new_value = array();
foreach($this->value as $v){
if( $v instanceof Less_Tree_Comment ){
continue;
}
$new_value[] = $v;
}
$this->value = $new_value;
}
}
}
/**
* Extend
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Extend extends Less_Tree{
public $selector;
public $option;
public $index;
public $selfSelectors = array();
public $allowBefore;
public $allowAfter;
public $firstExtendOnThisSelectorPath;
public $type = 'Extend';
public $ruleset;
public $object_id;
public $parent_ids = array();
/**
* @param integer $index
*/
function __construct($selector, $option, $index){
static $i = 0;
$this->selector = $selector;
$this->option = $option;
$this->index = $index;
switch($option){
case "all":
$this->allowBefore = true;
$this->allowAfter = true;
break;
default:
$this->allowBefore = false;
$this->allowAfter = false;
break;
}
$this->object_id = $i++;
$this->parent_ids = array($this->object_id);
}
function accept( $visitor ){
$this->selector = $visitor->visitObj( $this->selector );
}
function compile( $env ){
Less_Parser::$has_extends = true;
$this->selector = $this->selector->compile($env);
return $this;
//return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
}
function findSelfSelectors( $selectors ){
$selfElements = array();
for( $i = 0, $selectors_len = count($selectors); $i < $selectors_len; $i++ ){
$selectorElements = $selectors[$i]->elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor
if( $i && $selectorElements && $selectorElements[0]->combinator === "") {
$selectorElements[0]->combinator = ' ';
}
$selfElements = array_merge( $selfElements, $selectors[$i]->elements );
}
$this->selfSelectors = array(new Less_Tree_Selector($selfElements));
}
}
/**
* CSS @import node
*
* The general strategy here is that we don't want to wait
* for the parsing to be completed, before we start importing
* the file. That's because in the context of a browser,
* most of the time will be spent waiting for the server to respond.
*
* On creation, we push the import path to our import queue, though
* `import,push`, we also pass it a callback, which it'll call once
* the file has been fetched, and parsed.
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Import extends Less_Tree{
public $options;
public $index;
public $path;
public $features;
public $currentFileInfo;
public $css;
public $skip;
public $root;
public $type = 'Import';
function __construct($path, $features, $options, $index, $currentFileInfo = null ){
$this->options = $options;
$this->index = $index;
$this->path = $path;
$this->features = $features;
$this->currentFileInfo = $currentFileInfo;
if( is_array($options) ){
$this->options += array('inline'=>false);
if( isset($this->options['less']) || $this->options['inline'] ){
$this->css = !isset($this->options['less']) || !$this->options['less'] || $this->options['inline'];
} else {
$pathValue = $this->getPath();
if( $pathValue && preg_match('/css([\?;].*)?$/',$pathValue) ){
$this->css = true;
}
}
}
}
//
// The actual import node doesn't return anything, when converted to CSS.
// The reason is that it's used at the evaluation stage, so that the rules
// it imports can be treated like any other rules.
//
// In `eval`, we make sure all Import nodes get evaluated, recursively, so
// we end up with a flat structure, which can easily be imported in the parent
// ruleset.
//
function accept($visitor){
if( $this->features ){
$this->features = $visitor->visitObj($this->features);
}
$this->path = $visitor->visitObj($this->path);
if( !$this->options['inline'] && $this->root ){
$this->root = $visitor->visit($this->root);
}
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
if( $this->css ){
$output->add( '@import ', $this->currentFileInfo, $this->index );
$this->path->genCSS( $output );
if( $this->features ){
$output->add( ' ' );
$this->features->genCSS( $output );
}
$output->add( ';' );
}
}
function toCSS(){
$features = $this->features ? ' ' . $this->features->toCSS() : '';
if ($this->css) {
return "@import " . $this->path->toCSS() . $features . ";\n";
} else {
return "";
}
}
/**
* @return string
*/
function getPath(){
if ($this->path instanceof Less_Tree_Quoted) {
$path = $this->path->value;
return ( isset($this->css) || preg_match('/(\.[a-z]*$)|([\?;].*)$/',$path)) ? $path : $path . '.less';
} else if ($this->path instanceof Less_Tree_URL) {
return $this->path->value->value;
}
return null;
}
function compileForImport( $env ){
return new Less_Tree_Import( $this->path->compile($env), $this->features, $this->options, $this->index, $this->currentFileInfo);
}
function compilePath($env) {
$path = $this->path->compile($env);
$rootpath = '';
if( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ){
$rootpath = $this->currentFileInfo['rootpath'];
}
if( !($path instanceof Less_Tree_URL) ){
if( $rootpath ){
$pathValue = $path->value;
// Add the base path if the import is relative
if( $pathValue && Less_Environment::isPathRelative($pathValue) ){
$path->value = $this->currentFileInfo['uri_root'].$pathValue;
}
}
$path->value = Less_Environment::normalizePath($path->value);
}
return $path;
}
function compile( $env ){
$evald = $this->compileForImport($env);
//get path & uri
$path_and_uri = null;
if( is_callable(Less_Parser::$options['import_callback']) ){
$path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$evald);
}
if( !$path_and_uri ){
$path_and_uri = $evald->PathAndUri();
}
if( $path_and_uri ){
list($full_path, $uri) = $path_and_uri;
}else{
$full_path = $uri = $evald->getPath();
}
//import once
if( $evald->skip( $full_path, $env) ){
return array();
}
if( $this->options['inline'] ){
//todo needs to reference css file not import
//$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
Less_Parser::AddParsedFile($full_path);
$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
if( $this->features ){
return new Less_Tree_Media( array($contents), $this->features->value );
}
return array( $contents );
}
// css ?
if( $evald->css ){
$features = ( $evald->features ? $evald->features->compile($env) : null );
return new Less_Tree_Import( $this->compilePath( $env), $features, $this->options, $this->index);
}
return $this->ParseImport( $full_path, $uri, $env );
}
/**
* Using the import directories, get the full absolute path and uri of the import
*
* @param Less_Tree_Import $evald
*/
function PathAndUri(){
$evald_path = $this->getPath();
if( $evald_path ){
$import_dirs = array();
if( Less_Environment::isPathRelative($evald_path) ){
//if the path is relative, the file should be in the current directory
$import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
}else{
//otherwise, the file should be relative to the server root
$import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
//if the user supplied entryPath isn't the actual root
$import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
}
// always look in user supplied import directories
$import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
foreach( $import_dirs as $rootpath => $rooturi){
if( is_callable($rooturi) ){
list($path, $uri) = call_user_func($rooturi, $evald_path);
if( is_string($path) ){
$full_path = $path;
return array( $full_path, $uri );
}
}else{
$path = rtrim($rootpath,'/\\').'/'.ltrim($evald_path,'/\\');
if( file_exists($path) ){
$full_path = Less_Environment::normalizePath($path);
$uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path));
return array( $full_path, $uri );
}
}
}
}
}
/**
* Parse the import url and return the rules
*
* @return Less_Tree_Media|array
*/
function ParseImport( $full_path, $uri, $env ){
$import_env = clone $env;
if( (isset($this->options['reference']) && $this->options['reference']) || isset($this->currentFileInfo['reference']) ){
$import_env->currentFileInfo['reference'] = true;
}
if( (isset($this->options['multiple']) && $this->options['multiple']) ){
$import_env->importMultiple = true;
}
$parser = new Less_Parser($import_env);
$root = $parser->parseFile($full_path, $uri, true);
$ruleset = new Less_Tree_Ruleset(array(), $root->rules );
$ruleset->evalImports($import_env);
return $this->features ? new Less_Tree_Media($ruleset->rules, $this->features->value) : $ruleset->rules;
}
/**
* Should the import be skipped?
*
* @return boolean|null
*/
private function Skip($path, $env){
$path = realpath($path);
if( $path && Less_Parser::FileParsed($path) ){
if( isset($this->currentFileInfo['reference']) ){
return true;
}
return !isset($this->options['multiple']) && !$env->importMultiple;
}
}
}
/**
* Javascript
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Javascript extends Less_Tree{
public $type = 'Javascript';
public $escaped;
public $expression;
public $index;
/**
* @param boolean $index
* @param boolean $escaped
*/
public function __construct($string, $index, $escaped){
$this->escaped = $escaped;
$this->expression = $string;
$this->index = $index;
}
public function compile(){
return new Less_Tree_Anonymous('/* Sorry, can not do JavaScript evaluation in PHP... :( */');
}
}
/**
* Keyword
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Keyword extends Less_Tree{
public $value;
public $type = 'Keyword';
/**
* @param string $value
*/
public function __construct($value){
$this->value = $value;
}
public function compile(){
return $this;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
if( $this->value === '%') {
throw new Less_Exception_Compiler("Invalid % without number");
}
$output->add( $this->value );
}
public function compare($other) {
if ($other instanceof Less_Tree_Keyword) {
return $other->value === $this->value ? 0 : 1;
} else {
return -1;
}
}
}
/**
* Media
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Media extends Less_Tree{
public $features;
public $rules;
public $index;
public $currentFileInfo;
public $isReferenced;
public $type = 'Media';
public $allExtends;
public function __construct($value = array(), $features = array(), $index = null, $currentFileInfo = null ){
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
$selectors = $this->emptySelectors();
$this->features = new Less_Tree_Value($features);
$this->rules = array(new Less_Tree_Ruleset($selectors, $value));
$this->rules[0]->allowImports = true;
}
function accept( $visitor ){
$this->features = $visitor->visitObj($this->features);
$this->rules = $visitor->visitArray($this->rules);
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$output->add( '@media ', $this->currentFileInfo, $this->index );
$this->features->genCSS( $output );
Less_Tree::outputRuleset( $output, $this->rules);
}
public function compile($env) {
$media = new Less_Tree_Media(array(), array(), $this->index, $this->currentFileInfo );
$strictMathBypass = false;
if( Less_Parser::$options['strictMath'] === false) {
$strictMathBypass = true;
Less_Parser::$options['strictMath'] = true;
}
$media->features = $this->features->compile($env);
if( $strictMathBypass ){
Less_Parser::$options['strictMath'] = false;
}
$env->mediaPath[] = $media;
$env->mediaBlocks[] = $media;
array_unshift($env->frames, $this->rules[0]);
$media->rules = array($this->rules[0]->compile($env));
array_shift($env->frames);
array_pop($env->mediaPath);
return !$env->mediaPath ? $media->compileTop($env) : $media->compileNested($env);
}
public function variable($name) {
return $this->rules[0]->variable($name);
}
public function find($selector) {
return $this->rules[0]->find($selector, $this);
}
public function emptySelectors(){
$el = new Less_Tree_Element('','&', $this->index, $this->currentFileInfo );
$sels = array( new Less_Tree_Selector(array($el), array(), null, $this->index, $this->currentFileInfo) );
$sels[0]->mediaEmpty = true;
return $sels;
}
public function markReferenced(){
$this->rules[0]->markReferenced();
$this->isReferenced = true;
Less_Tree::ReferencedArray($this->rules[0]->rules);
}
// evaltop
public function compileTop($env) {
$result = $this;
if (count($env->mediaBlocks) > 1) {
$selectors = $this->emptySelectors();
$result = new Less_Tree_Ruleset($selectors, $env->mediaBlocks);
$result->multiMedia = true;
}
$env->mediaBlocks = array();
$env->mediaPath = array();
return $result;
}
public function compileNested($env) {
$path = array_merge($env->mediaPath, array($this));
// Extract the media-query conditions separated with `,` (OR).
foreach ($path as $key => $p) {
$value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
$path[$key] = is_array($value) ? $value : array($value);
}
// Trace all permutations to generate the resulting media-query.
//
// (a, b and c) with nested (d, e) ->
// a and d
// a and e
// b and c and d
// b and c and e
$permuted = $this->permute($path);
$expressions = array();
foreach($permuted as $path){
for( $i=0, $len=count($path); $i < $len; $i++){
$path[$i] = Less_Parser::is_method($path[$i], 'toCSS') ? $path[$i] : new Less_Tree_Anonymous($path[$i]);
}
for( $i = count($path) - 1; $i > 0; $i-- ){
array_splice($path, $i, 0, array(new Less_Tree_Anonymous('and')));
}
$expressions[] = new Less_Tree_Expression($path);
}
$this->features = new Less_Tree_Value($expressions);
// Fake a tree-node that doesn't output anything.
return new Less_Tree_Ruleset(array(), array());
}
public function permute($arr) {
if (!$arr)
return array();
if (count($arr) == 1)
return $arr[0];
$result = array();
$rest = $this->permute(array_slice($arr, 1));
foreach ($rest as $r) {
foreach ($arr[0] as $a) {
$result[] = array_merge(
is_array($a) ? $a : array($a),
is_array($r) ? $r : array($r)
);
}
}
return $result;
}
function bubbleSelectors($selectors) {
if( !$selectors) return;
$this->rules = array(new Less_Tree_Ruleset( $selectors, array($this->rules[0])));
}
}
/**
* A simple css name-value pair
* ex: width:100px;
*
* In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule)
* Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000;
*
* @package Less
* @subpackage tree
*/
class Less_Tree_NameValue extends Less_Tree{
public $name;
public $value;
public $index;
public $currentFileInfo;
public $type = 'NameValue';
public function __construct($name, $value = null, $index = null, $currentFileInfo = null ){
$this->name = $name;
$this->value = $value;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
function genCSS( $output ){
$output->add(
$this->name
. Less_Environment::$_outputMap[': ']
. $this->value
. (((Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";")
, $this->currentFileInfo, $this->index);
}
public function compile ($env){
return $this;
}
}
/**
* Negative
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Negative extends Less_Tree{
public $parensInOp;
public $value;
public $type = 'Negative';
function __construct($node){
$this->value = $node;
}
//function accept($visitor) {
// $this->value = $visitor->visit($this->value);
//}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$output->add( '-' );
$this->value->genCSS( $output );
}
function compile($env) {
if( Less_Environment::isMathOn() ){
$ret = new Less_Tree_Operation('*', array( new Less_Tree_Dimension(-1), $this->value ) );
return $ret->compile($env);
}
return new Less_Tree_Negative( $this->value->compile($env) );
}
}
/**
* Operation
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Operation extends Less_Tree{
public $parensInOp;
public $op;
public $operands;
public $isSpaced;
public $type = 'Operation';
/**
* @param string $op
*/
public function __construct($op, $operands, $isSpaced = false){
$this->op = trim($op);
$this->operands = $operands;
$this->isSpaced = $isSpaced;
}
function accept($visitor) {
$this->operands = $visitor->visitArray($this->operands);
}
public function compile($env){
$a = $this->operands[0]->compile($env);
$b = $this->operands[1]->compile($env);
if( Less_Environment::isMathOn() ){
if( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ){
$a = $a->toColor();
}elseif( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ){
$b = $b->toColor();
}
if( !method_exists($a,'operate') ){
throw new Less_Exception_Compiler("Operation on an invalid type");
}
return $a->operate( $this->op, $b);
}
return new Less_Tree_Operation($this->op, array($a, $b), $this->isSpaced );
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$this->operands[0]->genCSS( $output );
if( $this->isSpaced ){
$output->add( " " );
}
$output->add( $this->op );
if( $this->isSpaced ){
$output->add( ' ' );
}
$this->operands[1]->genCSS( $output );
}
}
/**
* Paren
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Paren extends Less_Tree{
public $value;
public $type = 'Paren';
public function __construct($value) {
$this->value = $value;
}
function accept($visitor){
$this->value = $visitor->visitObj($this->value);
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$output->add( '(' );
$this->value->genCSS( $output );
$output->add( ')' );
}
public function compile($env) {
return new Less_Tree_Paren($this->value->compile($env));
}
}
/**
* Quoted
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Quoted extends Less_Tree{
public $escaped;
public $value;
public $quote;
public $index;
public $currentFileInfo;
public $type = 'Quoted';
/**
* @param string $str
*/
public function __construct($str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ){
$this->escaped = $escaped;
$this->value = $content;
if( $str ){
$this->quote = $str[0];
}
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
if( !$this->escaped ){
$output->add( $this->quote, $this->currentFileInfo, $this->index );
}
$output->add( $this->value );
if( !$this->escaped ){
$output->add( $this->quote );
}
}
public function compile($env){
$value = $this->value;
if( preg_match_all('/`([^`]+)`/', $this->value, $matches) ){
foreach($matches as $i => $match){
$js = new Less_Tree_JavaScript($matches[1], $this->index, true);
$js = $js->compile()->value;
$value = str_replace($matches[0][$i], $js, $value);
}
}
if( preg_match_all('/@\{([\w-]+)\}/',$value,$matches) ){
foreach($matches[1] as $i => $match){
$v = new Less_Tree_Variable('@' . $match, $this->index, $this->currentFileInfo );
$v = $v->compile($env);
$v = ($v instanceof Less_Tree_Quoted) ? $v->value : $v->toCSS();
$value = str_replace($matches[0][$i], $v, $value);
}
}
return new Less_Tree_Quoted($this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo);
}
function compare($x) {
if( !Less_Parser::is_method($x, 'toCSS') ){
return -1;
}
$left = $this->toCSS();
$right = $x->toCSS();
if ($left === $right) {
return 0;
}
return $left < $right ? -1 : 1;
}
}
/**
* Rule
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Rule extends Less_Tree{
public $name;
public $value;
public $important;
public $merge;
public $index;
public $inline;
public $variable;
public $currentFileInfo;
public $type = 'Rule';
/**
* @param string $important
*/
public function __construct($name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false){
$this->name = $name;
$this->value = ($value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset) ? $value : new Less_Tree_Value(array($value));
$this->important = $important ? ' ' . trim($important) : '';
$this->merge = $merge;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
$this->inline = $inline;
$this->variable = ( is_string($name) && $name[0] === '@');
}
function accept($visitor) {
$this->value = $visitor->visitObj( $this->value );
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index);
try{
$this->value->genCSS( $output);
}catch( Less_Exception_Parser $e ){
$e->index = $this->index;
$e->currentFile = $this->currentFileInfo;
throw $e;
}
$output->add( $this->important . (($this->inline || (Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";"), $this->currentFileInfo, $this->index);
}
public function compile ($env){
$name = $this->name;
if( is_array($name) ){
// expand 'primitive' name directly to get
// things faster (~10% for benchmark.less):
if( count($name) === 1 && $name[0] instanceof Less_Tree_Keyword ){
$name = $name[0]->value;
}else{
$name = $this->CompileName($env,$name);
}
}
$strictMathBypass = Less_Parser::$options['strictMath'];
if( $name === "font" && !Less_Parser::$options['strictMath'] ){
Less_Parser::$options['strictMath'] = true;
}
try {
$evaldValue = $this->value->compile($env);
if( !$this->variable && $evaldValue->type === "DetachedRuleset") {
throw new Less_Exception_Compiler("Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo);
}
if( Less_Environment::$mixin_stack ){
$return = new Less_Tree_Rule($name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline);
}else{
$this->name = $name;
$this->value = $evaldValue;
$return = $this;
}
}catch( Less_Exception_Parser $e ){
if( !is_numeric($e->index) ){
$e->index = $this->index;
$e->currentFile = $this->currentFileInfo;
}
throw $e;
}
Less_Parser::$options['strictMath'] = $strictMathBypass;
return $return;
}
function CompileName( $env, $name ){
$output = new Less_Output();
foreach($name as $n){
$n->compile($env)->genCSS($output);
}
return $output->toString();
}
function makeImportant(){
return new Less_Tree_Rule($this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline);
}
}
/**
* Ruleset
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Ruleset extends Less_Tree{
protected $lookups;
public $_variables;
public $_rulesets;
public $strictImports;
public $selectors;
public $rules;
public $root;
public $allowImports;
public $paths;
public $firstRoot;
public $type = 'Ruleset';
public $multiMedia;
public $allExtends;
public $extendOnEveryPath;
var $ruleset_id;
var $originalRuleset;
var $first_oelements;
public function SetRulesetIndex(){
$this->ruleset_id = Less_Parser::$next_id++;
$this->originalRuleset = $this->ruleset_id;
if( $this->selectors ){
foreach($this->selectors as $sel){
if( $sel->_oelements ){
$this->first_oelements[$sel->_oelements[0]] = true;
}
}
}
}
public function __construct($selectors, $rules, $strictImports = null){
$this->selectors = $selectors;
$this->rules = $rules;
$this->lookups = array();
$this->strictImports = $strictImports;
$this->SetRulesetIndex();
}
function accept( $visitor ){
if( $this->paths ){
$paths_len = count($this->paths);
for($i = 0,$paths_len; $i < $paths_len; $i++ ){
$this->paths[$i] = $visitor->visitArray($this->paths[$i]);
}
}elseif( $this->selectors ){
$this->selectors = $visitor->visitArray($this->selectors);
}
if( $this->rules ){
$this->rules = $visitor->visitArray($this->rules);
}
}
public function compile($env){
$ruleset = $this->PrepareRuleset($env);
// Store the frames around mixin definitions,
// so they can be evaluated like closures when the time comes.
$rsRuleCnt = count($ruleset->rules);
for( $i = 0; $i < $rsRuleCnt; $i++ ){
if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){
$ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
}
}
$mediaBlockCount = 0;
if( $env instanceof Less_Environment ){
$mediaBlockCount = count($env->mediaBlocks);
}
// Evaluate mixin calls.
$this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
// Evaluate everything else
for( $i=0; $i<$rsRuleCnt; $i++ ){
if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){
$ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
}
}
// Evaluate everything else
for( $i=0; $i<$rsRuleCnt; $i++ ){
$rule = $ruleset->rules[$i];
// for rulesets, check if it is a css guard and can be removed
if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){
// check if it can be folded in (e.g. & where)
if( $rule->selectors[0]->isJustParentSelector() ){
array_splice($ruleset->rules,$i--,1);
$rsRuleCnt--;
for($j = 0; $j < count($rule->rules); $j++ ){
$subRule = $rule->rules[$j];
if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){
array_splice($ruleset->rules, ++$i, 0, array($subRule));
$rsRuleCnt++;
}
}
}
}
}
// Pop the stack
$env->shiftFrame();
if ($mediaBlockCount) {
$len = count($env->mediaBlocks);
for($i = $mediaBlockCount; $i < $len; $i++ ){
$env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors);
}
}
return $ruleset;
}
/**
* Compile Less_Tree_Mixin_Call objects
*
* @param Less_Tree_Ruleset $ruleset
* @param integer $rsRuleCnt
*/
private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){
for($i=0; $i < $rsRuleCnt; $i++){
$rule = $ruleset->rules[$i];
if( $rule instanceof Less_Tree_Mixin_Call ){
$rule = $rule->compile($env);
$temp = array();
foreach($rule as $r){
if( ($r instanceof Less_Tree_Rule) && $r->variable ){
// do not pollute the scope if the variable is
// already there. consider returning false here
// but we need a way to "return" variable from mixins
if( !$ruleset->variable($r->name) ){
$temp[] = $r;
}
}else{
$temp[] = $r;
}
}
$temp_count = count($temp)-1;
array_splice($ruleset->rules, $i, 1, $temp);
$rsRuleCnt += $temp_count;
$i += $temp_count;
$ruleset->resetCache();
}elseif( $rule instanceof Less_Tree_RulesetCall ){
$rule = $rule->compile($env);
$rules = array();
foreach($rule->rules as $r){
if( ($r instanceof Less_Tree_Rule) && $r->variable ){
continue;
}
$rules[] = $r;
}
array_splice($ruleset->rules, $i, 1, $rules);
$temp_count = count($rules);
$rsRuleCnt += $temp_count - 1;
$i += $temp_count-1;
$ruleset->resetCache();
}
}
}
/**
* Compile the selectors and create a new ruleset object for the compile() method
*
*/
private function PrepareRuleset($env){
$hasOnePassingSelector = false;
$selectors = array();
if( $this->selectors ){
Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,");
foreach($this->selectors as $s){
$selector = $s->compile($env);
$selectors[] = $selector;
if( $selector->evaldCondition ){
$hasOnePassingSelector = true;
}
}
Less_Tree_DefaultFunc::reset();
} else {
$hasOnePassingSelector = true;
}
if( $this->rules && $hasOnePassingSelector ){
$rules = $this->rules;
}else{
$rules = array();
}
$ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports);
$ruleset->originalRuleset = $this->ruleset_id;
$ruleset->root = $this->root;
$ruleset->firstRoot = $this->firstRoot;
$ruleset->allowImports = $this->allowImports;
// push the current ruleset to the frames stack
$env->unshiftFrame($ruleset);
// Evaluate imports
if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){
$ruleset->evalImports($env);
}
return $ruleset;
}
function evalImports($env) {
$rules_len = count($this->rules);
for($i=0; $i < $rules_len; $i++){
$rule = $this->rules[$i];
if( $rule instanceof Less_Tree_Import ){
$rules = $rule->compile($env);
if( is_array($rules) ){
array_splice($this->rules, $i, 1, $rules);
$temp_count = count($rules)-1;
$i += $temp_count;
$rules_len += $temp_count;
}else{
array_splice($this->rules, $i, 1, array($rules));
}
$this->resetCache();
}
}
}
function makeImportant(){
$important_rules = array();
foreach($this->rules as $rule){
if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset ){
$important_rules[] = $rule->makeImportant();
}else{
$important_rules[] = $rule;
}
}
return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports );
}
public function matchArgs($args){
return !$args;
}
// lets you call a css selector with a guard
public function matchCondition( $args, $env ){
$lastSelector = end($this->selectors);
if( !$lastSelector->evaldCondition ){
return false;
}
if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){
return false;
}
return true;
}
function resetCache(){
$this->_rulesets = null;
$this->_variables = null;
$this->lookups = array();
}
public function variables(){
$this->_variables = array();
foreach( $this->rules as $r){
if ($r instanceof Less_Tree_Rule && $r->variable === true) {
$this->_variables[$r->name] = $r;
}
}
}
public function variable($name){
if( is_null($this->_variables) ){
$this->variables();
}
return isset($this->_variables[$name]) ? $this->_variables[$name] : null;
}
public function find( $selector, $self = null ){
$key = implode(' ',$selector->_oelements);
if( !isset($this->lookups[$key]) ){
if( !$self ){
$self = $this->ruleset_id;
}
$this->lookups[$key] = array();
$first_oelement = $selector->_oelements[0];
foreach($this->rules as $rule){
if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){
if( isset($rule->first_oelements[$first_oelement]) ){
foreach( $rule->selectors as $ruleSelector ){
$match = $selector->match($ruleSelector);
if( $match ){
if( $selector->elements_len > $match ){
$this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self));
} else {
$this->lookups[$key][] = $rule;
}
break;
}
}
}
}
}
}
return $this->lookups[$key];
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
if( !$this->root ){
Less_Environment::$tabLevel++;
}
$tabRuleStr = $tabSetStr = '';
if( !Less_Parser::$options['compress'] ){
if( Less_Environment::$tabLevel ){
$tabRuleStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel );
$tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 );
}else{
$tabSetStr = $tabRuleStr = "\n";
}
}
$ruleNodes = array();
$rulesetNodes = array();
foreach($this->rules as $rule){
$class = get_class($rule);
if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){
$rulesetNodes[] = $rule;
}else{
$ruleNodes[] = $rule;
}
}
// If this is the root node, we don't render
// a selector, or {}.
if( !$this->root ){
/*
debugInfo = tree.debugInfo(env, this, tabSetStr);
if (debugInfo) {
output.add(debugInfo);
output.add(tabSetStr);
}
*/
$paths_len = count($this->paths);
for( $i = 0; $i < $paths_len; $i++ ){
$path = $this->paths[$i];
$firstSelector = true;
foreach($path as $p){
$p->genCSS( $output, $firstSelector );
$firstSelector = false;
}
if( $i + 1 < $paths_len ){
$output->add( ',' . $tabSetStr );
}
}
$output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr );
}
// Compile rules and rulesets
$ruleNodes_len = count($ruleNodes);
$rulesetNodes_len = count($rulesetNodes);
for( $i = 0; $i < $ruleNodes_len; $i++ ){
$rule = $ruleNodes[$i];
// @page{ directive ends up with root elements inside it, a mix of rules and rulesets
// In this instance we do not know whether it is the last property
if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){
Less_Environment::$lastRule = true;
}
$rule->genCSS( $output );
if( !Less_Environment::$lastRule ){
$output->add( $tabRuleStr );
}else{
Less_Environment::$lastRule = false;
}
}
if( !$this->root ){
$output->add( $tabSetStr . '}' );
Less_Environment::$tabLevel--;
}
$firstRuleset = true;
$space = ($this->root ? $tabRuleStr : $tabSetStr);
for( $i = 0; $i < $rulesetNodes_len; $i++ ){
if( $ruleNodes_len && $firstRuleset ){
$output->add( $space );
}elseif( !$firstRuleset ){
$output->add( $space );
}
$firstRuleset = false;
$rulesetNodes[$i]->genCSS( $output);
}
if( !Less_Parser::$options['compress'] && $this->firstRoot ){
$output->add( "\n" );
}
}
function markReferenced(){
if( !$this->selectors ){
return;
}
foreach($this->selectors as $selector){
$selector->markReferenced();
}
}
public function joinSelectors( $context, $selectors ){
$paths = array();
if( is_array($selectors) ){
foreach($selectors as $selector) {
$this->joinSelector( $paths, $context, $selector);
}
}
return $paths;
}
public function joinSelector( &$paths, $context, $selector){
$hasParentSelector = false;
foreach($selector->elements as $el) {
if( $el->value === '&') {
$hasParentSelector = true;
}
}
if( !$hasParentSelector ){
if( $context ){
foreach($context as $context_el){
$paths[] = array_merge($context_el, array($selector) );
}
}else {
$paths[] = array($selector);
}
return;
}
// The paths are [[Selector]]
// The first list is a list of comma seperated selectors
// The inner list is a list of inheritance seperated selectors
// e.g.
// .a, .b {
// .c {
// }
// }
// == [[.a] [.c]] [[.b] [.c]]
//
// the elements from the current selector so far
$currentElements = array();
// the current list of new selectors to add to the path.
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
// by the parents
$newSelectors = array(array());
foreach( $selector->elements as $el){
// non parent reference elements just get added
if( $el->value !== '&' ){
$currentElements[] = $el;
} else {
// the new list of selectors to add
$selectorsMultiplied = array();
// merge the current list of non parent selector elements
// on to the current list of selectors to add
if( $currentElements ){
$this->mergeElementsOnToSelectors( $currentElements, $newSelectors);
}
// loop through our current selectors
foreach($newSelectors as $sel){
// if we don't have any parent paths, the & might be in a mixin so that it can be used
// whether there are parents or not
if( !$context ){
// the combinator used on el should now be applied to the next element instead so that
// it is not lost
if( $sel ){
$sel[0]->elements = array_slice($sel[0]->elements,0);
$sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo );
}
$selectorsMultiplied[] = $sel;
}else {
// and the parent selectors
foreach($context as $parentSel){
// We need to put the current selectors
// then join the last selector's elements on to the parents selectors
// our new selector path
$newSelectorPath = array();
// selectors from the parent after the join
$afterParentJoin = array();
$newJoinedSelectorEmpty = true;
//construct the joined selector - if & is the first thing this will be empty,
// if not newJoinedSelector will be the last set of elements in the selector
if( $sel ){
$newSelectorPath = $sel;
$lastSelector = array_pop($newSelectorPath);
$newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) );
$newJoinedSelectorEmpty = false;
}
else {
$newJoinedSelector = $selector->createDerived(array());
}
//put together the parent selectors after the join
if ( count($parentSel) > 1) {
$afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) );
}
if ( $parentSel ){
$newJoinedSelectorEmpty = false;
// join the elements so far with the first part of the parent
$newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo);
$newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) );
}
if (!$newJoinedSelectorEmpty) {
// now add the joined selector
$newSelectorPath[] = $newJoinedSelector;
}
// and the rest of the parent
$newSelectorPath = array_merge($newSelectorPath, $afterParentJoin);
// add that to our new set of selectors
$selectorsMultiplied[] = $newSelectorPath;
}
}
}
// our new selectors has been multiplied, so reset the state
$newSelectors = $selectorsMultiplied;
$currentElements = array();
}
}
// if we have any elements left over (e.g. .a& .b == .b)
// add them on to all the current selectors
if( $currentElements ){
$this->mergeElementsOnToSelectors($currentElements, $newSelectors);
}
foreach( $newSelectors as $new_sel){
if( $new_sel ){
$paths[] = $new_sel;
}
}
}
function mergeElementsOnToSelectors( $elements, &$selectors){
if( !$selectors ){
$selectors[] = array( new Less_Tree_Selector($elements) );
return;
}
foreach( $selectors as &$sel){
// if the previous thing in sel is a parent this needs to join on to it
if( $sel ){
$last = count($sel)-1;
$sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) );
}else{
$sel[] = new Less_Tree_Selector( $elements );
}
}
}
}
/**
* RulesetCall
*
* @package Less
* @subpackage tree
*/
class Less_Tree_RulesetCall extends Less_Tree{
public $variable;
public $type = "RulesetCall";
function __construct($variable){
$this->variable = $variable;
}
function accept($visitor) {}
function compile( $env ){
$variable = new Less_Tree_Variable($this->variable);
$detachedRuleset = $variable->compile($env);
return $detachedRuleset->callEval($env);
}
}
/**
* Selector
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Selector extends Less_Tree{
public $elements;
public $condition;
public $extendList = array();
public $_css;
public $index;
public $evaldCondition = false;
public $type = 'Selector';
public $currentFileInfo = array();
public $isReferenced;
public $mediaEmpty;
public $elements_len = 0;
public $_oelements;
public $_oelements_len;
public $cacheable = true;
/**
* @param boolean $isReferenced
*/
public function __construct( $elements, $extendList = array() , $condition = null, $index=null, $currentFileInfo=null, $isReferenced=null ){
$this->elements = $elements;
$this->elements_len = count($elements);
$this->extendList = $extendList;
$this->condition = $condition;
if( $currentFileInfo ){
$this->currentFileInfo = $currentFileInfo;
}
$this->isReferenced = $isReferenced;
if( !$condition ){
$this->evaldCondition = true;
}
$this->CacheElements();
}
function accept($visitor) {
$this->elements = $visitor->visitArray($this->elements);
$this->extendList = $visitor->visitArray($this->extendList);
if( $this->condition ){
$this->condition = $visitor->visitObj($this->condition);
}
if( $visitor instanceof Less_Visitor_extendFinder ){
$this->CacheElements();
}
}
function createDerived( $elements, $extendList = null, $evaldCondition = null ){
$newSelector = new Less_Tree_Selector( $elements, ($extendList ? $extendList : $this->extendList), null, $this->index, $this->currentFileInfo, $this->isReferenced);
$newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition;
return $newSelector;
}
public function match( $other ){
if( !$other->_oelements || ($this->elements_len < $other->_oelements_len) ){
return 0;
}
for( $i = 0; $i < $other->_oelements_len; $i++ ){
if( $this->elements[$i]->value !== $other->_oelements[$i]) {
return 0;
}
}
return $other->_oelements_len; // return number of matched elements
}
public function CacheElements(){
$this->_oelements = array();
$css = '';
foreach($this->elements as $v){
$css .= $v->combinator;
if( !$v->value_is_object ){
$css .= $v->value;
continue;
}
if( !property_exists($v->value,'value') || !is_string($v->value->value) ){
$this->cacheable = false;
return;
}
$css .= $v->value->value;
}
$this->_oelements_len = preg_match_all('/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches);
if( $this->_oelements_len ){
$this->_oelements = $matches[0];
if( $this->_oelements[0] === '&' ){
array_shift($this->_oelements);
$this->_oelements_len--;
}
}
}
public function isJustParentSelector(){
return !$this->mediaEmpty &&
count($this->elements) === 1 &&
$this->elements[0]->value === '&' &&
($this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '');
}
public function compile($env) {
$elements = array();
foreach($this->elements as $el){
$elements[] = $el->compile($env);
}
$extendList = array();
foreach($this->extendList as $el){
$extendList[] = $el->compile($el);
}
$evaldCondition = false;
if( $this->condition ){
$evaldCondition = $this->condition->compile($env);
}
return $this->createDerived( $elements, $extendList, $evaldCondition );
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output, $firstSelector = true ){
if( !$firstSelector && $this->elements[0]->combinator === "" ){
$output->add(' ', $this->currentFileInfo, $this->index);
}
foreach($this->elements as $element){
$element->genCSS( $output );
}
}
function markReferenced(){
$this->isReferenced = true;
}
function getIsReferenced(){
return !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] || $this->isReferenced;
}
function getIsOutput(){
return $this->evaldCondition;
}
}
/**
* UnicodeDescriptor
*
* @package Less
* @subpackage tree
*/
class Less_Tree_UnicodeDescriptor extends Less_Tree{
public $value;
public $type = 'UnicodeDescriptor';
public function __construct($value){
$this->value = $value;
}
/**
* @see Less_Tree::genCSS
*/
public function genCSS( $output ){
$output->add( $this->value );
}
public function compile(){
return $this;
}
}
/**
* Unit
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Unit extends Less_Tree{
var $numerator = array();
var $denominator = array();
public $backupUnit;
public $type = 'Unit';
function __construct($numerator = array(), $denominator = array(), $backupUnit = null ){
$this->numerator = $numerator;
$this->denominator = $denominator;
$this->backupUnit = $backupUnit;
}
function __clone(){
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
if( $this->numerator ){
$output->add( $this->numerator[0] );
}elseif( $this->denominator ){
$output->add( $this->denominator[0] );
}elseif( !Less_Parser::$options['strictUnits'] && $this->backupUnit ){
$output->add( $this->backupUnit );
return ;
}
}
function toString(){
$returnStr = implode('*',$this->numerator);
foreach($this->denominator as $d){
$returnStr .= '/'.$d;
}
return $returnStr;
}
function __toString(){
return $this->toString();
}
/**
* @param Less_Tree_Unit $other
*/
function compare($other) {
return $this->is( $other->toString() ) ? 0 : -1;
}
function is($unitString){
return $this->toString() === $unitString;
}
function isLength(){
$css = $this->toCSS();
return !!preg_match('/px|em|%|in|cm|mm|pc|pt|ex/',$css);
}
function isAngle() {
return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
}
function isEmpty(){
return !$this->numerator && !$this->denominator;
}
function isSingular() {
return count($this->numerator) <= 1 && !$this->denominator;
}
function usedUnits(){
$result = array();
foreach(Less_Tree_UnitConversions::$groups as $groupName){
$group = Less_Tree_UnitConversions::${$groupName};
foreach($this->numerator as $atomicUnit){
if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
$result[$groupName] = $atomicUnit;
}
}
foreach($this->denominator as $atomicUnit){
if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
$result[$groupName] = $atomicUnit;
}
}
}
return $result;
}
function cancel(){
$counter = array();
$backup = null;
foreach($this->numerator as $atomicUnit){
if( !$backup ){
$backup = $atomicUnit;
}
$counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) + 1;
}
foreach($this->denominator as $atomicUnit){
if( !$backup ){
$backup = $atomicUnit;
}
$counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) - 1;
}
$this->numerator = array();
$this->denominator = array();
foreach($counter as $atomicUnit => $count){
if( $count > 0 ){
for( $i = 0; $i < $count; $i++ ){
$this->numerator[] = $atomicUnit;
}
}elseif( $count < 0 ){
for( $i = 0; $i < -$count; $i++ ){
$this->denominator[] = $atomicUnit;
}
}
}
if( !$this->numerator && !$this->denominator && $backup ){
$this->backupUnit = $backup;
}
sort($this->numerator);
sort($this->denominator);
}
}
/**
* UnitConversions
*
* @package Less
* @subpackage tree
*/
class Less_Tree_UnitConversions{
public static $groups = array('length','duration','angle');
public static $length = array(
'm'=> 1,
'cm'=> 0.01,
'mm'=> 0.001,
'in'=> 0.0254,
'px'=> 0.000264583, // 0.0254 / 96,
'pt'=> 0.000352778, // 0.0254 / 72,
'pc'=> 0.004233333, // 0.0254 / 72 * 12
);
public static $duration = array(
's'=> 1,
'ms'=> 0.001
);
public static $angle = array(
'rad' => 0.1591549430919, // 1/(2*M_PI),
'deg' => 0.002777778, // 1/360,
'grad'=> 0.0025, // 1/400,
'turn'=> 1
);
}
/**
* Url
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Url extends Less_Tree{
public $attrs;
public $value;
public $currentFileInfo;
public $isEvald;
public $type = 'Url';
public function __construct($value, $currentFileInfo = null, $isEvald = null){
$this->value = $value;
$this->currentFileInfo = $currentFileInfo;
$this->isEvald = $isEvald;
}
function accept( $visitor ){
$this->value = $visitor->visitObj($this->value);
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$output->add( 'url(' );
$this->value->genCSS( $output );
$output->add( ')' );
}
/**
* @param Less_Functions $ctx
*/
public function compile($ctx){
$val = $this->value->compile($ctx);
if( !$this->isEvald ){
// Add the base path if the URL is relative
if( Less_Parser::$options['relativeUrls']
&& $this->currentFileInfo
&& is_string($val->value)
&& Less_Environment::isPathRelative($val->value)
){
$rootpath = $this->currentFileInfo['uri_root'];
if ( !$val->quote ){
$rootpath = preg_replace('/[\(\)\'"\s]/', '\\$1', $rootpath );
}
$val->value = $rootpath . $val->value;
}
$val->value = Less_Environment::normalizePath( $val->value);
}
// Add cache buster if enabled
if( Less_Parser::$options['urlArgs'] ){
if( !preg_match('/^\s*data:/',$val->value) ){
$delimiter = strpos($val->value,'?') === false ? '?' : '&';
$urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
$hash_pos = strpos($val->value,'#');
if( $hash_pos !== false ){
$val->value = substr_replace($val->value,$urlArgs, $hash_pos, 0);
} else {
$val->value .= $urlArgs;
}
}
}
return new Less_Tree_URL($val, $this->currentFileInfo, true);
}
}
/**
* Value
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Value extends Less_Tree{
public $type = 'Value';
public $value;
public function __construct($value){
$this->value = $value;
}
function accept($visitor) {
$this->value = $visitor->visitArray($this->value);
}
public function compile($env){
$ret = array();
$i = 0;
foreach($this->value as $i => $v){
$ret[] = $v->compile($env);
}
if( $i > 0 ){
return new Less_Tree_Value($ret);
}
return $ret[0];
}
/**
* @see Less_Tree::genCSS
*/
function genCSS( $output ){
$len = count($this->value);
for($i = 0; $i < $len; $i++ ){
$this->value[$i]->genCSS( $output );
if( $i+1 < $len ){
$output->add( Less_Environment::$_outputMap[','] );
}
}
}
}
/**
* Variable
*
* @package Less
* @subpackage tree
*/
class Less_Tree_Variable extends Less_Tree{
public $name;
public $index;
public $currentFileInfo;
public $evaluating = false;
public $type = 'Variable';
public $parensInOp ;
/**
* @param string $name
*/
public function __construct($name, $index = null, $currentFileInfo = null) {
$this->name = $name;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
public function compile($env) {
if( $this->name[1] === '@' ){
$v = new Less_Tree_Variable(substr($this->name, 1), $this->index + 1);
$name = '@' . $v->compile($env)->value;
}else{
$name = $this->name;
}
if ($this->evaluating) {
throw new Less_Exception_Compiler("Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo);
}
$this->evaluating = true;
foreach($env->frames as $frame){
if( $v = $frame->variable($name) ){
$this->evaluating = false;
return $v->value->compile($env);
}
}
throw new Less_Exception_Compiler("variable " . $name . " is undefined", null, $this->index );
}
}
class Less_Tree_Mixin_Call extends Less_Tree{
public $selector;
public $arguments;
public $index;
public $currentFileInfo;
public $important;
public $type = 'MixinCall';
/**
* less.js: tree.mixin.Call
*
*/
public function __construct($elements, $args, $index, $currentFileInfo, $important = false){
$this->selector = new Less_Tree_Selector($elements);
$this->arguments = $args;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
$this->important = $important;
}
//function accept($visitor){
// $this->selector = $visitor->visit($this->selector);
// $this->arguments = $visitor->visit($this->arguments);
//}
public function compile($env){
$rules = array();
$match = false;
$isOneFound = false;
$candidates = array();
$defaultUsed = false;
$conditionResult = array();
$args = array();
foreach($this->arguments as $a){
$args[] = array('name'=> $a['name'], 'value' => $a['value']->compile($env) );
}
foreach($env->frames as $frame){
$mixins = $frame->find($this->selector);
if( !$mixins ){
continue;
}
$isOneFound = true;
$defNone = 0;
$defTrue = 1;
$defFalse = 2;
// To make `default()` function independent of definition order we have two "subpasses" here.
// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
// and build candidate list with corresponding flags. Then, when we know all possible matches,
// we make a final decision.
$mixins_len = count($mixins);
for( $m = 0; $m < $mixins_len; $m++ ){
$mixin = $mixins[$m];
if( $this->IsRecursive( $env, $mixin ) ){
continue;
}
if( $mixin->matchArgs($args, $env) ){
$candidate = array('mixin' => $mixin, 'group' => $defNone);
if( $mixin instanceof Less_Tree_Ruleset ){
for( $f = 0; $f < 2; $f++ ){
Less_Tree_DefaultFunc::value($f);
$conditionResult[$f] = $mixin->matchCondition( $args, $env);
}
if( $conditionResult[0] || $conditionResult[1] ){
if( $conditionResult[0] != $conditionResult[1] ){
$candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
}
$candidates[] = $candidate;
}
}else{
$candidates[] = $candidate;
}
$match = true;
}
}
Less_Tree_DefaultFunc::reset();
$count = array(0, 0, 0);
for( $m = 0; $m < count($candidates); $m++ ){
$count[ $candidates[$m]['group'] ]++;
}
if( $count[$defNone] > 0 ){
$defaultResult = $defFalse;
} else {
$defaultResult = $defTrue;
if( ($count[$defTrue] + $count[$defFalse]) > 1 ){
throw Exception( 'Ambiguous use of `default()` found when matching for `'. $this->format($args) + '`' );
}
}
$candidates_length = count($candidates);
$length_1 = ($candidates_length == 1);
for( $m = 0; $m < $candidates_length; $m++){
$candidate = $candidates[$m]['group'];
if( ($candidate === $defNone) || ($candidate === $defaultResult) ){
try{
$mixin = $candidates[$m]['mixin'];
if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
$mixin = new Less_Tree_Mixin_Definition('', array(), $mixin->rules, null, false);
$mixin->originalRuleset = $mixins[$m]->originalRuleset;
}
$rules = array_merge($rules, $mixin->evalCall($env, $args, $this->important)->rules);
} catch (Exception $e) {
//throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
throw new Less_Exception_Compiler($e->getMessage(), null, null, $this->currentFileInfo);
}
}
}
if( $match ){
if( !$this->currentFileInfo || !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] ){
Less_Tree::ReferencedArray($rules);
}
return $rules;
}
}
if( $isOneFound ){
throw new Less_Exception_Compiler('No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo);
}else{
throw new Less_Exception_Compiler(trim($this->selector->toCSS()) . " is undefined", null, $this->index);
}
}
/**
* Format the args for use in exception messages
*
*/
private function Format($args){
$message = array();
if( $args ){
foreach($args as $a){
$argValue = '';
if( $a['name'] ){
$argValue += $a['name']+':';
}
if( is_object($a['value']) ){
$argValue += $a['value']->toCSS();
}else{
$argValue += '???';
}
$message[] = $argValue;
}
}
return implode(', ',$message);
}
/**
* Are we in a recursive mixin call?
*
* @return bool
*/
private function IsRecursive( $env, $mixin ){
foreach($env->frames as $recur_frame){
if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
if( $mixin === $recur_frame ){
return true;
}
if( isset($recur_frame->originalRuleset) && $mixin->ruleset_id === $recur_frame->originalRuleset ){
return true;
}
}
}
return false;
}
}
class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset{
public $name;
public $selectors;
public $params;
public $arity = 0;
public $rules;
public $lookups = array();
public $required = 0;
public $frames = array();
public $condition;
public $variadic;
public $type = 'MixinDefinition';
// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
public function __construct($name, $params, $rules, $condition, $variadic = false, $frames = null ){
$this->name = $name;
$this->selectors = array(new Less_Tree_Selector(array( new Less_Tree_Element(null, $name))));
$this->params = $params;
$this->condition = $condition;
$this->variadic = $variadic;
$this->rules = $rules;
if( $params ){
$this->arity = count($params);
foreach( $params as $p ){
if (! isset($p['name']) || ($p['name'] && !isset($p['value']))) {
$this->required++;
}
}
}
$this->frames = $frames;
$this->SetRulesetIndex();
}
//function accept( $visitor ){
// $this->params = $visitor->visit($this->params);
// $this->rules = $visitor->visit($this->rules);
// $this->condition = $visitor->visit($this->condition);
//}
public function toCSS(){
return '';
}
// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
public function compileParams($env, $mixinFrames, $args = array() , &$evaldArguments = array() ){
$frame = new Less_Tree_Ruleset(null, array());
$params = $this->params;
$mixinEnv = null;
$argsLength = 0;
if( $args ){
$argsLength = count($args);
for($i = 0; $i < $argsLength; $i++ ){
$arg = $args[$i];
if( $arg && $arg['name'] ){
$isNamedFound = false;
foreach($params as $j => $param){
if( !isset($evaldArguments[$j]) && $arg['name'] === $params[$j]['name']) {
$evaldArguments[$j] = $arg['value']->compile($env);
array_unshift($frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile($env) ) );
$isNamedFound = true;
break;
}
}
if ($isNamedFound) {
array_splice($args, $i, 1);
$i--;
$argsLength--;
continue;
} else {
throw new Less_Exception_Compiler("Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found');
}
}
}
}
$argIndex = 0;
foreach($params as $i => $param){
if ( isset($evaldArguments[$i]) ){ continue; }
$arg = null;
if( isset($args[$argIndex]) ){
$arg = $args[$argIndex];
}
if (isset($param['name']) && $param['name']) {
if( isset($param['variadic']) ){
$varargs = array();
for ($j = $argIndex; $j < $argsLength; $j++) {
$varargs[] = $args[$j]['value']->compile($env);
}
$expression = new Less_Tree_Expression($varargs);
array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $expression->compile($env)));
}else{
$val = ($arg && $arg['value']) ? $arg['value'] : false;
if ($val) {
$val = $val->compile($env);
} else if ( isset($param['value']) ) {
if( !$mixinEnv ){
$mixinEnv = new Less_Environment();
$mixinEnv->frames = array_merge( array($frame), $mixinFrames);
}
$val = $param['value']->compile($mixinEnv);
$frame->resetCache();
} else {
throw new Less_Exception_Compiler("Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")");
}
array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $val));
$evaldArguments[$i] = $val;
}
}
if ( isset($param['variadic']) && $args) {
for ($j = $argIndex; $j < $argsLength; $j++) {
$evaldArguments[$j] = $args[$j]['value']->compile($env);
}
}
$argIndex++;
}
ksort($evaldArguments);
$evaldArguments = array_values($evaldArguments);
return $frame;
}
public function compile($env) {
if( $this->frames ){
return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
}
return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
}
public function evalCall($env, $args = NULL, $important = NULL) {
Less_Environment::$mixin_stack++;
$_arguments = array();
if( $this->frames ){
$mixinFrames = array_merge($this->frames, $env->frames);
}else{
$mixinFrames = $env->frames;
}
$frame = $this->compileParams($env, $mixinFrames, $args, $_arguments);
$ex = new Less_Tree_Expression($_arguments);
array_unshift($frame->rules, new Less_Tree_Rule('@arguments', $ex->compile($env)));
$ruleset = new Less_Tree_Ruleset(null, $this->rules);
$ruleset->originalRuleset = $this->ruleset_id;
$ruleSetEnv = new Less_Environment();
$ruleSetEnv->frames = array_merge( array($this, $frame), $mixinFrames );
$ruleset = $ruleset->compile( $ruleSetEnv );
if( $important ){
$ruleset = $ruleset->makeImportant();
}
Less_Environment::$mixin_stack--;
return $ruleset;
}
public function matchCondition($args, $env) {
if( !$this->condition ){
return true;
}
$frame = $this->compileParams($env, array_merge($this->frames,$env->frames), $args );
$compile_env = new Less_Environment();
$compile_env->frames = array_merge(
array($frame) // the parameter variables
, $this->frames // the parent namespace/mixin frames
, $env->frames // the current environment frames
);
return (bool)$this->condition->compile($compile_env);
}
public function matchArgs($args, $env = NULL){
$argsLength = count($args);
if( !$this->variadic ){
if( $argsLength < $this->required ){
return false;
}
if( $argsLength > count($this->params) ){
return false;
}
}else{
if( $argsLength < ($this->required - 1)){
return false;
}
}
$len = min($argsLength, $this->arity);
for( $i = 0; $i < $len; $i++ ){
if( !isset($this->params[$i]['name']) && !isset($this->params[$i]['variadic']) ){
if( $args[$i]['value']->compile($env)->toCSS() != $this->params[$i]['value']->compile($env)->toCSS() ){
return false;
}
}
}
return true;
}
}
/**
* Extend Finder Visitor
*
* @package Less
* @subpackage visitor
*/
class Less_Visitor_extendFinder extends Less_Visitor{
public $contexts = array();
public $allExtendsStack;
public $foundExtends;
function __construct(){
$this->contexts = array();
$this->allExtendsStack = array(array());
parent::__construct();
}
/**
* @param Less_Tree_Ruleset $root
*/
function run($root){
$root = $this->visitObj($root);
$root->allExtends =& $this->allExtendsStack[0];
return $root;
}
function visitRule($ruleNode, &$visitDeeper ){
$visitDeeper = false;
}
function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
$visitDeeper = false;
}
function visitRuleset($rulesetNode){
if( $rulesetNode->root ){
return;
}
$allSelectorsExtendList = array();
// get &:extend(.a); rules which apply to all selectors in this ruleset
if( $rulesetNode->rules ){
foreach($rulesetNode->rules as $rule){
if( $rule instanceof Less_Tree_Extend ){
$allSelectorsExtendList[] = $rule;
$rulesetNode->extendOnEveryPath = true;
}
}
}
// now find every selector and apply the extends that apply to all extends
// and the ones which apply to an individual extend
foreach($rulesetNode->paths as $selectorPath){
$selector = end($selectorPath); //$selectorPath[ count($selectorPath)-1];
$j = 0;
foreach($selector->extendList as $extend){
$this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
}
foreach($allSelectorsExtendList as $extend){
$this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
}
}
$this->contexts[] = $rulesetNode->selectors;
}
function allExtendsStackPush($rulesetNode, $selectorPath, $extend, &$j){
$this->foundExtends = true;
$extend = clone $extend;
$extend->findSelfSelectors( $selectorPath );
$extend->ruleset = $rulesetNode;
if( $j === 0 ){
$extend->firstExtendOnThisSelectorPath = true;
}
$end_key = count($this->allExtendsStack)-1;
$this->allExtendsStack[$end_key][] = $extend;
$j++;
}
function visitRulesetOut( $rulesetNode ){
if( !is_object($rulesetNode) || !$rulesetNode->root ){
array_pop($this->contexts);
}
}
function visitMedia( $mediaNode ){
$mediaNode->allExtends = array();
$this->allExtendsStack[] =& $mediaNode->allExtends;
}
function visitMediaOut(){
array_pop($this->allExtendsStack);
}
function visitDirective( $directiveNode ){
$directiveNode->allExtends = array();
$this->allExtendsStack[] =& $directiveNode->allExtends;
}
function visitDirectiveOut(){
array_pop($this->allExtendsStack);
}
}
/*
class Less_Visitor_import extends Less_VisitorReplacing{
public $_visitor;
public $_importer;
public $importCount;
function __construct( $evalEnv ){
$this->env = $evalEnv;
$this->importCount = 0;
parent::__construct();
}
function run( $root ){
$root = $this->visitObj($root);
$this->isFinished = true;
//if( $this->importCount === 0) {
// $this->_finish();
//}
}
function visitImport($importNode, &$visitDeeper ){
$importVisitor = $this;
$inlineCSS = $importNode->options['inline'];
if( !$importNode->css || $inlineCSS ){
$evaldImportNode = $importNode->compileForImport($this->env);
if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){
$importNode = $evaldImportNode;
$this->importCount++;
$env = clone $this->env;
if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){
$env->importMultiple = true;
}
//get path & uri
$path_and_uri = null;
if( is_callable(Less_Parser::$options['import_callback']) ){
$path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode);
}
if( !$path_and_uri ){
$path_and_uri = $importNode->PathAndUri();
}
if( $path_and_uri ){
list($full_path, $uri) = $path_and_uri;
}else{
$full_path = $uri = $importNode->getPath();
}
//import once
if( $importNode->skip( $full_path, $env) ){
return array();
}
if( $importNode->options['inline'] ){
//todo needs to reference css file not import
//$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true );
Less_Parser::AddParsedFile($full_path);
$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
if( $importNode->features ){
return new Less_Tree_Media( array($contents), $importNode->features->value );
}
return array( $contents );
}
// css ?
if( $importNode->css ){
$features = ( $importNode->features ? $importNode->features->compile($env) : null );
return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index);
}
return $importNode->ParseImport( $full_path, $uri, $env );
}
}
$visitDeeper = false;
return $importNode;
}
function visitRule( $ruleNode, &$visitDeeper ){
$visitDeeper = false;
return $ruleNode;
}
function visitDirective($directiveNode, $visitArgs){
array_unshift($this->env->frames,$directiveNode);
return $directiveNode;
}
function visitDirectiveOut($directiveNode) {
array_shift($this->env->frames);
}
function visitMixinDefinition($mixinDefinitionNode, $visitArgs) {
array_unshift($this->env->frames,$mixinDefinitionNode);
return $mixinDefinitionNode;
}
function visitMixinDefinitionOut($mixinDefinitionNode) {
array_shift($this->env->frames);
}
function visitRuleset($rulesetNode, $visitArgs) {
array_unshift($this->env->frames,$rulesetNode);
return $rulesetNode;
}
function visitRulesetOut($rulesetNode) {
array_shift($this->env->frames);
}
function visitMedia($mediaNode, $visitArgs) {
array_unshift($this->env->frames, $mediaNode->ruleset);
return $mediaNode;
}
function visitMediaOut($mediaNode) {
array_shift($this->env->frames);
}
}
*/
/**
* Join Selector Visitor
*
* @package Less
* @subpackage visitor
*/
class Less_Visitor_joinSelector extends Less_Visitor{
public $contexts = array( array() );
/**
* @param Less_Tree_Ruleset $root
*/
function run( $root ){
return $this->visitObj($root);
}
function visitRule( $ruleNode, &$visitDeeper ){
$visitDeeper = false;
}
function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
$visitDeeper = false;
}
function visitRuleset( $rulesetNode ){
$paths = array();
if( !$rulesetNode->root ){
$selectors = array();
if( $rulesetNode->selectors && $rulesetNode->selectors ){
foreach($rulesetNode->selectors as $selector){
if( $selector->getIsOutput() ){
$selectors[] = $selector;
}
}
}
if( !$selectors ){
$rulesetNode->selectors = null;
$rulesetNode->rules = null;
}else{
$context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
$paths = $rulesetNode->joinSelectors( $context, $selectors);
}
$rulesetNode->paths = $paths;
}
$this->contexts[] = $paths; //different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths
}
function visitRulesetOut(){
array_pop($this->contexts);
}
function visitMedia($mediaNode) {
$context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
if( !count($context) || (is_object($context[0]) && $context[0]->multiMedia) ){
$mediaNode->rules[0]->root = true;
}
}
}
/**
* Process Extends Visitor
*
* @package Less
* @subpackage visitor
*/
class Less_Visitor_processExtends extends Less_Visitor{
public $allExtendsStack;
/**
* @param Less_Tree_Ruleset $root
*/
public function run( $root ){
$extendFinder = new Less_Visitor_extendFinder();
$extendFinder->run( $root );
if( !$extendFinder->foundExtends){
return $root;
}
$root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends);
$this->allExtendsStack = array();
$this->allExtendsStack[] = &$root->allExtends;
return $this->visitObj( $root );
}
private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0){
//
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
// the selector we would do normally, but we are also adding an extend with the same target selector
// this means this new extend can then go and alter other extends
//
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
// we look at each selector at a time, as is done in visitRuleset
$extendsToAdd = array();
//loop through comparing every extend with every target extend.
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
// and the second is the target.
// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
// case when processing media queries
for( $extendIndex = 0, $extendsList_len = count($extendsList); $extendIndex < $extendsList_len; $extendIndex++ ){
for( $targetExtendIndex = 0; $targetExtendIndex < count($extendsListTarget); $targetExtendIndex++ ){
$extend = $extendsList[$extendIndex];
$targetExtend = $extendsListTarget[$targetExtendIndex];
// look for circular references
if( in_array($targetExtend->object_id, $extend->parent_ids,true) ){
continue;
}
// find a match in the target extends self selector (the bit before :extend)
$selectorPath = array( $targetExtend->selfSelectors[0] );
$matches = $this->findMatch( $extend, $selectorPath);
if( $matches ){
// we found a match, so for each self selector..
foreach($extend->selfSelectors as $selfSelector ){
// process the extend as usual
$newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector);
// but now we create a new extend from it
$newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0);
$newExtend->selfSelectors = $newSelector;
// add the extend onto the list of extends for that selector
end($newSelector)->extendList = array($newExtend);
//$newSelector[ count($newSelector)-1]->extendList = array($newExtend);
// record that we need to add it.
$extendsToAdd[] = $newExtend;
$newExtend->ruleset = $targetExtend->ruleset;
//remember its parents for circular references
$newExtend->parent_ids = array_merge($newExtend->parent_ids,$targetExtend->parent_ids,$extend->parent_ids);
// only process the selector once.. if we have :extend(.a,.b) then multiple
// extends will look at the same selector path, so when extending
// we know that any others will be duplicates in terms of what is added to the css
if( $targetExtend->firstExtendOnThisSelectorPath ){
$newExtend->firstExtendOnThisSelectorPath = true;
$targetExtend->ruleset->paths[] = $newSelector;
}
}
}
}
}
if( $extendsToAdd ){
// try to detect circular references to stop a stack overflow.
// may no longer be needed. $this->extendChainCount++;
if( $iterationCount > 100) {
try{
$selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS();
$selectorTwo = $extendsToAdd[0]->selector->toCSS();
}catch(Exception $e){
$selectorOne = "{unable to calculate}";
$selectorTwo = "{unable to calculate}";
}
throw new Less_Exception_Parser("extend circular reference detected. One of the circular extends is currently:"+$selectorOne+":extend(" + $selectorTwo+")");
}
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
$extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount+1);
}
return array_merge($extendsList, $extendsToAdd);
}
protected function visitRule( $ruleNode, &$visitDeeper ){
$visitDeeper = false;
}
protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
$visitDeeper = false;
}
protected function visitSelector( $selectorNode, &$visitDeeper ){
$visitDeeper = false;
}
protected function visitRuleset($rulesetNode){
if( $rulesetNode->root ){
return;
}
$allExtends = end($this->allExtendsStack);
$paths_len = count($rulesetNode->paths);
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
foreach($allExtends as $allExtend){
for($pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ){
// extending extends happens initially, before the main pass
if( isset($rulesetNode->extendOnEveryPath) && $rulesetNode->extendOnEveryPath ){
continue;
}
$selectorPath = $rulesetNode->paths[$pathIndex];
if( end($selectorPath)->extendList ){
continue;
}
$this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath);
}
}
}
private function ExtendMatch( $rulesetNode, $extend, $selectorPath ){
$matches = $this->findMatch($extend, $selectorPath);
if( $matches ){
foreach($extend->selfSelectors as $selfSelector ){
$rulesetNode->paths[] = $this->extendSelector($matches, $selectorPath, $selfSelector);
}
}
}
private function findMatch($extend, $haystackSelectorPath ){
if( !$this->HasMatches($extend, $haystackSelectorPath) ){
return false;
}
//
// look through the haystack selector path to try and find the needle - extend.selector
// returns an array of selector matches that can then be replaced
//
$needleElements = $extend->selector->elements;
$potentialMatches = array();
$potentialMatches_len = 0;
$potentialMatch = null;
$matches = array();
// loop through the haystack elements
$haystack_path_len = count($haystackSelectorPath);
for($haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ){
$hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex];
$haystack_elements_len = count($hackstackSelector->elements);
for($hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ){
$haystackElement = $hackstackSelector->elements[$hackstackElementIndex];
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
if( $extend->allowBefore || ($haystackSelectorIndex === 0 && $hackstackElementIndex === 0) ){
$potentialMatches[] = array('pathIndex'=> $haystackSelectorIndex, 'index'=> $hackstackElementIndex, 'matched'=> 0, 'initialCombinator'=> $haystackElement->combinator);
$potentialMatches_len++;
}
for($i = 0; $i < $potentialMatches_len; $i++ ){
$potentialMatch = &$potentialMatches[$i];
$potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex );
// if we are still valid and have finished, test whether we have elements after and whether these are allowed
if( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ){
$potentialMatch['finished'] = true;
if( !$extend->allowAfter && ($hackstackElementIndex+1 < $haystack_elements_len || $haystackSelectorIndex+1 < $haystack_path_len) ){
$potentialMatch = null;
}
}
// if null we remove, if not, we are still valid, so either push as a valid match or continue
if( $potentialMatch ){
if( $potentialMatch['finished'] ){
$potentialMatch['length'] = $extend->selector->elements_len;
$potentialMatch['endPathIndex'] = $haystackSelectorIndex;
$potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match
$potentialMatches = array(); // we don't allow matches to overlap, so start matching again
$potentialMatches_len = 0;
$matches[] = $potentialMatch;
}
continue;
}
array_splice($potentialMatches, $i, 1);
$potentialMatches_len--;
$i--;
}
}
}
return $matches;
}
// Before going through all the nested loops, lets check to see if a match is possible
// Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s
private function HasMatches($extend, $haystackSelectorPath){
if( !$extend->selector->cacheable ){
return true;
}
$first_el = $extend->selector->_oelements[0];
foreach($haystackSelectorPath as $hackstackSelector){
if( !$hackstackSelector->cacheable ){
return true;
}
if( in_array($first_el, $hackstackSelector->_oelements) ){
return true;
}
}
return false;
}
/**
* @param integer $hackstackElementIndex
*/
private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ){
if( $potentialMatch['matched'] > 0 ){
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
// what the resulting combinator will be
$targetCombinator = $haystackElement->combinator;
if( $targetCombinator === '' && $hackstackElementIndex === 0 ){
$targetCombinator = ' ';
}
if( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ){
return null;
}
}
// if we don't match, null our match to indicate failure
if( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value) ){
return null;
}
$potentialMatch['finished'] = false;
$potentialMatch['matched']++;
return $potentialMatch;
}
private function isElementValuesEqual( $elementValue1, $elementValue2 ){
if( $elementValue1 === $elementValue2 ){
return true;
}
if( is_string($elementValue1) || is_string($elementValue2) ) {
return false;
}
if( $elementValue1 instanceof Less_Tree_Attribute ){
return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 );
}
$elementValue1 = $elementValue1->value;
if( $elementValue1 instanceof Less_Tree_Selector ){
return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 );
}
return false;
}
/**
* @param Less_Tree_Selector $elementValue1
*/
private function isSelectorValuesEqual( $elementValue1, $elementValue2 ){
$elementValue2 = $elementValue2->value;
if( !($elementValue2 instanceof Less_Tree_Selector) || $elementValue1->elements_len !== $elementValue2->elements_len ){
return false;
}
for( $i = 0; $i < $elementValue1->elements_len; $i++ ){
if( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ){
if( $i !== 0 || ($elementValue1->elements[$i]->combinator || ' ') !== ($elementValue2->elements[$i]->combinator || ' ') ){
return false;
}
}
if( !$this->isElementValuesEqual($elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value) ){
return false;
}
}
return true;
}
/**
* @param Less_Tree_Attribute $elementValue1
*/
private function isAttributeValuesEqual( $elementValue1, $elementValue2 ){
if( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ){
return false;
}
if( !$elementValue1->value || !$elementValue2->value ){
if( $elementValue1->value || $elementValue2->value ) {
return false;
}
return true;
}
$elementValue1 = ($elementValue1->value->value ? $elementValue1->value->value : $elementValue1->value );
$elementValue2 = ($elementValue2->value->value ? $elementValue2->value->value : $elementValue2->value );
return $elementValue1 === $elementValue2;
}
private function extendSelector($matches, $selectorPath, $replacementSelector){
//for a set of matches, replace each match with the replacement selector
$currentSelectorPathIndex = 0;
$currentSelectorPathElementIndex = 0;
$path = array();
$selectorPath_len = count($selectorPath);
for($matchIndex = 0, $matches_len = count($matches); $matchIndex < $matches_len; $matchIndex++ ){
$match = $matches[$matchIndex];
$selector = $selectorPath[ $match['pathIndex'] ];
$firstElement = new Less_Tree_Element(
$match['initialCombinator'],
$replacementSelector->elements[0]->value,
$replacementSelector->elements[0]->index,
$replacementSelector->elements[0]->currentFileInfo
);
if( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ){
$last_path = end($path);
$last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
$currentSelectorPathElementIndex = 0;
$currentSelectorPathIndex++;
}
$newElements = array_merge(
array_slice($selector->elements, $currentSelectorPathElementIndex, ($match['index'] - $currentSelectorPathElementIndex) ) // last parameter of array_slice is different than the last parameter of javascript's slice
, array($firstElement)
, array_slice($replacementSelector->elements,1)
);
if( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ){
$last_key = count($path)-1;
$path[$last_key]->elements = array_merge($path[$last_key]->elements,$newElements);
}else{
$path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ));
$path[] = new Less_Tree_Selector( $newElements );
}
$currentSelectorPathIndex = $match['endPathIndex'];
$currentSelectorPathElementIndex = $match['endPathElementIndex'];
if( $currentSelectorPathElementIndex >= count($selectorPath[$currentSelectorPathIndex]->elements) ){
$currentSelectorPathElementIndex = 0;
$currentSelectorPathIndex++;
}
}
if( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ){
$last_path = end($path);
$last_path->elements = array_merge( $last_path->elements, array_slice($selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
$currentSelectorPathIndex++;
}
$slice_len = $selectorPath_len - $currentSelectorPathIndex;
$path = array_merge($path, array_slice($selectorPath, $currentSelectorPathIndex, $slice_len));
return $path;
}
protected function visitMedia( $mediaNode ){
$newAllExtends = array_merge( $mediaNode->allExtends, end($this->allExtendsStack) );
$this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $mediaNode->allExtends);
}
protected function visitMediaOut(){
array_pop( $this->allExtendsStack );
}
protected function visitDirective( $directiveNode ){
$newAllExtends = array_merge( $directiveNode->allExtends, end($this->allExtendsStack) );
$this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $directiveNode->allExtends);
}
protected function visitDirectiveOut(){
array_pop($this->allExtendsStack);
}
}
/**
* toCSS Visitor
*
* @package Less
* @subpackage visitor
*/
class Less_Visitor_toCSS extends Less_VisitorReplacing{
private $charset;
function __construct(){
parent::__construct();
}
/**
* @param Less_Tree_Ruleset $root
*/
function run( $root ){
return $this->visitObj($root);
}
function visitRule( $ruleNode ){
if( $ruleNode->variable ){
return array();
}
return $ruleNode;
}
function visitMixinDefinition($mixinNode){
// mixin definitions do not get eval'd - this means they keep state
// so we have to clear that state here so it isn't used if toCSS is called twice
$mixinNode->frames = array();
return array();
}
function visitExtend(){
return array();
}
function visitComment( $commentNode ){
if( $commentNode->isSilent() ){
return array();
}
return $commentNode;
}
function visitMedia( $mediaNode, &$visitDeeper ){
$mediaNode->accept($this);
$visitDeeper = false;
if( !$mediaNode->rules ){
return array();
}
return $mediaNode;
}
function visitDirective( $directiveNode ){
if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){
return array();
}
if( $directiveNode->name === '@charset' ){
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive would
// be considered illegal css as it has to be on the first line
if( isset($this->charset) && $this->charset ){
//if( $directiveNode->debugInfo ){
// $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
// $comment->debugInfo = $directiveNode->debugInfo;
// return $this->visit($comment);
//}
return array();
}
$this->charset = true;
}
return $directiveNode;
}
function checkPropertiesInRoot( $rulesetNode ){
if( !$rulesetNode->firstRoot ){
return;
}
foreach($rulesetNode->rules as $ruleNode){
if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){
$msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null);
throw new Less_Exception_Compiler($msg);
}
}
}
function visitRuleset( $rulesetNode, &$visitDeeper ){
$visitDeeper = false;
$this->checkPropertiesInRoot( $rulesetNode );
if( $rulesetNode->root ){
return $this->visitRulesetRoot( $rulesetNode );
}
$rulesets = array();
$rulesetNode->paths = $this->visitRulesetPaths($rulesetNode);
// by pass error; debug later - KHANH
if (!$rulesetNode->rules) return $rulesets;
//;
// Compile rules and rulesets
$nodeRuleCnt = count($rulesetNode->rules);
for( $i = 0; $i < $nodeRuleCnt; ){
$rule = $rulesetNode->rules[$i];
if( property_exists($rule,'rules') ){
// visit because we are moving them out from being a child
$rulesets[] = $this->visitObj($rule);
array_splice($rulesetNode->rules,$i,1);
$nodeRuleCnt--;
continue;
}
$i++;
}
// accept the visitor to remove rules and refactor itself
// then we can decide now whether we want it or not
if( $nodeRuleCnt > 0 ){
$rulesetNode->accept($this);
if( $rulesetNode->rules ){
if( count($rulesetNode->rules) > 1 ){
$this->_mergeRules( $rulesetNode->rules );
$this->_removeDuplicateRules( $rulesetNode->rules );
}
// now decide whether we keep the ruleset
if( $rulesetNode->paths ){
//array_unshift($rulesets, $rulesetNode);
array_splice($rulesets,0,0,array($rulesetNode));
}
}
}
if( count($rulesets) === 1 ){
return $rulesets[0];
}
return $rulesets;
}
/**
* Helper function for visitiRuleset
*
* return array|Less_Tree_Ruleset
*/
private function visitRulesetRoot( $rulesetNode ){
$rulesetNode->accept( $this );
if( $rulesetNode->firstRoot || $rulesetNode->rules ){
return $rulesetNode;
}
return array();
}
/**
* Helper function for visitRuleset()
*
* @return array
*/
private function visitRulesetPaths($rulesetNode){
$paths = array();
foreach($rulesetNode->paths as $p){
if( $p[0]->elements[0]->combinator === ' ' ){
$p[0]->elements[0]->combinator = '';
}
foreach($p as $pi){
if( $pi->getIsReferenced() && $pi->getIsOutput() ){
$paths[] = $p;
break;
}
}
}
return $paths;
}
function _removeDuplicateRules( &$rules ){
// remove duplicates
$ruleCache = array();
for( $i = count($rules)-1; $i >= 0 ; $i-- ){
$rule = $rules[$i];
if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){
if( !isset($ruleCache[$rule->name]) ){
$ruleCache[$rule->name] = $rule;
}else{
$ruleList =& $ruleCache[$rule->name];
if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){
$ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
}
$ruleCSS = $rule->toCSS();
if( array_search($ruleCSS,$ruleList) !== false ){
array_splice($rules,$i,1);
}else{
$ruleList[] = $ruleCSS;
}
}
}
}
}
function _mergeRules( &$rules ){
$groups = array();
//obj($rules);
$rules_len = count($rules);
for( $i = 0; $i < $rules_len; $i++ ){
$rule = $rules[$i];
if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){
$key = $rule->name;
if( $rule->important ){
$key .= ',!';
}
if( !isset($groups[$key]) ){
$groups[$key] = array();
}else{
array_splice($rules, $i--, 1);
$rules_len--;
}
$groups[$key][] = $rule;
}
}
foreach($groups as $parts){
if( count($parts) > 1 ){
$rule = $parts[0];
$spacedGroups = array();
$lastSpacedGroup = array();
$parts_mapped = array();
foreach($parts as $p){
if( $p->merge === '+' ){
if( $lastSpacedGroup ){
$spacedGroups[] = self::toExpression($lastSpacedGroup);
}
$lastSpacedGroup = array();
}
$lastSpacedGroup[] = $p;
}
$spacedGroups[] = self::toExpression($lastSpacedGroup);
$rule->value = self::toValue($spacedGroups);
}
}
}
static function toExpression($values){
$mapped = array();
foreach($values as $p){
$mapped[] = $p->value;
}
return new Less_Tree_Expression( $mapped );
}
static function toValue($values){
//return new Less_Tree_Value($values); ??
$mapped = array();
foreach($values as $p){
$mapped[] = $p;
}
return new Less_Tree_Value($mapped);
}
}
/**
* Parser Exception
*
* @package Less
* @subpackage exception
*/
class Less_Exception_Parser extends Exception{
/**
* The current file
*
* @var Less_ImportedFile
*/
public $currentFile;
/**
* The current parser index
*
* @var integer
*/
public $index;
protected $input;
protected $details = array();
/**
* Constructor
*
* @param string $message
* @param Exception $previous Previous exception
* @param integer $index The current parser index
* @param Less_FileInfo|string $currentFile The file
* @param integer $code The exception code
*/
public function __construct($message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
if (PHP_VERSION_ID < 50300) {
$this->previous = $previous;
parent::__construct($message, $code);
} else {
parent::__construct($message, $code, $previous);
}
$this->currentFile = $currentFile;
$this->index = $index;
$this->genMessage();
}
protected function getInput(){
if( !$this->input && $this->currentFile && $this->currentFile['filename'] ){
$this->input = file_get_contents( $this->currentFile['filename'] );
}
}
/**
* Converts the exception to string
*
* @return string
*/
public function genMessage(){
if( $this->currentFile && $this->currentFile['filename'] ){
$this->message .= ' in '.basename($this->currentFile['filename']);
}
if( $this->index !== null ){
$this->getInput();
if( $this->input ){
$line = self::getLineNumber();
$this->message .= ' on line '.$line.', column '.self::getColumn();
$lines = explode("\n",$this->input);
$count = count($lines);
$start_line = max(0, $line-3);
$last_line = min($count, $start_line+6);
$num_len = strlen($last_line);
for( $i = $start_line; $i < $last_line; $i++ ){
$this->message .= "\n".str_pad($i+1,$num_len,'0',STR_PAD_LEFT).'| '.$lines[$i];
}
}
}
}
/**
* Returns the line number the error was encountered
*
* @return integer
*/
public function getLineNumber(){
if( $this->index ){
return substr_count($this->input, "\n", 0, $this->index) + 1;
}
return 1;
}
/**
* Returns the column the error was encountered
*
* @return integer
*/
public function getColumn(){
$part = substr($this->input, 0, $this->index);
$pos = strrpos($part,"\n");
return $this->index - $pos;
}
}
/**
* Chunk Exception
*
* @package Less
* @subpackage exception
*/
class Less_Exception_Chunk extends Less_Exception_Parser{
protected $parserCurrentIndex = 0;
protected $emitFrom = 0;
protected $input_len;
/**
* Constructor
*
* @param string $input
* @param Exception $previous Previous exception
* @param integer $index The current parser index
* @param Less_FileInfo|string $currentFile The file
* @param integer $code The exception code
*/
public function __construct($input, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
$this->message = 'ParseError: Unexpected input'; //default message
$this->index = $index;
$this->currentFile = $currentFile;
$this->input = $input;
$this->input_len = strlen($input);
$this->Chunks();
$this->genMessage();
}
/**
* See less.js chunks()
* We don't actually need the chunks
*
*/
function Chunks(){
$level = 0;
$parenLevel = 0;
$lastMultiCommentEndBrace = null;
$lastOpening = null;
$lastMultiComment = null;
$lastParen = null;
for( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ){
$cc = $this->CharCode($this->parserCurrentIndex);
if ((($cc >= 97) && ($cc <= 122)) || ($cc < 34)) {
// a-z or whitespace
continue;
}
switch ($cc) {
// (
case 40:
$parenLevel++;
$lastParen = $this->parserCurrentIndex;
continue 2;
// )
case 41:
$parenLevel--;
if( $parenLevel < 0 ){
return $this->fail("missing opening `(`");
}
continue 2;
// ;
case 59:
//if (!$parenLevel) { $this->emitChunk(); }
continue 2;
// {
case 123:
$level++;
$lastOpening = $this->parserCurrentIndex;
continue 2;
// }
case 125:
$level--;
if( $level < 0 ){
return $this->fail("missing opening `{`");
}
//if (!$level && !$parenLevel) { $this->emitChunk(); }
continue 2;
// \
case 92:
if ($this->parserCurrentIndex < $this->input_len - 1) { $this->parserCurrentIndex++; continue 2; }
return $this->fail("unescaped `\\`");
// ", ' and `
case 34:
case 39:
case 96:
$matched = 0;
$currentChunkStartIndex = $this->parserCurrentIndex;
for ($this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
$cc2 = $this->CharCode($this->parserCurrentIndex);
if ($cc2 > 96) { continue; }
if ($cc2 == $cc) { $matched = 1; break; }
if ($cc2 == 92) { // \
if ($this->parserCurrentIndex == $this->input_len - 1) {
return $this->fail("unescaped `\\`");
}
$this->parserCurrentIndex++;
}
}
if ($matched) { continue 2; }
return $this->fail("unmatched `" + chr($cc) + "`", $currentChunkStartIndex);
// /, check for comment
case 47:
if ($parenLevel || ($this->parserCurrentIndex == $this->input_len - 1)) { continue 2; }
$cc2 = $this->CharCode($this->parserCurrentIndex+1);
if ($cc2 == 47) {
// //, find lnfeed
for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
$cc2 = $this->CharCode($this->parserCurrentIndex);
if (($cc2 <= 13) && (($cc2 == 10) || ($cc2 == 13))) { break; }
}
} else if ($cc2 == 42) {
// /*, find */
$lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++) {
$cc2 = $this->CharCode($this->parserCurrentIndex);
if ($cc2 == 125) { $lastMultiCommentEndBrace = $this->parserCurrentIndex; }
if ($cc2 != 42) { continue; }
if ($this->CharCode($this->parserCurrentIndex+1) == 47) { break; }
}
if ($this->parserCurrentIndex == $this->input_len - 1) {
return $this->fail("missing closing `*/`", $currentChunkStartIndex);
}
}
continue 2;
// *, check for unmatched */
case 42:
if (($this->parserCurrentIndex < $this->input_len - 1) && ($this->CharCode($this->parserCurrentIndex+1) == 47)) {
return $this->fail("unmatched `/*`");
}
continue 2;
}
}
if( $level !== 0 ){
if( ($lastMultiComment > $lastOpening) && ($lastMultiCommentEndBrace > $lastMultiComment) ){
return $this->fail("missing closing `}` or `*/`", $lastOpening);
} else {
return $this->fail("missing closing `}`", $lastOpening);
}
} else if ( $parenLevel !== 0 ){
return $this->fail("missing closing `)`", $lastParen);
}
//chunk didn't fail
//$this->emitChunk(true);
}
function CharCode($pos){
return ord($this->input[$pos]);
}
function fail( $msg, $index = null ){
if( !$index ){
$this->index = $this->parserCurrentIndex;
}else{
$this->index = $index;
}
$this->message = 'ParseError: '.$msg;
}
/*
function emitChunk( $force = false ){
$len = $this->parserCurrentIndex - $this->emitFrom;
if ((($len < 512) && !$force) || !$len) {
return;
}
$chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
$this->emitFrom = $this->parserCurrentIndex + 1;
}
*/
}
/**
* Compiler Exception
*
* @package Less
* @subpackage exception
*/
class Less_Exception_Compiler extends Less_Exception_Parser{
}
/**
* Parser output with source map
*
* @package Less
* @subpackage Output
*/
class Less_Output_Mapped extends Less_Output {
/**
* The source map generator
*
* @var Less_SourceMap_Generator
*/
protected $generator;
/**
* Current line
*
* @var integer
*/
protected $lineNumber = 0;
/**
* Current column
*
* @var integer
*/
protected $column = 0;
/**
* Array of contents map (file and its content)
*
* @var array
*/
protected $contentsMap = array();
/**
* Constructor
*
* @param array $contentsMap Array of filename to contents map
* @param Less_SourceMap_Generator $generator
*/
public function __construct(array $contentsMap, $generator){
$this->contentsMap = $contentsMap;
$this->generator = $generator;
}
/**
* Adds a chunk to the stack
* The $index for less.php may be different from less.js since less.php does not chunkify inputs
*
* @param string $chunk
* @param string $fileInfo
* @param integer $index
* @param mixed $mapLines
*/
public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
//ignore adding empty strings
if( $chunk === '' ){
return;
}
$sourceLines = array();
$sourceColumns = ' ';
if( $fileInfo ){
$url = $fileInfo['currentUri'];
if( isset($this->contentsMap[$url]) ){
$inputSource = substr($this->contentsMap[$url], 0, $index);
$sourceLines = explode("\n", $inputSource);
$sourceColumns = end($sourceLines);
}else{
throw new Exception('Filename '.$url.' not in contentsMap');
}
}
$lines = explode("\n", $chunk);
$columns = end($lines);
if($fileInfo){
if(!$mapLines){
$this->generator->addMapping(
$this->lineNumber + 1, // generated_line
$this->column, // generated_column
count($sourceLines), // original_line
strlen($sourceColumns), // original_column
$fileInfo['currentUri']
);
}else{
for($i = 0, $count = count($lines); $i < $count; $i++){
$this->generator->addMapping(
$this->lineNumber + $i + 1, // generated_line
$i === 0 ? $this->column : 0, // generated_column
count($sourceLines) + $i, // original_line
$i === 0 ? strlen($sourceColumns) : 0, // original_column
$fileInfo['currentUri']
);
}
}
}
if(count($lines) === 1){
$this->column += strlen($columns);
}else{
$this->lineNumber += count($lines) - 1;
$this->column = strlen($columns);
}
// add only chunk
parent::add($chunk);
}
}
/**
* Encode / Decode Base64 VLQ.
*
* @package Less
* @subpackage SourceMap
*/
class Less_SourceMap_Base64VLQ {
/**
* Shift
*
* @var integer
*/
private $shift = 5;
/**
* Mask
*
* @var integer
*/
private $mask = 0x1F; // == (1 << shift) == 0b00011111
/**
* Continuation bit
*
* @var integer
*/
private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
/**
* Char to integer map
*
* @var array
*/
private $charToIntMap = array(
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
);
/**
* Integer to char map
*
* @var array
*/
private $intToCharMap = array(
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
63 => '/',
);
/**
* Constructor
*/
public function __construct(){
// I leave it here for future reference
// foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
// {
// $this->charToIntMap[$char] = $i;
// $this->intToCharMap[$i] = $char;
// }
}
/**
* Convert from a two-complement value to a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
* We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
* even on a 64 bit machine.
* @param string $aValue
*/
public function toVLQSigned($aValue){
return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0);
}
/**
* Convert to a two-complement value from a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
* We assume that the value was generated with a 32 bit machine in mind.
* Hence
* 1 becomes -2147483648
* even on a 64 bit machine.
* @param integer $aValue
*/
public function fromVLQSigned($aValue){
return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1);
}
/**
* Return the base 64 VLQ encoded value.
*
* @param string $aValue The value to encode
* @return string The encoded value
*/
public function encode($aValue){
$encoded = '';
$vlq = $this->toVLQSigned($aValue);
do
{
$digit = $vlq & $this->mask;
$vlq = $this->zeroFill($vlq, $this->shift);
if($vlq > 0){
$digit |= $this->continuationBit;
}
$encoded .= $this->base64Encode($digit);
} while($vlq > 0);
return $encoded;
}
/**
* Return the value decoded from base 64 VLQ.
*
* @param string $encoded The encoded value to decode
* @return integer The decoded value
*/
public function decode($encoded){
$vlq = 0;
$i = 0;
do
{
$digit = $this->base64Decode($encoded[$i]);
$vlq |= ($digit & $this->mask) << ($i * $this->shift);
$i++;
} while($digit & $this->continuationBit);
return $this->fromVLQSigned($vlq);
}
/**
* Right shift with zero fill.
*
* @param integer $a number to shift
* @param integer $b number of bits to shift
* @return integer
*/
public function zeroFill($a, $b){
return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1));
}
/**
* Encode single 6-bit digit as base64.
*
* @param integer $number
* @return string
* @throws Exception If the number is invalid
*/
public function base64Encode($number){
if($number < 0 || $number > 63){
throw new Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number));
}
return $this->intToCharMap[$number];
}
/**
* Decode single 6-bit digit from base64
*
* @param string $char
* @return number
* @throws Exception If the number is invalid
*/
public function base64Decode($char){
if(!array_key_exists($char, $this->charToIntMap)){
throw new Exception(sprintf('Invalid base 64 digit "%s" given.', $char));
}
return $this->charToIntMap[$char];
}
}
/**
* Source map generator
*
* @package Less
* @subpackage Output
*/
class Less_SourceMap_Generator extends Less_Configurable {
/**
* What version of source map does the generator generate?
*/
const VERSION = 3;
/**
* Array of default options
*
* @var array
*/
protected $defaultOptions = array(
// an optional source root, useful for relocating source files
// on a server or removing repeated values in the 'sources' entry.
// This value is prepended to the individual entries in the 'source' field.
'sourceRoot' => '',
// an optional name of the generated code that this source map is associated with.
'sourceMapFilename' => null,
// url of the map
'sourceMapURL' => null,
// absolute path to a file to write the map to
'sourceMapWriteTo' => null,
// output source contents?
'outputSourceFiles' => false,
// base path for filename normalization
'sourceMapBasepath' => ''
);
/**
* The base64 VLQ encoder
*
* @var Less_SourceMap_Base64VLQ
*/
protected $encoder;
/**
* Array of mappings
*
* @var array
*/
protected $mappings = array();
/**
* The root node
*
* @var Less_Tree_Ruleset
*/
protected $root;
/**
* Array of contents map
*
* @var array
*/
protected $contentsMap = array();
/**
* File to content map
*
* @var array
*/
protected $sources = array();
/**
* Constructor
*
* @param Less_Tree_Ruleset $root The root node
* @param array $options Array of options
*/
public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){
$this->root = $root;
$this->contentsMap = $contentsMap;
$this->encoder = new Less_SourceMap_Base64VLQ();
$this->SetOptions($options);
// fix windows paths
if( isset($this->options['sourceMapBasepath']) ){
$this->options['sourceMapBasepath'] = str_replace('\\', '/', $this->options['sourceMapBasepath']);
}
}
/**
* Generates the CSS
*
* @return string
*/
public function generateCSS(){
$output = new Less_Output_Mapped($this->contentsMap, $this);
// catch the output
$this->root->genCSS($output);
$sourceMapUrl = $this->getOption('sourceMapURL');
$sourceMapFilename = $this->getOption('sourceMapFilename');
$sourceMapContent = $this->generateJson();
$sourceMapWriteTo = $this->getOption('sourceMapWriteTo');
if( !$sourceMapUrl && $sourceMapFilename ){
$sourceMapUrl = $this->normalizeFilename($sourceMapFilename);
}
// write map to a file
if( $sourceMapWriteTo ){
$this->saveMap($sourceMapWriteTo, $sourceMapContent);
}
// inline the map
if( !$sourceMapUrl ){
$sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent));
}
if( $sourceMapUrl ){
$output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) );
}
return $output->toString();
}
/**
* Saves the source map to a file
*
* @param string $file The absolute path to a file
* @param string $content The content to write
* @throws Exception If the file could not be saved
*/
protected function saveMap($file, $content){
$dir = dirname($file);
// directory does not exist
if( !is_dir($dir) ){
// FIXME: create the dir automatically?
throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir));
}
// FIXME: proper saving, with dir write check!
if(file_put_contents($file, $content) === false){
throw new Exception(sprintf('Cannot save the source map to "%s"', $file));
}
return true;
}
/**
* Normalizes the filename
*
* @param string $filename
* @return string
*/
protected function normalizeFilename($filename){
$filename = str_replace('\\', '/', $filename);
$basePath = $this->getOption('sourceMapBasepath');
if( $basePath && ($pos = strpos($filename, $basePath)) !== false ){
$filename = substr($filename, $pos + strlen($basePath));
if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){
$filename = substr($filename, 1);
}
}
return sprintf('%s%s', $this->getOption('sourceMapRootpath'), $filename);
}
/**
* Adds a mapping
*
* @param integer $generatedLine The line number in generated file
* @param integer $generatedColumn The column number in generated file
* @param integer $originalLine The line number in original file
* @param integer $originalColumn The column number in original file
* @param string $sourceFile The original source file
*/
public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile){
$this->mappings[] = array(
'generated_line' => $generatedLine,
'generated_column' => $generatedColumn,
'original_line' => $originalLine,
'original_column' => $originalColumn,
'source_file' => $sourceFile
);
$norm_file = $this->normalizeFilename($sourceFile);
$this->sources[$norm_file] = $sourceFile;
}
/**
* Generates the JSON source map
*
* @return string
* @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
*/
protected function generateJson(){
$sourceMap = array();
$mappings = $this->generateMappings();
// File version (always the first entry in the object) and must be a positive integer.
$sourceMap['version'] = self::VERSION;
// An optional name of the generated code that this source map is associated with.
$file = $this->getOption('sourceMapFilename');
if( $file ){
$sourceMap['file'] = $file;
}
// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field.
$root = $this->getOption('sourceRoot');
if( $root ){
$sourceMap['sourceRoot'] = $root;
}
// A list of original sources used by the 'mappings' entry.
$sourceMap['sources'] = array_keys($this->sources);
// A list of symbol names used by the 'mappings' entry.
$sourceMap['names'] = array();
// A string with the encoded mapping data.
$sourceMap['mappings'] = $mappings;
if( $this->getOption('outputSourceFiles') ){
// An optional list of source content, useful when the 'source' can't be hosted.
// The contents are listed in the same order as the sources above.
// 'null' may be used if some original sources should be retrieved by name.
$sourceMap['sourcesContent'] = $this->getSourcesContent();
}
// less.js compat fixes
if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){
unset($sourceMap['sourceRoot']);
}
return json_encode($sourceMap);
}
/**
* Returns the sources contents
*
* @return array|null
*/
protected function getSourcesContent(){
if(empty($this->sources)){
return;
}
$content = array();
foreach($this->sources as $sourceFile){
$content[] = file_get_contents($sourceFile);
}
return $content;
}
/**
* Generates the mappings string
*
* @return string
*/
public function generateMappings(){
if( !count($this->mappings) ){
return '';
}
// group mappings by generated line number.
$groupedMap = $groupedMapEncoded = array();
foreach($this->mappings as $m){
$groupedMap[$m['generated_line']][] = $m;
}
ksort($groupedMap);
$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
foreach($groupedMap as $lineNumber => $line_map){
while(++$lastGeneratedLine < $lineNumber){
$groupedMapEncoded[] = ';';
}
$lineMapEncoded = array();
$lastGeneratedColumn = 0;
foreach($line_map as $m){
$mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn);
$lastGeneratedColumn = $m['generated_column'];
// find the index
if( $m['source_file'] ){
$index = $this->findFileIndex($this->normalizeFilename($m['source_file']));
if( $index !== false ){
$mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex);
$lastOriginalIndex = $index;
// lines are stored 0-based in SourceMap spec version 3
$mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine);
$lastOriginalLine = $m['original_line'] - 1;
$mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn);
$lastOriginalColumn = $m['original_column'];
}
}
$lineMapEncoded[] = $mapEncoded;
}
$groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';';
}
return rtrim(implode($groupedMapEncoded), ';');
}
/**
* Finds the index for the filename
*
* @param string $filename
* @return integer|false
*/
protected function findFileIndex($filename){
return array_search($filename, array_keys($this->sources));
}
}
lessphp/less.php 0000604 00000002223 15245530303 0007674 0 ustar 00 <?php
/**
*------------------------------------------------------------------------------
* @package T3 Framework for Joomla!
*------------------------------------------------------------------------------
* @copyright Copyright (C) 2004-2013 JoomlArt.com. All Rights Reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
* @authors JoomlArt, JoomlaBamboo, (contribute to this project at github
* & Google group to become co-author)
* @Google group: https://groups.google.com/forum/#!forum/t3fw
* @Link: http://t3-framework.org
*------------------------------------------------------------------------------
*/
// No direct access
defined('_JEXEC') or die();
T3::import('lessphp/less/less');
/**
* T3LessCompiler class compile less
*
* @package T3
*/
// prevent over max_nesting config in some case
@ini_set('xdebug.max_nesting_level', 120);
class T3LessCompiler
{
public static function compile ($source, $importdirs) {
$parser = new Less_Parser();
$parser->SetImportDirs($importdirs);
$parser->parse($source);
$output = $parser->getCss();
return $output;
}
}
lessphp/lessc.inc.php 0000604 00000263262 15245530303 0010623 0 ustar 00 <?php
/**
* lessphp v0.4.0
* 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
*/
/**
* The less compiler and parser.
*
* Converting LESS to CSS is a three stage process. The incoming file is parsed
* by `lessc_parser` into a syntax tree, then it is compiled into another tree
* representing the CSS structure by `lessc`. The CSS tree is fed into a
* formatter, like `lessc_formatter` which then outputs CSS as a string.
*
* During the first compile, all values are *reduced*, which means that their
* types are brought to the lowest form before being dump as strings. This
* handles math equations, variable dereferences, and the like.
*
* The `parse` function of `lessc` is the entry point.
*
* In summary:
*
* The `lessc` class creates an intstance of the parser, feeds it LESS code,
* then transforms the resulting tree to a CSS tree. This class also holds the
* evaluation context, such as all available mixins and variables at any given
* time.
*
* The `lessc_parser` class is only concerned with parsing its input.
*
* The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
* handling things like indentation.
*/
class lessc {
static public $VERSION = "v0.4.0";
static protected $TRUE = array("keyword", "true");
static protected $FALSE = array("keyword", "false");
protected $libFunctions = array();
protected $registeredVars = array();
protected $preserveComments = false;
public $vPrefix = '@'; // prefix of abstract properties
public $mPrefix = '$'; // prefix of abstract blocks
public $parentSelector = '&';
public $importDisabled = false;
public $importDir = '';
protected $numberPrecision = null;
protected $allParsedFiles = array();
// set to the parser that generated the current line when compiling
// so we know how to create error messages
protected $sourceParser = null;
protected $sourceLoc = null;
static public $defaultValue = array("keyword", "");
static protected $nextImportId = 0; // uniquely identify imports
// attempts to find the path of an import url, returns null for css files
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;
}
protected function fileExists($name) {
return is_file($name);
}
static public function compressList($items, $delim) {
if (!isset($items[1]) && isset($items[0])) return $items[0];
else return array('list', $delim, $items);
}
static public function preg_quote($what) {
return preg_quote($what, '/');
}
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 */");
}
if (isset($this->allParsedFiles[realpath($realPath)])) {
return array(false, null);
}
$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);
}
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.
*
* See lessc::compileProp()
*
*/
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");
}
}
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);
$block->scope = $env; // mixins carry scope with them!
$this->popEnv();
}
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();
}
protected function mediaParent($scope) {
while (!empty($scope->parent)) {
if (!empty($scope->type) && $scope->type != "media") {
break;
}
$scope = $scope->parent;
}
return $scope;
}
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();
}
protected function compileRoot($root) {
$this->pushEnv();
$this->scope = $this->makeOutputBlock($root->type);
$this->compileProps($root, $this->scope);
$this->popEnv();
}
protected function compileProps($block, $out) {
foreach ($this->sortProps($block->props) as $prop) {
$this->compileProp($prop, $block, $out);
}
$out->lines = array_values(array_unique($out->lines));
}
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);
}
}
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;
}
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);
}
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;
}
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
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
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;
}
protected function eq($left, $right) {
return $left == $right;
}
protected function patternMatch($block, $orderedArgs, $keywordArgs) {
// 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, $orderedArgs, $keywordArgs);
$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;
}
}
if (empty($block->args)) {
return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
}
$remainingArgs = $block->args;
if ($keywordArgs) {
$remainingArgs = array();
foreach ($block->args as $arg) {
if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
continue;
}
$remainingArgs[] = $arg;
}
}
$i = -1; // no args
// try to match by arity or by argument literal
foreach ($remainingArgs as $i => $arg) {
switch ($arg[0]) {
case "lit":
if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
return false;
}
break;
case "arg":
// no arg and no default value
if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
return false;
}
break;
case "rest":
$i--; // rest can be empty
break 2;
}
}
if ($block->isVararg) {
return true; // not having enough is handled above
} else {
$numMatched = $i + 1;
// greater than becuase default values always match
return $numMatched >= count($orderedArgs);
}
}
protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
$matches = null;
foreach ($blocks as $block) {
// skip seen blocks that don't have arguments
if (isset($skip[$block->id]) && !isset($block->args)) {
continue;
}
if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
$matches[] = $block;
}
}
return $matches;
}
// attempt to find blocks matched by path and args
protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $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, $orderedArgs, $keywordArgs, $seen);
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), $orderedArgs, $keywordArgs, $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, $orderedArgs, $keywordArgs, $seen);
}
// sets all argument names in $args to either the default value
// or the one passed in through $values
protected function zipSetArgs($args, $orderedValues, $keywordValues) {
$assignedValues = array();
$i = 0;
foreach ($args as $a) {
if ($a[0] == "arg") {
if (isset($keywordValues[$a[1]])) {
// has keyword arg
$value = $keywordValues[$a[1]];
} elseif (isset($orderedValues[$i])) {
// has ordered arg
$value = $orderedValues[$i];
$i++;
} elseif (isset($a[2])) {
// has default value
$value = $a[2];
} else {
$this->throwError("Failed to assign arg " . $a[1]);
$value = null; // :(
}
$value = $this->reduce($value);
$this->set($a[1], $value);
$assignedValues[] = $value;
} else {
// a lit
$i++;
}
}
// check for a rest
$last = end($args);
if ($last[0] == "rest") {
$rest = array_slice($orderedValues, count($args) - 1);
$this->set($last[1], $this->reduce(array("list", " ", $rest)));
}
// wow is this the only true use of PHP's + operator for arrays?
$this->env->arguments = $assignedValues + $orderedValues;
}
// compile a prop and update $lines or $blocks appropriately
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;
$orderedArgs = array();
$keywordArgs = array();
foreach ((array)$args as $arg) {
$argval = null;
switch ($arg[0]) {
case "arg":
if (!isset($arg[2])) {
$orderedArgs[] = $this->reduce(array("variable", $arg[1]));
} else {
$keywordArgs[$arg[1]] = $this->reduce($arg[2]);
}
break;
case "lit":
$orderedArgs[] = $this->reduce($arg[1]);
break;
default:
$this->throwError("Unknown arg type: " . $arg[0]);
}
}
$mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
if ($mixins === null) {
// fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n");
break; // throw error here??
}
foreach ($mixins as $mixin) {
if ($mixin === $block && !$orderedArgs) {
continue;
}
$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, $orderedArgs, $keywordArgs);
}
$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) {
if (isset($import[1])) {
$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.
*/
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':
list(, $num, $unit) = $value;
// [1] - the number
// [2] - the unit
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':
// [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) { // rgba
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]");
}
}
protected function lib_pow($args) {
list($base, $exp) = $this->assertArgs($args, 2, "pow");
return pow($this->assertNumber($base), $this->assertNumber($exp));
}
protected function lib_pi() {
return pi();
}
protected function lib_mod($args) {
list($a, $b) = $this->assertArgs($args, 2, "mod");
return $this->assertNumber($a) % $this->assertNumber($b);
}
protected function lib_tan($num) {
return tan($this->assertNumber($num));
}
protected function lib_sin($num) {
return sin($this->assertNumber($num));
}
protected function lib_cos($num) {
return cos($this->assertNumber($num));
}
protected function lib_atan($num) {
$num = atan($this->assertNumber($num));
return array("number", $num, "rad");
}
protected function lib_asin($num) {
$num = asin($this->assertNumber($num));
return array("number", $num, "rad");
}
protected function lib_acos($num) {
$num = acos($this->assertNumber($num));
return array("number", $num, "rad");
}
protected function lib_sqrt($num) {
return sqrt($this->assertNumber($num));
}
protected function lib_extract($value) {
list($list, $idx) = $this->assertArgs($value, 2, "extract");
$idx = $this->assertNumber($idx);
// 1 indexed
if ($list[0] == "list" && isset($list[2][$idx - 1])) {
return $list[2][$idx - 1];
}
}
protected function lib_isnumber($value) {
return $this->toBool($value[0] == "number");
}
protected function lib_isstring($value) {
return $this->toBool($value[0] == "string");
}
protected function lib_iscolor($value) {
return $this->toBool($this->coerceColor($value));
}
protected function lib_iskeyword($value) {
return $this->toBool($value[0] == "keyword");
}
protected function lib_ispixel($value) {
return $this->toBool($value[0] == "number" && $value[2] == "px");
}
protected function lib_ispercentage($value) {
return $this->toBool($value[0] == "number" && $value[2] == "%");
}
protected function lib_isem($value) {
return $this->toBool($value[0] == "number" && $value[2] == "em");
}
protected function lib_isrem($value) {
return $this->toBool($value[0] == "number" && $value[2] == "rem");
}
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]);
}
protected function lib_argb($color){
return $this->lib_rgbahex($color);
}
// utility func to unquote a 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));
}
}
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));
}
protected function lib_floor($arg) {
$value = $this->assertNumber($arg);
return array("number", floor($value), $arg[2]);
}
protected function lib_ceil($arg) {
$value = $this->assertNumber($arg);
return array("number", ceil($value), $arg[2]);
}
protected function lib_round($arg) {
$value = $this->assertNumber($arg);
return array("number", round($value), $arg[2]);
}
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
*/
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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;
}
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;
}
protected function lib_hue($color) {
$hsl = $this->toHSL($this->assertColor($color));
return round($hsl[1]);
}
protected function lib_saturation($color) {
$hsl = $this->toHSL($this->assertColor($color));
return round($hsl[2]);
}
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
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
protected function lib_fade($args) {
list($color, $alpha) = $this->colorArgs($args);
$color[4] = $this->clamp($alpha / 100.0);
return $color;
}
protected function lib_percentage($arg) {
$num = $this->assertNumber($arg);
return array("number", $num*100, "%");
}
// mixes two colors by weight
// mix(@color1, @color2, [@weight: 50%]);
// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
protected function lib_mix($args) {
if ($args[0] != "list" || count($args[2]) < 2)
$this->throwError("mix expects (color1, color2, weight)");
list($first, $second) = $args[2];
$first = $this->assertColor($first);
$second = $this->assertColor($second);
$first_a = $this->lib_alpha($first);
$second_a = $this->lib_alpha($second);
if (isset($args[2][2])) {
$weight = $args[2][2][1] / 100.0;
} else {
$weight = 0.5;
}
$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);
}
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;
}
protected function assertColor($value, $error = "expected color value") {
$color = $this->coerceColor($value);
if (is_null($color)) $this->throwError($error);
return $color;
}
protected function assertNumber($value, $error = "expecting number") {
if ($value[0] == "number") return $value[1];
$this->throwError($error);
}
protected function assertArgs($value, $expectedArgs, $name="") {
if ($expectedArgs == 1) {
return $value;
} else {
if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
$values = $value[2];
$numValues = count($values);
if ($expectedArgs != $numValues) {
if ($name) {
$name = $name . ": ";
}
$this->throwError("{$name}expecting $expectedArgs arguments, got $numValues");
}
return $values;
}
}
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) $out[] = $color[4]; // copy alpha
return $out;
}
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
*/
protected function toRGB($color) {
if ($color[0] == '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) $out[] = $color[4]; // copy alpha
return $out;
}
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.
*/
protected function funcToColor($func) {
$fname = $func[1];
if ($func[2][0] != 'list') return false; // need a list of arguments
$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;
}
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 ($res[0] == "raw_color") {
$res = $this->coerceColor($res);
}
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
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--) { // 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
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
protected function flattenList($value) {
if ($value[0] == "list" && count($value[2]) == 1) {
return $this->flattenList($value[2][0]);
}
return $value;
}
protected function toBool($a) {
if ($a) return self::$TRUE;
else return self::$FALSE;
}
// evaluate an expression
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));
}
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
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;
}
protected function op_number_color($op, $lft, $rgt) {
if ($op == '+' || $op == '*') {
return $this->op_color_number($op, $rgt, $lft);
}
}
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]));
}
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);
}
function lib_red($color){
$color = $this->coerceColor($color);
if (is_null($color)) {
$this->throwError('color expected for red()');
}
return $color[1];
}
function lib_green($color){
$color = $this->coerceColor($color);
if (is_null($color)) {
$this->throwError('color expected for green()');
}
return $color[2];
}
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
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);
}
/* environment functions */
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
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
protected function popEnv() {
$old = $this->env;
$this->env = $this->env->parent;
return $old;
}
// set something in the current env
protected function set($name, $value) {
$this->env->store[$name] = $value;
}
// get the highest occurrence entry for a name
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
protected function injectVariables($args) {
$this->pushEnv();
$parser = new lessc_parser($this, __METHOD__);
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
* $opts isn't used yet
*/
public function __construct($fname = null) {
if ($fname !== null) {
// used for deprecated parse method
$this->_parseFile = $fname;
}
}
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);
}
$this->sourceParser = $this->parser; // used for error messages
$this->compileBlock($root);
ob_start();
$this->formatter->block($this->scope);
$out = ob_get_clean();
setlocale(LC_NUMERIC, $locale);
return $out;
}
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->addParsedFile($fname);
$out = $this->compile(file_get_contents($fname), $fname);
$this->importDir = $oldImport;
if ($outFname !== null) {
return file_put_contents($outFname, $out);
}
return $out;
}
// compile only if changed input has changed or output doesn't exist
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;
}
}
// parse and compile buffer
// This is deprecated
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;
}
protected function makeParser($name) {
$parser = new lessc_parser($this, $name);
$parser->writeComments = $this->preserveComments;
return $parser;
}
public function setFormatter($name) {
$this->formatterName = $name;
}
protected function newFormatter() {
$className = "lessc_formatter_lessjs";
if (!empty($this->formatterName)) {
if (!is_string($this->formatterName))
return $this->formatterName;
$className = "lessc_formatter_$this->formatterName";
}
return new $className;
}
public function setPreserveComments($preserve) {
$this->preserveComments = $preserve;
}
public function registerFunction($name, $func) {
$this->libFunctions[$name] = $func;
}
public function unregisterFunction($name) {
unset($this->libFunctions[$name]);
}
public function setVariables($variables) {
$this->registeredVars = array_merge($this->registeredVars, $variables);
}
public function unsetVariable($name) {
unset($this->registeredVars[$name]);
}
public function setImportDir($dirs) {
$this->importDir = (array)$dirs;
}
public function addImportDir($dir) {
$this->importDir = (array)$this->importDir;
$this->importDir[] = $dir;
}
public function allParsedFiles() {
return $this->allParsedFiles;
}
protected function addParsedFile($file) {
$this->allParsedFiles[realpath($file)] = filemtime($file);
}
/**
* Uses the current value of $this->count to show line and line number
*/
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
public static function ccompile($in, $out, $less = null) {
if ($less === null) {
$less = new self;
}
return $less->checkedCompile($in, $out);
}
public static function cexecute($in, $force = false, $less = null) {
if ($less === null) {
$less = new self;
}
return $less->cachedCompile($in, $force);
}
static protected $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'
);
}
// responsible for taking a string of LESS code and converting it into a
// syntax tree
class lessc_parser {
static protected $nextBlockId = 0; // used to uniquely identify blocks
static protected $precedence = array(
'=<' => 0,
'>=' => 0,
'=' => 0,
'<' => 0,
'>' => 0,
'+' => 1,
'-' => 1,
'*' => 2,
'/' => 2,
'%' => 2,
);
static protected $whitePattern;
static protected $commentMulti;
static protected $commentSingle = "//";
static protected $commentMultiLeft = "/*";
static protected $commentMultiRight = "*/";
// regex string to match any of the operators
static protected $operatorString;
// these properties will supress division unless it's inside parenthases
static protected $supressDivisionProps =
array('/border-radius$/i', '/^font$/i');
protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
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
static protected $literalCache = array();
public function __construct($lessc, $sourceName = null) {
$this->eatWhiteDefault = true;
// reference to less needed for vPrefix, mPrefix, and parentSelector
$this->lessc = $lessc;
$this->sourceName = $sourceName; // name used for error messages
$this->writeComments = false;
if (!self::$operatorString) {
self::$operatorString =
'('.implode('|', array_map(array('lessc', 'preg_quote'),
array_keys(self::$precedence))).')';
$commentSingle = lessc::preg_quote(self::$commentSingle);
$commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
$commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
}
}
public function parse($buffer) {
$this->count = 0;
$this->line = 1;
$this->env = null; // block stack
$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.
*/
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->argumentDef($argv, $isVararg) || 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;
return false; // got nothing, throw error
}
protected function isDirective($dirname, $directives) {
// TODO: cache pattern in parser
$pattern = implode("|",
array_map(array("lessc", "preg_quote"), $directives));
$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
return preg_match($pattern, $dirname);
}
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
protected function expressionList(&$exps) {
$values = array();
while ($this->expression($exp)) {
$values[] = $exp;
}
if (count($values) == 0) return false;
$exps = lessc::compressList($values, ' ');
return true;
}
/**
* Attempt to consume an expression.
* @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
*/
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
*/
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
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 = lessc::compressList($values, ', ');
return true;
}
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
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
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;
}
}
protected function mediaQueryList(&$out) {
if ($this->genericList($list, "mediaQuery", ",", false)) {
$out = $list[2];
return true;
}
return false;
}
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;
}
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
protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
$oldWhite = $this->eatWhiteDefault;
$this->eatWhiteDefault = false;
$stop = array("'", '"', "@{", $end);
$stop = array_map(array("lessc", "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 (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
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;
}
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]*?)(@\{|\\\\|' .
lessc::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]);
$content[] = "@{"; // ignore it
}
} elseif ($m[2] == '\\') {
$content[] = $m[2];
if ($this->literal($delim, false)) {
$content[] = $delim;
}
} else {
$this->count -= strlen($delim);
break; // delim
}
}
$this->eatWhiteDefault = $oldWhite;
if ($this->literal($delim)) {
$out = array("string", $delim, $content);
return true;
}
$this->seek($s);
return false;
}
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;
}
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
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 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 ...
// arguments are separated by , unless a ; is in the list, then ; is the
// delimiter.
protected function argumentDef(&$args, &$isVararg) {
$s = $this->seek();
if (!$this->literal('(')) return false;
$values = array();
$delim = ",";
$method = "expressionList";
$isVararg = false;
while (true) {
if ($this->literal("...")) {
$isVararg = true;
break;
}
if ($this->$method($value)) {
if ($value[0] == "variable") {
$arg = array("arg", $value[1]);
$ss = $this->seek();
if ($this->assign() && $this->$method($rhs)) {
$arg[] = $rhs;
} else {
$this->seek($ss);
if ($this->literal("...")) {
$arg[0] = "rest";
$isVararg = true;
}
}
$values[] = $arg;
if ($isVararg) break;
continue;
} else {
$values[] = array("lit", $value);
}
}
if (!$this->literal($delim)) {
if ($delim == "," && $this->literal(";")) {
// found new delim, convert existing args
$delim = ";";
$method = "propertyValue";
// transform arg list
if (isset($values[1])) { // 2 items
$newList = array();
foreach ($values as $i => $arg) {
switch($arg[0]) {
case "arg":
if ($i) {
$this->throwError("Cannot mix ; and , as delimiter types");
}
$newList[] = $arg[2];
break;
case "lit":
$newList[] = $arg[1];
break;
case "rest":
$this->throwError("Unexpected rest before semicolon");
}
}
$newList = array("list", ", ", $newList);
switch ($values[0][0]) {
case "arg":
$newArg = array("arg", $values[0][1], $newList);
break;
case "lit":
$newArg = array("lit", $newList);
break;
}
} elseif ($values) { // 1 item
$newArg = $values[0];
}
if ($newArg) {
$values = array($newArg);
}
} else {
break;
}
}
}
if (!$this->literal(')')) {
$this->seek($s);
return false;
}
$args = $values;
return true;
}
// consume a list of tags
// this accepts a hanging delimiter
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 >)
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)
protected function tagBracket(&$parts, &$hasExpression) {
// speed shortcut
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
return false;
}
$s = $this->seek();
$hasInterpolation = false;
if ($this->literal("[", false)) {
$attrParts = array("[");
// keyword, string, operator
while (true) {
if ($this->literal("]", false)) {
$this->count--;
break; // get out early
}
if ($this->match('\s+', $m)) {
$attrParts[] = " ";
continue;
}
if ($this->string($str)) {
// escape parent selector, (yuck)
foreach ($str[2] as &$chunk) {
$chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
}
$attrParts[] = $str;
$hasInterpolation = true;
continue;
}
if ($this->keyword($word)) {
$attrParts[] = $word;
continue;
}
if ($this->interpolation($inter, false)) {
$attrParts[] = $inter;
$hasInterpolation = true;
continue;
}
// operator, handles attr namespace too
if ($this->match('[|-~\$\*\^=]+', $m)) {
$attrParts[] = $m[0];
continue;
}
break;
}
if ($this->literal("]", false)) {
$attrParts[] = "]";
foreach ($attrParts as $part) {
$parts[] = $part;
}
$hasExpression = $hasExpression || $hasInterpolation;
return true;
}
$this->seek($s);
}
$this->seek($s);
return false;
}
// a space separated list of selectors
protected function tag(&$tag, $simple = false) {
if ($simple)
$chars = '^@,:;{}\][>\(\) "\'';
else
$chars = '^@,;{}["\'';
$s = $this->seek();
$hasExpression = false;
$parts = array();
while ($this->tagBracket($parts, $hasExpression));
$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($parts, $hasExpression));
continue;
}
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
if ($this->interpolation($interp)) {
$hasExpression = true;
$interp[2] = true; // don't unescape
$parts[] = $interp;
continue;
}
if ($this->literal("@")) {
$parts[] = "@";
continue;
}
}
if ($this->unit($unit)) { // for keyframes
$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
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
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
*/
protected function assign($name = null) {
if ($name) $this->currentProperty = $name;
return $this->literal(':') || $this->literal('=');
}
// consume a keyword
protected function keyword(&$word) {
if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
$word = $m[1];
return true;
}
return false;
}
// consume an end of statement delimiter
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;
}
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
// TODO rename to guardGroup
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;
}
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 */
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;
}
// goes below...
} else {
return false;
}
}
if (!isset(self::$literalCache[$what])) {
self::$literalCache[$what] = lessc::preg_quote($what);
}
return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
}
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
protected function to($what, &$out, $until = false, $allowNewline = false) {
if (is_string($allowNewline)) {
$validChars = $allowNewline;
} else {
$validChars = $allowNewline ? "." : "[^\n]";
}
if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
if ($until) $this->count -= strlen($what); // give back $what
$out = $m[1];
return true;
}
// try to match something on head of buffer
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;
}
// match some whitespace
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
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
protected function seek($where = null) {
if ($where === null) return $this->count;
else $this->count = $where;
return true;
}
/* misc functions */
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");
}
}
protected function pushBlock($selectors=null, $type=null) {
$b = new stdclass;
$b->parent = $this->env;
$b->type = $type;
$b->id = self::$nextBlockId++;
$b->isVararg = false; // TODO: kill me from here
$b->tags = $selectors;
$b->props = array();
$b->children = array();
$this->env = $b;
return $b;
}
// push a block that doesn't multiply tags
protected function pushSpecialBlock($type) {
return $this->pushBlock(null, $type);
}
// append a property to the current block
protected function append($prop, $pos = null) {
if ($pos !== null) $prop[-1] = $pos;
$this->env->props[] = $prop;
}
// pop something off the stack
protected function pop() {
$old = $this->env;
$this->env = $this->env->parent;
return $old;
}
// remove comments from $text
// todo: make it work for all functions, not just url
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;
}
}
class lessc_formatter_classic {
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 function __construct() {
$this->indentLevel = 0;
}
public function indentStr($n = 0) {
return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
}
public function property($name, $value) {
return $name . $this->assignSeparator . $value . ";";
}
protected function isEmpty($block) {
if (empty($block->lines)) {
foreach ($block->children as $child) {
if (!$this->isEmpty($child)) return false;
}
return true;
}
return false;
}
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--;
}
}
}
class lessc_formatter_compressed extends lessc_formatter_classic {
public $disableSingle = true;
public $open = "{";
public $selectorSeparator = ",";
public $assignSeparator = ":";
public $break = "";
public $compressColors = true;
public function indentStr($n = 0) {
return "";
}
}
class lessc_formatter_lessjs extends lessc_formatter_classic {
public $disableSingle = true;
public $breakSelectors = true;
public $assignSeparator = ": ";
public $selectorSeparator = ",";
}
menu/t3bootstrap.php 0000604 00000011415 15245530303 0010503 0 ustar 00 <?php
/**
*------------------------------------------------------------------------------
* @package T3 Framework for Joomla!
*------------------------------------------------------------------------------
* @copyright Copyright (C) 2004-2013 JoomlArt.com. All Rights Reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
* @authors JoomlArt, JoomlaBamboo, (contribute to this project at github
* & Google group to become co-author)
* @Google group: https://groups.google.com/forum/#!forum/t3fw
* @Link: http://t3-framework.org
*------------------------------------------------------------------------------
*/
if (!class_exists('T3BootstrapTpl', false)) {
T3::import('menu/t3bootstrap.tpl');
}
class T3Bootstrap
{
/**
* Internal variables
*/
protected $menutype;
protected $menu;
/**
* @param string $menutype
*/
function __construct($menutype = 'mainmenu')
{
$this->menutype = $menutype;
$this->menu = '';
}
/**
* @return string
*/
function render()
{
if(!$this->menu){
ob_start();
T3BootstrapTpl::render($this->getList());
$this->menu = ob_get_contents();
ob_end_clean();
}
return $this->menu;
}
/**
* @return mixed
*/
function getList()
{
$app = JFactory::getApplication();
$menu = $app->getMenu();
// Get active menu item
$items = $menu->getItems('menutype', $this->menutype);
$lastitem = 0;
if ($items) {
foreach ($items as $i => $item) {
$item->deeper = false;
$item->shallower = false;
$item->level_diff = 0;
$itemParams = version_compare(JVERSION, '4','ge') ? $item->getParams() : $item->params;
if (isset($items[$lastitem])) {
$items[$lastitem]->deeper = ($item->level > $items[$lastitem]->level);
$items[$lastitem]->shallower = ($item->level < $items[$lastitem]->level);
$items[$lastitem]->level_diff = ($items[$lastitem]->level - $item->level);
}
$item->parent = (boolean)$menu->getItems('parent_id', (int)$item->id, true);
$lastitem = $i;
$item->active = false;
$item->flink = $item->link;
// Reverted back for CMS version 2.5.6
switch ($item->type) {
case 'separator':
case 'heading':
// No further action needed.
break;
case 'url':
if ((strpos($item->link, 'index.php?') === 0) && (strpos($item->link, 'Itemid=') === false)) {
// If this is an internal Joomla link, ensure the Itemid is set.
$item->flink = $item->link . '&Itemid=' . $item->id;
}
break;
case 'alias':
// If this is an alias use the item id stored in the parameters to make the link.
$item->flink = 'index.php?Itemid=' . $itemParams->get('aliasoptions');
break;
default:
$router = $app::getRouter();
if(version_compare(JVERSION, '4', 'lt')){
if ($router->getMode() == JROUTER_MODE_SEF) {
$item->flink = 'index.php?Itemid=' . $item->id;
} else {
$item->flink .= '&Itemid=' . $item->id;
}
}
else
{
if(strpos($item->flink,'Itemid=') === false)
$item->flink .= (strpos($item->flink,'?') === false ? '?' : '&').'Itemid=' . $item->id;
}
break;
}
if (strcasecmp(substr($item->flink, 0, 4), 'http') && (strpos($item->flink, 'index.php?') !== false)) {
$item->flink = JRoute::_($item->flink, true, $itemParams->get('secure'));
} else {
$item->flink = JRoute::_($item->flink);
}
// We prevent the double encoding because for some reason the $item is shared for menu modules and we get double encoding
// when the cause of that is found the argument should be removed
$item->title = htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8', false);
$item->anchor_css = htmlspecialchars($itemParams->get('menu-anchor_css', ''), ENT_COMPAT, 'UTF-8', false);
$item->anchor_title = htmlspecialchars($itemParams->get('menu-anchor_title', ''), ENT_COMPAT, 'UTF-8', false);
$item->menu_image = $itemParams->get('menu_image', '') ? htmlspecialchars($itemParams->get('menu_image', ''), ENT_COMPAT, 'UTF-8', false) : '';
}
if (isset($items[$lastitem])) {
$items[$lastitem]->deeper = (1 > $items[$lastitem]->level);
$items[$lastitem]->shallower = (1 < $items[$lastitem]->level);
$items[$lastitem]->level_diff = ($items[$lastitem]->level - 1);
}
}
return $items;
}
/**
* Get base menu item.
*
* @return object
*/
public static function getBase()
{
return self::getActive();
}
/**
* Get active menu item.
*
* @return object
*/
public static function getActive()
{
$menu = JFactory::getApplication()->getMenu();
return $menu->getActive() ? $menu->getActive() : $menu->getDefault();
}
}
menu/megamenu.php 0000604 00000026036 15245530303 0010022 0 ustar 00 <?php
/**
*------------------------------------------------------------------------------
* @package T3 Framework for Joomla!
*------------------------------------------------------------------------------
* @copyright Copyright (C) 2004-2013 JoomlArt.com. All Rights Reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
* @authors JoomlArt, JoomlaBamboo, (contribute to this project at github
* & Google group to become co-author)
* @Google group: https://groups.google.com/forum/#!forum/t3fw
* @Link: http://t3-framework.org
*------------------------------------------------------------------------------
*/
if(!class_exists('T3MenuMegamenuTpl', false)){
T3::import('menu/megamenu.tpl');
}
if (is_file(T3_TEMPLATE_PATH.'/html/megamenu.php')) {
require_once T3_TEMPLATE_PATH.'/html/megamenu.php';
}
#[AllowDynamicProperties]
class T3MenuMegamenu {
/**
* Internal variables
*/
protected $_items = array();
protected $children = array();
protected $settings = null;
protected $params = null;
protected $menu = '';
protected $active_id = 0;
protected $active_tree = array();
protected $top_level_caption = false;
/**
* @param string $menutype menu type to render
* @param array $settings settings information
* @param null $params other parameters
*/
function __construct($menutype = 'mainmenu', $settings = array(), $params = null) {
$app = JFactory::getApplication();
$menu = $app->getMenu('site');
$attributes = array('menutype');
$values = array($menutype);
if(isset($settings['access'])){
$attributes[] = 'access';
$values[] = $settings['access'];
} else {
$settings['access'] = array(1);
}
if(isset($settings['language'])){
$attributes[] = 'language';
$values[] = $settings['language'];
}
$items = $menu->getItems($attributes, $values);
$active = ($menu->getActive()) ? $menu->getActive() : $menu->getDefault();
$this->active_id = $active ? $active->id : 0;
$this->active_tree = $active->tree;
$this->settings = $settings;
$this->params = $params;
$this->editmode = isset($settings['editmode']);
foreach ($items as &$item) {
//remove all non-parent item (the parent has access higher access level)
if($item->level >= 2 && !isset($this->_items[$item->parent_id])){
continue;
}
//intergration with new params joomla 3.6.x (menu_show)
$menu_show = $item->getParams()->get('menu_show');
if (empty($menu_show) && $menu_show!==null)
continue;
$parent = isset($this->children[$item->parent_id]) ? $this->children[$item->parent_id] : array();
$parent[] = $item;
$this->children[$item->parent_id] = $parent;
$this->_items[$item->id] = $item;
}
foreach ($items as &$item) {
// bind setting for this item
$key = 'item-' . $item->id;
$setting = isset($this->settings[$key]) ? $this->settings[$key] : array();
// decode html tag
if (isset($setting['caption']) && $setting['caption'])
$setting['caption'] = str_replace(array('[lt]', '[gt]'), array('<', '>'), $setting['caption']);
if ($item->level == 1 && isset($setting['caption']) && $setting['caption'])
$this->top_level_caption = true;
// active - current
$class = '';
if ($item->id == $this->active_id) {
$class .= ' current';
}
if (in_array($item->id, $this->active_tree)) {
$class .= ' active';
} elseif ($item->type == 'alias') {
$aliasToId = $item->getParams()->get('aliasoptions');
if (count($this->active_tree) > 0 && $aliasToId == $this->active_tree[count($this->active_tree) - 1]) {
$class .= ' active';
} elseif (in_array($aliasToId, $this->active_tree)) {
$class .= ' alias-parent-active';
}
}
$item->class = $class;
$item->mega = 0;
$item->group = 0;
$item->dropdown = 0;
if (isset($setting['group']) && $item->level > 1) {
$item->group = 1;
} else {
if ((isset($this->children[$item->id]) && ($this->editmode || !isset($setting['hidesub']))) || isset($setting['sub'])) {
$item->dropdown = 1;
}
}
$item->mega = $item->group || $item->dropdown;
// set default sub if not exists
if ($item->mega) {
if (!isset($setting['sub'])) $setting['sub'] = array();
if (isset($this->children[$item->id]) && (!isset($setting['sub']['rows']) || !count($setting['sub']['rows']))) {
$c = $this->children[$item->id][0]->id;
$setting['sub'] = array('rows'=>array(array(array('width'=>12, 'item'=>$c))));
}
}
$item->setting = $setting;
$item->flink = $item->link;
// Reverted back for CMS version 2.5.6
switch ($item->type) {
case 'separator':
case 'heading':
// No further action needed.
break;
case 'url':
if ((strpos($item->link, 'index.php?') === 0) && (strpos($item->link, 'Itemid=') === false)) {
// If this is an internal Joomla link, ensure the Itemid is set.
$item->flink = $item->link . '&Itemid=' . $item->id;
}
break;
case 'alias':
// If this is an alias use the item id stored in the parameters to make the link.
$item->flink = 'index.php?Itemid=' . $item->getParams()->get('aliasoptions');
break;
default:
//$router = JSite::getRouter();
$item->flink = 'index.php?Itemid=' . $item->id;
break;
}
if (strcasecmp(substr($item->flink, 0, 4), 'http') && (strpos($item->flink, 'index.php?') !== false)) {
$item->flink = JRoute::_($item->flink, true, $item->getParams()->get('secure'));
} else {
$item->flink = JRoute::_($item->flink);
}
// We prevent the double encoding because for some reason the $item is shared for menu modules and we get double encoding
// when the cause of that is found the argument should be removed
$item->title = htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8', false);
$item->anchor_css = htmlspecialchars($item->getParams()->get('menu-anchor_css', ''), ENT_COMPAT, 'UTF-8', false);
$item->anchor_title = htmlspecialchars($item->getParams()->get('menu-anchor_title', ''), ENT_COMPAT, 'UTF-8', false);
$item->anchor_rel = htmlspecialchars($item->getParams()->get('menu-anchor_rel', ''), ENT_COMPAT, 'UTF-8', false);
$item->menu_image = $item->getParams()->get('menu_image', '') ? htmlspecialchars($item->getParams()->get('menu_image', ''), ENT_COMPAT, 'UTF-8', false) : '';
$item->menu_image_css = htmlspecialchars($item->getParams()->get('menu_image_css', ''), ENT_COMPAT, 'UTF-8', false);
}
}
function render($return = false) {
$this->menu = '';
$this->_('beginmenu');
$keys = array_keys($this->_items);
if (count($keys)) { //in case the keys is empty array
$this->nav(null, $keys[0]);
}
$this->_('endmenu');
if ($return) {
return $this->menu;
} else {
echo $this->menu;
}
}
function nav($pitem, $start = 0, $end = 0) {
if ($start > 0) {
if (!isset($this->_items[$start]))
return;
$pid = $this->_items[$start]->parent_id;
$items = array();
$started = false;
foreach ($this->children[$pid] as $item) {
if ($started) {
if ($item->id == $end)
break;
$items[] = $item;
} else {
if ($item->id == $start) {
$started = true;
$items[] = $item;
}
}
}
if (!count($items))
return;
} else if ($start === 0) {
$pid = $pitem->id;
if (!isset($this->children[$pid]))
return;
$items = $this->children[$pid];
} else {
//empty menu
return;
}
$this->_('beginnav', array(
'item' => $pitem
));
foreach ($items as $item) {
$this->item($item);
}
$this->_('endnav', array(
'item' => $pitem
));
}
function item($item) {
// item content
$setting = $item->setting;
$this->_('beginitem', array(
'item' => $item,
'setting' => $setting,
'menu' => $this
));
$this->menu .= $this->_('item', array(
'item' => $item,
'setting' => $setting,
'menu' => $this
));
if ($item->mega) {
$this->mega($item);
}
$this->_('enditem', array(
'item' => $item
));
}
function mega($item) {
$setting = $item->setting;
$sub = $setting['sub'];
$items = isset($this->children[$item->id]) ? $this->children[$item->id] : array();
$firstitem = count($items) ? $items[0]->id : 0;
$this->_('beginmega', array(
'item' => $item
));
$endItems = array();
$k1 = $k2 = 0;
foreach ($sub['rows'] as $row) {
foreach ($row as $col) {
if (!isset($col['position'])) {
if ($k1) {
$k2 = $col['item'];
if (!isset($this->_items[$k2]) || $this->_items[$k2]->parent_id != $item->id)
break;
$endItems[$k1] = $k2;
}
$k1 = $col['item'];
}
}
}
$endItems[$k1] = 0;
$firstitemscol = true;
foreach ($sub['rows'] as $row) {
$this->_('beginrow', array(
'menu' => $this
));
foreach ($row as $col) {
$this->_('begincol', array(
'setting' => $col,
'menu' => $this
));
if (isset($col['position'])) {
$this->module($col['position']);
} else {
if (!isset($endItems[$col['item']]))
continue;
$toitem = $endItems[$col['item']];
$startitem = $firstitemscol ? $firstitem : $col['item'];
$this->nav($item, $startitem, $toitem);
$firstitemscol = false;
}
$this->_('endcol');
}
$this->_('endrow');
}
$this->_('endmega');
}
function module($module) {
// load module
$id = intval($module);
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query
->select('m.id, m.title, m.module, m.position, m.content, m.showtitle, m.params')
->from('#__modules AS m')
->where('m.id = ' . $id)
->where('m.published = 1')
->where('m.access IN ('.implode(',', $this->settings['access']).')');
$db->setQuery($query);
$module = $db->loadObject();
//check in case the module is unpublish or deleted
if ($module && $module->id) {
$style = 'T3Xhtml';
$content = JModuleHelper::renderModule($module, array(
'style' => $style
));
$app = JFactory::getApplication();
$frontediting = $app->get('frontediting', 1);
$user = JFactory::getUser();
$canEdit = $user->id && $frontediting && !(T3::isAdmin() && $frontediting < 2) && $user->authorise('core.edit', 'com_modules');
$menusEditing = ($frontediting == 2) && $user->authorise('core.edit', 'com_menus');
if ($app->isClient('site') && $canEdit && trim($content) != '' && $user->authorise('core.edit', 'com_modules.module.' . $module->id))
{
$displayData = array('moduleHtml' => &$content, 'module' => $module, 'position' => $module->position, 'menusediting' => $menusEditing);
JLayoutHelper::render('joomla.edit.frontediting_modules', $displayData);
}
$this->menu .= $content . "\n";
}
}
function _($tmpl, $vars = array()) {
$vars['menu'] = $this;
$this->menu .= T3MenuMegamenuTpl::_($tmpl, $vars);
}
function get($prop) {
if (isset($this->$prop))
return $this->$prop;
return null;
}
function getParam($name, $default = null) {
if (!$this->params)
return $default;
return $this->params->get($name, $default);
}
}
menu/megamenu.tpl.php 0000604 00000024345 15245530303 0010621 0 ustar 00 <?php
/**
*------------------------------------------------------------------------------
* @package T3 Framework for Joomla!
*------------------------------------------------------------------------------
* @copyright Copyright (C) 2004-2013 JoomlArt.com. All Rights Reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
* @authors JoomlArt, JoomlaBamboo, (contribute to this project at github
* & Google group to become co-author)
* @Google group: https://groups.google.com/forum/#!forum/t3fw
* @Link: http://t3-framework.org
*------------------------------------------------------------------------------
*/
class T3MenuMegamenuTpl
{
static function beginmenu($vars)
{
$menu = $vars['menu'];
$animation = $menu->getParam('navigation_animation', '');
$trigger = $menu->getParam('navigation_trigger', 'hover');
$responsive = $menu->getParam('responsive', 1);
$anim_duration = $menu->getParam('navigation_animation_duration', 0);
$cls = ' class="t3-megamenu' . ($trigger == 'hover' && $animation ? ' animate ' . $animation : '') . '"';
$data = $animation && $anim_duration ? ' data-duration="' . $anim_duration . '"' : '';
$data = $data . ($responsive ? ' data-responsive="true"' : '');
return "<div $cls $data>";
}
static function endmenu($vars)
{
return '</div>';
}
static function beginnav($vars)
{
$item = $vars['item'];
$cls = '';
if (!$item) {
// first nav
$cls = 'nav navbar-nav level0';
} else {
$cls .= ' mega-nav';
$cls .= ' level' . $item->level;
}
if ($cls) $cls = 'class="' . trim($cls) . '"';
return '<ul itemscope itemtype="http://www.schema.org/SiteNavigationElement" ' . $cls . '>';
}
static function endnav($vars)
{
return '</ul>';
}
static function beginmega($vars)
{
$item = $vars['item'];
$setting = $item->setting;
$sub = $setting['sub'];
$cls = 'nav-child ' . ($item->dropdown ? 'dropdown-menu mega-dropdown-menu' : 'mega-group-ct');
$style = '';
$data = '';
if (isset($sub['class'])) {
$data .= " data-class=\"{$sub['class']}\"";
$cls .= " {$sub['class']}";
}
if (isset($setting['alignsub']) && $setting['alignsub'] == 'justify') {
$cls .= ' ' . ($vars['menu']->editmode ? 'span' : T3_BASE_NONRSP_WIDTH_PREFIX) . '12';
} else {
if (isset($sub['width'])) {
if ($item->dropdown) $style = ' style="width: ' . str_replace('px', '', $sub['width']) . 'px"';
$data .= ' data-width="' . str_replace('px', '', $sub['width']) . '"';
}
}
if ($cls) $cls = 'class="' . trim($cls) . '"';
return "<div $cls $style $data><div class=\"mega-dropdown-inner\">";
}
static function endmega($vars)
{
return '</div></div>';
}
static function beginrow($vars)
{
return '<div class="' . ($vars['menu']->editmode ? 'row-fluid' : T3_BASE_ROW_FLUID_PREFIX) . '">';
}
static function endrow($vars)
{
return '</div>';
}
static function begincol($vars)
{
$setting = isset($vars['setting']) ? $vars['setting'] : array();
$width = isset($setting['width']) ? $setting['width'] : T3_BASE_MAX_GRID;
$data = "data-width=\"$width\"";
$cls = ($vars['menu']->editmode ? 'span' : T3_BASE_NONRSP_WIDTH_PREFIX) . $width;
if (isset($setting['position'])) {
$cls .= " mega-col-module";
$data .= " data-position=\"{$setting['position']}\"";
} else {
$cls .= " mega-col-nav";
}
if (isset($setting['class'])) {
$cls .= " {$setting['class']}";
$data .= " data-class=\"{$setting['class']}\"";
}
if (isset($setting['hidewcol'])) {
$cls .= " hidden-collapse";
$data .= " data-hidewcol=\"1\"";
}
if (isset($setting['groupstyle']) && $setting['groupstyle']) {
$cls .= " " . $setting['groupstyle'];
$data .= " data-groupstyle=\"{$setting['groupstyle']}\"";
}
return "<div class=\"$cls\" $data><div class=\"mega-inner\">";
}
static function endcol($vars)
{
return '</div></div>';
}
static function beginitem($vars)
{
$item = $vars['item'];
$setting = $item->setting;
$cls = $item->class;
if ($item->dropdown) {
$cls .= $item->level == 1 ? ' dropdown' : ' dropdown-submenu';
}
if ($item->mega) $cls .= ' mega';
if ($item->group) $cls .= ' mega-group';
if ($item->type == 'separator' && !$item->group && !$item->mega) $cls .= ' divider';
$data = "data-id=\"{$item->id}\" data-level=\"{$item->level}\"";
if ($item->group) $data .= " data-group=\"1\"";
if (isset($setting['class'])) {
$cls .= " {$setting['class']}";
$data .= " data-class=\"{$setting['class']}\"";
}
if (isset($setting['alignsub'])) {
$cls .= " mega-align-{$setting['alignsub']}";
$data .= " data-alignsub=\"{$setting['alignsub']}\"";
}
if (isset($setting['hidesub'])) $data .= " data-hidesub=\"1\"";
if (isset($setting['xicon'])) $data .= " data-xicon=\"{$setting['xicon']}\"";
if (isset($setting['caption'])) $data .= " data-caption=\"" . htmlspecialchars($setting['caption']) . "\"";
if (isset($setting['hidewcol'])) {
$data .= " data-hidewcol=\"1\"";
$cls .= " sub-hidden-collapse";
}
if ($cls) $cls = 'class="' . trim($cls) . '"';
return "<li itemprop='name' $cls $data>";
}
static function enditem($vars)
{
return '</li>';
}
static function item($vars)
{
$item = $vars['item'];
$setting = $item->setting;
// Note. It is important to remove spaces between elements.
$vars['class'] = $item->anchor_css ? $item->anchor_css : '';
$vars['title'] = $item->anchor_title ? ' title="' . $item->anchor_title . '" ' : '';
$vars['rel'] = $item->anchor_rel ? ' rel="' . $item->anchor_rel . '" ' : '';
$vars['dropdown'] = ' data-target="#"';
$vars['caret'] = '';
$vars['icon'] = '';
$vars['caption'] = '';
$itemParams = version_compare(JVERSION, '4', 'ge') ? $item->getParams() : $item->params;
if ($item->dropdown && $item->level < 2) {
$vars['class'] .= ' dropdown-toggle';
$vars['dropdown'] .= ' data-toggle="dropdown"'; // Note: data-target for JomSocial old bootstrap lib
$vars['caret'] = '<em class="caret"></em>';
}
if($item->group){
$vars['class'] .= ' dropdown-header mega-group-title';
}
if ($item->menu_image) {
$itemParams->get('menu_text', 1) ?
$vars['linktype'] = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" /><span class="image-title">' . $item->title . '</span> ' :
$vars['linktype'] = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" />';
} else {
$vars['linktype'] = $item->title;
}
if (isset($setting['xicon']) && $setting['xicon']) {
$vars['icon'] = '<span class="' . $setting['xicon'] . '"></span>';
}
if (isset($setting['caption']) && $setting['caption']) {
$vars['caption'] = '<span class="mega-caption">' . $setting['caption'] . '</span>';
} else if ($item->level == 1 && $vars['menu']->get('top_level_caption')) {
$vars['caption'] = '<span class="mega-caption mega-caption-empty"> </span>';
}
switch ($item->type) {
case 'separator':
case 'heading':
$html = self::_('item_separator', $vars);
break;
case 'component':
$html = self::_('item_component', $vars);
break;
case 'url':
default:
$html = self::_('item_url', $vars);
}
return $html;
}
static function item_url($vars)
{
$item = $vars['item'];
$class = $vars['class'];
$rel = $vars['rel'];
$title = $vars['title'];
$dropdown = $vars['dropdown'];
$caret = $vars['caret'];
$linktype = $vars['linktype'];
$icon = $vars['icon'];
$caption = $vars['caption'];
$flink = $item->flink;
$flink = JFilterOutput::ampReplace(htmlspecialchars($flink));
switch ($item->browserNav) :
default:
case 0:
$link = "<a itemprop='url' class=\"$class\" $rel href=\"$flink\" $title $dropdown>$icon$linktype$caret$caption</a>";
break;
case 1:
// _blank
$link = "<a itemprop='url' class=\"$class\" $rel href=\"$flink\" target=\"_blank\" $title $dropdown>$icon$linktype$caret$caption</a>";
break;
case 2:
// window.open
$options = 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes';
$link = "<a itemprop='url' class=\"$class\" $rel href=\"$flink\"" . (!$vars['menu']->editmode ? " onclick=\"window.open(this.href,'targetWindow','$options');return false;\"" : "") . " $title $dropdown>$icon$linktype$caret$caption</a>";
break;
endswitch;
return $link;
}
static function item_separator($vars)
{
$item = $vars['item'];
$class = $vars['class'];
$title = $vars['title'];
$dropdown = $vars['dropdown'];
$caret = $vars['caret'];
$linktype = $vars['linktype'];
$icon = $vars['icon'];
$caption = $vars['caption'];
// Note. It is important to remove spaces between elements.
$class .= " separator";
return "<span class=\"$class\" $title $dropdown>$icon$title $linktype$caret$caption</span>";
}
static function item_component($vars)
{
$item = $vars['item'];
$class = $vars['class'];
$rel = $vars['rel'];
$title = $vars['title'];
$dropdown = $vars['dropdown'];
$caret = $vars['caret'];
$linktype = $vars['linktype'];
$icon = $vars['icon'];
$caption = $vars['caption'];
// Note. It is important to remove spaces between elements.
switch ($item->browserNav) :
default:
case 0:
$link = "<a itemprop='url' class=\"$class\" $rel href=\"{$item->flink}\" $title $dropdown>$icon$linktype $caret$caption</a>";
break;
case 1:
// _blank
$link = "<a itemprop='url' class=\"$class\" $rel href=\"{$item->flink}\" target=\"_blank\" $title $dropdown>$icon$linktype $caret$caption</a>";
break;
case 2:
// window.open
$link = "<a itemprop='url' class=\"$class\" $rel href=\"{$item->flink}\"" . (!$vars['menu']->editmode ? " onclick=\"window.open(this.href,'targetWindow','toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes');return false;\"" : "") . " $title $dropdown>$icon$linktype $caret$caption</a>";
break;
endswitch;
return $link;
}
static function _($tmpl, $vars) {
if (function_exists($func = 'T3MenuMegamenuTpl_'.$tmpl)) {
return $func($vars) . "\n";
} else if (method_exists('T3MenuMegamenuTpl', $tmpl)) {
return T3MenuMegamenuTpl::$tmpl($vars) . "\n";
} else {
return "$tmpl\n";
}
}
} menu/t3bootstrap.tpl.php 0000604 00000016223 15245530303 0011303 0 ustar 00 <?php
/**
*------------------------------------------------------------------------------
* @package T3 Framework for Joomla!
*------------------------------------------------------------------------------
* @copyright Copyright (C) 2004-2013 JoomlArt.com. All Rights Reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
* @authors JoomlArt, JoomlaBamboo, (contribute to this project at github
* & Google group to become co-author)
* @Google group: https://groups.google.com/forum/#!forum/t3fw
* @Link: http://t3-framework.org
*------------------------------------------------------------------------------
*/
class T3BootstrapTpl
{
static function render($list)
{
$base = T3Bootstrap::getBase();
$active = T3Bootstrap::getActive();
$active_id = $active->id;
$path = $base->tree;
?>
<ul class="nav navbar-nav">
<?php
foreach ($list as &$item) :
$ItemParams = version_compare(JVERSION, '4', 'ge') ? $item->getParams() : $item->params;
$item->itemParams = $ItemParams;
//intergration with new params joomla 3.6.x (menu_show)
$menu_show = (int)$ItemParams->get('menu_show', 1);
if ($menu_show!=1)
continue;
$class = 'item-' . $item->id;
if ($item->id == $active_id) {
$class .= ' current';
}
if (in_array($item->id, $path)) {
$class .= ' active';
} elseif ($item->type == 'alias') {
$aliasToId = $ItemParams->get('aliasoptions');
if (count($path) > 0 && $aliasToId == $path[count($path) - 1]) {
$class .= ' active';
} elseif (in_array($aliasToId, $path)) {
$class .= ' alias-parent-active';
}
}
if ($item->type == 'separator' || $item->type == 'heading') {
$class .= ' divider';
}
if ($item->deeper) {
if ($item->level > 1) {
$class .= ' dropdown-submenu';
} else {
$class .= ' deeper dropdown';
}
}
if ($item->parent) {
$class .= ' parent';
}
if (!empty($class)) {
$class = ' class="' . trim($class) . '"';
}
echo '<li' . $class . '>';
// Render the menu item.
switch ($item->type) :
case 'separator':
case 'url':
case 'component':
case 'heading':
echo self::item($item->type, $item);
break;
default:
echo self::item('url', $item);
break;
endswitch;
// The next item is deeper.
if ($item->deeper) {
echo '<ul class="dropdown-menu" role="menu">';
} // The next item is shallower.
elseif ($item->shallower) {
echo '</li>';
echo str_repeat('</ul></li>', $item->level_diff);
} // The next item is on the same level.
else {
echo '</li>';
}
endforeach;
?>
</ul>
<?php
}
public static function item($type, $item)
{
if (method_exists(__CLASS__, $type)) {
return self::$type($item);
} else {
return $type;
}
}
public static function component($item)
{
// Note. It is important to remove spaces between elements.
$class = $item->anchor_css ? $item->anchor_css : '';
$title = $item->anchor_title ? 'title="' . $item->anchor_title . '" ' : '';
$caret = '';
$dropdown = '';
if($item->deeper && $item->level < 2){
$class .= ' dropdown-toggle';
$dropdown = ' data-toggle="dropdown"';
$caret = '<em class="caret"></em>';
}
if(!empty($class)){
$class = 'class="'. trim($class) .'" ';
}
if ($item->menu_image) {
$item->itemParams->get('menu_text', 1) ?
$linktype = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" /><span class="image-title">' . $item->title . '</span> ' :
$linktype = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" />';
} else {
$linktype = $item->title;
}
switch ($item->browserNav) :
default:
case 0:
?>
<a <?php echo $class; ?>href="<?php echo $item->flink; ?>" <?php echo $title, $dropdown; ?>><?php echo $linktype, $caret; ?></a>
<?php
break;
case 1:
// _blank
?>
<a <?php echo $class; ?>href="<?php echo $item->flink; ?>" target="_blank" <?php echo $title, $dropdown; ?>><?php echo $linktype, $caret; ?></a>
<?php
break;
case 2:
// window.open
?>
<a <?php echo $class; ?>href="<?php echo $item->flink; ?>" onclick="window.open(this.href,'targetWindow','toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes');return false;" <?php echo $title, $dropdown; ?>><?php echo $linktype, $caret; ?></a>
<?php
break;
endswitch;
}
public static function heading($item)
{
?>
<span class="nav-header"><?php echo $item->title; ?></span>
<?php
}
public static function separator($item)
{
// Note. It is important to remove spaces between elements.
$title = $item->anchor_title ? ' title="' . $item->anchor_title . '" ' : '';
if ($item->menu_image) {
$item->itemParams->get('menu_text', 1) ?
$linktype = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" /><span class="image-title">' . $item->title . '</span> ' :
$linktype = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" />';
} else {
$linktype = $item->title;
}
?>
<span class="separator" <?php echo $title; ?>><?php echo $linktype; ?></span>
<?php
}
public static function url($item)
{
// Note. It is important to remove spaces between elements.
$class = $item->anchor_css ? $item->anchor_css : '';
$title = $item->anchor_title ? 'title="' . $item->anchor_title . '" ' : '';
$caret = '';
$dropdown = '';
if($item->deeper && $item->level < 2){
$class .= ' dropdown-toggle';
$dropdown = ' data-toggle="dropdown"';
$caret = '<em class="caret"></em>';
}
if(!empty($class)){
$class = 'class="'. trim($class) .'" ';
}
if ($item->menu_image) {
$item->itemParams->get('menu_text', 1) ?
$linktype = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" /><span class="image-title">' . $item->title . '</span> ' :
$linktype = '<img class="' . $item->menu_image_css . '" src="' . $item->menu_image . '" alt="' . $item->title . '" />';
} else {
$linktype = $item->title;
}
$flink = $item->flink;
$flink = JFilterOutput::ampReplace(htmlspecialchars($flink));
switch ($item->browserNav) :
default:
case 0:
?>
<a <?php echo $class; ?>href="<?php echo $flink; ?>" <?php echo $title, $dropdown; ?>><?php echo $linktype, $caret; ?></a>
<?php
break;
case 1:
// _blank
?>
<a <?php echo $class; ?>href="<?php echo $flink; ?>" target="_blank" <?php echo $title, $dropdown; ?>><?php echo $linktype, $caret; ?></a>
<?php
break;
case 2:
// window.open
$options = 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,' . $item->itemParams->get('window_open');
?>
<a <?php echo $class; ?>href="<?php echo $flink; ?>" onclick="window.open(this.href,'targetWindow','<?php echo $options; ?>');return false;" <?php echo $title, $dropdown; ?>><?php echo $linktype, $caret; ?></a>
<?php
break;
endswitch;
}
}
depend/images/apply.gif 0000604 00000001116 15245530303 0011057 0 ustar 00 GIF89a � ��1��S{�@��J������������������������k��V��E����͊�R�꒠�c��U�ವꁢ�k�摈�4��������u�4}�>��l���퉡�`��p��L��N��8��U��z�B��㋾\t�8����ю��k��g��r�ψ��L��������~����ʂ�+���z�>��]�������J��q��ز�y������߱�ӓ�ם��ܯ�{w�.~�;��Jw�9��K��Ι�Rm�)���S������@}�G�߯��J��^��\��g��m����Ј��ts�7��T��X��n��a��w�ͅ��kw�;�����P�ܩ��f�����~��7�⺔�i��ȑ�F��-��i���m�1y�<����\���!� , �����aY��N]9��HSv��@.Kw_lJ�32)[50�:gx+�`�% R �/u&G�o>