Библиотека для cis, online, cms1
This commit is contained in:
commit
3c2e614d87
269 changed files with 39854 additions and 0 deletions
477
PHPTAL/RepeatController.php
Normal file
477
PHPTAL/RepeatController.php
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
<?php
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4: */
|
||||
//
|
||||
// Copyright (c) 2004-2005 Laurent Bedubourg
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// Authors: Laurent Bedubourg <lbedubourg@motion-twin.com>
|
||||
//
|
||||
|
||||
/**
|
||||
* Stores tal:repeat information during template execution.
|
||||
*
|
||||
* An instance of this class is created and stored into PHPTAL context on each
|
||||
* tal:repeat usage.
|
||||
*
|
||||
* repeat/item/index
|
||||
* repeat/item/number
|
||||
* ...
|
||||
* are provided by this instance.
|
||||
*
|
||||
* 'repeat' is an StdClass instance created to handle RepeatControllers,
|
||||
* 'item' is an instance of this class.
|
||||
*
|
||||
* @package phptal
|
||||
* @author Laurent Bedubourg <lbedubourg@motion-twin.com>
|
||||
*/
|
||||
class PHPTAL_RepeatController implements Iterator
|
||||
{
|
||||
private $key;
|
||||
private $current;
|
||||
private $valid;
|
||||
private $validOnNext;
|
||||
|
||||
protected $iterator;
|
||||
protected $index;
|
||||
protected $end;
|
||||
protected $length;
|
||||
|
||||
/**
|
||||
* Construct a new RepeatController.
|
||||
*
|
||||
* @param $source array, string, iterator, iterable.
|
||||
*/
|
||||
public function __construct($source)
|
||||
{
|
||||
if ( is_string($source) ) {
|
||||
$this->iterator = new ArrayIterator( str_split($source) ); // FIXME: invalid for UTF-8 encoding, use preg_match_all('/./u') trick
|
||||
} else if ( is_array($source) ) {
|
||||
$this->iterator = new ArrayIterator($source);
|
||||
} else if ( $source instanceof IteratorAggregate ) {
|
||||
$this->iterator = $source->getIterator();
|
||||
} else if ( $source instanceof Iterator ) {
|
||||
$this->iterator = $source;
|
||||
} else if ( $source instanceof SimpleXMLElement) { // has non-unique keys!
|
||||
$array = array();
|
||||
foreach ( $source as $v ) {
|
||||
$array[] = $v;
|
||||
}
|
||||
$this->iterator = new ArrayIterator($array);
|
||||
} else if ( $source instanceof Traversable || $source instanceof DOMNodeList ) {
|
||||
// PDO Statements for example implement the engine internal Traversable
|
||||
// interface. To make it fully iterable we traverse the set to populate
|
||||
// an array which will be actually used for iteration.
|
||||
$array = array();
|
||||
foreach ( $source as $k=>$v ) {
|
||||
$array[$k] = $v;
|
||||
}
|
||||
$this->iterator = new ArrayIterator($array);
|
||||
} else {
|
||||
$this->iterator = new ArrayIterator( array() );
|
||||
}
|
||||
|
||||
// Try to find the set length
|
||||
$this->length = 0;
|
||||
if ( $this->iterator instanceof Countable ) {
|
||||
$this->length = count($this->iterator);
|
||||
} else if ( is_object($this->iterator) ) {
|
||||
// This should be removed since there is already the Countable interface in PHP5
|
||||
if ( method_exists( $this->iterator, 'size' ) ) {
|
||||
$this->length = $this->iterator->size();
|
||||
} else if ( method_exists( $this->iterator, 'length' ) ) {
|
||||
$this->length = $this->iterator->length();
|
||||
}
|
||||
}
|
||||
|
||||
$this->groups = new PHPTAL_RepeatController_Groups();
|
||||
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current element value in the iteration
|
||||
*
|
||||
* @return Mixed The current element value
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current element key in the iteration
|
||||
*
|
||||
* @return String/Int The current element key
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the iteration is over
|
||||
*
|
||||
* @return bool True if the iteration is not finished yet
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
$valid = $this->valid || $this->validOnNext;
|
||||
$this->validOnNext = $this->valid;
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the iteration process going back to the first element
|
||||
*
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->index = 0;
|
||||
$this->end = false;
|
||||
|
||||
$this->iterator->rewind();
|
||||
|
||||
// Prefetch the next element
|
||||
if ( $this->iterator->valid() ) {
|
||||
$this->validOnNext = true;
|
||||
$this->prefetch();
|
||||
} else {
|
||||
$this->validOnNext = false;
|
||||
}
|
||||
|
||||
// Notify the grouping helper of the change
|
||||
$this->groups->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the next element in the iteration and advances the pointer
|
||||
*
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
$this->index++;
|
||||
|
||||
// Prefetch the next element
|
||||
$this->prefetch();
|
||||
|
||||
// Notify the grouping helper of the change
|
||||
$this->groups->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object property
|
||||
*
|
||||
* @return $var Mixed The variable value
|
||||
*/
|
||||
public function __get( $var )
|
||||
{
|
||||
switch ( $var ) {
|
||||
case 'index':
|
||||
case 'end':
|
||||
case 'length':
|
||||
return $this->$var;
|
||||
case 'number':
|
||||
return $this->index + 1;
|
||||
case 'start':
|
||||
return $this->index === 0;
|
||||
case 'even':
|
||||
return ($this->index % 2) === 0;
|
||||
case 'odd':
|
||||
return ($this->index % 2) === 1;
|
||||
case 'key':
|
||||
return $this->key();
|
||||
case 'letter':
|
||||
return strtolower( $this->int2letter($this->index+1) );
|
||||
case 'Letter':
|
||||
return strtoupper( $this->int2letter($this->index+1) );
|
||||
case 'roman':
|
||||
return strtolower( $this->int2roman($this->index+1) );
|
||||
case 'Roman':
|
||||
return strtoupper( $this->int2roman($this->index+1) );
|
||||
|
||||
case 'first':
|
||||
// Compare the current one with the previous in the dictionary
|
||||
$res = $this->groups->first( $this->current );
|
||||
return is_bool($res) ? $res : $this->groups;
|
||||
case 'last':
|
||||
// Compare the next one with the dictionary
|
||||
$res = $this->groups->last( $this->iterator->current() );
|
||||
return is_bool($res) ? $res : $this->groups;
|
||||
|
||||
default:
|
||||
throw new PHPTAL_Exception( "Unable to find part '$var' in repeater controller" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the next element from the source data store and
|
||||
* updates the end flag if needed.
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
protected function prefetch()
|
||||
{
|
||||
$this->valid = true;
|
||||
$this->key = $this->iterator->key();
|
||||
$this->current = $this->iterator->current();
|
||||
|
||||
$this->iterator->next();
|
||||
if ( !$this->iterator->valid() ) {
|
||||
$this->valid = false;
|
||||
$this->end = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an integer number (1 based) to a sequence of letters
|
||||
*
|
||||
* @param $int Int The number to convert
|
||||
* @return String The letters equivalent as a, b, c-z ... aa, ab, ac-zz ...
|
||||
* @access protected
|
||||
*/
|
||||
protected function int2letter( $int )
|
||||
{
|
||||
$lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$size = strlen($lookup);
|
||||
|
||||
$letters = '';
|
||||
while ( $int > 0 ) {
|
||||
$int--;
|
||||
$letters = $lookup[$int % $size] . $letters;
|
||||
$int = floor($int / $size);
|
||||
}
|
||||
return $letters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an integer number (1 based) to a roman numeral
|
||||
*
|
||||
* @param $int Int The number to convert
|
||||
* @return String The roman numeral
|
||||
* @access protected
|
||||
*/
|
||||
protected function int2roman( $int )
|
||||
{
|
||||
$lookup = array(
|
||||
'1000' => 'M',
|
||||
'900' => 'CM',
|
||||
'500' => 'D',
|
||||
'400' => 'CD',
|
||||
'100' => 'C',
|
||||
'90' => 'XC',
|
||||
'50' => 'L',
|
||||
'40' => 'XL',
|
||||
'10' => 'X',
|
||||
'9' => 'IX',
|
||||
'5' => 'V',
|
||||
'4' => 'IV',
|
||||
'1' => 'I',
|
||||
);
|
||||
|
||||
$roman = '';
|
||||
foreach ( $lookup as $max => $letters ) {
|
||||
while ( $int >= $max ) {
|
||||
$roman .= $letters;
|
||||
$int -= $max;
|
||||
}
|
||||
}
|
||||
|
||||
return $roman;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Keeps track of variable contents when using grouping in a path (first/ and last/)
|
||||
*
|
||||
* @package phptal
|
||||
* @author Iván Montes <drslump@pollinimini.net>
|
||||
*/
|
||||
class PHPTAL_RepeatController_Groups {
|
||||
|
||||
protected $dict = array();
|
||||
protected $cache = array();
|
||||
protected $data = null;
|
||||
protected $vars = array();
|
||||
protected $branch;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->dict = array();
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the result caches. Use it to signal an iteration in the loop
|
||||
*
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->cache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the data passed is the first one in a group
|
||||
*
|
||||
* @param $data Mixed The data to evaluate
|
||||
* @return Mixed True if the first item in the group, false if not and
|
||||
* this same object if the path is not finished
|
||||
*/
|
||||
public function first( $data )
|
||||
{
|
||||
if ( !is_array($data) && !is_object($data) && !is_null($data) ) {
|
||||
|
||||
if ( !isset($this->cache['F']) ) {
|
||||
|
||||
$hash = md5($data);
|
||||
|
||||
if ( !isset($this->dict['F']) || $this->dict['F'] !== $hash ) {
|
||||
$this->dict['F'] = $hash;
|
||||
$res = true;
|
||||
} else {
|
||||
$res = false;
|
||||
}
|
||||
|
||||
$this->cache['F'] = $res;
|
||||
}
|
||||
|
||||
return $this->cache['F'];
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
$this->branch = 'F';
|
||||
$this->vars = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the data passed is the last one in a group
|
||||
*
|
||||
* @param $data Mixed The data to evaluate
|
||||
* @return Mixed True if the last item in the group, false if not and
|
||||
* this same object if the path is not finished
|
||||
*/
|
||||
public function last( $data )
|
||||
{
|
||||
if ( !is_array($data) && !is_object($data) && !is_null($data) ) {
|
||||
|
||||
if ( !isset($this->cache['L']) ) {
|
||||
|
||||
$hash = md5($data);
|
||||
|
||||
if (empty($this->dict['L'])) {
|
||||
$this->dict['L'] = $hash;
|
||||
$res = false;
|
||||
} else if ( $this->dict['L'] !== $hash ) {
|
||||
$this->dict['L'] = $hash;
|
||||
$res = true;
|
||||
} else {
|
||||
$res = false;
|
||||
}
|
||||
|
||||
$this->cache['L'] = $res;
|
||||
}
|
||||
|
||||
return $this->cache['L'];
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
$this->branch = 'L';
|
||||
$this->vars = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles variable accesses for the tal path resolver
|
||||
*
|
||||
* @param $var String The variable name to check
|
||||
* @return Mixed An object/array if the path is not over or a boolean
|
||||
*
|
||||
* @todo replace the phptal_path() with custom code
|
||||
*/
|
||||
public function __get( $var )
|
||||
{
|
||||
// When the iterator item is empty we just let the tal
|
||||
// expression consume by continuously returning this
|
||||
// same object which should evaluate to true for 'last'
|
||||
if ( is_null($this->data) ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Find the requested variable
|
||||
$value = @phptal_path( $this->data, $var, true );
|
||||
|
||||
// Check if it's an object or an array
|
||||
if ( is_array($value) || is_object($value) ) {
|
||||
// Move the context to the requested variable and return
|
||||
$this->data = $value;
|
||||
$this->addVarName( $var );
|
||||
return $this;
|
||||
}
|
||||
|
||||
// get a hash of the variable contents
|
||||
$hash = md5( $value );
|
||||
|
||||
// compute a path for the variable to use as dictionary key
|
||||
$path = $this->branch . $this->getVarPath() . $var;
|
||||
|
||||
// If we don't know about this var store in the dictionary
|
||||
if ( !isset($this->cache[$path]) ) {
|
||||
|
||||
if ( !isset($this->dict[$path]) ) {
|
||||
$this->dict[$path] = $hash;
|
||||
$res = $this->branch === 'F';
|
||||
} else {
|
||||
// Check if the value has changed
|
||||
if ( $this->dict[$path] !== $hash ) {
|
||||
$this->dict[$path] = $hash;
|
||||
$res = true;
|
||||
} else {
|
||||
$res = false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache[$path] = $res;
|
||||
}
|
||||
|
||||
return $this->cache[$path];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a variable name to the current path of variables
|
||||
*
|
||||
* @param $varname String The variable name to store as a path part
|
||||
* @access protected
|
||||
*/
|
||||
protected function addVarName( $varname )
|
||||
{
|
||||
$this->vars[] = $varname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current variable path separated by a slash
|
||||
*
|
||||
* @return String The current variable path
|
||||
* @access protected
|
||||
*/
|
||||
protected function getVarPath()
|
||||
{
|
||||
return implode('/', $this->vars) . '/';
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue