Subversion Repositories PHPX

Compare Revisions

Last modification

Regard whitespace Rev 1 → Rev HEAD

/trunk/footnotes.class.php
0,0 → 1,294
<?php
 
/**
* A footnote list contains {@link Footnote Footnotes}
*
* @author Thomas 'PointedEars' Lahn &lt;php@PointedEars.de&gt;
*/
class FootnoteList
{
/**
* The footnotes of this list
*
* @var Array
*/
protected $_footnotes;
 
/**
* Last used number sign for a footnote
*
* @var int
*/
protected $_lastNumberSign;
 
protected $_defaultPrefix;
protected $_defaultSuffix;
 
protected $_makeTooltip;
 
/* (non-PHPdoc) @see Footnote::_html4Compat */
protected $_html4Compat;
 
public function __construct($defaultPrefix = '', $defaultSuffix = '', $makeTooltip = false, $html4Compatible = false)
{
$this->clear();
$this->_defaultPrefix = $defaultPrefix;
$this->_defaultSuffix = $defaultSuffix;
$this->_makeTooltip = $makeTooltip;
$this->_html4Compat = $html4Compatible;
}
 
public function __get($name)
{
if (property_exists($this, "_$name"))
{
return $this->{"_$name"};
}
 
throw new InvalidArgumentException(
'No such property ' . get_class($this) . "::\$$name");
}
 
/**
* Clears the footnote list
*/
public function clear()
{
$this->_footnotes = array();
$this->_lastNumberSign = 0;
}
 
/**
* Adds a footnote to the list (unless already specified)
*
* @param string $name
* Name of the footnote
* @param string $sign
* Sign of the footnote. If empty, the next available number is used.
* @param string $text
* Text for the footnote
* @param string $tooltip
* Tooltip for the footnote
* @return string
* The code for printing the footnote reference.
*/
public function add($name, $sign = '', $text = '', $tooltip = null)
{
$footnotes =& $this->_footnotes;
 
if (!isset($footnotes[$name]))
{
if (!$sign)
{
$sign = ++$this->_lastNumberSign;
}
else if (is_int($sign))
{
$this->_lastNumberSign = $sign;
}
 
$footnotes[$name] = new Footnote($name, $sign, $text,
$this->_defaultSuffix, $this->_defaultPrefix,
$this->_makeTooltip ? ($tooltip !== null ? $tooltip : $text) : '',
$this->_html4Compat);
}
 
return $footnotes[$name]->getRef();
}
 
/**
* Prints the list of footnotes
*/
public function printMe()
{
$footnotes =& $this->_footnotes;
 
uasort($footnotes, function ($a, $b) {
/* Sort numbered footnotes first */
if (is_int($a->sign) && !is_int($b->sign))
{
return -1;
}
 
if (is_int($b->sign) && !is_int($a->sign))
{
return 1;
}
 
/* Sort footnotes either numerically or alphabetically */
/* TODO: Sort "1b" before "12a" */
if ($a->sign < $b->sign)
{
return -1;
}
 
if ($a->sign > $b->sign)
{
return 1;
}
 
return 0;
});
 
?><table class="footnotes">
<?php
 
foreach ($footnotes as $name => &$footnote)
{
$footnote->printMe();
}
 
?></table><?php
}
 
/**
* Prints the list of footnotes and clears the list in memory
*/
public function flush()
{
$this->printMe();
$this->clear();
}
}
 
/**
* A footnote to be used in a {@link FootnoteList "footnote list"}
*
* @author Thomas 'PointedEars' Lahn &lt;php@PointedEars.de&gt;
*/
class Footnote
{
/**
* The name of this footnote
*
* @var string
*/
protected $_name = '';
 
/**
* The sign used for referring to this footnote
*
* @var string
*/
protected $_sign = '';
 
/**
* The text for this footnote
*
* @var string
*/
protected $_text = '';
 
protected $_prefix = '';
protected $_suffix = '';
protected $_tooltip = '';
 
/**
* HTML 4 compatibility: If <code>true</code>, generates
* <code>name</code> attributes. Default: <code>false</code>.
*
* @var boolean
*/
protected $_html4Compat = false;
 
/**
* The number of times this footnote has been referred
*
* @var int
*/
protected $_references = 0;
 
/**
* Creates a footnote
*
* @param string $name
* The name of this footnote
* @param string $sign
* The sign that should be used for referring to this footnote
* @param string $text
* The text for this footnote
* @param string $suffix
* The suffix for this footnote
* @param string $prefix
* The prefix for this footnote
*/
public function __construct($name, $sign, $text, $suffix = '', $prefix = '',
$tooltip = '', $html4Compatible = false)
{
$this->_name = $name;
$this->_sign = (is_numeric($sign) ? (int) $sign : $sign);
$this->_text = $text;
$this->_suffix = $suffix;
$this->_prefix = $prefix;
$this->_tooltip = $tooltip;
$this->_html4Compat = $html4Compatible;
}
 
/**
* Universal getter
*
* @param string $name
* Name of the property to be read-accessed. Currently only 'sign'
* is supported.
* @throws InvalidArgumentException if a non-existing property is accessed
* @return mixed
* Property value
*/
public function __get($name)
{
if (property_exists($this, "_$name"))
{
return $this->{"_$name"};
}
 
throw new InvalidArgumentException(
'No such property ' . get_class($this) . "::\$$name");
}
 
/**
* Returns the reference for this footnote
*
* @return string
*/
public function getRef()
{
$s = $this->_name;
 
$ret = "<sup><a href='#footnote-{$s}'"
. ($this->_references === 0
? (($this->_html4Compat ? " name='fn-{$s}-ref'" : "") . " id='fn-{$s}-ref'")
: '')
. ' class="footnote"'
. ($this->_tooltip
? ' title="'
. preg_replace('/"/', '&quot;',
trim(reduceWhitespace(strip_tags($this->_tooltip))))
. '"'
: '')
. ">{$this->_prefix}{$this->_sign}{$this->_suffix}"
. '</a></sup>';
 
++$this->_references;
 
return $ret;
}
 
/**
* Prints this footnote in a footnote list
*/
public function printMe()
{
$s = $this->_name;
 
echo " <tr>
<th><sup><a" . ($this->_html4Compat ? " name='footnote-{$s}'" : "")
. " id='footnote-{$s}' class='footnote'
>{$this->_sign}</a></sup><a
href='#fn-{$s}-ref' class='backref'>&#8593;</a></th>
<td>{$this->_text}</td>
</tr>
";
}
}
 
?>
/trunk/Db/Database.php
0,0 → 1,1060
<?php
 
namespace PointedEars\PHPX\Db;
 
require_once __DIR__ . '/../global.inc';
 
/**
* Generic database model class using PDO (PHP Data Objects)
*
* @property-read PDO $connection
* Database connection. Established on read access to this
* property if not yet established.
* @property-read array $lastError
* Last error information of the database operation.
* See {@link PDOStatement::errorInfo()}.
* @property-read string $lastInsertId
* ID of the last inserted row, or the last value from a sequence object,
* depending on the underlying driver. May not be supported by all databases.
* @property-read array $lastResult
* Last result of the database operation
* @property-read boolean $lastSuccess
* Last success value of the database operation
* @author Thomas Lahn
*/
class Database extends \PointedEars\PHPX\AbstractModel
{
/* Access properties */
 
/**
* DSN of the database
* @var string
*/
protected $_dsn = '';
 
/**
* Username to access the database
* @var string
*/
protected $_username;
 
/**
* Password to access the database
* @var string
*/
protected $_password;
 
/**
* PDO driver-specific options
* @var array
*/
protected $_options = array();
 
/**
* Database-specific string to use for quoting a name or value
* left-hand side (for security reasons and to prevent a name
* from being parsed as a keyword).
* @var string
*/
protected $_leftQuote = '';
 
/**
* Database-specific string to use for quoting a name or value
* left-hand side (for security reasons and to prevent a name
* from being parsed as a keyword).
* @var string
*/
protected $_rightQuote = '';
 
/* Status properties */
 
/**
* Database connection
* @var PDO
*/
protected $_connection;
 
/**
* Last success value of the database operation
* @var boolean
*/
protected $_lastSuccess;
 
/**
* Last error information of the database operation
* @var array
*/
protected $_lastError;
 
/**
* Last result of the database operation
* @var array
*/
protected $_lastResult;
 
/**
* ID of the last inserted row, or the last value from a sequence object,
* depending on the underlying driver. May not be supported by all databases.
* @var string
*/
protected $_lastInsertId = '';
 
/**
* Creates a new <code>Database</code> instance.
*
* Each of the parameters is optional and can also be given
* by a protected property where the parameter name is preceded
* by <code>_</code>. Parameter values overwrite the default
* property values. It is recommended to use default property
* values of inheriting classes except for small applications
* and testing purposes.
*
* @param string $dsn
* @param string $username
* @param string $password
* @param array $options
* @see PDO::__construct()
*/
public function __construct ($dsn = '', $username = null,
$password = null, array $options = array())
{
if ($dsn !== '')
{
$this->_dsn = $dsn;
}
 
if ($username !== null)
{
$this->_username = $username;
}
 
if ($password !== null)
{
$this->_password = $password;
}
 
if ($options)
{
$this->_options = $options;
}
}
 
/**
* Reads the connection configuration for this database
* from the configuration file, .config
*
* There must be an INI section named "database:" followed
* by the value of the <code>$_dbname</code> property
* containing keys and values for the properties of the
* <code>Database</code> instance. Except for the key
* <code>dbname</code>, which allows for aliases, all
* keys are ignored if the corresponding properties
* were set. That is, definitions in the class file
* override those in the configuration file.
*
* @return array|boolean
* The configuration array if the configuration
* file could be read, <code>false</code> otherwise.
*/
public function readConfig ()
{
/* FIXME: Configuration file path should not be hardcoded */
$config = parse_ini_file('.config', true);
if ($config !== false)
{
$section = 'database:' . $this->_dbname;
if (isset($config[$section]))
{
$dbconfig = $config[$section];
$options = array(
'host', 'port', 'dbname', 'username', 'password', 'charset'
);
 
foreach ($options as $key)
{
$property = "_$key";
if (isset($dbconfig[$key])
&& ($key == 'dbname'
|| (property_exists($this, $property)
&& $this->$property === null)))
{
$this->$property = $dbconfig[$key];
}
}
 
return $config[$section];
}
}
 
return $config;
}
 
/**
* @return PDO
*/
public function getConnection ()
{
if ($this->_connection === null)
{
$this->_connection =
new \PDO($this->_dsn, $this->_username, $this->_password, $this->_options);
}
 
return $this->_connection;
}
 
/**
* Creates a database according to the specified parameters
*
* Should be overwritten and called by inheriting classes.
*
* @param string $dsn
* Connection DSN (required; must not include the database
* name).
* @param string $username = null
* Connection username. The default is specified by the
* <code>$_username</code> property. Note that creating
* the database usually requires a user with more privileges
* than the one accessing the database or its tables.
* @param string $password = null
* Connection password. The default is specified by the
* <code>$_password</code> property.
* @param array? $options = null
* Connection options. The default is specified by the
* <code>$_options</code> property.
* @param string $spec = null
* Additional database specifications, like character encoding
* and collation.
* @param boolean $force = false
* If a true-value, the database will be attempted to be
* created even if there is a database of the name specified
* by the <code>$_dbname</code> property.
* @return int
* The number of rows affected by the CREATE DATABASE statement.
* @see PDO::__construct()
* @see PDO::exec()
*/
public function create ($dsn, $username = null, $password = null,
array $options = null, $dbspec = null, $force = false)
{
$connection = new \PDO($dsn,
$username !== null ? $username : $this->_username,
$password !== null ? $password : $this->_password,
$options !== null ? $options : $this->_options);
 
$query = 'CREATE DATABASE'
. (!$force ? ' IF NOT EXISTS' : '')
. ' ' . $this->escapeName($this->_dbname)
. ($dbspec ? ' ' . $dbspec : '');
 
return $connection->exec($query);
}
 
/**
* Maps column meta-information to a column definition.
*
* Should be overwritten and called by inheriting classes.
*
* @todo
* @param array $value
* @param string $column_name
* @return string
*/
protected function _columnDef (array $data, $column_name)
{
$def = (isset($data['unsigned']) && $data['unsigned'] ? 'UNSIGNED ' : '')
. $data['type']
. (isset($data['not_null']) && $data['not_null'] ? ' NOT NULL' : ' NULL')
. (isset($data['default']) && $data['default'] ? " DEFAULT {$data['default']}" : '')
. (isset($data['auto_inc']) && $data['auto_inc'] ? ' AUTO_INCREMENT' : '')
. (isset($data['unique']) && $data['unique'] ? ' UNIQUE KEY' : '')
. (isset($data['primary']) && $data['primary'] ? ' PRIMARY KEY' : '')
. (isset($data['comment']) && $data['comment'] ? " COMMENT '{$data['comment']}'" : '');
 
return $this->escapeName($column_name) . ' ' . $def;
}
 
/**
* Creates a database table according to the specified parameters
*
* @todo
* @param string $name
* @param array $columns
* @param array $options = null
* @return bool
* @see PDOStatement::execute()
*/
public function createTable ($name, array $columns, array $options = null)
{
$class = \get_class($this);
$query = 'CREATE TABLE '
. $this->escapeName($name)
. '('
. array_map(array($this, '_columnDef'), $columns, array_keys($columns)) . ')';
 
$stmt = $this->prepare($query);
 
/* DEBUG */
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'query' => $query,
));
}
 
$success =& $this->_lastSuccess;
$success = $stmt->execute();
 
$errorInfo =& $this->_lastError;
$errorInfo = $stmt->errorInfo();
 
$this->_resetLastInsertId();
 
$result =& $this->_lastResult;
$result = $stmt->fetchAll();
 
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'_lastSuccess' => $success,
'_lastError' => $errorInfo,
'_lastResult' => $result
));
}
 
return $success;
}
 
/**
* Initiates a transaction
*
* @return bool
* @see PDO::beginTransaction()
*/
public function beginTransaction()
{
return $this->connection->beginTransaction();
}
 
/**
* Rolls back a transaction
*
* @return bool
* @see PDO::rollBack()
*/
public function rollBack()
{
return $this->connection->rollBack();
}
 
/**
* Commits a transaction
*
* @return bool
* @see PDO::commit()
*/
public function commit()
{
return $this->connection->commit();
}
 
/**
* Prepares a statement for execution with the database
* @param string $query
*/
public function prepare($query, array $driver_options = array())
{
return $this->connection->prepare($query, $driver_options);
}
 
/**
* Returns the ID of the last inserted row, or the last value from
* a sequence object, depending on the underlying driver.
*
* @return int
*/
public function getLastInsertId()
{
return $this->_lastInsertId;
}
 
/**
* Returns the date of last modification of this database or
* one of its tables.
*
* To be overridden by inheriting classes.
*
* @param string $table (optional)
* Table name. If not provided, all tables of this database
* are considered.
* @return int|null
* Timestamp of last modification, or <code>null</code> if
* unavailable.
*/
public function getLastModified ($table = null)
{
return null;
}
 
/**
* Escapes a database name so that it can be used in a query.
*
* @param string $name
* The name to be escaped
* @return string
* The escaped name
*/
public function escapeName($name)
{
return $this->_leftQuote . $name . $this->_rightQuote;
}
 
/**
* Determines if an array is associative (has not all integer keys).
*
* @author
* Algorithm courtesy of squirrel, <http://stackoverflow.com/a/5969617/855543>.
* @param array $a
* @return boolean
* <code>true</code> if <var>$a</var> is associative,
* <code>false</code> otherwise
*/
protected function _isAssociativeArray(array $a)
{
for (reset($a); is_int(key($a)); next($a));
return !is_null(key($a));
}
 
/**
* Escapes an associative array so that its string representation can be used
* as list with table or column aliases in a query.
*
* This method does not actually escape anything; it only inserts the
* 'AS' keyword. It should be overridden by inheriting methods.
*
* NOTE: This method intentionally does not check whether the array actually
* is associative.
*
* @param array &$array
* The array to be escaped
* @return array
* The escaped array
*/
protected function _escapeAliasArray(array &$array)
{
foreach ($array as $column => &$value)
{
$quotedColumn = $column;
if (strpos($column, $this->_leftQuote) === false
&& strpos($column, $this->_rightQuote) === false)
{
$quotedColumn = $this->_leftQuote . $column . $this->_rightQuote;
}
 
$value = $value . ' AS ' . $quotedColumn;
}
 
return $array;
}
 
/**
* @param array $a
* @param string $prefix
*/
private static function _expand(array $a, $prefix)
{
$a2 = array();
 
foreach ($a as $key => $value)
{
$a2[] = ':' . $prefix . ($key + 1);
}
 
return $a2;
}
 
/**
* Escapes an associative array so that its string representation can be used
* as value list in a query.
*
* This method should be overridden by inheriting classes to escape
* column names as fitting for the database schema they support. It is
* strongly recommended that the overriding methods call this method with
* an appropriate <var>$escape</var> parameter, pass all other parameters
* on unchanged, and return its return value.
*
* NOTE: Intentionally does not check whether the array actually is associative!
*
* @param array &$array
* The array to be escaped
* @param string $suffix
* The string to be appended to the column name for the value placeholder.
* The default is the empty string.
* @param array $escape
* The strings to use left-hand side (index 0) and right-hand side (index 1)
* of the column name. The default is the empty string, respectively.
* @return array
* The escaped array
*/
protected function _escapeValueArray(array &$array, $suffix = '')
{
$result = array();
foreach ($array as $column => $value)
{
$op = '=';
$placeholder = ":{$column}";
 
if (is_array($value) && $this->_isAssociativeArray($value))
{
reset($value);
$op = ' ' . key($value) . ' ';
 
$value = $value[key($value)];
}
 
if (is_array($value))
{
$placeholder = '(' . implode(', ', self::_expand($value, $column)) . ')';
}
 
$result[] = $this->_leftQuote . $column . $this->_rightQuote . "{$op}{$placeholder}{$suffix}";
}
 
return $result;
}
 
/**
* Constructs the WHERE part of a query
*
* @param string|array $where
* Condition
* @param string $suffix
* The string to be appended to the column name for the value placeholder,
* passed on to {@link Database::_escapeValueArray()}. The default is
* the empty string.
* @return string
* @see Database::_escapeValueArray()
*/
protected function _where($where, $suffix = '')
{
if (!is_null($where))
{
if (is_array($where))
{
if (count($where) < 1)
{
return '';
}
 
if ($this->_isAssociativeArray($where))
{
$where = $this->_escapeValueArray($where, $suffix);
}
 
$where = '(' . implode(') AND (', $where) . ')';
}
 
return ' WHERE ' . $where;
}
 
return '';
}
 
/**
* Selects data from one or more tables; the resulting records are stored
* in the <code>result</code> property and returned as an associative array,
* where the keys are the column (alias) names.
*
* @param string|array[string] $tables Table(s) to select from
* @param string|array[string] $columns Column(s) to select from (optional)
* @param string|array $where Condition (optional)
* @param string $order Sort order (optional)
* If provided, MUST start with ORDER BY or GROUP BY
* @param string $limit Limit (optional)
* @param int $fetch_style
* The mode that should be used for {@link PDOStatement::fetchAll()}.
* The default is {@link PDO::FETCH_ASSOC}.
* @param bool $fetchAll
* If <code>true</code> (default), fetches all rows at once.
* You can set this to <code>false</code> in case of memory problems,
* in which case this function will return the prepared
* {@link PDOStatement} instead of the result array. You can then use
* {@link PDOStatement::fetch()} to get the returned rows iteratively.
* <var>$fetch_style</var> will be ignored then, so that you can safely
* pass <code>null</code> for it, for example.
* @return array|PDOStatement
* @see Database::prepare()
* @see PDOStatement::fetchAll()
*/
public function select($tables, $columns = null, $where = null,
$order = null, $limit = null, $fetch_style = \PDO::FETCH_ASSOC,
$fetchAll = true)
{
if (is_null($columns))
{
$columns = array('*');
}
 
if (is_array($columns))
{
if ($this->_isAssociativeArray($columns))
{
$columns = $this->_escapeAliasArray($columns);
}
 
$columns = implode(', ', $columns);
}
 
if (is_array($tables))
{
if ($this->_isAssociativeArray($columns))
{
$columns = $this->_escapeAliasArray($columns);
}
 
$tables = implode(', ', $tables);
}
 
$query = "SELECT {$columns} FROM {$tables}" . $this->_where($where);
 
if (!is_null($order))
{
if (is_array($order))
{
$order = 'ORDER BY ' . implode(', ', $order);
}
 
$query .= " $order";
}
 
if (!is_null($limit))
{
$query .= " LIMIT $limit";
}
 
$stmt = ($fetchAll
? $this->prepare($query)
: $this->prepare($query, array(\PDO::ATTR_CURSOR => \PDO::CURSOR_SCROLL)));
 
$params = array();
 
if (is_array($where) && $this->_isAssociativeArray($where))
{
/* FIXME: Export and reuse this */
foreach ($where as $column => $condition)
{
/* TODO: Also handle function calls as keys */
if (is_array($condition) && $this->_isAssociativeArray($condition))
{
reset($condition);
$condition = $condition[key($condition)];
 
if (is_array($condition))
{
foreach (self::_expand($condition, $column) as $param_index => $param_name)
{
$params[$param_name] = $condition[$param_index];
}
}
}
else
{
$params[":{$column}"] = $condition;
}
}
}
 
/* DEBUG */
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'query' => $query,
'params' => $params
));
}
 
$success =& $this->_lastSuccess;
$success = $stmt->execute($params);
 
$errorInfo =& $this->_lastError;
$errorInfo = $stmt->errorInfo();
 
$result =& $this->_lastResult;
 
$result = ($fetchAll
? $stmt->fetchAll($fetch_style)
: $stmt);
 
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'_lastSuccess' => $success,
'_lastError' => $errorInfo,
'_lastResult' => $result
));
}
 
return $result;
}
 
/**
* Sets and returns the ID of the last inserted row, or the last value from
* a sequence object, depending on the underlying driver.
*
* @param string $name
* Name of the sequence object from which the ID should be returned.
* @return string
*/
protected function _setLastInsertId($name = null)
{
return ($this->_lastInsertId = $this->connection->lastInsertId($name));
}
 
/**
* Resets the the ID of the last inserted row, or the last value from
* a sequence object, depending on the underlying driver.
*
* @return string
* The default value
*/
protected function _resetLastInsertId()
{
return ($this->_lastInsertId = '');
}
 
/**
* Updates one or more records
*
* @param string|array $tables
* Table name
* @param array $updates
* Associative array of column-value pairs
* @param array|string $where
* Only the records matching this condition are updated
* @return bool
* @see PDOStatement::execute()
*/
public function update ($tables, array $updates, $where = null)
{
if (!$tables)
{
throw new InvalidArgumentException('No table specified');
}
 
if (is_array($tables))
{
$tables = implode(', ', $tables);
}
 
if (!$updates)
{
throw new InvalidArgumentException('No values specified');
}
 
$params = array();
 
if ($this->_isAssociativeArray($updates))
{
foreach ($updates as $key => $condition)
{
$params[":{$key}"] = $condition;
}
}
 
$updates = implode(', ', $this->_escapeValueArray($updates));
 
/* TODO: Should escape table names with escapeName(), but what about aliases? */
$query = "UPDATE {$tables} SET {$updates}" . $this->_where($where, '2');
 
$stmt = $this->prepare($query);
 
if (is_array($where) && $this->_isAssociativeArray($where))
{
foreach ($where as $column => $condition)
{
if (is_array($condition) && $this->_isAssociativeArray($condition))
{
reset($condition);
$condition = $condition[key($condition)];
 
if (is_array($condition))
{
foreach (self::_expand($condition, $column) as $param_index => $param_name)
{
$params[$param_name] = $condition[$param_index];
}
}
}
else
{
$params[":{$column}2"] = $condition;
}
}
}
 
/* DEBUG */
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'query' => $query,
'params' => $params
));
}
 
$success =& $this->_lastSuccess;
$success = $stmt->execute($params);
 
$errorInfo =& $this->_lastError;
$errorInfo = $stmt->errorInfo();
 
$this->_resetLastInsertId();
 
$result =& $this->_lastResult;
$result = $stmt->fetchAll();
 
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'_lastSuccess' => $success,
'_lastError' => $errorInfo,
'_lastResult' => $result
));
}
 
return $success;
}
 
/**
* Inserts a record into a table.<p>The AUTO_INCREMENT value of the inserted
* row, if any (> 0), is stored in the {@link $lastInsertId} property of
* the <code>Database</code> instance.</p>
*
* @param string $table
* Table name
* @param array|string $values
* Associative array of column-value pairs, indexed array,
* or comma-separated list of values. If <var>$values</var> is not
* an associative array, <var>$cols</var> must be passed if the
* values are not in column order (see below).
* @param array|string $cols
* Indexed array, or comma-separated list of column names.
* Needs only be passed if <var>$values</var> is not an associative array
* and the values are not in column order (default: <code>null</code>);
* is ignored otherwise. <strong>You SHOULD NOT rely on column order.</strong>
* @return bool
* @see PDOStatement::execute()
*/
public function insert ($table, $values, $cols = null)
{
if ($cols != null)
{
$cols = ' ('
. (is_array($cols)
? implode(', ', array_map(array($this, 'escapeName'), $cols))
: $cols) . ')';
}
else
{
$cols = '';
}
 
/* DEBUG */
if (defined('DEBUG') && DEBUG > 2)
{
debug(array('values' => $values));
}
 
$params = array();
 
if (is_array($values))
{
if ($this->_isAssociativeArray($values))
{
foreach ($values as $key => $condition)
{
$params[":{$key}"] = $condition;
}
 
$values = $this->_escapeValueArray($values);
 
$cols = '';
$values = 'SET ' . implode(', ', $values);
}
else
{
foreach ($values as &$value)
{
if (is_string($value))
{
$value = "'" . $value . "'";
}
}
 
$values = 'VALUES (' . implode(', ', $values) . ')';
}
}
 
/* TODO: Should escape table names with escapeName(), but what about aliases? */
$query = "INSERT INTO {$table} {$cols} {$values}";
 
$stmt = $this->prepare($query);
 
/* DEBUG */
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'query' => $query,
'params' => $params
));
}
 
$success =& $this->_lastSuccess;
$success = $stmt->execute($params);
 
$errorInfo =& $this->_lastError;
$errorInfo = $stmt->errorInfo();
 
$this->_setLastInsertId();
 
$result =& $this->_lastResult;
$result = $stmt->fetchAll();
 
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'_lastSuccess' => $success,
'_lastError' => $errorInfo,
'_lastInsertId' => $this->_lastInsertId,
'_lastResult' => $result
));
}
 
return $success;
}
 
/**
* Retrieves all rows from a table
*
* @param int[optional] $fetch_style
* @param int[optional] $column_index
* @param array[optional] $ctor_args
* @return array
* @see PDOStatement::fetchAll()
*/
public function fetchAll($table, $fetch_style = null, $column_index = null, array $ctor_args = null)
{
/* NOTE: Cannot use table name as statement parameter */
$stmt = $this->prepare("SELECT * FROM $table");
$this->_lastSuccess = $stmt->execute();
 
$this->_lastError = $stmt->errorInfo();
 
$result =& $this->_lastResult;
 
if (is_null($fetch_style))
{
$fetch_style = \PDO::FETCH_ASSOC;
}
 
if (!is_null($ctor_args))
{
$result = $stmt->fetchAll($fetch_style, $column_index, $ctor_args);
}
else if (!is_null($column_index))
{
$result = $stmt->fetchAll($fetch_style, $column_index);
}
else if (!is_null($fetch_style))
{
$result = $stmt->fetchAll($fetch_style);
}
else
{
$result = $stmt->fetchAll();
}
 
return $result;
}
 
/**
* Deletes one or more records
*
* @param string|array $tables
* Table name(s)
* @param array|string $where
* Only the records matching this condition are deleted
* @return bool
* @see PDOStatement::execute()
*/
public function delete($tables, $where = null)
{
if (!$tables)
{
throw new InvalidArgumentException('No table specified');
}
 
if (is_array($tables))
{
$tables = implode(', ', $tables);
}
 
$params = array();
 
$query = "DELETE FROM {$tables}" . $this->_where($where);
 
$stmt = $this->prepare($query);
 
if ($this->_isAssociativeArray($where))
{
foreach ($where as $column => $condition)
{
if (is_array($condition) && $this->_isAssociativeArray($condition))
{
reset($condition);
$condition = $condition[key($condition)];
 
if (is_array($condition))
{
foreach (self::_expand($condition, $column) as $param_index => $param_name)
{
$params[$param_name] = $condition[$param_index];
}
}
}
else
{
$params[":{$column}"] = $condition;
}
}
}
 
/* DEBUG */
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'query' => $query,
'params' => $params
));
}
 
$success =& $this->_lastSuccess;
$success = $stmt->execute($params);
 
$result =& $this->_lastResult;
$result = $stmt->fetchAll();
 
$errorInfo =& $this->_lastError;
$errorInfo = $stmt->errorInfo();
 
if (defined('DEBUG') && DEBUG > 1)
{
debug(array(
'_lastSuccess' => $success,
'_lastError' => $errorInfo,
'_lastResult' => $result
));
}
 
return $success;
}
}
/trunk/Db/MySQLDB.php
0,0 → 1,160
<?php
 
namespace PointedEars\PHPX\Db;
 
class MySQLDB extends Database
{
/**
* Database host
* @var string
*/
protected $_host;
 
/**
* Database port on the host
* @var int
*/
protected $_port;
 
/**
* MySQL Unix socket (shouldn't be used with host or port).
* @var string
*/
protected $_unix_socket;
 
/**
* Database name
* @var string
*/
protected $_dbname;
 
/**
* Username to access the database
* @var string
*/
protected $_username;
 
/**
* Password to access the database
* @var string
*/
protected $_password;
 
/**
* Optional charset parameter value
* @var string
*/
protected $_charset = null;
 
/**
* (non-PHPdoc)
* @see Database::_leftQuote
*/
protected $_leftQuote = '`';
 
/**
* (non-PHPdoc)
* @see Database::_rightQuote
*/
protected $_rightQuote = '`';
 
public function __construct()
{
$dbconfig = $this->readConfig();
if ($dbconfig === false)
{
return;
}
 
if (isset($dbconfig['unix_socket']))
{
$this->_unix_socket = $dbconfig['unix_socket'];
}
 
$this->_dsn = "mysql:host={$this->_host}"
. (!is_null($this->_port) ? ";port={$this->_port}" : '')
. (!is_null($this->_unix_socket) ? ";unix_socket={$this->_unix_socket}" : '')
. (!is_null($this->_dbname) ? ";dbname={$this->_dbname}" : '')
. (!is_null($this->_charset) ? ";charset={$this->_charset}" : '');
 
if (!is_null($this->_charset))
{
$this->_options[\PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES " . $this->_charset;
}
 
parent::__construct();
}
 
/**
* (non-PHPdoc)
* @see \PointedEars\PHPX\Db\Database::_columnDef()
*/
protected function _columnDef (array $data, $columnName)
{
$standard_Def = parent::_columnDef($data, $columnName);
$mySQL_def = $standardDef
. (isset($data['format']) && $data['format'] ? " COLUMN_FORMAT {$data['format']}" : '')
. (isset($data['storage']) && $data['storage'] ? " STORAGE {$data['storage']}" : '');
 
return $mySQL_def;
}
 
/**
* Creates this MySQL database
*
* @param string $dsn
* Ignored. Required for strict compatibility only.
* @param string $username = null
* @param string $password = null
* @param array? $options = null
* Ignored. Required for strict compatibility only.
* @param string $dbspec = null
* Currently ignored.
* @param boolean $force = false
* @see Database::create()
*/
public function create ($dsn, $username = null, $password = null,
array $options = null, $dbspec = null, $force = false)
{
return parent::create(
"mysql:host={$this->_host}"
. (!is_null($this->_charset) ? ";charset={$this->_charset}" : ''),
$username, $password, null, $force);
}
 
/**
* (non-PHPdoc)
* @see \PointedEars\PHPX\Db\Database::getLastModified()
*/
public function getLastModified ($table = null)
{
$filter = array('TABLE_SCHEMA' => $this->_dbname);
 
if ($table !== null)
{
$filter['TABLE_NAME'] = $table;
}
 
$times = $this->select(
'`information_schema`.`tables`',
array('CREATE_TIME', 'UPDATE_TIME'),
$filter,
array('UPDATE_TIME DESC', 'CREATE_TIME DESC'),
1
);
 
$modi = ($times ? $times[0] : null);
 
if ($modi)
{
$modi = $modi['UPDATE_TIME'] ?: $modi['CREATE_TIME'];
 
if ($modi)
{
$modi = strtotime($modi . ' GMT');
}
}
 
return $modi;
}
}
/trunk/Db/Table.php
0,0 → 1,401
<?php
 
namespace PointedEars\PHPX\Db;
 
use \PointedEars\PHPX\Application;
 
/**
* Generic database table model class
*
* @author Thomas Lahn
* @property Database $database
* @property-read int $lastInsertId
* ID of the last inserted row, or the last value from
a sequence object, depending on the underlying driver.
*/
class Table extends \PointedEars\PHPX\AbstractModel
{
/**
* Name of the table
* @var string
*/
protected static $_name = '';
 
/**
* Columns definition
* @var array
* @see Table::create()
*/
protected static $_columns;
 
/**
* Indexes definition
* @var array
* @see Table::create()
*/
protected static $_indexes;
 
/**
* Constraints definition
* @var array
* @see Table::create()
*/
protected static $_constraints;
 
/**
* Database of the table
* @var Database|string
* @see Table::create()
*/
protected static $_database;
 
/**
* Name of the primary key column of the table
* @var string
*/
protected static $_id = 'id';
 
/**
* Creates a new <code>Table</code> instance.
*
* Each of the parameters is optional and can also be given
* by a protected property where the parameter name is preceded
* by <code>_</code>. Parameter values overwrite the default
* property values. It is recommended to use default property
* values of inheriting classes except for small applications
* and testing purposes.
*
* @param Database $database
* Database of the table (required in order to use a fitting
* query language)
* @param string $name
* Table name
* @param string $id
* Name of the primary key column
* @throws InvalidArgumentException
*/
public function __construct ($database = null, $name = '', $id = '')
{
if ($database === null)
{
/* Call getter to convert to Database if possible */
if ($this->database === null)
{
$this->database = Application::getInstance()->getDefaultDatabase();
}
}
else
{
$this->database = $database;
}
 
if ($name !== '')
{
$this->name = $name;
}
 
$name = $this->name;
if (!\is_string($name))
{
throw new \InvalidArgumentException(
'Expected string for table name, saw '
. (\is_object($name) ? \get_class($name) : \gettype($name)));
}
 
if ($id !== '')
{
$this->id = $id;
}
}
 
/**
* @param string $value
*/
public function setName ($value)
{
$class = \get_class($this);
$class::$_name = (string) $value;
}
 
/**
* @return string
*/
public function getName ()
{
$class = \get_class($this);
return $class::$_name;
}
 
/**
* Returns the database for the table
* @return Database
*/
public function getDatabase()
{
/* FIXME: What about tables from different databases? */
$class = \get_class($this);
if (\is_string($class::$_database))
{
/* Call setter to convert to Database */
$this->setDatabase($class::$_database);
}
 
return $class::$_database;
}
 
/**
* @param Database|string $value
* @throws InvalidArgumentException
*/
public function setDatabase ($value)
{
$class = \get_class($this);
if ($value instanceof Database)
{
$class::$_database = $value;
}
else if ($value !== null)
{
$database = new $value();
if (!($database instanceof Database))
{
throw new \InvalidArgumentException(
'Expected Database instance or string for class name, saw '
. (\is_object($value) ? \get_class($value) : \gettype($value))
);
}
 
$class::$_database = $database;
}
}
 
/**
* @param string $value
*/
public function setId ($value)
{
$class = \get_class($this);
$class::$_id = (string) $value;
}
 
/**
* @return string
*/
public function getId ()
{
$class = \get_class($this);
return $class::$_id;
}
 
/**
* Returns the <var>options</var> array for {@link Database::createTable}
*
* Should be called and overridden by inheriting classes.
*
* @return array
*/
protected function _createOptions ()
{
$options = array();
 
foreach (array('indexes', 'constraints') as $option)
{
if ($class::${"_$option"})
{
$options[$option] = $class::${"_$option"};
}
}
 
return $options;
}
 
/**
* Creates the table for this model
*
* @return bool
*/
public function create ()
{
$class = \get_class($this);
return $this->database->createTable(
$class::$_name, $class::$_columns, $this->_createOptions());
}
 
/**
* Initiates a transaction
*
* @return bool
* @see Database::beginTransaction()
*/
public function beginTransaction()
{
return $this->database->beginTransaction();
}
 
/**
* Rolls back a transaction
*
* @return bool
* @see Database::rollBack()
*/
public function rollBack()
{
return $this->database->rollBack();
}
 
/**
* Commits a transaction
*
* @return bool
* @see Database::commit()
*/
public function commit()
{
return $this->database->commit();
}
 
/**
* Retrieves all rows from the table
*
* @return array
* @see Database::fetchAll()
*/
public function fetchAll($fetch_style = null, $column_index = null, array $ctor_args = null)
{
return $this->database->fetchAll($this->name, $fetch_style, $column_index, $ctor_args);
}
 
/**
* Selects data from one or more tables
*
* @return array
* @see Database::select()
*/
public function select($columns = null, $where = null, $order = null, $limit = null)
{
return $this->database->select($this->name, $columns, $where, $order, $limit);
}
 
/**
* Updates records in one or more tables
*
* @return bool
* @see Database::update()
*/
public function update($data, $condition)
{
return $this->database->update($this->name, $data, $condition);
}
 
/**
* Inserts a record into the table
*
* @return bool
* @see Database::insert()
*/
public function insert($data, $cols = null)
{
return $this->database->insert($this->name, $data, $cols);
}
 
/**
* Returns the ID of the last inserted row, or the last value from
* a sequence object, depending on the underlying driver.
*
* @return int
* @see Database::getLastInsertId()
*/
public function getLastInsertId()
{
return $this->database->lastInsertId;
}
 
/**
* Delete a record from the table
*
* @param int $id
* ID of the record to delete. May be <code>null</code>,
* in which case <var>$condition</var> must specify
* the records to be deleted.
* @param array[optional] $condition
* Conditions that must be met for a record to be deleted.
* Ignored if <var>$id</var> is not <code>null</code>.
* @return bool
* @throws InvalidArgumentException if both <var>$id</var> and
* <var>$condition</var> are <code>null</code>.
* @see Database::delete()
*/
public function delete ($id, array $condition = null)
{
if ($id !== null)
{
$condition = array($this->id => $id);
}
else if ($condition === null)
{
throw new InvalidArgumentException(
'$id and $condition cannot both be null');
}
 
return $this->database->delete($this->name, $condition);
}
 
/**
* Inserts a row into the table or updates an existing one
*
* @param array $data
* Associative array of column-value pairs to be updated/inserted
* @param string|array $condition
* If there are no records matching this condition, a row
* will be inserted; otherwise matching records are updated.
* @return bool
* The return value of Table::update() or Table::insert()
* @see Table::update()
* @see Table::insert()
*/
public function updateOrInsert($data, array $condition = null)
{
if ($this->select($this->id, $condition))
{
return $this->update($data, $condition);
}
 
return $this->insert($data);
}
 
/**
* Finds a record by ID
*
* @param mixed $id
* @return array
*/
public function find ($id)
{
/* DEBUG */
if (defined('DEBUG') && DEBUG > 0)
{
debug($id);
}
 
$result = $this->select(null, array($this->id => $id));
 
if ($result)
{
$result = $result[0];
}
 
return $result;
}
 
/**
* Returns the date of last modification of this table.
*
* @return int|null
* Timestamp of last modification, or <code>null</code> if
* unavailable.
*/
public function getLastModified ()
{
return $this->database->getLastModified($this->name);
}
}
/trunk/Db/Mapper.php
0,0 → 1,100
<?php
 
namespace PointedEars\PHPX\Db;
 
/**
* Generic abstract database mapper class
*
* @property-read \PointedEars\PHPX\Db\Table $table
* The <code>Table</code> for this mapper
* @author Thomas Lahn
*/
abstract class Mapper extends \PointedEars\PHPX\AbstractModel
{
/**
* Class name of the associated table model
*
* @var string
*/
protected $_table = 'Table';
 
protected $_dbTable;
 
/**
* Sets the {@link Table} for this mapper
* @param string|Table $dbTable
* Class name of the new instance, or an existing instance
* @throws Exception if <var>$dbTable</var> is not a <code>Table</code>
*/
public function setDbTable($table)
{
if (is_string($table))
{
$table = new $table();
}
 
if (!($table instanceof Table)) {
throw new Exception('Invalid table data gateway provided');
}
 
$this->_dbTable = $table;
}
 
/**
* Gets the {@link Table} for this mapper
*
* @param string|Table $table
* Class name of the new instance or an existing instance.
* The default is the value of the <code>$_table</code> property.
* @return Table
* @throws Exception if <var>$dbTable</var> is not a <code>Table</code>
* @see Mapper::setDbTable()
*/
public function getDbTable($table = null)
{
if (is_null($this->_dbTable))
{
if (is_null($table))
{
$table = $this->_table;
}
 
$this->setDbTable($table);
}
 
return $this->_dbTable;
}
 
/**
* Returns the <code>Table</code> for this object.
*
* @return Table
*/
public function getTable ()
{
return $this->getDbTable();
}
 
/**
* Sorts an array of objects by the property of the nested object.
* To be used with the u*sort() functions.
*
* @param object $a First operand of the comparison
* @param object $b Second operand of the comparison
* @param string $property
* Name of the property of the nested object by which to sort the outer array
* @return int
* 0 if <var>$a</var> and <var>$b</var> are "equal",
* <code>-1</code> if <var>$a</var> is "less" than <var>$b</var>,
* <code>1</code> otherwise.
*/
protected static function sortByProperty($a, $b, $property)
{
if ($a->$property === $b->$property)
{
return 0;
}
 
return ($a->$property < $b->$property) ? -1 : 1;
}
}
/trunk/Db/MySQLTable.php
0,0 → 1,38
<?php
 
namespace PointedEars\PHPX\Db;
 
/**
* @author
* Copyright (c) 2013 Thomas 'PointedEars' Lahn
*/
class MySQLTable extends Table
{
/**
* Engine for this table.
*
* @var string
* @see Table::create()
*/
protected static $_engine;
 
/**
* (non-PHPdoc)
* @see \PointedEars\PHPX\Db\Table::_createOptions()
*/
protected function _createOptions ()
{
$options = parent::_createOptions();
 
$class = \get_class($this);
foreach (array('engine') as $option)
{
if ($class::${"_$option"})
{
$options[$option] = $class::${"_$option"};
}
}
 
return $options;
}
}
/trunk/Db/database.class.php
0,0 → 1,527
<?php
 
require_once __DIR__ . '/../global.inc';
 
/* NOTE: Obsolete with autoloader */
//require_once 'Zend/Registry.php';
//require_once 'Zend/Db/Table.php';
 
/**
* @property-read array $lastId The last generated ID
* @property-read array $result The result of the last query
*
* @author rvejseli, tlahn
*/
class Database
{
private $db_host = DB_HOST;
private $db_user = DB_USER;
private $db_pass = DB_PASS;
private $db_name = DB_NAME;
 
private $connection = false;
private $_result = array();
 
/**
* The last inserted ID
*
* @var int
*/
private $_lastId;
 
/* Anlegen der Instanz */
private static $instance = NULL;
 
/* Konstruktor private, damit die Klasse nur aus sich selbst heraus instanziiert werden kann. */
private function __construct()
{
$this->connect();
$this->_resetLastID();
}
 
/**
* Returns a singleton Database instance
* @return Database
*/
public static function getInstance()
{
if (self::$instance === NULL)
{
self::$instance = new self;
}
 
return self::$instance;
}
 
/* Klonen per 'clone()' von aussen verbieten. */
private function __clone() {}
 
/**
* Universal getter
*
* @param string $name
*/
public function __get($name)
{
switch ($name)
{
case 'lastId':
case 'result':
return $this->{"_$name"};
}
}
 
/**
* Connects to the Database
*/
public function connect()
{
if (!$this->connection)
{
$myconn = @mysql_connect($this->db_host, $this->db_user, $this->db_pass);
if ($myconn)
{
$seldb = @mysql_select_db($this->db_name, $myconn);
if ($seldb)
{
$this->connection = true;
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
return true;
}
}
 
/**
* Resets the last ID to a default value, indicating that no rows where inserted
*/
private function _resetLastID()
{
$this->_lastId = -1;
}
 
public function setDatabase($name)
{
if ($this->connection)
{
if (@mysql_close())
{
$this->connection = false;
$this->_result = null;
$this->db_name = $name;
$this->connect();
}
}
}
 
/**
* Makes an encoding-safe database query
*
* @param string $query
* @param string encoding
* The encoding in which the query result should be returned from the DBMS.
* The default is 'utf8'.
* @return resource|null
* The query result if successful, <code>null</code> otherwise
*/
private function _query($query, $encoding = 'utf8')
{
$this->_resetLastID();
 
if (mysql_query("SET CHARACTER SET $encoding"))
{
if (DEBUG > 1)
{
echo "<pre>$query</pre>";
}
 
return @mysql_query($query);
}
else
{
/* DEBUG */
echo mysql_errno() . ':' . mysql_error();
exit;
}
 
return null;
}
 
private function _tableExists($table)
{
$tablesInDb = $this->_query("SHOW TABLES FROM {$this->db_name} LIKE '{$table}'");
if ($tablesInDb)
{
if (mysql_num_rows($tablesInDb) == 1)
{
return true;
}
else
{
return false;
}
}
}
/**
* Determines if an array is associative (does not have a '0' key)
*
* @param array $a
* @return boolean
* <code>true</code> if <var>$a</var> is associative,
* <code>false</code> otherwise
*/
private function _isAssociativeArray(array $a)
{
return !array_key_exists(0, $a);
}
 
/**
* Escapes an associative array so that its string representation can be used
* in a query.
*
* NOTE: Intentionally does not check whether the array actually is associative!
*
* @param array &$array
* The array to be escaped
* @return array
* The escaped array
*/
private function _escapeArray(array &$array)
{
foreach ($array as $column => &$value)
{
if (is_string($value))
{
$value = mysql_real_escape_string($value);
}
 
$value = "`" . mysql_real_escape_string($column) . "`='{$value}'";
}
return $array;
}
/**
* Constructs the WHERE part of a query
*
* @param string|array $where Condition
* @return string
*/
private function _where($where)
{
if (!is_null($where))
{
if (is_array($where))
{
if (count($where) < 1)
{
return '';
}
if ($this->_isAssociativeArray($where))
{
$this->_escapeArray($where);
}
 
$where = '(' . join(') AND (', $where) . ')';
}
 
return ' WHERE ' . $where;
}
return '';
}
/**
* Selects data from one or more tables; the resulting records are stored
* in the <code>result</code> property.
*
* @param string|array[string] $tables Table(s) to select from
* @param string|array[string] $columns Column(s) to select from (optional)
* @param string|array $where Condition (optional)
* @param string $order Sort order (optional)
* @param string $limit Limit (optional)
* @return bool
* @throws <code>Exception</code> if the query fails
*/
public function select($tables, $columns = null, $where = null, $order = null, $limit = null)
{
$this->_result = array();
 
if (is_null($columns))
{
$columns = array('*');
}
if (!is_array($columns))
{
$columns = array($columns);
}
 
$columns = join(',', $columns);
if (!is_array($tables))
{
$tables = array($tables);
}
 
$tables = join(',', $tables);
 
$q = "SELECT $columns FROM $tables" . $this->_where($where);
 
if (!is_null($order))
{
$q .= " ORDER BY $order";
}
 
if (!is_null($limit))
{
$q .= " LIMIT $limit";
}
if (DEBUG > 1)
{
echo "<pre>$q</pre>";
}
 
/* DEBUG */
// debug($q);
 
$_result = $this->_query($q);
if ($_result)
{
$this->numResults = mysql_num_rows($_result);
for ($i = 0, $len = $this->numResults; $i < $len; ++$i)
{
$_results = mysql_fetch_array($_result);
 
/* DEBUG */
// debug($_results);
 
$keys = array_keys($_results);
foreach ($keys as $key)
{
if (!is_int($key))
{
if (mysql_num_rows($_result) > 0)
{
$this->_result[$i][$key] = $_results[$key];
}
else if (mysql_num_rows($_result) < 1)
{
$this->_result = null;
}
}
}
}
 
return $q;
}
else
{
throw new Exception(mysql_error() . ". Query: " . $q);
}
}
 
/**
* Inserts a record into a table.<p>The AUTO_INCREMENT value of the inserted
* row, if any (> 0), is stored in the {@link $lastId} property of
* the <code>Database</code> instance.</p>
*
* @param string $table
* Table name
* @param array|string $values
* Associative array of column-value pairs, indexed array,
* or comma-separated list of values. If <var>$values</var> is not
* an associative array, <var>$cols</var> must be passed if the
* values are not in column order (see below).
* @param array|string $cols
* Indexed array, or comma-separated list of column names.
* Needs only be passed if <var>$values</var> is not an associative array
* and the values are not in column order (default: <code>null</code>);
* is ignored otherwise. <strong>You SHOULD NOT rely on column order.</strong>
* @return bool
* <code>true</code> if successful, <code>false</code> otherwise
*/
public function insert($table, $values, $cols = null)
{
if ($cols != null)
{
$cols = ' ('
. (is_array($cols)
? join(',', array_map(create_function('$s', 'return "`$s`";'), $cols))
: $cols) . ')';
}
else
{
$cols = '';
}
 
/* DEBUG */
// debug($values);
 
if ($this->_isAssociativeArray($values))
{
$this->_escapeArray($values);
 
$cols = '';
$values = 'SET ' . join(', ', $values);
}
else
{
foreach ($values as &$value)
{
if (is_string($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}
}
$values = ' VALUES (' . join(', ', $values) . ')';
}
 
$insert = "INSERT INTO `{$table}` {$cols} {$values}";
 
/* DEBUG */
// echo "Insert:<br>";
// debug($insert);
 
$ins = $this->_query($insert);
$this->_lastId = mysql_insert_id();
 
if ($ins)
{
return true;
}
else
{
return false;
}
}
 
/**
* Deletes records from a table
*
* @param string $table
* @param string|array|null $where Condition for deletion
* @return bool
*/
public function delete($table, $where = null)
{
$delete = "DELETE FROM `{$table}`" . $this->_where($where);
 
/* DEBUG */
// debug($delete);
// $result = true;
 
$result = $this->_query($delete);
 
return ($result) ? true : false;
}
 
/**
* Updates one or more records
*
* @param string|array $tables
* Table name
* @param array $values
* Associative array of column-value pairs
* @param array|string $where
* Only the records matching this condition are updated
* @return string|bool
*/
public function update($tables, $updates, $where = null)
{
if (!$tables)
{
throw new InvalidArgumentException('No table specified');
}
if (is_array($tables))
{
$tables = join(',', $tables);
}
if (!$updates)
{
throw new InvalidArgumentException('No values specified');
}
$updates = join(',', $this->_escapeArray($updates));
$query = "UPDATE {$tables} SET {$updates}" . $this->_where($where);
/* DEBUG */
// echo "Update:<br>";
// debug($query);
$_result = $this->_query($query);
 
if (!$_result)
{
//debug(mysql_error());
return false;
}
 
return $query;
}
 
/**
* Inserts a row into a table or updates an existing one
*
* @param string $sTable
* Table name
* @param array $aValues
* Associative array of column-value pairs to be updated/inserted
* @param string|array $condition
* If there are no records matching this condition, a row will be inserted;
* otherwise matching records are updated
* @return mixed
*/
public function updateOrInsert($sTable, $aValues, $condition)
{
$result = $this->select($sTable, '*', $condition);
if (!$result)
{
exit;
}
 
// debug($this->result);
if (count($this->result) > 0)
{
$result = $this->update($sTable, $aValues, $condition);
}
else
{
$result = $this->insert($sTable, $aValues);
}
 
return $result;
}
 
/**
* @see Database::updateOrInsert()
*/
public function insertOrUpdate($sTable, $aValues, $condition)
{
return $this->updateOrInsert($sTable, $aValues, $condition);
}
 
public function getResult()
{
return $this->result;
}
}
?>
/trunk/Db/ODBCDB.php
0,0 → 1,31
<?php
 
require_once __DIR__ . '/Database.php';
 
class ODBCDB extends Database
{
/**
* ODBC alias
* @var string
*/
protected $_alias;
/**
* (non-PHPdoc)
* @see Database::_leftQuote
*/
protected $_leftQuote = '[';
/**
* (non-PHPdoc)
* @see Database::_rightQuote
*/
protected $_rightQuote = ']';
public function __construct()
{
// $this->_connection = @odbc_connect($this->_alias, $this->_username, $this->_password);
$this->_dsn = 'odbc:' . $this->_alias;
parent::__construct();
}
}
/trunk/Db/Adapter.php
0,0 → 1,101
<?php
 
require_once __DIR__ . '/../Model.php';
 
abstract class Adapter
{
/**
* Database used by the adapter
* @var Database
*/
protected $_database = null;
 
/**
* Constructs the adapter, associating a {@link Database} with it
* @param Database $database
*/
/* Singleton */
protected function __construct(Database $database)
{
$this->_database = $database;
}
/**
* Selects data from one or more tables
*
* @return array
* @see Database::select()
*/
public function select($table, $columns = null, $where = null, $order = null, $limit = null)
{
return $this->_database->select($table, $columns, $where, $order, $limit);
}
/**
* Finds all records matching the set properties of a model object
*
* @param Model $object
* @return array[Model]
*/
public function findAll(Model $object, $order = null, $limit = null)
{
$properties = $object->getPropertyVars();
$where = array();
foreach ($properties as $property)
{
if (!is_null($object->$property))
{
$where[$property] = $object->$property;
}
}
$class = get_class($object);
$query_result = $this->select($class::persistentTable, null, $where, $order, $limit);
$num_results = count($query_result);
if ($num_results === 0)
{
return null;
}
 
$result = array();
foreach ($query_result as $row)
{
$result[] = new $class($row);
}
 
return $result;
}
/**
* Finds the record for a model object by its primary key
*
* @param Model $object
* @return Model
* The filled object if the primary key value could be found,
* <code>null</code> otherwise
*/
public function find(Model $object)
{
$class = get_class($object);
$primaryKey = $class::persistentPrimaryKey;
if (is_array($primaryKey))
{
/* TODO */
}
 
$result = $this->select($class::persistentTable, null, array($primaryKey => $object->$primaryKey));
if (0 == count($result))
{
return null;
}
$row = $result[0];
$object->map($row);
return $object;
}
}
/trunk/Db/MySQLAdapter.php
0,0 → 1,17
<?php
 
require_once __DIR__ . '/Adapter.php';
require_once __DIR__ . '/MySQLDB.php';
 
class MySQLAdapter extends Adapter
{
/**
* Constructs the adapter, associating a {@link MySQLDB} with it
* @param MySQLDB $database
*/
/* Singleton */
protected function __construct(MySQLDB $database)
{
parent::__construct($database);
}
}
/trunk/Db/AccessDB.php
0,0 → 1,33
<?php
 
require_once __DIR__ . '/ODBCDB.php';
 
class AccessDB extends ODBCDB
{
/**
* Escapes a database name so that it can be used in a query.
*
* @param string $name
* The name to be escaped
* @return string
* The escaped name
*/
public function escapeName($name)
{
return '[' . $name . ']';
}
 
/**
* (non-PHPdoc)
* @see Database::_escapeAliasArray()
*/
protected function _escapeAliasArray(array &$array)
{
foreach ($array as $column => &$value)
{
$value = $value . ' AS [' . $column . ']';
}
return $array;
}
}
/trunk/Application.php
0,0 → 1,329
<?php
 
namespace PointedEars\PHPX;
 
if (!defined('\\DIRECTORY_SEPARATOR'))
{
define('DIRECTORY_SEPARATOR', '/');
}
 
function autoload ($class)
{
if (\strpos($class, '..') !== false)
{
throw new \InvalidArgumentException(
"Refusing to load unsafe class '{$class}'");
}
 
require_once \str_replace('\\', DIRECTORY_SEPARATOR,
\preg_replace(
'#^' . \preg_quote(__NAMESPACE__, '#') .'#',
__DIR__,
$class
)
) . '.php';
}
 
\spl_autoload_register(__NAMESPACE__ . '\\autoload');
 
/**
* Basic application class
*
* @author Thomas Lahn
*/
class Application
{
/**
* Relative path to the controllers directory
* @var string
*/
protected $_controllerPath = 'controllers';
 
/**
* Default controller of the application
* @var string
*/
protected $_defaultController = 'Index';
 
/**
* Registry key for the default database of the application
* @var string
*/
protected $_defaultDatabase;
 
/**
* Currently active controller of this application
* @var Controller
*/
protected $_currentController;
 
/**
* Singleton
*
* @var Application
*/
private static $_instance;
 
protected function __construct ()
{
/* Singleton pattern */
}
 
/**
* Gets a reference to the <code>Application</code> instance
*
* @param Application $instance
* The instance to be used as application. The default is a new
* application. This parameter is ignored if the application was
* already initialized.
* @return Application
*/
public static function getInstance (Application $instance = null)
{
if (self::$_instance === null)
{
self::$_instance = ($instance === null) ? new self() : $instance;
}
 
return self::$_instance;
}
 
/**
* Getter for properties
*
* @param string $name
* @throws ModelPropertyException
* @return mixed
*/
public function __get ($name)
{
/* Support for Object-Relational Mappers */
if (\strpos($name, 'persistent') === 0)
{
$class = \get_class($this);
return $class::${$name};
}
 
$method = 'get' . \ucfirst($name);
 
if (\method_exists($this, $method))
{
return $this->$method();
}
 
if (\property_exists($this, "_$name"))
{
return $this->{"_$name"};
}
 
return $this->$name;
}
 
/**
* Setter for properties
*
* @param string $name
* @param mixed $value The new property value before assignment
* @throws ModelPropertyException
*/
public function __set ($name, $value)
{
$method = 'set' . \ucfirst($name);
 
if (\method_exists($this, $method))
{
return $this->$method($value);
}
 
if (\property_exists($this, "_$name"))
{
$this->{"_$name"} = $value;
return $this->{"_$name"};
}
 
/* NOTE: Attempts to set other properties are _silently_ _ignored_ */
}
 
/**
* Runs the application, setting up session management and
* constructing the controller indicated by the URI
*/
public function run ()
{
$this->startSession();
 
$controller = self::getParam('controller', $_REQUEST);
if (!$controller)
{
$controller = $this->_defaultController;
}
 
$controller = \ucfirst($controller);
 
$controller = $controller . 'Controller';
require_once "{$this->_controllerPath}/{$controller}.php";
$this->_currentController = new $controller();
 
return $this;
}
 
protected function startSession ()
{
\session_start();
}
 
/**
* Gets a request parameter
*
* @param string $key
* Key to look up in the array
* @param array $array
* Array where to look up <var>$key</var>.
* The default is <code>$_GET</code>.
* @return mixed
* <code>null</code> if there is no such <var>$key</var>
* in <var>$array</var>
*/
public static function getParam ($key, array $array = null)
{
if ($array === null)
{
$array = $_GET;
}
 
return isset($array[$key]) ? $array[$key] : null;
}
 
/**
* Registers a database
*
* @param string $key
* @param Database $database
* @return string Registry key
* @see Application::setDefaultDatabase()
*/
public function registerDatabase ($key, Db\Database $database)
{
Registry::set($key, $database);
return $key;
}
 
/**
* Sets the default database
* @param string Registry key to refer to the {@link Database}
*/
public function setDefaultDatabase ($key)
{
$this->_defaultDatabase = $key;
}
 
/**
* Sets the current controller for this application
*
* @param Controller $controller
* @return Application
*/
public function setCurrentController (Controller $controller)
{
$this->_currentController = $controller;
return $this;
}
 
/**
* Returns the current controller for this application
*
* @return Controller
*/
public function getCurrentController ()
{
return $this->_currentController;
}
 
/**
* Returns the default database for this application
*
* @return Database
*/
public function getDefaultDatabase ()
{
return Registry::get($this->_defaultDatabase);
}
 
/**
* Returns a relative URI-reference for an action of the
* application
*
* @param string[optional] $controller
* @param string[optional] $action
* @param int[optional] $id
*/
public function getURL ($controller = null, $action = null, $id = null)
{
/* Apache module */
$url = self::getParam('SCRIPT_URL', $_SERVER);
if ($url === null)
{
/* FastCGI */
$url = self::getParam('URL', $_SERVER);
if ($url === null)
{
/* Server/PHP too old, compute URI */
$url = self::getParam('REQUEST_URI', $_SERVER);
if (\preg_match('/^[^?]+/', $url, $matches) > 0)
{
$url = $matches[0];
}
else
{
/* Has .php in it, but at least it works */
$url = self::getParam('SCRIPT_NAME', $_SERVER);
if ($url === null)
{
throw new \Exception(
'None of $_SERVER["SCRIPT_URL"], $_SERVER["URL"],'
. ' $_SERVER["REQUEST_URI"], or $_SERVER["SCRIPT_NAME"]'
. ' is available, cannot continue.');
}
}
}
}
 
$query = (($controller !== null) ? 'controller=' . $controller : '')
. (($action !== null) ? '&action=' . $action : '')
. (($id !== null) ? '&id=' . $id : '');
 
return $url . ($query ? '?' . $query : '');
}
 
/**
* Performs a server-side redirect within the application
*/
public static function redirect ($query = '')
{
$script_uri = self::getParam('SCRIPT_URI', $_SERVER);
if ($script_uri === null)
{
/* Server/PHP too old, compute URI */
if (\preg_match('/^[^?]+/',
self::getParam('REQUEST_URI', $_SERVER), $matches) > 0)
{
$query_prefix = $matches[0];
}
else
{
/* Has .php in it, but at least it works */
$query_prefix = self::getParam('SCRIPT_NAME', $_SERVER);
}
 
/* TODO: Let user decide which ports map to which URI scheme */
$script_uri = (self::getParam('SERVER_PORT', $_SERVER) == 443
? 'https://'
: 'http://')
. self::getParam('HTTP_HOST', $_SERVER)
. $query_prefix;
}
 
\header('Location: ' . $script_uri
. ($query ? (substr($query, 0, 1) === '?' ? '' : '?') . $query : ''));
}
}
/trunk/StrictModel.php
0,0 → 1,13
<?php
 
namespace PointedEars\PHPX;
 
/**
* Abstract model class for strict Object-Relational Mapping
*
* @author Thomas Lahn
*/
abstract class StrictModel extends \PointedEars\PHPX\Model
{
protected static $_strict = true;
}
/trunk/Base.php
0,0 → 1,133
<?php
 
namespace PointedEars\PHPX;
 
/**
* Base class providing generic wrappers for reading from and
* and writing to inaccessible properties.
*
* For each such property there must exist a public dynamically
* bound method (the PHP default) in the inheriting class whose
* name is prefixed with 'get' (a <i>getter</i>, for read access)
* and/or with 'set' (a <i>setter</i>, for write access) followed
* by the property name. (It is recommended to write the first
* letter of the property name in the method name in uppercase.
* For runtime-efficiency, underscores in the property name are
* <em>not</em> converted to camel-case ["property_name" requires
* "getProperty_name", <em>not</em> "getPropertyName"].)
*
* The getter or setter would then access the non-public property
* whose name is the name of the accessed property prefixed with
* underscore ('_'), called the <em>underscore property</em>.
* It can make sure that the value of the underscore property is
* of a specific type or within a specific range, i. e. perform
* automatic type conversion, normalize values that are out of
* range when the property is read, and reject/ignore attempts
* to set unsuitable property values. This is particularly useful
* with instances of model classes; see {@link AbstractModel} and
* {@link Model}.
*
* Properties that do not have a getter are not available
* from unprivileged context, and an exception is thrown
* when attempting to read from them there.
*
* Properties that do not have a setter are effectively
* <em>read-only</em> from unprivileged context, and
* an exception is thrown when attempting to write to them there.
*
* @author Thomas 'PointedEars' Lahn
*/
abstract class Base
{
/**
* Determines if a strict model is enforced.
*
* If <code>true</code>, all publicly accessible properties
* must have a getter if readable, and a setter if writable.
* Otherwise, accesses to non-existing public properties will be
* forwarded to the correspnding underline property if no getter
* or setter has been defined for it. The default is
* <code>false</code> (non-strict) as that speeds up property
* accesses and eases implementation considerably.
*
* @var bool
*/
protected static $_strict = false;
 
/**
* Retrieves a property value.
*
* Automagically called when attempting to read data
* from inaccessible properties.
*
* @param string $name
* Property name
* @throws InvalidArgumentException
* if the underscore property for the property
* named <code><var>$name</var></code> does not exist
* or has no getter
* @return mixed
* Return value of the property-specific getter
*/
public function __get ($name)
{
$getter = 'get' . ucfirst($name);
if (method_exists($this, $getter))
{
return $this->$getter();
}
 
if (property_exists($this, "_$name"))
{
$class = get_class($this);
if ($class::$_strict)
{
throw new \InvalidArgumentException("Strict model: Property '{$name}' has no getter");
}
 
return $this->{"_$name"};
}
 
throw new \InvalidArgumentException("No such property: '{$name}'");
}
 
/**
* Sets a property value.
*
* Automagically called when attempting to write data
* to inaccessible properties.
*
* @param string $name
* Property name
* @param mixed $value
* Property value
* @throws InvalidArgumentException
* if the protected underscore property for the property
* named <code><var>$name</var></code> does not exist
* or has no setter (is read-only)
* @return mixed
* Return value of the property-specific setter
*/
public function __set ($name, $value)
{
$setter = 'set' . ucfirst($name);
if (method_exists($this, $setter))
{
return $this->$setter($value);
}
 
if (property_exists($this, "_$name"))
{
$class = get_class($this);
if ($class::$_strict)
{
throw new \InvalidArgumentException("Strict model: Property '{$name}' has no setter");
}
 
$this->{"_$name"} = $value;
return $value;
}
 
throw new \InvalidArgumentException("No such property: '{$name}'");
}
}
/trunk/View.php
0,0 → 1,358
<?php
 
namespace PointedEars\PHPX;
 
/**
* A general view handled by a controller according to the MVC pattern
*
* @author Thomas 'PointedEars' Lahn &lt;php@PointedEars.de>
*/
class View
{
/**
* Default template resource path
*
* @var string
*/
protected $_template = '';
 
/**
* Content that can be inserted in the template
*
* @var string
*/
protected $_content = '';
 
/**
* Template variables. The variable name serves as item key, the item's value
* is the variable value.
*
* @var array
*/
protected $_template_vars = array();
 
/**
* Stylesheets to be inserted into the <code>head</code> element
*
* @var array
*/
protected $_stylesheets = array();
 
/**
* Scripts to be inserted into the <code>head</code> or
* <code>body</code> element
*
* @var array
*/
protected $_scripts = array();
 
/**
* Creates a new view
*
* @param string $template
* Template resource path
*/
public function __construct ($template)
{
$this->_template = $template;
}
 
/**
* Magic setter method used for defining template variables
*
* @param string $name
* Variable name
* @param mixed $value
* Variable value
*/
public function __set ($name, $value)
{
$this->_template_vars[$name] = $value;
}
 
/**
* Magic getter method used for retrieving values of template variables
*
* @param string $name
* Variable name
*/
public function __get ($name)
{
return $this->_template_vars[$name];
}
 
/**
* Returns <var>$v</var> with occurences of '&' (ampersand), '"' (double quote),
* "'" (single quote), '<' (less than), and '>' (greater than) replaced by their
* HTML character entity references, if any, or their numeric HTML character
* reference (as required primarily in HTML for attribute values and element
* content).
*
* @param mixed $value
*/
public function escape ($value)
{
if (is_array($value))
{
return array_map(array('self', 'escape'), $value);
}
else if (is_object($value))
{
if ($value instanceof AbstractModel)
{
foreach ($value->getPropertyVars() as $varName)
{
$value->$varName = self::escape($value->$varName);
}
}
 
return $value;
}
else
{
if (is_string($value))
{
$encoding = mb_detect_encoding($value);
if ($encoding === 'ASCII')
{
$encoding = 'ISO-8859-1';
}
return htmlspecialchars($value, ENT_QUOTES, $encoding);
}
 
return $value;
}
}
 
/**
* Assigns a value to a template variable
*
* @param string $name
* Variable name
* @param mixed $value
* Variable value
* @param bool $escape
* If <code>true</code>, replace all potentially conflicting characters
* in <var>$value</var> with their HTML entity references. The default is
* <code>false</code>.
* @return mixed The assigned value (after possible HTML encoding)
* @see View::escape()
*/
public function assign ($name, $value, $escape = false)
{
if ($escape)
{
$value = $this->escape($value);
}
 
$this->$name = $value;
return $value;
}
 
/**
* Adds an CSS resource (stylesheet)
* to the list of external stylesheets
*
* @param string $uri
* Stylesheet URI
* @param mixed $key (optional)
* Array key for the script. May be used later to exclude
* or include the code for a specific stylesheet.
* @return array
* The list of stylesheets
*/
public function addStylesheet ($uri, $key = null)
{
$stylesheets =& $this->_stylesheets;
 
if ($key !== null)
{
$stylesheets[$key] = $uri;
}
else
{
$stylesheets[] = $uri;
}
 
return $stylesheets;
}
 
/**
* Adds several CSS resources (stylesheets)
* to the list of external stylesheets
*
* @param array $uris
* Stylesheet URIs
* @return array
* The list of stylesheets
*/
public function addStylesheets (array $uris)
{
$stylesheets = $this->_stylesheets;
 
foreach ($uris as $uri)
{
$stylesheets = $this->addStylesheet($uri);
}
 
return $stylesheets;
}
 
/**
* Adds an ECMAScript resource (script)
* to the list of external scripts
*
* @param string $uri
* Script URI
* @param mixed $key (optional)
* Array key for the script. May be used later to exclude
* or include the code for a specific script.
*/
public function addScript ($uri, $key = null)
{
$scripts =& $this->_scripts;
 
if ($key !== null)
{
$scripts[$key] = $uri;
}
else
{
$scripts[] = $uri;
}
 
return $scripts;
}
 
/**
* Adds several ECMAScript resources (scripts)
* to the list of external scripts
*
* @param array $uris
* Script URIs
* @return array
* The list of scripts
*/
public function addScripts (array $uris)
{
$scripts = $this->_scripts;
 
foreach ($uris as $uri)
{
$scripts = $this->addScript($uri);
}
 
return $scripts;
}
 
/**
* Renders the view by including a template
*
* @param string $template
* Optional alternative template resource path.
* If not provided, the default template ($template property) will be used.
* @throws Exception if no template has been defined before
*/
public function render ($template = null, $content = null)
{
if (!is_null($content))
{
ob_start();
require_once $content;
$this->_content = ob_get_contents();
ob_end_clean();
}
 
if (!is_null($template))
{
require $template;
}
elseif ($this->_template)
{
require $this->_template;
}
else
{
throw new \Exception('No template defined');
}
}
 
/**
* Returns the code for including all stylesheets,
* with exclusions.
*
* @param array $exclusions (optional, future)
* The keys of the stylesheets that should be excluded
* @return string
*/
public function getStylesheets ($exclusions = array())
{
return (
implode(PHP_EOL, array_map(function ($uri) {
return '<link rel="stylesheet" href="' . $this->escape($uri) . '">';
}, array_diff_key($this->_stylesheets, array_flip($exclusions))))
. PHP_EOL);
}
 
/**
* Returns the code for including a specific stylesheet
*
* @param mixed $key
* @return string
*/
public function getStylesheet ($key)
{
return '<link rel="stylesheet" href="'
. $this->escape($this->_stylesheets[$key])
. '">' . PHP_EOL;
}
 
/**
* Returns the code for including all stylesheets,
* with exclusions.
*
* @param array $exclusions (optional, future)
* The keys of the scripts that should be excluded.
* Usually you would specify those scripts that you
* want to include with {@link #getScript()}.
* @return string
*/
public function getScripts ($exclusions = array())
{
return (
implode(PHP_EOL, array_map(function ($uri) {
return '<script type="text/javascript" src="'
. $this->escape($uri)
. '"></script>';
}, array_diff_key($this->_scripts, array_flip($exclusions))))
. PHP_EOL);
}
 
public function getScript ($key)
{
return '<link rel="stylesheet" href="'
. $this->escape($this->_scripts[$key])
. '">' . PHP_EOL;
}
 
/**
* Returns the content for insertion into the template
*/
public function getContent ()
{
return $this->_content;
}
 
/**
* @param string[optional] $controller
* @param string[optional] $action
* @param int[optional] $id
* @see Application::getURL()
*/
public function getURL ($controller = null, $action = null, $id = null)
{
return Application::getInstance()->getURL($controller, $action, $id);
}
}
 
?>
/trunk/Controller.php
0,0 → 1,138
<?php
 
namespace PointedEars\PHPX;
 
/**
* A general controller that can handle views according to
* the MVC pattern
*
* @author tlahn
*/
abstract class Controller
{
/**
* Default action of the controller
* @var string
*/
protected $_defaultAction = 'index';
 
/**
* If an array, maps an action name to an action method of the controller.
*
* Fallback for IE 7 where the content of a <code>button</code> element
* is submitted as value.
*
* @var array
*/
protected $_actionMap = null;
 
/**
* The {@link View} used by this controller
* @var View
*/
protected $_view = null;
 
/**
* Constructs a controller, initializes the related view,
* and calls the controller's URI-indicated action method.
*
* @param string $viewClass
* View class. The default is <code>'View'</code>.
* @param string $template
* Resource path of the template for the view. The default
* is to <code>null</code> (no template).
*
* @see View::__construct()
*/
protected function __construct ($viewClass = 'View', $template = null)
{
$this->_view = new $viewClass($template);
 
Application::getInstance()->setCurrentController($this);
 
$action = Application::getParam('action', $_REQUEST);
 
/* NOTE: No `==='; treat empty action like no specific action */
if ($action == null)
{
$action = $this->_defaultAction;
}
 
$actionMethod = lcfirst($action) . 'Action';
 
/* Fallback for IE 7 where the content of a `button' element is submitted as value */
if (!method_exists($this, $actionMethod))
{
if (is_array($this->_actionMap) && array_key_exists($action, $this->_actionMap))
{
$actionMethod = $this->_actionMap[$action];
}
}
 
$this->$actionMethod();
}
 
/**
* (non-PHPDoc)
* @see View::assign()
*/
protected function assign ($name, $value, $encodeHTML = false)
{
return $this->_view->assign($name, $value, $encodeHTML);
}
 
/**
* (non-PHPDoc)
* @see View::addStylesheet()
*/
protected function addStylesheet ($uri, $key = null)
{
return $this->_view->addStylesheet($uri, $key);
}
 
/**
* (non-PHPDoc)
* @see View::addStylesheets()
*/
protected function addStylesheets (array $uris)
{
return $this->_view->addStylesheets($uris);
}
 
/**
* (non-PHPDoc)
* @see View::addSscript()
*/
protected function addScript ($uri, $key = null)
{
return $this->_view->addScript($uri, $key);
}
 
/**
* (non-PHPDoc)
* @see View::addSscripts()
*/
protected function addScripts (array $uris)
{
return $this->_view->addScripts($uris);
}
 
/**
* Renders the {@link View} associated with this controller
* by including the <code>View</code>'s template.
* <code>Controller</code>s should call this method instead of
* <code>View::render()</code>.
*
* @param string $template
* Optional alternative template resource path.
* If not provided, the default template (the
* <code>View</code>'s <code>$template</code> property)
* will be used.
*/
public function render ($template = null, $content = null)
{
$this->_view->render($template, $content);
}
}
 
?>
/trunk/Model.php
0,0 → 1,308
<?php
 
namespace PointedEars\PHPX;
 
/**
* Abstract model class for Object-Relational Mapping
*
* Provides simple mapping of a model object to records of
* a table of a relational database.
*
* @property Db\Table $persistentTable
* @author Thomas Lahn
*/
abstract class Model extends \PointedEars\PHPX\AbstractModel
{
/**
* The <code>Table</code> for instances of this model
*
* @type Table|string
*/
protected static $_persistentTable;
 
/**
* The name(s) of the property or properties whose value(s)
* identify this object in the <code>Table</code>. They are
* used for comparing against the primary key column(s) of
* the <code>Table</code>.
*
* @type string|array[string]
*/
protected static $_persistentId = 'id';
 
/**
* The names of the properties that should be used in database
* queries, and their mapping to the columns of
* the <code>Table</code>, if specified (keys are property names,
* values are column names, or both if the key is numeric).
*
* NOTE: It should not be necessary to include the
* <code>persistentId</code> property value here. If an object
* is not in the database, it should be assigned an ID
* automatically when saved; if it is in the database,
* you already have its ID as you searched by it.
*
* @type array
*/
protected static $_persistentProperties = array();
 
/**
* Creates a new model object
*
* @see AbstractModel::__construct()
*/
public function __construct (
array $data = null, array $mapping = null, $exclusiveMapping = false)
{
parent::__construct($data, $mapping, $exclusiveMapping);
}
 
public function getPersistentTable ()
{
$class = \get_class($this);
if (\is_string($class::$_persistentTable))
{
/* Call setter to convert to Table */
$this->setPersistentTable($class::$_persistentTable);
}
 
return $class::$_persistentTable;
}
 
public function setPersistentTable ($value)
{
$class = \get_class($this);
if ($value instanceof Table)
{
$class::$_persistentTable = $value;
}
else
{
$table = new $value();
if (!($table instanceof Db\Table))
{
throw new \InvalidArgumentException(
'Parameter does not specify a subclass of \\PointedEars\\PHPX\\Table: '
. $value
);
}
 
$class::$_persistentTable = $table;
}
}
 
/**
* Returns an array containing the property-column mapping.
*
* @param array $propertyNames = null
* Names of the properties that should be included.
* The default is to include all persistent properties.
* @return array
*/
public static function getPersistentMapping (array $propertyNames = null)
{
$a = array();
 
$persistent_properties = static::$_persistentProperties;
 
if ($propertyNames === null)
{
$propertyNames = $persistent_properties;
}
 
foreach ($propertyNames as $property_name)
{
if (!array_key_exists($property_name, $persistent_properties))
{
$column_name = $property_name;
}
else
{
$column_name = $persistent_properties[$property_name];
}
 
$a[$property_name] = $column_name;
}
 
return $a;
}
 
/**
* Returns an array for database queries containing the
* property values of this object, using the specified
* property-to-column mapping.
*
* @param array $propertyNames = null
* Names of the properties that should be included.
* The default is to include all persistent properties.
* @return array
*/
public function getPropertyArray (array $propertyNames = null)
{
$a = array();
 
if ($propertyNames === null)
{
$class = \get_class($this);
$propertyNames = $class::$_persistentProperties;
}
 
foreach ($propertyNames as $propertyName => $columnName)
{
if (is_numeric($propertyName))
{
$propertyName = $columnName;
}
 
$a[$columnName] = $this->$propertyName;
}
 
return $a;
}
 
/**
* Finds the record for the model object in the table, fills
* the object with missing data, and returns the result.
*
* @param mixed $id = null
* The ID of the object to find. If <code>null</code> (default),
* the object is searched for by its current ID.
* @return Model|null
* This object filled with missing data, or <code>null</code>
* if there is no data for this object
* @see Table::find(int)
*/
public function find ($id = null)
{
$class = \get_class($this);
if ($id === null)
{
$id = $this->{$class::$_persistentId};
}
 
$result = $this->persistentTable->find($id);
if ($id !== null)
{
$this->{$class::$_persistentId} = $id;
}
 
$result = $this->persistentTable->find($this->{$class::$_persistentId});
if ($result)
{
return $this->map($result);
}
 
return null;
}
 
/**
* Finds the records for model objects in the table by value,
* and returns the result.
*
* @param string $name
* Name of the property to search for.
* @param mixed $value = null
* The property value to find. If <code>null</code>,
* the current value of the specified property is used.
* (To search for null, you need to make sure that the
* current property value is <code>null</code>, which is
* the PHP default.)
* @return array[Model]|null
* Model objects, or <code>null</code> if there is no data
* for this property and value
* @see Table::select(Model)
*/
public function findByProperty ($name, $value = null)
{
$class = \get_class($this);
$mapping = $class::getPersistentMapping(array($name));
 
if ($value === null)
{
$value = $this->$name;
}
 
$results = $this->persistentTable->select(null,
array($mapping[$name] => $value));
 
if ($results)
{
return array_map(array($this, 'map'), $results);
}
 
return null;
}
 
/**
* Saves the model object in the table
*
* @param array $propertyNames = null
* Names of the properties whose values should be saved
* in the database. The default is to save the values
* of all persistent properties.
* @return boolean
* The return value of
* <code>$this->persistentTable->updateOrInsert()</code>.
* @see Model::getPropertyArray()
* @see Table::updateOrInsert()
*/
public function save (array $propertyNames = null)
{
$table = $this->persistentTable;
$class = \get_class($this);
$idPropertyName = $class::$_persistentId;
 
$result = $table->updateOrInsert(
$this->getPropertyArray($propertyNames),
array(
$table->id => $this->$idPropertyName
)
);
 
if ($result && ($lastInsertId = $table->lastInsertId))
{
$this->$idPropertyName = $lastInsertId;
}
 
return $result;
}
 
/**
* Inserts the model object into the table
*
* @param array $propertyNames = null
* Names of the properties whose values should be insert
* in the database. The default is to insert the values
* of all persistent properties.
* @return boolean
* @see Model::getPropertyArray()
* @see Table::insert()
*/
public function insert (array $propertyNames = null)
{
$table = $this->persistentTable;
$class = \get_class($this);
$idPropertyName = $class::$_persistentId;
 
$result = $table->insert($this->getPropertyArray($propertyNames));
 
if ($result && ($lastInsertId = $table->lastInsertId))
{
$this->$idPropertyName = $lastInsertId;
}
 
return $result;
}
 
/**
* Deletes a model object from the <code>Table</code>
*
* @return bool
* @see Table::delete()
*/
public function delete ()
{
$class = \get_class($this);
return $this->persistentTable->delete($this->{$class::$_persistentId});
}
}
/trunk/AbstractModel.php
0,0 → 1,140
<?php
 
namespace PointedEars\PHPX;
 
/**
* Interface to be implemented if the model should be localizable
*/
interface ILocalizable
{
/**
* Localizes this model. The actual implementation is left to
* the model class implementing this interface.
*/
function localize ();
}
 
/**
* Abstract model class
*
* Provides a constructor to initialize properties using setters and getters.
*
* @author Thomas Lahn
*/
abstract class AbstractModel extends Base
{
/**
* Creates a new model object
*
* @param array $data
* Initialization data (optional)
* @param array $mapping
* Mapping for initialization data (optional)
* @param bool $exclusiveMapping
* Exclusive mapping (optional; default: inclusive)
* @see AbstractModel::map()
*/
protected function __construct (
array $data = null, array $mapping = null, $exclusiveMapping = false)
{
if (!is_null($data))
{
$this->map($data, $mapping, $exclusiveMapping);
}
}
 
/**
* Returns <code>true</code> if a variable name is a property variable name
* (starts with <tt>$_</tt>), <code>false</code> otherwise.
*
* @param string $varName
* @return boolean
* @see getPropertyVars()
*/
private static function _isPropertyVar ($varName)
{
return preg_match('/^_\\w/', $varName) > 0;
}
 
/**
* Returns <code>true</code> if a variable name is a property variable name
* (starts with <tt>$_</tt>), <code>false</code> otherwise.
*
* @param string $varName
* @return string
* @see getPropertyVars()
*/
private static function _toPropertyVar ($varName)
{
return preg_replace('/^_(\\w)/', '\\1', $varName);
}
 
/**
* Returns the public names of the property variables of a {@link Model}
* as an array of strings
*
* @return array
*/
public function getPropertyVars ()
{
return array_map(
array('self', '_toPropertyVar'),
array_filter(
array_keys(get_object_vars($this)),
array('self', '_isPropertyVar')
)
);
}
 
/**
* Maps the values of an associative array to a model object
*
* @param array $data
* Data to be mapped to properties of the object
* @param array $mapping = null
* <p>If <var>$mapping</var> is not provided, or <code>null</code> (default),
* the values of <var>$data</var> are mapped to properties of
* the model object as specified by the keys of <var>$data</var>.</p>
* <p>If <var>$mapping</var> is provided and an array, the keys of
* <var>$data</var> are mapped to properties as specified by
* the corresponding values of <var>$mapping</var>. If a value of
* <var>$mapping</var> is <code>null</code>, the corresponding value
* in <var>$data</var> is not mapped; if a key is missing in
* <var>$mapping</var>, the value is mapped as if <var>$mapping</var>
* was <code>null</code>.</p>
* @param bool $exclusive = false
* <p>If <code>true</code>, <em>only</em> the keys of <var>$data</var>
* that are present in <var>$mapping</var> are mapped.</p>
* @return AbstractModel
* The modified object
*/
public function map (array $data, array $mapping = null, $exclusive = false)
{
if (is_null($mapping))
{
foreach ($data as $key => $value)
{
$this->$key = $value;
}
}
else
{
foreach ($data as $key => $value)
{
if (array_key_exists($key, $mapping))
{
if ($exclusive || !is_null($mapping[$key]))
{
$this->{$mapping[$key]} = $value;
}
}
else
{
$this->$key = $value;
}
}
}
 
return $this;
}
}
/trunk/Registry.php
0,0 → 1,41
<?php
 
namespace PointedEars\PHPX;
 
/**
* Basic registry class
*
* @author Thomas Lahn
*/
abstract class Registry
{
/**
* Data storage
* @var array
*/
protected static $_data = array();
 
/**
* Puts data in storage
*
* @param string $key
* @param mixed $value
* @return mixed The stored value
*/
public static function set($key, $value)
{
self::$_data[$key] = $value;
return self::get($key);
}
 
/**
* Gets data from storage
*
* @param string $key
* @return mixed
*/
public static function get($key)
{
return self::$_data[$key];
}
}
/trunk/dom/tests/DOMDocument2Test.php
0,0 → 1,48
<?php
 
require_once __DIR__ . '/../DOMDocument2.php';
 
use de\pointedears\dom\DOMDocument2;
 
class DOMDocument2Test extends \PHPUnit_Framework_Testcase
{
public function testRenameElementNode ()
{
$d = new DOMDocument2();
$d->appendChild($d->createElement("foo"))
->appendChild($d->createElement("bar"))
->setAttributeNS(null, 'evil', 23);
$d->firstChild->setAttributeNS(null, 'answer', 42);
 
echo "\nBefore:\n\n" . $d->saveXML() . "\n";
 
$d->renameNode($d->firstChild, null, "baz");
echo "After:\n\n" . $d->saveXML() . "\n";
 
$this->assertTrue($d->firstChild->nodeName === 'baz');
$this->assertTrue($d->firstChild->firstChild->nodeName === 'bar');
}
 
public function testRenameAttributeNode ()
{
$d = new DOMDocument2();
$d->appendChild($d->createElement("foo"))
->setAttributeNS(null, 'evil', 42);
 
echo "\nBefore:\n\n" . $d->saveXML() . "\n";
 
$attrNode = $d->firstChild->getAttributeNode("evil");
 
$d->renameNode($attrNode, null, "answer");
echo "After:\n\n" . $d->saveXML();
 
$this->assertTrue($d->firstChild->getAttribute("answer") === "42");
}
 
public function testRenameStandaloneAttr ()
{
$d = new DOMDocument2();
$attr = $d->renameNode(new \DOMAttr('evil', '42'), null, "answer");
$this->assertTrue($attr->name === "answer" && $attr->value === "42");
}
}
/trunk/dom/DOMDocument2.php
0,0 → 1,93
<?php
 
namespace de\pointedears\dom;
 
class DOMDocument2 extends \DOMDocument
{
/**
* Renames an existing node of type <code>XML_ELEMENT_NODE</code>
* or <code>XML_ATTRIBUTE_NODE</code>
*
* @param \DOMNode $node
* @param string $namespaceURI = null
* @param string $qualifiedName
* @return \DOMNode The renamed node
*/
public function renameNode (\DOMNode $node, $namespaceURI,
$qualifiedName)
{
$result = false;
 
if ($node->nodeType === XML_ELEMENT_NODE)
{
/*
* Per W3C DOM Level 3 Core
* <http://www.w3.org/TR/DOM-Level-3-Core/core.html#Document3-renameNode>
*
* "If simply changing the name of the given node is not possible,
* the following operations are performed:
*
* a new node is created, ...
*/
$newNode = $this->createElementNS($namespaceURI,
$qualifiedName, $node->nodeValue);
 
/*
* any registered event listener is registered on the new node
* [skipped], any user data attached to the old node is removed
* from that node the old node is removed from its parent if it
* has one [done by replaceChild]
*/
 
/* the children are moved to the new node */
while (($childNode = $node->firstChild))
{
$newNode->appendChild($childNode);
}
 
/*
* if the renamed node is an Element its attributes are moved
* to the new node,
*/
foreach ($node->attributes as $attribute)
{
$newNode->setAttributeNodeNS($attribute);
}
/*
* the new node is inserted at the position the old node used
* to have in its parent's child nodes list if it has one,
* the user data that was attached to the old node is attached
* to the new node."
*/
$result = $node->parentNode->replaceChild($newNode, $node);
}
else if ($node->nodeType === XML_ATTRIBUTE_NODE)
{
/*
* "When the node being renamed is an Attr that is attached
* to an Element, the node is first removed from the Element
* attributes map. Then, once renamed, either by modifying
* the existing node or creating a new one as described above,
* it is put back."
*/
if (($ownerElement = $node->ownerElement))
{
$ownerElement->removeAttributeNS($node->namespaceURI, $node->name);
 
$result = $ownerElement->setAttributeNS($namespaceURI, $qualifiedName, $node->value);
 
if ($result !== false)
{
$newNode = $ownerElement->getAttributeNode($qualifiedName);
}
}
else
{
/* Standalone attribute node */
return new \DOMAttr($qualifiedName, $node->value);
}
}
 
return ($result === false ? false : $newNode);
}
}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/SplAutoloader.php
0,0 → 1,63
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
 
namespace Zend\Loader;
 
use Traversable;
 
if (interface_exists('Zend\Loader\SplAutoloader')) return;
 
/**
* Defines an interface for classes that may register with the spl_autoload
* registry
*/
interface SplAutoloader
{
/**
* Constructor
*
* Allow configuration of the autoloader via the constructor.
*
* @param null|array|Traversable $options
*/
public function __construct($options = null);
 
/**
* Configure the autoloader
*
* In most cases, $options should be either an associative array or
* Traversable object.
*
* @param array|Traversable $options
* @return SplAutoloader
*/
public function setOptions($options);
 
/**
* Autoload a class
*
* @param $class
* @return mixed
* False [if unable to load $class]
* get_class($class) [if $class is successfully loaded]
*/
public function autoload($class);
 
/**
* Register the autoloader with spl_autoload registry
*
* Typically, the body of this will simply be:
* <code>
* spl_autoload_register(array($this, 'autoload'));
* </code>
*
* @return void
*/
public function register();
}
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/StandardAutoloader.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/StandardAutoloader.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/StandardAutoloader.php (revision 80)
@@ -0,0 +1,327 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader;
+
+// Grab SplAutoloader interface
+require_once __DIR__ . '/SplAutoloader.php';
+
+/**
+ * PSR-0 compliant autoloader
+ *
+ * Allows autoloading both namespaced and vendor-prefixed classes. Class
+ * lookups are performed on the filesystem. If a class file for the referenced
+ * class is not found, a PHP warning will be raised by include().
+ */
+class StandardAutoloader implements SplAutoloader
+{
+ const NS_SEPARATOR = '\\';
+ const PREFIX_SEPARATOR = '_';
+ const LOAD_NS = 'namespaces';
+ const LOAD_PREFIX = 'prefixes';
+ const ACT_AS_FALLBACK = 'fallback_autoloader';
+ const AUTOREGISTER_ZF = 'autoregister_zf';
+
+ /**
+ * @var array Namespace/directory pairs to search; ZF library added by default
+ */
+ protected $namespaces = array();
+
+ /**
+ * @var array Prefix/directory pairs to search
+ */
+ protected $prefixes = array();
+
+ /**
+ * @var bool Whether or not the autoloader should also act as a fallback autoloader
+ */
+ protected $fallbackAutoloaderFlag = false;
+
+ /**
+ * Constructor
+ *
+ * @param null|array|\Traversable $options
+ */
+ public function __construct($options = null)
+ {
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Configure autoloader
+ *
+ * Allows specifying both "namespace" and "prefix" pairs, using the
+ * following structure:
+ * <code>
+ * array(
+ * 'namespaces' => array(
+ * 'Zend' => '/path/to/Zend/library',
+ * 'Doctrine' => '/path/to/Doctrine/library',
+ * ),
+ * 'prefixes' => array(
+ * 'Phly_' => '/path/to/Phly/library',
+ * ),
+ * 'fallback_autoloader' => true,
+ * )
+ * </code>
+ *
+ * @param array|\Traversable $options
+ * @throws Exception\InvalidArgumentException
+ * @return StandardAutoloader
+ */
+ public function setOptions($options)
+ {
+ if (!is_array($options) && !($options instanceof \Traversable)) {
+ require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+ throw new Exception\InvalidArgumentException('Options must be either an array or Traversable');
+ }
+
+ foreach ($options as $type => $pairs) {
+ switch ($type) {
+ case self::AUTOREGISTER_ZF:
+ if ($pairs) {
+ $this->registerNamespace('Zend', dirname(__DIR__));
+ }
+ break;
+ case self::LOAD_NS:
+ if (is_array($pairs) || $pairs instanceof \Traversable) {
+ $this->registerNamespaces($pairs);
+ }
+ break;
+ case self::LOAD_PREFIX:
+ if (is_array($pairs) || $pairs instanceof \Traversable) {
+ $this->registerPrefixes($pairs);
+ }
+ break;
+ case self::ACT_AS_FALLBACK:
+ $this->setFallbackAutoloader($pairs);
+ break;
+ default:
+ // ignore
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set flag indicating fallback autoloader status
+ *
+ * @param bool $flag
+ * @return StandardAutoloader
+ */
+ public function setFallbackAutoloader($flag)
+ {
+ $this->fallbackAutoloaderFlag = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is this autoloader acting as a fallback autoloader?
+ *
+ * @return bool
+ */
+ public function isFallbackAutoloader()
+ {
+ return $this->fallbackAutoloaderFlag;
+ }
+
+ /**
+ * Register a namespace/directory pair
+ *
+ * @param string $namespace
+ * @param string $directory
+ * @return StandardAutoloader
+ */
+ public function registerNamespace($namespace, $directory)
+ {
+ $namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR;
+ $this->namespaces[$namespace] = $this->normalizeDirectory($directory);
+ return $this;
+ }
+
+ /**
+ * Register many namespace/directory pairs at once
+ *
+ * @param array $namespaces
+ * @throws Exception\InvalidArgumentException
+ * @return StandardAutoloader
+ */
+ public function registerNamespaces($namespaces)
+ {
+ if (!is_array($namespaces) && !$namespaces instanceof \Traversable) {
+ require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+ throw new Exception\InvalidArgumentException('Namespace pairs must be either an array or Traversable');
+ }
+
+ foreach ($namespaces as $namespace => $directory) {
+ $this->registerNamespace($namespace, $directory);
+ }
+ return $this;
+ }
+
+ /**
+ * Register a prefix/directory pair
+ *
+ * @param string $prefix
+ * @param string $directory
+ * @return StandardAutoloader
+ */
+ public function registerPrefix($prefix, $directory)
+ {
+ $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_SEPARATOR;
+ $this->prefixes[$prefix] = $this->normalizeDirectory($directory);
+ return $this;
+ }
+
+ /**
+ * Register many namespace/directory pairs at once
+ *
+ * @param array $prefixes
+ * @throws Exception\InvalidArgumentException
+ * @return StandardAutoloader
+ */
+ public function registerPrefixes($prefixes)
+ {
+ if (!is_array($prefixes) && !$prefixes instanceof \Traversable) {
+ require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+ throw new Exception\InvalidArgumentException('Prefix pairs must be either an array or Traversable');
+ }
+
+ foreach ($prefixes as $prefix => $directory) {
+ $this->registerPrefix($prefix, $directory);
+ }
+ return $this;
+ }
+
+ /**
+ * Defined by Autoloadable; autoload a class
+ *
+ * @param string $class
+ * @return false|string
+ */
+ public function autoload($class)
+ {
+ $isFallback = $this->isFallbackAutoloader();
+ if (false !== strpos($class, self::NS_SEPARATOR)) {
+ if ($this->loadClass($class, self::LOAD_NS)) {
+ return $class;
+ } elseif ($isFallback) {
+ return $this->loadClass($class, self::ACT_AS_FALLBACK);
+ }
+ return false;
+ }
+ if (false !== strpos($class, self::PREFIX_SEPARATOR)) {
+ if ($this->loadClass($class, self::LOAD_PREFIX)) {
+ return $class;
+ } elseif ($isFallback) {
+ return $this->loadClass($class, self::ACT_AS_FALLBACK);
+ }
+ return false;
+ }
+ if ($isFallback) {
+ return $this->loadClass($class, self::ACT_AS_FALLBACK);
+ }
+ return false;
+ }
+
+ /**
+ * Register the autoloader with spl_autoload
+ *
+ * @return void
+ */
+ public function register()
+ {
+ spl_autoload_register(array($this, 'autoload'));
+ }
+
+ /**
+ * Transform the class name to a filename
+ *
+ * @param string $class
+ * @param string $directory
+ * @return string
+ */
+ protected function transformClassNameToFilename($class, $directory)
+ {
+ // $class may contain a namespace portion, in which case we need
+ // to preserve any underscores in that portion.
+ $matches = array();
+ preg_match('/(?P<namespace>.+\\\)?(?P<class>[^\\\]+$)/', $class, $matches);
+
+ $class = (isset($matches['class'])) ? $matches['class'] : '';
+ $namespace = (isset($matches['namespace'])) ? $matches['namespace'] : '';
+
+ return $directory
+ . str_replace(self::NS_SEPARATOR, '/', $namespace)
+ . str_replace(self::PREFIX_SEPARATOR, '/', $class)
+ . '.php';
+ }
+
+ /**
+ * Load a class, based on its type (namespaced or prefixed)
+ *
+ * @param string $class
+ * @param string $type
+ * @return bool|string
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function loadClass($class, $type)
+ {
+ if (!in_array($type, array(self::LOAD_NS, self::LOAD_PREFIX, self::ACT_AS_FALLBACK))) {
+ require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+ throw new Exception\InvalidArgumentException();
+ }
+
+ // Fallback autoloading
+ if ($type === self::ACT_AS_FALLBACK) {
+ // create filename
+ $filename = $this->transformClassNameToFilename($class, '');
+ $resolvedName = stream_resolve_include_path($filename);
+ if ($resolvedName !== false) {
+ return include $resolvedName;
+ }
+ return false;
+ }
+
+ // Namespace and/or prefix autoloading
+ foreach ($this->$type as $leader => $path) {
+ if (0 === strpos($class, $leader)) {
+ // Trim off leader (namespace or prefix)
+ $trimmedClass = substr($class, strlen($leader));
+
+ // create filename
+ $filename = $this->transformClassNameToFilename($trimmedClass, $path);
+ if (file_exists($filename)) {
+ return include $filename;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Normalize the directory to include a trailing directory separator
+ *
+ * @param string $directory
+ * @return string
+ */
+ protected function normalizeDirectory($directory)
+ {
+ $last = $directory[strlen($directory) - 1];
+ if (in_array($last, array('/', '\\'))) {
+ $directory[strlen($directory) - 1] = DIRECTORY_SEPARATOR;
+ return $directory;
+ }
+ $directory .= DIRECTORY_SEPARATOR;
+ return $directory;
+ }
+}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/StandardAutoloader.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/MissingResourceNamespaceException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/MissingResourceNamespaceException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/MissingResourceNamespaceException.php (revision 80)
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/ExceptionInterface.php';
+
+class MissingResourceNamespaceException extends \Exception implements
+ ExceptionInterface
+{}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/MissingResourceNamespaceException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/ExceptionInterface.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/ExceptionInterface.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/ExceptionInterface.php (revision 80)
@@ -0,0 +1,13 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+interface ExceptionInterface
+{}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/ExceptionInterface.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/BadMethodCallException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/BadMethodCallException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/BadMethodCallException.php (revision 80)
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/ExceptionInterface.php';
+
+class BadMethodCallException extends \BadMethodCallException implements
+ ExceptionInterface
+{
+}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/BadMethodCallException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidPathException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidPathException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidPathException.php (revision 80)
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/ExceptionInterface.php';
+
+class InvalidPathException extends \Exception implements ExceptionInterface
+{}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidPathException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/RuntimeException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/RuntimeException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/RuntimeException.php (revision 80)
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/ExceptionInterface.php';
+
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/RuntimeException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/PluginLoaderException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/PluginLoaderException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/PluginLoaderException.php (revision 80)
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/DomainException.php';
+
+/**
+ * Plugin class loader exceptions
+ */
+class PluginLoaderException extends DomainException
+{
+}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/PluginLoaderException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/DomainException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/DomainException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/DomainException.php (revision 80)
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/ExceptionInterface.php';
+
+class DomainException extends \DomainException implements ExceptionInterface
+{}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/DomainException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/SecurityException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/SecurityException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/SecurityException.php (revision 80)
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/DomainException.php';
+
+class SecurityException extends DomainException
+{}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/SecurityException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidArgumentException.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidArgumentException.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidArgumentException.php (revision 80)
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Loader\Exception;
+
+require_once __DIR__ . '/ExceptionInterface.php';
+
+class InvalidArgumentException extends \InvalidArgumentException implements
+ ExceptionInterface
+{}
/trunk/ZendFramework-2.1.2/library/Zend/Loader/Exception/InvalidArgumentException.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Translator.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Translator.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Translator.php (revision 80)
@@ -0,0 +1,586 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\I18n\Translator;
+
+use Locale;
+use Traversable;
+use Zend\Cache;
+use Zend\Cache\Storage\StorageInterface as CacheStorage;
+use Zend\I18n\Exception;
+use Zend\I18n\Translator\Loader\FileLoaderInterface;
+use Zend\I18n\Translator\Loader\RemoteLoaderInterface;
+use Zend\Stdlib\ArrayUtils;
+
+/**
+ * Translator.
+ */
+class Translator
+{
+ /**
+ * Messages loaded by the translator.
+ *
+ * @var array
+ */
+ protected $messages = array();
+
+ /**
+ * Files used for loading messages.
+ *
+ * @var array
+ */
+ protected $files = array();
+
+ /**
+ * Patterns used for loading messages.
+ *
+ * @var array
+ */
+ protected $patterns = array();
+
+ /**
+ * Remote locations for loading messages.
+ *
+ * @var array
+ */
+ protected $remote = array();
+
+ /**
+ * Default locale.
+ *
+ * @var string
+ */
+ protected $locale;
+
+ /**
+ * Locale to use as fallback if there is no translation.
+ *
+ * @var string
+ */
+ protected $fallbackLocale;
+
+ /**
+ * Translation cache.
+ *
+ * @var CacheStorage
+ */
+ protected $cache;
+
+ /**
+ * Plugin manager for translation loaders.
+ *
+ * @var LoaderPluginManager
+ */
+ protected $pluginManager;
+
+ /**
+ * Instantiate a translator
+ *
+ * @param array|Traversable $options
+ * @return Translator
+ * @throws Exception\InvalidArgumentException
+ */
+ public static function factory($options)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ } elseif (!is_array($options)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects an array or Traversable object; received "%s"',
+ __METHOD__,
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ $translator = new static();
+
+ // locales
+ if (isset($options['locale'])) {
+ $locales = (array) $options['locale'];
+ $translator->setLocale(array_shift($locales));
+ if (count($locales) > 0) {
+ $translator->setFallbackLocale(array_shift($locales));
+ }
+ }
+
+ // file patterns
+ if (isset($options['translation_file_patterns'])) {
+ if (!is_array($options['translation_file_patterns'])) {
+ throw new Exception\InvalidArgumentException(
+ '"translation_file_patterns" should be an array'
+ );
+ }
+
+ $requiredKeys = array('type', 'base_dir', 'pattern');
+ foreach ($options['translation_file_patterns'] as $pattern) {
+ foreach ($requiredKeys as $key) {
+ if (!isset($pattern[$key])) {
+ throw new Exception\InvalidArgumentException(
+ "'{$key}' is missing for translation pattern options"
+ );
+ }
+ }
+
+ $translator->addTranslationFilePattern(
+ $pattern['type'],
+ $pattern['base_dir'],
+ $pattern['pattern'],
+ isset($pattern['text_domain']) ? $pattern['text_domain'] : 'default'
+ );
+ }
+ }
+
+ // files
+ if (isset($options['translation_files'])) {
+ if (!is_array($options['translation_files'])) {
+ throw new Exception\InvalidArgumentException(
+ '"translation_files" should be an array'
+ );
+ }
+
+ $requiredKeys = array('type', 'filename');
+ foreach ($options['translation_files'] as $file) {
+ foreach ($requiredKeys as $key) {
+ if (!isset($file[$key])) {
+ throw new Exception\InvalidArgumentException(
+ "'{$key}' is missing for translation file options"
+ );
+ }
+ }
+
+ $translator->addTranslationFile(
+ $file['type'],
+ $file['filename'],
+ isset($file['text_domain']) ? $file['text_domain'] : 'default',
+ isset($file['locale']) ? $file['locale'] : null
+ );
+ }
+ }
+
+ // remote
+ if (isset($options['remote_translation'])) {
+ if (!is_array($options['remote_translation'])) {
+ throw new Exception\InvalidArgumentException(
+ '"remote_translation" should be an array'
+ );
+ }
+
+ $requiredKeys = array('type');
+ foreach ($options['remote_translation'] as $remote) {
+ foreach ($requiredKeys as $key) {
+ if (!isset($remote[$key])) {
+ throw new Exception\InvalidArgumentException(
+ "'{$key}' is missing for remote translation options"
+ );
+ }
+ }
+
+ $translator->addRemoteTranslations(
+ $remote['type'],
+ isset($remote['text_domain']) ? $remote['text_domain'] : 'default'
+ );
+ }
+ }
+
+ // cache
+ if (isset($options['cache'])) {
+ if ($options['cache'] instanceof CacheStorage) {
+ $translator->setCache($options['cache']);
+ } else {
+ $translator->setCache(Cache\StorageFactory::factory($options['cache']));
+ }
+ }
+
+ return $translator;
+ }
+
+ /**
+ * Set the default locale.
+ *
+ * @param string $locale
+ * @return Translator
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+
+ return $this;
+ }
+
+ /**
+ * Get the default locale.
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ if ($this->locale === null) {
+ $this->locale = Locale::getDefault();
+ }
+
+ return $this->locale;
+ }
+
+ /**
+ * Set the fallback locale.
+ *
+ * @param string $locale
+ * @return Translator
+ */
+ public function setFallbackLocale($locale)
+ {
+ $this->fallbackLocale = $locale;
+
+ return $this;
+ }
+
+ /**
+ * Get the fallback locale.
+ *
+ * @return string
+ */
+ public function getFallbackLocale()
+ {
+ return $this->fallbackLocale;
+ }
+
+ /**
+ * Sets a cache
+ *
+ * @param CacheStorage $cache
+ * @return Translator
+ */
+ public function setCache(CacheStorage $cache = null)
+ {
+ $this->cache = $cache;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set cache
+ *
+ * @return CacheStorage The set cache
+ */
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+ /**
+ * Set the plugin manager for translation loaders
+ *
+ * @param LoaderPluginManager $pluginManager
+ * @return Translator
+ */
+ public function setPluginManager(LoaderPluginManager $pluginManager)
+ {
+ $this->pluginManager = $pluginManager;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the plugin manager for translation loaders.
+ *
+ * Lazy loads an instance if none currently set.
+ *
+ * @return LoaderPluginManager
+ */
+ public function getPluginManager()
+ {
+ if (!$this->pluginManager instanceof LoaderPluginManager) {
+ $this->setPluginManager(new LoaderPluginManager());
+ }
+
+ return $this->pluginManager;
+ }
+
+ /**
+ * Translate a message.
+ *
+ * @param string $message
+ * @param string $textDomain
+ * @param string $locale
+ * @return string
+ */
+ public function translate($message, $textDomain = 'default', $locale = null)
+ {
+ $locale = ($locale ?: $this->getLocale());
+ $translation = $this->getTranslatedMessage($message, $locale, $textDomain);
+
+ if ($translation !== null && $translation !== '') {
+ return $translation;
+ }
+
+ if (null !== ($fallbackLocale = $this->getFallbackLocale())
+ && $locale !== $fallbackLocale
+ ) {
+ return $this->translate($message, $textDomain, $fallbackLocale);
+ }
+
+ return $message;
+ }
+
+ /**
+ * Translate a plural message.
+ *
+ * @param string $singular
+ * @param string $plural
+ * @param int $number
+ * @param string $textDomain
+ * @param string|null $locale
+ * @return string
+ * @throws Exception\OutOfBoundsException
+ */
+ public function translatePlural(
+ $singular,
+ $plural,
+ $number,
+ $textDomain = 'default',
+ $locale = null
+ ) {
+ $locale = $locale ?: $this->getLocale();
+ $translation = $this->getTranslatedMessage($singular, $locale, $textDomain);
+
+ if ($translation === null || $translation === '') {
+ if (null !== ($fallbackLocale = $this->getFallbackLocale())
+ && $locale !== $fallbackLocale
+ ) {
+ return $this->translatePlural(
+ $singular,
+ $plural,
+ $number,
+ $textDomain,
+ $fallbackLocale
+ );
+ }
+
+ return ($number == 1 ? $singular : $plural);
+ }
+
+ $index = $this->messages[$textDomain][$locale]
+ ->getPluralRule()
+ ->evaluate($number);
+
+ if (!isset($translation[$index])) {
+ throw new Exception\OutOfBoundsException(sprintf(
+ 'Provided index %d does not exist in plural array', $index
+ ));
+ }
+
+ return $translation[$index];
+ }
+
+ /**
+ * Get a translated message.
+ *
+ * @param string $message
+ * @param string $locale
+ * @param string $textDomain
+ * @return string|null
+ */
+ protected function getTranslatedMessage(
+ $message,
+ $locale = null,
+ $textDomain = 'default'
+ ) {
+ if ($message === '') {
+ return '';
+ }
+
+ if (!isset($this->messages[$textDomain][$locale])) {
+ $this->loadMessages($textDomain, $locale);
+ }
+
+ if (!isset($this->messages[$textDomain][$locale][$message])) {
+ return null;
+ }
+
+ return $this->messages[$textDomain][$locale][$message];
+ }
+
+ /**
+ * Add a translation file.
+ *
+ * @param string $type
+ * @param string $filename
+ * @param string $textDomain
+ * @param string $locale
+ * @return Translator
+ */
+ public function addTranslationFile(
+ $type,
+ $filename,
+ $textDomain = 'default',
+ $locale = null
+ ) {
+ $locale = $locale ?: '*';
+
+ if (!isset($this->files[$textDomain])) {
+ $this->files[$textDomain] = array();
+ }
+
+ $this->files[$textDomain][$locale][] = array(
+ 'type' => $type,
+ 'filename' => $filename,
+ );
+
+ return $this;
+ }
+
+ /**
+ * Add multiple translations with a file pattern.
+ *
+ * @param string $type
+ * @param string $baseDir
+ * @param string $pattern
+ * @param string $textDomain
+ * @return Translator
+ */
+ public function addTranslationFilePattern(
+ $type,
+ $baseDir,
+ $pattern,
+ $textDomain = 'default'
+ ) {
+ if (!isset($this->patterns[$textDomain])) {
+ $this->patterns[$textDomain] = array();
+ }
+
+ $this->patterns[$textDomain][] = array(
+ 'type' => $type,
+ 'baseDir' => rtrim($baseDir, '/'),
+ 'pattern' => $pattern,
+ );
+
+ return $this;
+ }
+
+ /**
+ * Add remote translations.
+ *
+ * @param string $type
+ * @param string $textDomain
+ * @return Translator
+ */
+ public function addRemoteTranslations($type, $textDomain = 'default')
+ {
+ if (!isset($this->remote[$textDomain])) {
+ $this->remote[$textDomain] = array();
+ }
+
+ $this->remote[$textDomain][] = $type;
+
+ return $this;
+ }
+
+ /**
+ * Load messages for a given language and domain.
+ *
+ * @param string $textDomain
+ * @param string $locale
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ protected function loadMessages($textDomain, $locale)
+ {
+ if (!isset($this->messages[$textDomain])) {
+ $this->messages[$textDomain] = array();
+ }
+
+ if (null !== ($cache = $this->getCache())) {
+ $cacheId = 'Zend_I18n_Translator_Messages_' . md5($textDomain . $locale);
+
+ if (null !== ($result = $cache->getItem($cacheId))) {
+ $this->messages[$textDomain][$locale] = $result;
+
+ return;
+ }
+ }
+
+ $hasToCache = false;
+
+ // Try to load from remote sources
+ if (isset($this->remote[$textDomain])) {
+ foreach ($this->remote[$textDomain] as $loaderType) {
+ $loader = $this->getPluginManager()->get($loaderType);
+
+ if (!$loader instanceof RemoteLoaderInterface) {
+ throw new Exception\RuntimeException('Specified loader is not a remote loader');
+ }
+
+ if (isset($this->messages[$textDomain][$locale])) {
+ $this->messages[$textDomain][$locale]->exchangeArray(array_merge(
+ (array) $this->messages[$textDomain][$locale],
+ (array) $loader->load($locale, $textDomain)
+ ));
+ } else {
+ $this->messages[$textDomain][$locale] = $loader->load($locale, $textDomain);
+ }
+ $hasToCache = true;
+ }
+ }
+
+ // Try to load from pattern
+ if (isset($this->patterns[$textDomain])) {
+ foreach ($this->patterns[$textDomain] as $pattern) {
+ $filename = $pattern['baseDir'] . '/' . sprintf($pattern['pattern'], $locale);
+
+ if (is_file($filename)) {
+ $loader = $this->getPluginManager()->get($pattern['type']);
+
+ if (!$loader instanceof FileLoaderInterface) {
+ throw new Exception\RuntimeException('Specified loader is not a file loader');
+ }
+
+ if (isset($this->messages[$textDomain][$locale])) {
+ $this->messages[$textDomain][$locale]->exchangeArray(array_merge(
+ (array) $this->messages[$textDomain][$locale],
+ (array) $loader->load($locale, $filename)
+ ));
+ } else {
+ $this->messages[$textDomain][$locale] = $loader->load($locale, $filename);
+ }
+ $hasToCache = true;
+ }
+ }
+ }
+
+ // Try to load from concrete files
+ foreach (array($locale, '*') as $currentLocale) {
+ if (!isset($this->files[$textDomain][$currentLocale])) {
+ continue;
+ }
+ foreach ($this->files[$textDomain][$currentLocale] as $file) {
+ $loader = $this->getPluginManager()->get($file['type']);
+
+ if (!$loader instanceof FileLoaderInterface) {
+ throw new Exception\RuntimeException('Specified loader is not a file loader');
+ }
+
+ if (isset($this->messages[$textDomain][$locale])) {
+ $this->messages[$textDomain][$locale]->exchangeArray(array_merge(
+ (array) $this->messages[$textDomain][$locale],
+ (array) $loader->load($locale, $file['filename'])
+ ));
+ } else {
+ $this->messages[$textDomain][$locale] = $loader->load($locale, $file['filename']);
+ }
+ $hasToCache = true;
+ }
+ unset($this->files[$textDomain][$currentLocale]);
+ }
+
+ // Cache the loaded text domain
+ if ($hasToCache && $cache !== null) {
+ $cache->setItem($cacheId, $this->messages[$textDomain][$locale]);
+ }
+ }
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/TextDomain.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/TextDomain.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/TextDomain.php (revision 80)
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\I18n\Translator;
+
+use ArrayObject;
+use Zend\I18n\Translator\Plural\Rule as PluralRule;
+
+/**
+ * Text domain.
+ */
+class TextDomain extends ArrayObject
+{
+ /**
+ * Plural rule.
+ *
+ * @var PluralRule
+ */
+ protected $pluralRule;
+
+ /**
+ * Set the plural rule
+ *
+ * @param PluralRule $rule
+ * @return TextDomain
+ */
+ public function setPluralRule(PluralRule $rule)
+ {
+ $this->pluralRule = $rule;
+ return $this;
+ }
+
+ /**
+ * Get the plural rule.
+ *
+ * Lazy loads a default rule if none already registered
+ *
+ * @return PluralRule
+ */
+ public function getPluralRule()
+ {
+ if ($this->pluralRule === null) {
+ $this->setPluralRule(PluralRule::fromString('nplurals=2; plural=n==1'));
+ }
+
+ return $this->pluralRule;
+ }
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Loader/Gettext.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Loader/Gettext.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Loader/Gettext.php (revision 80)
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\I18n\Translator\Loader;
+
+use Zend\I18n\Exception;
+use Zend\I18n\Translator\Plural\Rule as PluralRule;
+use Zend\I18n\Translator\TextDomain;
+use Zend\Stdlib\ErrorHandler;
+
+/**
+ * Gettext loader.
+ */
+class Gettext implements FileLoaderInterface
+{
+ /**
+ * Current file pointer.
+ *
+ * @var resource
+ */
+ protected $file;
+
+ /**
+ * Whether the current file is little endian.
+ *
+ * @var bool
+ */
+ protected $littleEndian;
+
+ /**
+ * load(): defined by FileLoaderInterface.
+ *
+ * @see FileLoaderInterface::load()
+ * @param string $locale
+ * @param string $filename
+ * @return TextDomain
+ * @throws Exception\InvalidArgumentException
+ */
+ public function load($locale, $filename)
+ {
+ if (!is_file($filename) || !is_readable($filename)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Could not open file %s for reading',
+ $filename
+ ));
+ }
+
+ $textDomain = new TextDomain();
+
+ ErrorHandler::start();
+ $this->file = fopen($filename, 'rb');
+ $error = ErrorHandler::stop();
+ if (false === $this->file) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Could not open file %s for reading',
+ $filename
+ ), 0, $error);
+ }
+
+ // Verify magic number
+ $magic = fread($this->file, 4);
+
+ if ($magic == "\x95\x04\x12\xde") {
+ $this->littleEndian = false;
+ } elseif ($magic == "\xde\x12\x04\x95") {
+ $this->littleEndian = true;
+ } else {
+ fclose($this->file);
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s is not a valid gettext file',
+ $filename
+ ));
+ }
+
+ // Verify major revision (only 0 and 1 supported)
+ $majorRevision = ($this->readInteger() >> 16);
+
+ if ($majorRevision !== 0 && $majorRevision !== 1) {
+ fclose($this->file);
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s has an unknown major revision',
+ $filename
+ ));
+ }
+
+ // Gather main information
+ $numStrings = $this->readInteger();
+ $originalStringTableOffset = $this->readInteger();
+ $translationStringTableOffset = $this->readInteger();
+
+ // Usually there follow size and offset of the hash table, but we have
+ // no need for it, so we skip them.
+ fseek($this->file, $originalStringTableOffset);
+ $originalStringTable = $this->readIntegerList(2 * $numStrings);
+
+ fseek($this->file, $translationStringTableOffset);
+ $translationStringTable = $this->readIntegerList(2 * $numStrings);
+
+ // Read in all translations
+ for ($current = 0; $current < $numStrings; $current++) {
+ $sizeKey = $current * 2 + 1;
+ $offsetKey = $current * 2 + 2;
+ $originalStringSize = $originalStringTable[$sizeKey];
+ $originalStringOffset = $originalStringTable[$offsetKey];
+ $translationStringSize = $translationStringTable[$sizeKey];
+ $translationStringOffset = $translationStringTable[$offsetKey];
+
+ $originalString = array('');
+ if ($originalStringSize > 0) {
+ fseek($this->file, $originalStringOffset);
+ $originalString = explode("\0", fread($this->file, $originalStringSize));
+ }
+
+ if ($translationStringSize > 0) {
+ fseek($this->file, $translationStringOffset);
+ $translationString = explode("\0", fread($this->file, $translationStringSize));
+
+ if (count($originalString) > 1 && count($translationString) > 1) {
+ $textDomain[$originalString[0]] = $translationString;
+
+ array_shift($originalString);
+
+ foreach ($originalString as $string) {
+ $textDomain[$string] = '';
+ }
+ } else {
+ $textDomain[$originalString[0]] = $translationString[0];
+ }
+ }
+ }
+
+ // Read header entries
+ if (array_key_exists('', $textDomain)) {
+ $rawHeaders = explode("\n", trim($textDomain['']));
+
+ foreach ($rawHeaders as $rawHeader) {
+ list($header, $content) = explode(':', $rawHeader, 2);
+
+ if (trim(strtolower($header)) === 'plural-forms') {
+ $textDomain->setPluralRule(PluralRule::fromString($content));
+ }
+ }
+
+ unset($textDomain['']);
+ }
+
+ fclose($this->file);
+
+ return $textDomain;
+ }
+
+ /**
+ * Read a single integer from the current file.
+ *
+ * @return integer
+ */
+ protected function readInteger()
+ {
+ if ($this->littleEndian) {
+ $result = unpack('Vint', fread($this->file, 4));
+ } else {
+ $result = unpack('Nint', fread($this->file, 4));
+ }
+
+ return $result['int'];
+ }
+
+ /**
+ * Read an integer from the current file.
+ *
+ * @param integer $num
+ * @return integer
+ */
+ protected function readIntegerList($num)
+ {
+ if ($this->littleEndian) {
+ return unpack('V' . $num, fread($this->file, 4 * $num));
+ }
+
+ return unpack('N' . $num, fread($this->file, 4 * $num));
+ }
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Loader/FileLoaderInterface.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Loader/FileLoaderInterface.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/Loader/FileLoaderInterface.php (revision 80)
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\I18n\Translator\Loader;
+
+/**
+ * File loader interface.
+ */
+interface FileLoaderInterface
+{
+ /**
+ * Load translations from a file.
+ *
+ * @param string $locale
+ * @param string $filename
+ * @return \Zend\I18n\Translator\TextDomain|null
+ */
+ public function load($locale, $filename);
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/LoaderPluginManager.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/LoaderPluginManager.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/I18n/Translator/LoaderPluginManager.php (revision 80)
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\I18n\Translator;
+
+use Zend\I18n\Exception;
+use Zend\ServiceManager\AbstractPluginManager;
+
+/**
+ * Plugin manager implementation for translation loaders.
+ *
+ * Enforces that loaders retrieved are either instances of
+ * Loader\FileLoaderInterface or Loader\RemoteLoaderInterface. Additionally,
+ * it registers a number of default loaders.
+ */
+class LoaderPluginManager extends AbstractPluginManager
+{
+ /**
+ * Default set of loaders.
+ *
+ * @var array
+ */
+ protected $invokableClasses = array(
+ 'gettext' => 'Zend\I18n\Translator\Loader\Gettext',
+ 'ini' => 'Zend\I18n\Translator\Loader\Ini',
+ 'phparray' => 'Zend\I18n\Translator\Loader\PhpArray',
+ );
+
+ /**
+ * Validate the plugin.
+ *
+ * Checks that the filter loaded is an instance of
+ * Loader\FileLoaderInterface or Loader\RemoteLoaderInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\RuntimeException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Loader\FileLoaderInterface || $plugin instanceof Loader\RemoteLoaderInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\RuntimeException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Loader\FileLoaderInterface or %s\Loader\RemoteLoaderInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/Stdlib/ErrorHandler.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/Stdlib/ErrorHandler.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/Stdlib/ErrorHandler.php (revision 80)
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Stdlib;
+
+use ErrorException;
+
+/**
+ * ErrorHandler that can be used to catch internal PHP errors
+ * and convert to a ErrorException instance.
+ */
+abstract class ErrorHandler
+{
+ /**
+ * Active stack
+ *
+ * @var array
+ */
+ protected static $stack = array();
+
+ /**
+ * Check if this error handler is active
+ *
+ * @return boolean
+ */
+ public static function started()
+ {
+ return (bool) static::getNestedLevel();
+ }
+
+ /**
+ * Get the current nested level
+ *
+ * @return int
+ */
+ public static function getNestedLevel()
+ {
+ return count(static::$stack);
+ }
+
+ /**
+ * Starting the error handler
+ *
+ * @param int $errorLevel
+ */
+ public static function start($errorLevel = \E_WARNING)
+ {
+ if (!static::$stack) {
+ set_error_handler(array(get_called_class(), 'addError'), $errorLevel);
+ }
+
+ static::$stack[] = null;
+ }
+
+ /**
+ * Stopping the error handler
+ *
+ * @param bool $throw Throw the ErrorException if any
+ * @return null|ErrorException
+ * @throws ErrorException If an error has been catched and $throw is true
+ */
+ public static function stop($throw = false)
+ {
+ $errorException = null;
+
+ if (static::$stack) {
+ $errorException = array_pop(static::$stack);
+
+ if (!static::$stack) {
+ restore_error_handler();
+ }
+
+ if ($errorException && $throw) {
+ throw $errorException;
+ }
+ }
+
+ return $errorException;
+ }
+
+ /**
+ * Stop all active handler
+ *
+ * @return void
+ */
+ public static function clean()
+ {
+ if (static::$stack) {
+ restore_error_handler();
+ }
+
+ static::$stack = array();
+ }
+
+ /**
+ * Add an error to the stack
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @return void
+ */
+ public static function addError($errno, $errstr = '', $errfile = '', $errline = 0)
+ {
+ $stack = & static::$stack[ count(static::$stack) - 1 ];
+ $stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack);
+ }
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceManager.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceManager.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceManager.php (revision 80)
@@ -0,0 +1,986 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\ServiceManager;
+
+use ReflectionClass;
+
+class ServiceManager implements ServiceLocatorInterface
+{
+
+ /**@#+
+ * Constants
+ */
+ const SCOPE_PARENT = 'parent';
+ const SCOPE_CHILD = 'child';
+ /**@#-*/
+
+ /**
+ * Lookup for canonicalized names.
+ *
+ * @var array
+ */
+ protected $canonicalNames = array();
+
+ /**
+ * @var bool
+ */
+ protected $allowOverride = false;
+
+ /**
+ * @var array
+ */
+ protected $invokableClasses = array();
+
+ /**
+ * @var string|callable|\Closure|FactoryInterface[]
+ */
+ protected $factories = array();
+
+ /**
+ * @var AbstractFactoryInterface[]
+ */
+ protected $abstractFactories = array();
+
+ /**
+ * @var array
+ */
+ protected $pendingAbstractFactoryRequests = array();
+
+ /**
+ * @var array
+ */
+ protected $shared = array();
+
+ /**
+ * Registered services and cached values
+ *
+ * @var array
+ */
+ protected $instances = array();
+
+ /**
+ * @var array
+ */
+ protected $aliases = array();
+
+ /**
+ * @var array
+ */
+ protected $initializers = array();
+
+ /**
+ * @var ServiceManager[]
+ */
+ protected $peeringServiceManagers = array();
+
+ /**
+ * Whether or not to share by default
+ *
+ * @var bool
+ */
+ protected $shareByDefault = true;
+
+ /**
+ * @var bool
+ */
+ protected $retrieveFromPeeringManagerFirst = false;
+
+ /**
+ * @var bool Track whether not to throw exceptions during create()
+ */
+ protected $throwExceptionInCreate = true;
+
+ /**
+ * @var array map of characters to be replaced through strtr
+ */
+ protected $canonicalNamesReplacements = array('-' => '', '_' => '', ' ' => '', '\\' => '', '/' => '');
+
+ /**
+ * Constructor
+ *
+ * @param ConfigInterface $config
+ */
+ public function __construct(ConfigInterface $config = null)
+ {
+ if ($config) {
+ $config->configureServiceManager($this);
+ }
+ }
+
+ /**
+ * Set allow override
+ *
+ * @param $allowOverride
+ * @return ServiceManager
+ */
+ public function setAllowOverride($allowOverride)
+ {
+ $this->allowOverride = (bool) $allowOverride;
+ return $this;
+ }
+
+ /**
+ * Get allow override
+ *
+ * @return bool
+ */
+ public function getAllowOverride()
+ {
+ return $this->allowOverride;
+ }
+
+ /**
+ * Set flag indicating whether services are shared by default
+ *
+ * @param bool $shareByDefault
+ * @return ServiceManager
+ * @throws Exception\RuntimeException if allowOverride is false
+ */
+ public function setShareByDefault($shareByDefault)
+ {
+ if ($this->allowOverride === false) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: cannot alter default shared service setting; container is marked immutable (allow_override is false)',
+ __METHOD__
+ ));
+ }
+ $this->shareByDefault = (bool) $shareByDefault;
+ return $this;
+ }
+
+ /**
+ * Are services shared by default?
+ *
+ * @return bool
+ */
+ public function shareByDefault()
+ {
+ return $this->shareByDefault;
+ }
+
+ /**
+ * Set throw exceptions in create
+ *
+ * @param bool $throwExceptionInCreate
+ * @return ServiceManager
+ */
+ public function setThrowExceptionInCreate($throwExceptionInCreate)
+ {
+ $this->throwExceptionInCreate = $throwExceptionInCreate;
+ return $this;
+ }
+
+ /**
+ * Get throw exceptions in create
+ *
+ * @return bool
+ */
+ public function getThrowExceptionInCreate()
+ {
+ return $this->throwExceptionInCreate;
+ }
+
+ /**
+ * Set flag indicating whether to pull from peering manager before attempting creation
+ *
+ * @param bool $retrieveFromPeeringManagerFirst
+ * @return ServiceManager
+ */
+ public function setRetrieveFromPeeringManagerFirst($retrieveFromPeeringManagerFirst = true)
+ {
+ $this->retrieveFromPeeringManagerFirst = (bool) $retrieveFromPeeringManagerFirst;
+ return $this;
+ }
+
+ /**
+ * Should we retrieve from the peering manager prior to attempting to create a service?
+ *
+ * @return bool
+ */
+ public function retrieveFromPeeringManagerFirst()
+ {
+ return $this->retrieveFromPeeringManagerFirst;
+ }
+
+ /**
+ * Set invokable class
+ *
+ * @param string $name
+ * @param string $invokableClass
+ * @param bool $shared
+ * @return ServiceManager
+ * @throws Exception\InvalidServiceNameException
+ */
+ public function setInvokableClass($name, $invokableClass, $shared = null)
+ {
+ $cName = $this->canonicalizeName($name);
+
+ if ($this->has(array($cName, $name), false)) {
+ if ($this->allowOverride === false) {
+ throw new Exception\InvalidServiceNameException(sprintf(
+ 'A service by the name or alias "%s" already exists and cannot be overridden; please use an alternate name',
+ $cName
+ ));
+ }
+ $this->unregisterService($cName);
+ }
+
+ if ($shared === null) {
+ $shared = $this->shareByDefault();
+ }
+
+ $this->invokableClasses[$cName] = $invokableClass;
+ $this->shared[$cName] = (bool) $shared;
+
+ return $this;
+ }
+
+ /**
+ * Set factory
+ *
+ * @param string $name
+ * @param string|FactoryInterface|callable $factory
+ * @param bool $shared
+ * @return ServiceManager
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\InvalidServiceNameException
+ */
+ public function setFactory($name, $factory, $shared = null)
+ {
+ $cName = $this->canonicalizeName($name);
+
+ if (!is_string($factory) && !$factory instanceof FactoryInterface && !is_callable($factory)) {
+ throw new Exception\InvalidArgumentException(
+ 'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
+ );
+ }
+
+ if ($this->has(array($cName, $name), false)) {
+ if ($this->allowOverride === false) {
+ throw new Exception\InvalidServiceNameException(sprintf(
+ 'A service by the name or alias "%s" already exists and cannot be overridden, please use an alternate name',
+ $cName
+ ));
+ }
+ $this->unregisterService($cName);
+ }
+
+ if ($shared === null) {
+ $shared = $this->shareByDefault();
+ }
+
+ $this->factories[$cName] = $factory;
+ $this->shared[$cName] = (bool) $shared;
+
+ return $this;
+ }
+
+ /**
+ * Add abstract factory
+ *
+ * @param AbstractFactoryInterface|string $factory
+ * @param bool $topOfStack
+ * @return ServiceManager
+ * @throws Exception\InvalidArgumentException if the abstract factory is invalid
+ */
+ public function addAbstractFactory($factory, $topOfStack = true)
+ {
+ if (!is_string($factory) && !$factory instanceof AbstractFactoryInterface) {
+ throw new Exception\InvalidArgumentException(
+ 'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
+ );
+ }
+ if (is_string($factory)) {
+ if (!class_exists($factory, true)) {
+ throw new Exception\InvalidArgumentException(
+ 'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
+ );
+ }
+ $refl = new ReflectionClass($factory);
+ if (!$refl->implementsInterface(__NAMESPACE__ . '\\AbstractFactoryInterface')) {
+ throw new Exception\InvalidArgumentException(
+ 'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
+ );
+ }
+ }
+
+ if ($topOfStack) {
+ array_unshift($this->abstractFactories, $factory);
+ } else {
+ array_push($this->abstractFactories, $factory);
+ }
+ return $this;
+ }
+
+ /**
+ * Add initializer
+ *
+ * @param callable|InitializerInterface $initializer
+ * @param bool $topOfStack
+ * @return ServiceManager
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addInitializer($initializer, $topOfStack = true)
+ {
+ if (!is_callable($initializer) && !$initializer instanceof InitializerInterface) {
+ if (!is_string($initializer)
+ || !$this->isSubclassOf($initializer, __NAMESPACE__ . '\InitializerInterface')
+ ) {
+ throw new Exception\InvalidArgumentException('$initializer should be callable.');
+ }
+ $initializer = new $initializer;
+ }
+
+ if ($topOfStack) {
+ array_unshift($this->initializers, $initializer);
+ } else {
+ array_push($this->initializers, $initializer);
+ }
+ return $this;
+ }
+
+ /**
+ * Register a service with the locator
+ *
+ * @param string $name
+ * @param mixed $service
+ * @return ServiceManager
+ * @throws Exception\InvalidServiceNameException
+ */
+ public function setService($name, $service)
+ {
+ $cName = $this->canonicalizeName($name);
+
+ if ($this->has($cName, false)) {
+ if ($this->allowOverride === false) {
+ throw new Exception\InvalidServiceNameException(sprintf(
+ '%s: A service by the name "%s" or alias already exists and cannot be overridden, please use an alternate name.',
+ __METHOD__,
+ $name
+ ));
+ }
+ $this->unregisterService($cName);
+ }
+
+ $this->instances[$cName] = $service;
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param bool $isShared
+ * @return ServiceManager
+ * @throws Exception\ServiceNotFoundException
+ */
+ public function setShared($name, $isShared)
+ {
+ $cName = $this->canonicalizeName($name);
+
+ if (
+ !isset($this->invokableClasses[$cName])
+ && !isset($this->factories[$cName])
+ && !$this->canCreateFromAbstractFactory($cName, $name)
+ ) {
+ throw new Exception\ServiceNotFoundException(sprintf(
+ '%s: A service by the name "%s" was not found and could not be marked as shared',
+ __METHOD__,
+ $name
+ ));
+ }
+
+ $this->shared[$cName] = (bool) $isShared;
+ return $this;
+ }
+
+ /**
+ * Retrieve a registered instance
+ *
+ * @param string $name
+ * @param bool $usePeeringServiceManagers
+ * @throws Exception\ServiceNotFoundException
+ * @return object|array
+ */
+ public function get($name, $usePeeringServiceManagers = true)
+ {
+ $cName = $this->canonicalizeName($name);
+ $isAlias = false;
+
+ if ($this->hasAlias($cName)) {
+ $isAlias = true;
+
+ do {
+ $cName = $this->aliases[$cName];
+ } while ($this->hasAlias($cName));
+ }
+
+ $instance = null;
+ $retrieveFromPeeringManagerFirst = $this->retrieveFromPeeringManagerFirst();
+
+ if ($usePeeringServiceManagers && $retrieveFromPeeringManagerFirst) {
+ $instance = $this->retrieveFromPeeringManager($name);
+
+ if(null !== $instance) {
+ return $instance;
+ }
+ }
+
+ if (isset($this->instances[$cName])) {
+ return $this->instances[$cName];
+ }
+
+ if (!$instance) {
+ if ($this->canCreate(array($cName, $name))) {
+ $instance = $this->create(array($cName, $name));
+ } elseif ($usePeeringServiceManagers && !$retrieveFromPeeringManagerFirst) {
+ $instance = $this->retrieveFromPeeringManager($name);
+ }
+ }
+
+ // Still no instance? raise an exception
+ if ($instance === null && !is_array($instance)) {
+ if ($isAlias) {
+ throw new Exception\ServiceNotFoundException(sprintf(
+ 'An alias "%s" was requested but no service could be found.',
+ $name
+ ));
+ }
+
+ throw new Exception\ServiceNotFoundException(sprintf(
+ '%s was unable to fetch or create an instance for %s',
+ __METHOD__,
+ $name
+ ));
+ }
+
+ if (
+ ($this->shareByDefault() && !isset($this->shared[$cName]))
+ || (isset($this->shared[$cName]) && $this->shared[$cName] === true)
+ ) {
+ $this->instances[$cName] = $instance;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Create an instance
+ *
+ * @param string|array $name
+ * @return bool|object
+ * @throws Exception\ServiceNotFoundException
+ * @throws Exception\ServiceNotCreatedException
+ */
+ public function create($name)
+ {
+ $instance = false;
+
+ if (is_array($name)) {
+ list($cName, $rName) = $name;
+ } else {
+ $rName = $name;
+ $cName = $this->canonicalizeName($rName);
+ }
+
+
+ if (isset($this->factories[$cName])) {
+ $instance = $this->createFromFactory($cName, $rName);
+ }
+
+ if ($instance === false && isset($this->invokableClasses[$cName])) {
+ $instance = $this->createFromInvokable($cName, $rName);
+ }
+
+ if ($instance === false && $this->canCreateFromAbstractFactory($cName, $rName)) {
+ $instance = $this->createFromAbstractFactory($cName, $rName);
+ }
+
+ if ($this->throwExceptionInCreate == true && $instance === false) {
+ throw new Exception\ServiceNotFoundException(sprintf(
+ 'No valid instance was found for %s%s',
+ $cName,
+ ($rName ? '(alias: ' . $rName . ')' : '')
+ ));
+ }
+
+ foreach ($this->initializers as $initializer) {
+ if ($initializer instanceof InitializerInterface) {
+ $initializer->initialize($instance, $this);
+ } elseif (is_object($initializer) && is_callable($initializer)) {
+ $initializer($instance, $this);
+ } else {
+ call_user_func($initializer, $instance, $this);
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Determine if we can create an instance.
+ *
+ * @param string|array $name
+ * @param bool $checkAbstractFactories
+ * @return bool
+ */
+ public function canCreate($name, $checkAbstractFactories = true)
+ {
+ if (is_array($name)) {
+ list($cName, $rName) = $name;
+ } else {
+ $rName = $name;
+ $cName = $this->canonicalizeName($rName);
+ }
+
+ if (
+ isset($this->invokableClasses[$cName])
+ || isset($this->factories[$cName])
+ || isset($this->aliases[$cName])
+ || isset($this->instances[$cName])
+ ) {
+ return true;
+ }
+
+ if ($checkAbstractFactories && $this->canCreateFromAbstractFactory($cName, $rName)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string|array $name
+ * @param bool $checkAbstractFactories
+ * @param bool $usePeeringServiceManagers
+ * @return bool
+ */
+ public function has($name, $checkAbstractFactories = true, $usePeeringServiceManagers = true)
+ {
+ if (is_array($name)) {
+ list($cName, $rName) = $name;
+ } else {
+ $rName = $name;
+ $cName = $this->canonicalizeName($rName);
+ }
+
+ if ($this->canCreate(array($cName, $rName), $checkAbstractFactories)) {
+ return true;
+ }
+
+ if ($usePeeringServiceManagers) {
+ foreach ($this->peeringServiceManagers as $peeringServiceManager) {
+ if ($peeringServiceManager->has($rName)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if we can create an instance from an abstract factory.
+ *
+ * @param string $cName
+ * @param string $rName
+ * @return bool
+ */
+ public function canCreateFromAbstractFactory($cName, $rName)
+ {
+ // check abstract factories
+ foreach ($this->abstractFactories as $index => $abstractFactory) {
+ // Support string abstract factory class names
+ if (is_string($abstractFactory) && class_exists($abstractFactory, true)) {
+ $this->abstractFactories[$index] = $abstractFactory = new $abstractFactory();
+ }
+
+ if (
+ isset($this->pendingAbstractFactoryRequests[get_class($abstractFactory)])
+ && $this->pendingAbstractFactoryRequests[get_class($abstractFactory)] == $rName
+ ) {
+ return false;
+ }
+
+ if ($abstractFactory->canCreateServiceWithName($this, $cName, $rName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param string $alias
+ * @param string $nameOrAlias
+ * @return ServiceManager
+ * @throws Exception\ServiceNotFoundException
+ * @throws Exception\InvalidServiceNameException
+ */
+ public function setAlias($alias, $nameOrAlias)
+ {
+ if (!is_string($alias) || !is_string($nameOrAlias)) {
+ throw new Exception\InvalidServiceNameException('Service or alias names must be strings.');
+ }
+
+ $cAlias = $this->canonicalizeName($alias);
+ $nameOrAlias = $this->canonicalizeName($nameOrAlias);
+
+ if ($alias == '' || $nameOrAlias == '') {
+ throw new Exception\InvalidServiceNameException('Invalid service name alias');
+ }
+
+ if ($this->allowOverride === false && $this->has(array($cAlias, $alias), false)) {
+ throw new Exception\InvalidServiceNameException(sprintf(
+ 'An alias by the name "%s" or "%s" already exists',
+ $cAlias,
+ $alias
+ ));
+ }
+
+ $this->aliases[$cAlias] = $nameOrAlias;
+ return $this;
+ }
+
+ /**
+ * Determine if we have an alias
+ *
+ * @param string $alias
+ * @return bool
+ */
+ public function hasAlias($alias)
+ {
+ $alias = $this->canonicalizeName($alias);
+ return (isset($this->aliases[$alias]));
+ }
+
+ /**
+ * Create scoped service manager
+ *
+ * @param string $peering
+ * @return ServiceManager
+ */
+ public function createScopedServiceManager($peering = self::SCOPE_PARENT)
+ {
+ $scopedServiceManager = new ServiceManager();
+ if ($peering == self::SCOPE_PARENT) {
+ $scopedServiceManager->peeringServiceManagers[] = $this;
+ }
+ if ($peering == self::SCOPE_CHILD) {
+ $this->peeringServiceManagers[] = $scopedServiceManager;
+ }
+ return $scopedServiceManager;
+ }
+
+ /**
+ * Add a peering relationship
+ *
+ * @param ServiceManager $manager
+ * @param string $peering
+ * @return ServiceManager
+ */
+ public function addPeeringServiceManager(ServiceManager $manager, $peering = self::SCOPE_PARENT)
+ {
+ if ($peering == self::SCOPE_PARENT) {
+ $this->peeringServiceManagers[] = $manager;
+ }
+ if ($peering == self::SCOPE_CHILD) {
+ $manager->peeringServiceManagers[] = $this;
+ }
+ return $this;
+ }
+
+ /**
+ * Canonicalize name
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function canonicalizeName($name)
+ {
+ if (isset($this->canonicalNames[$name])) {
+ return $this->canonicalNames[$name];
+ }
+
+ // this is just for performance instead of using str_replace
+ return $this->canonicalNames[$name] = strtolower(strtr($name, $this->canonicalNamesReplacements));
+ }
+
+ /**
+ * Create service via callback
+ *
+ * @param callable $callable
+ * @param string $cName
+ * @param string $rName
+ * @throws Exception\ServiceNotCreatedException
+ * @throws Exception\ServiceNotFoundException
+ * @throws Exception\CircularDependencyFoundException
+ * @return object
+ */
+ protected function createServiceViaCallback($callable, $cName, $rName)
+ {
+ static $circularDependencyResolver = array();
+ $depKey = spl_object_hash($this) . '-' . $cName;
+
+ if (isset($circularDependencyResolver[$depKey])) {
+ $circularDependencyResolver = array();
+ throw new Exception\CircularDependencyFoundException('Circular dependency for LazyServiceLoader was found for instance ' . $rName);
+ }
+
+ try {
+ $circularDependencyResolver[$depKey] = true;
+ $instance = call_user_func($callable, $this, $cName, $rName);
+ unset($circularDependencyResolver[$depKey]);
+ } catch (Exception\ServiceNotFoundException $e) {
+ unset($circularDependencyResolver[$depKey]);
+ throw $e;
+ } catch (\Exception $e) {
+ unset($circularDependencyResolver[$depKey]);
+ throw new Exception\ServiceNotCreatedException(
+ sprintf('An exception was raised while creating "%s"; no instance returned', $rName),
+ $e->getCode(),
+ $e
+ );
+ }
+ if ($instance === null) {
+ throw new Exception\ServiceNotCreatedException('The factory was called but did not return an instance.');
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Retrieve a keyed list of all registered services. Handy for debugging!
+ *
+ * @return array
+ */
+ public function getRegisteredServices()
+ {
+ return array(
+ 'invokableClasses' => array_keys($this->invokableClasses),
+ 'factories' => array_keys($this->factories),
+ 'aliases' => array_keys($this->aliases),
+ 'instances' => array_keys($this->instances),
+ );
+ }
+
+ /**
+ * Retrieve a keyed list of all canonical names. Handy for debugging!
+ *
+ * @return array
+ */
+ public function getCanonicalNames()
+ {
+ return $this->canonicalNames;
+ }
+
+ /**
+ * Allows to override the canonical names lookup map with predefined
+ * values.
+ *
+ * @param array $canonicalNames
+ * @return ServiceManager
+ */
+ public function setCanonicalNames($canonicalNames)
+ {
+ $this->canonicalNames = $canonicalNames;
+
+ return $this;
+ }
+
+ /**
+ * Attempt to retrieve an instance via a peering manager
+ *
+ * @param string $name
+ * @return mixed
+ */
+ protected function retrieveFromPeeringManager($name)
+ {
+ foreach ($this->peeringServiceManagers as $peeringServiceManager) {
+ if ($peeringServiceManager->has($name)) {
+ return $peeringServiceManager->get($name);
+ }
+ }
+
+ $name = $this->canonicalizeName($name);
+
+ if ($this->hasAlias($name)) {
+ do {
+ $name = $this->aliases[$name];
+ } while ($this->hasAlias($name));
+ }
+
+ foreach ($this->peeringServiceManagers as $peeringServiceManager) {
+ if ($peeringServiceManager->has($name)) {
+ return $peeringServiceManager->get($name);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempt to create an instance via an invokable class
+ *
+ * @param string $canonicalName
+ * @param string $requestedName
+ * @return null|\stdClass
+ * @throws Exception\ServiceNotFoundException If resolved class does not exist
+ */
+ protected function createFromInvokable($canonicalName, $requestedName)
+ {
+ $invokable = $this->invokableClasses[$canonicalName];
+ if (!class_exists($invokable)) {
+ throw new Exception\ServiceNotFoundException(sprintf(
+ '%s: failed retrieving "%s%s" via invokable class "%s"; class does not exist',
+ __METHOD__,
+ $canonicalName,
+ ($requestedName ? '(alias: ' . $requestedName . ')' : ''),
+ $invokable
+ ));
+ }
+ $instance = new $invokable;
+ return $instance;
+ }
+
+ /**
+ * Attempt to create an instance via a factory
+ *
+ * @param string $canonicalName
+ * @param string $requestedName
+ * @return mixed
+ * @throws Exception\ServiceNotCreatedException If factory is not callable
+ */
+ protected function createFromFactory($canonicalName, $requestedName)
+ {
+ $factory = $this->factories[$canonicalName];
+ if (is_string($factory) && class_exists($factory, true)) {
+ $factory = new $factory;
+ $this->factories[$canonicalName] = $factory;
+ }
+ if ($factory instanceof FactoryInterface) {
+ $instance = $this->createServiceViaCallback(array($factory, 'createService'), $canonicalName, $requestedName);
+ } elseif (is_callable($factory)) {
+ $instance = $this->createServiceViaCallback($factory, $canonicalName, $requestedName);
+ } else {
+ throw new Exception\ServiceNotCreatedException(sprintf(
+ 'While attempting to create %s%s an invalid factory was registered for this instance type.',
+ $canonicalName,
+ ($requestedName ? '(alias: ' . $requestedName . ')' : '')
+ ));
+ }
+ return $instance;
+ }
+
+ /**
+ * Attempt to create an instance via an abstract factory
+ *
+ * @param string $canonicalName
+ * @param string $requestedName
+ * @return object|null
+ * @throws Exception\ServiceNotCreatedException If abstract factory is not callable
+ */
+ protected function createFromAbstractFactory($canonicalName, $requestedName)
+ {
+ foreach ($this->abstractFactories as $index => $abstractFactory) {
+ // support factories as strings
+ if (is_string($abstractFactory) && class_exists($abstractFactory, true)) {
+ $this->abstractFactories[$index] = $abstractFactory = new $abstractFactory;
+ } elseif (!$abstractFactory instanceof AbstractFactoryInterface) {
+ throw new Exception\ServiceNotCreatedException(sprintf(
+ 'While attempting to create %s%s an abstract factory could not produce a valid instance.',
+ $canonicalName,
+ ($requestedName ? '(alias: ' . $requestedName . ')' : '')
+ ));
+ }
+ try {
+ if ($abstractFactory->canCreateServiceWithName($this, $canonicalName, $requestedName)) {
+ $this->pendingAbstractFactoryRequests[get_class($abstractFactory)] = $requestedName;
+ $instance = $this->createServiceViaCallback(
+ array($abstractFactory, 'createServiceWithName'),
+ $canonicalName,
+ $requestedName
+ );
+ unset($this->pendingAbstractFactoryRequests[get_class($abstractFactory)]);
+ } else {
+ $instance = false;
+ }
+ } catch (\Exception $e) {
+ unset($this->pendingAbstractFactoryRequests[get_class($abstractFactory)]);
+ throw new Exception\ServiceNotCreatedException(
+ sprintf(
+ 'An abstract factory could not create an instance of %s%s.',
+ $canonicalName,
+ ($requestedName ? '(alias: ' . $requestedName . ')' : '')
+ ),
+ $e->getCode(),
+ $e
+ );
+ }
+ if (is_object($instance)) {
+ break;
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Checks if the object has this class as one of its parents
+ *
+ * @see https://bugs.php.net/bug.php?id=53727
+ * @see https://github.com/zendframework/zf2/pull/1807
+ *
+ * @param string $className
+ * @param string $type
+ * @return bool
+ */
+ protected static function isSubclassOf($className, $type)
+ {
+ if (is_subclass_of($className, $type)) {
+ return true;
+ }
+ if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
+ return false;
+ }
+ if (!interface_exists($type)) {
+ return false;
+ }
+ $r = new ReflectionClass($className);
+ return $r->implementsInterface($type);
+ }
+
+ /**
+ * Unregister a service
+ *
+ * Called when $allowOverride is true and we detect that a service being
+ * added to the instance already exists. This will remove the duplicate
+ * entry, and also any shared flags previously registered.
+ *
+ * @param string $canonical
+ * @return void
+ */
+ protected function unregisterService($canonical)
+ {
+ $types = array('invokableClasses', 'factories', 'aliases');
+ foreach ($types as $type) {
+ if (isset($this->{$type}[$canonical])) {
+ unset($this->{$type}[$canonical]);
+ break;
+ }
+ }
+
+ if (isset($this->instances[$canonical])) {
+ unset($this->instances[$canonical]);
+ }
+
+ if (isset($this->shared[$canonical])) {
+ unset($this->shared[$canonical]);
+ }
+ }
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/AbstractPluginManager.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/AbstractPluginManager.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/AbstractPluginManager.php (revision 80)
@@ -0,0 +1,214 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\ServiceManager;
+
+/**
+ * ServiceManager implementation for managing plugins
+ *
+ * Automatically registers an initializer which should be used to verify that
+ * a plugin instance is of a valid type. Additionally, allows plugins to accept
+ * an array of options for the constructor, which can be used to configure
+ * the plugin when retrieved. Finally, enables the allowOverride property by
+ * default to allow registering factories, aliases, and invokables to take
+ * the place of those provided by the implementing class.
+ */
+abstract class AbstractPluginManager extends ServiceManager implements ServiceLocatorAwareInterface
+{
+ /**
+ * Allow overriding by default
+ *
+ * @var bool
+ */
+ protected $allowOverride = true;
+
+ /**
+ * Whether or not to auto-add a class as an invokable class if it exists
+ *
+ * @var bool
+ */
+ protected $autoAddInvokableClass = true;
+
+ /**
+ * Options to use when creating an instance
+ *
+ * @var mixed
+ */
+ protected $creationOptions = null;
+
+ /**
+ * The main service locator
+ *
+ * @var ServiceLocatorInterface
+ */
+ protected $serviceLocator;
+
+ /**
+ * Constructor
+ *
+ * Add a default initializer to ensure the plugin is valid after instance
+ * creation.
+ *
+ * @param null|ConfigInterface $configuration
+ */
+ public function __construct(ConfigInterface $configuration = null)
+ {
+ parent::__construct($configuration);
+ $self = $this;
+ $this->addInitializer(function ($instance) use ($self) {
+ if ($instance instanceof ServiceLocatorAwareInterface) {
+ $instance->setServiceLocator($self);
+ }
+ });
+ }
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the filter loaded is either a valid callback or an instance
+ * of FilterInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\RuntimeException if invalid
+ */
+ abstract public function validatePlugin($plugin);
+
+ /**
+ * Retrieve a service from the manager by name
+ *
+ * Allows passing an array of options to use when creating the instance.
+ * createFromInvokable() will use these and pass them to the instance
+ * constructor if not null and a non-empty array.
+ *
+ * @param string $name
+ * @param array $options
+ * @param bool $usePeeringServiceManagers
+ * @return object
+ */
+ public function get($name, $options = array(), $usePeeringServiceManagers = true)
+ {
+ // Allow specifying a class name directly; registers as an invokable class
+ if (!$this->has($name) && $this->autoAddInvokableClass && class_exists($name)) {
+ $this->setInvokableClass($name, $name);
+ }
+
+ $this->creationOptions = $options;
+ $instance = parent::get($name, $usePeeringServiceManagers);
+ $this->creationOptions = null;
+ $this->validatePlugin($instance);
+ return $instance;
+ }
+
+ /**
+ * Register a service with the locator.
+ *
+ * Validates that the service object via validatePlugin() prior to
+ * attempting to register it.
+ *
+ * @param string $name
+ * @param mixed $service
+ * @param bool $shared
+ * @return AbstractPluginManager
+ * @throws Exception\InvalidServiceNameException
+ */
+ public function setService($name, $service, $shared = true)
+ {
+ if ($service) {
+ $this->validatePlugin($service);
+ }
+ parent::setService($name, $service, $shared);
+ return $this;
+ }
+
+ /**
+ * Set the main service locator so factories can have access to it to pull deps
+ *
+ * @param ServiceLocatorInterface $serviceLocator
+ * @return AbstractPluginManager
+ */
+ public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
+ {
+ $this->serviceLocator = $serviceLocator;
+ return $this;
+ }
+
+ /**
+ * Get the main plugin manager. Useful for fetching dependencies from within factories.
+ *
+ * @return mixed
+ */
+ public function getServiceLocator()
+ {
+ return $this->serviceLocator;
+ }
+
+ /**
+ * Attempt to create an instance via an invokable class
+ *
+ * Overrides parent implementation by passing $creationOptions to the
+ * constructor, if non-null.
+ *
+ * @param string $canonicalName
+ * @param string $requestedName
+ * @return null|\stdClass
+ * @throws Exception\ServiceNotCreatedException If resolved class does not exist
+ */
+ protected function createFromInvokable($canonicalName, $requestedName)
+ {
+ $invokable = $this->invokableClasses[$canonicalName];
+
+ if (null === $this->creationOptions
+ || (is_array($this->creationOptions) && empty($this->creationOptions))
+ ) {
+ $instance = new $invokable();
+ } else {
+ $instance = new $invokable($this->creationOptions);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Attempt to create an instance via a factory class
+ *
+ * Overrides parent implementation by passing $creationOptions to the
+ * constructor, if non-null.
+ *
+ * @param string $canonicalName
+ * @param string $requestedName
+ * @return mixed
+ * @throws Exception\ServiceNotCreatedException If factory is not callable
+ */
+ protected function createFromFactory($canonicalName, $requestedName)
+ {
+ $factory = $this->factories[$canonicalName];
+ if (is_string($factory) && class_exists($factory, true)) {
+ if (null === $this->creationOptions || (is_array($this->creationOptions) && empty($this->creationOptions))) {
+ $factory = new $factory();
+ } else {
+ $factory = new $factory($this->creationOptions);
+ }
+
+ $this->factories[$canonicalName] = $factory;
+ }
+
+ if ($factory instanceof FactoryInterface) {
+ $instance = $this->createServiceViaCallback(array($factory, 'createService'), $canonicalName, $requestedName);
+ } elseif (is_callable($factory)) {
+ $instance = $this->createServiceViaCallback($factory, $canonicalName, $requestedName);
+ } else {
+ throw new Exception\ServiceNotCreatedException(sprintf(
+ 'While attempting to create %s%s an invalid factory was registered for this instance type.', $canonicalName, ($requestedName ? '(alias: ' . $requestedName . ')' : '')
+ ));
+ }
+
+ return $instance;
+ }
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceLocatorAwareInterface.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceLocatorAwareInterface.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceLocatorAwareInterface.php (revision 80)
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\ServiceManager;
+
+interface ServiceLocatorAwareInterface
+{
+ /**
+ * Set service locator
+ *
+ * @param ServiceLocatorInterface $serviceLocator
+ */
+ public function setServiceLocator(ServiceLocatorInterface $serviceLocator);
+
+ /**
+ * Get service locator
+ *
+ * @return ServiceLocatorInterface
+ */
+ public function getServiceLocator();
+}
Index: trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceLocatorInterface.php
===================================================================
--- trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceLocatorInterface.php (nonexistent)
+++ trunk/ZendFramework-2.1.2/library/Zend/ServiceManager/ServiceLocatorInterface.php (revision 80)
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\ServiceManager;
+
+/**
+ * Service locator interface
+ */
+interface ServiceLocatorInterface
+{
+ /**
+ * Retrieve a registered instance
+ *
+ * @param string $name
+ * @throws Exception\ServiceNotFoundException
+ * @return object|array
+ */
+ public function get($name);
+
+ /**
+ * Check for a registered instance
+ *
+ * @param string|array $name
+ * @return bool
+ */
+ public function has($name);
+}
Index: trunk/css/least/Mixins.php
===================================================================
--- trunk/css/least/Mixins.php (nonexistent)
+++ trunk/css/least/Mixins.php (revision 80)
@@ -0,0 +1,180 @@
+<?php
+
+namespace de\pointedears\css\least;
+
+/**
+ * Mix-ins for the LEAST CSS preprocessor
+ *
+ * @author Thomas 'PointedEars' Lahn &lt;php@PointedEars.de&gt;
+ */
+abstract class Mixins
+{
+ /** @section General functions */
+
+ /**
+ * Generates a CSS section for each keyword prefix.
+ *
+ * @param string $keyword
+ * Section keyword
+ * @param string $params
+ * Section parameters (name, medium, etc.)
+ * @param string $content
+ * Section content
+ * @param array[string] $prefixes
+ * Keyword prefixes
+ */
+ public static function prefix_section ($keyword, $params,
+ $content, array $prefixes)
+ {
+ ob_start();
+ foreach ($prefixes as $prefix)
+ {
+ echo "@{$prefix}{$keyword} {$params} {\n {$content}\n}\n";
+ }
+ ob_end_flush();
+ }
+
+ /**
+ * Generates a CSS property declaration whose value is a
+ * function call, for each function name prefix.
+ *
+ * @param string $property
+ * Property to be declared
+ * @param string $function
+ * CSS function to be called
+ * @param string $args
+ * Arguments to the function
+ * @param array[string] $prefixes
+ * Function name prefixes
+ */
+ public static function prefix_function ($property, $function, $args,
+ array $prefixes)
+ {
+ ob_start();
+ foreach ($prefixes as $prefix)
+ {
+ echo "{$property}: {$prefix}{$function}({$args});\n";
+ }
+ ob_end_flush();
+ }
+
+ /**
+ * Generates a CSS property declaration for each
+ * property name prefix.
+ *
+ * @param string $suffix
+ * Property name suffix
+ * @param string $value
+ * Property value
+ * @param array[string] $prefixes
+ * Property name prefixes
+ */
+ public static function prefix_property ($property, $suffix, $value,
+ array $prefixes)
+ {
+ ob_start();
+ foreach ($prefixes as $prefix)
+ {
+ echo "{$prefix}{$property}{$suffix}: {$value};\n";
+ }
+ ob_end_flush();
+ }
+
+ /** @section Gradients */
+
+ /**
+ * Generates a CSS property declaration whose value is a
+ * <code>linear-gradient()</code> function call for
+ * each function name prefix.
+ *
+ * @param string $property
+ * Property to be declared
+ * @param string $args
+ * Arguments to the <code>linear-gradient()</code> function
+ * @param array[string] $prefixes (optional)
+ * Pass to override supported function name prefixes
+ * @see self::prefix_function()
+ */
+ public static function linear_gradient ($property, $args,
+ array $prefixes = array('-moz-', '-o-', '-webkit-', ''))
+ {
+ self::prefix_function($property, 'linear-gradient', $args, $prefixes);
+ }
+
+ /**
+ * Generates a CSS property declaration whose value is a
+ * <code>radial-gradient()</code> function call for each
+ * function name prefix.
+ *
+ * @param string $property
+ * Property to be declared
+ * @param string $args
+ * Arguments to the <code>radial-gradient()</code> function
+ * @param array[string] $prefixes (optional)
+ * Pass to override supported function name prefixes
+ * @see self::prefix_function()
+ */
+ public static function radial_gradient ($property, $args,
+ array $prefixes = array('-moz-', '-webkit-'))
+ {
+ self::prefix_function($property, 'radial-gradient', $args, $prefixes);
+ }
+
+ /**
+ * Generates a CSS <code>transition</code> property declaration
+ * for each property name prefix.
+ *
+ * @param string $suffix
+ * Property name suffix
+ * @param string $value
+ * Property value
+ * @param array[string] $prefixes (optional)
+ * Pass to override supported property name prefixes
+ * @see self::prefix_property()
+ */
+ public static function transition ($suffix, $value,
+ array $prefixes = array('-moz-', '-webkit-', ''))
+ {
+ self::prefix_property('transition', $suffix, $value, $prefixes);
+ }
+
+ /** @section Animations */
+
+ /**
+ * Generates a CSS <code>@keyframes</code> section for
+ * each keyword prefix.
+ *
+ * @param string $name
+ * Animation name as referred by an <code>animation-name</code>
+ * property value.
+ * @param string $data
+ * Keyframes data
+ * @param array[string] $prefixes (optional)
+ * Pass to override supported keyword prefixes
+ * @see self::prefix_section()
+ */
+ public static function keyframes ($name, $data,
+ array $prefixes = array('-moz-', '-webkit-', ''))
+ {
+ self::prefix_section('keyframes', $name, $data, $prefixes);
+ }
+
+ /**
+ * Generates a CSS <code>animation</code> property declaration
+ * for each property name prefix.
+ *
+ * @param string $suffix
+ * Property name suffix, e.g. <tt>"-name"</tt> for
+ * <code>animation-name</code>
+ * @param string $value
+ * Property value
+ * @param array[string] $prefixes (optional)
+ * Pass to override supported property name prefixes
+ * @see self::prefix_property()
+ */
+ public static function animation ($suffix, $value,
+ array $prefixes = array('-moz-', '-webkit-', ''))
+ {
+ self::prefix_property('animation', $suffix, $value, $prefixes);
+ }
+}
\ No newline at end of file
Index: trunk/css/least/Parser.php
===================================================================
--- trunk/css/least/Parser.php (nonexistent)
+++ trunk/css/least/Parser.php (revision 80)
@@ -0,0 +1,345 @@
+<?php
+
+namespace de\pointedears\css\least;
+
+require_once __DIR__ . '/../../string/parser/Lexer.php';
+require_once __DIR__ . '/../../string/parser/Parser.php';
+
+/**
+ * Parses a LEAST stylesheet into a CSS stylesheet
+ *
+ * @author Thomas 'PointedEars' Lahn
+ * @property-read string $compiled
+ */
+class Parser extends \de\pointedears\string\parser\Parser
+{
+ /**
+ * CSS stylesheet code compiled from the LEAST stylesheet
+ *
+ * @var string
+ */
+ protected $_compiled;
+
+ /**
+ * Array of arrays of template variables, one inner array for
+ * each scope level.
+ * @var array[array]
+ */
+ protected $_vars = array(array());
+
+ /**
+ * The nesting level of the current variable scope.
+ *
+ * Used to hold variable definitions per nesting level.
+ *
+ * @var int
+ */
+ protected $_scope_level = 0;
+
+ /**
+ * Cache for lookup results.
+ *
+ * The lookup cache improves parser performance with
+ * nested scopes. By contrast to <code>$_vars</code>, it is
+ * a _shallow_ storage for template variable definitions and
+ * represents the current _certain_ variable knowledge of the
+ * parser, i.e. variables that have been defined in outer scopes
+ * and the same scope, and whose values have been looked up in
+ * <code>$_vars</code> before.
+ *
+ * The lookup cache is replaced by the variables of the outer
+ * scope when a scope is exited because definitions in the
+ * outer scope may have been shadowed by the inner scope.
+ * Note that definitions from the second-next, third-next
+ * aso. scopes need to be looked up again then.
+ *
+ * @var array
+ */
+ protected $_lookup_cache = array();
+
+ /**
+ * Mixins
+ * @var array
+ */
+ protected $_mixins = array();
+
+ /**
+ * <code>true</code> if the parser is parsing a mix-in
+ * @var boolean
+ */
+ protected $_in_mixin = false;
+
+ /**
+ * The currently parsed mix-in
+ * @var Mixin
+ */
+ protected $_current_mixin;
+
+ /**
+ * The nesting level in the currently parsed mix-in, 0 again when
+ * it just ended
+ * @var int
+ */
+ protected $_mixin_level = 0;
+
+ public function __construct ($code)
+ {
+ $lexer = new \de\pointedears\string\parser\Lexer($code);
+
+ $nl = 'n';
+ $escape = '\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f]';
+ $lexer->addTokens(array(
+ '(?P<PHP><\?php.*?\?>)',
+ "/\*[^*]*\*+([^/*][^*]*\*+)*/",
+ "\"([^\n\r\f\\\"]|\\{$nl}|{$escape})*\"",
+ "'([^\n\r\f\\']|\\{$nl}|{$escape})*'",
+// '\.(?P<mixin_def>[\w-]+)\s*\((?P<params>.*?)\)\s*(?!;)',
+// '\.(?P<mixin_call>[\w-]+)\s*\((?P<args>.*)?\)\s*;\\s*',
+ '[{}]',
+// '\$(?P<name>\w+)\s*=\s*(?P<value>\'.+?\'|".+"|.+?)\s*;\s*',
+// '\$(?P<ref>\w+)'
+ ));
+
+ $lexer->ignoreCase = true;
+ $lexer->dotAll = true;
+
+ parent::__construct($lexer);
+ $this->_compiled = $code;
+ }
+
+ protected function defineVar($name, $value)
+ {
+ $scope_level = $this->_scope_level;
+
+ if (defined('DEBUG') && DEBUG > 0)
+ {
+ echo "<?php // Least.php: Found definition \${$name} = `{$value}'"
+ . " at scope level {$scope_level} ?>\n";
+ }
+
+ $this->_vars[$scope_level][$name] = $value;
+ $this->_lookup_cache[$name] = $value;
+ }
+
+ /**
+ * Resolves a template variable reference against the template scope chain
+ * @param string $ref
+ * @return mixed
+ */
+ protected function resolveVar ($name)
+ {
+ $scope_level = $this->_scope_level + 1;
+ $orig_level = $scope_level - 1;
+
+ if (defined('DEBUG') && DEBUG > 0)
+ {
+ echo "<?php // Least.php: Found reference \${$name} at scope level "
+ . ($scope_level - 1) . " ?>\n";
+ }
+
+ /* Try the lookup cache for this scope level first */
+ if (array_key_exists($name, $this->_lookup_cache))
+ {
+ $value = $this->_lookup_cache[$name];
+
+ if (defined('DEBUG') && DEBUG > 1)
+ {
+ echo "<?php // Least.php: Resolved reference \${$name}"
+ . " at scope level {$orig_level}"
+ . " from lookup cache, value `{$value}' ?>\n";
+ }
+
+ return $value;
+ }
+
+ while ($scope_level--)
+ {
+ if (array_key_exists($name, $this->_vars[$scope_level]))
+ {
+ $value = $this->_vars[$scope_level][$name];
+ $this->_lookup_cache[$name] = $value;
+
+ if (defined('DEBUG') && DEBUG > 1)
+ {
+ echo "<?php // Least.php: Resolved reference \${$name}"
+ . " from scope level {$orig_level}"
+ . " at scope level {$scope_level}, value `{$value}' ?>\n";
+ }
+
+ return $value;
+ }
+ }
+
+ if (defined('DEBUG') && DEBUG > 0)
+ {
+ echo "<?php // Least.php: WARNING: Unresolved reference \${$name}"
+ . " at scope level {$orig_level}. Variable not in scope? ?>\n";
+ }
+ }
+
+ protected function defineMixin($name, $params)
+ {
+ $scope_level = $this->_scope_level;
+
+ if (defined('DEBUG') && DEBUG > 0)
+ {
+ echo "<?php // Least.php: Found mixin definition {$name}($params)"
+ . " at scope level {$scope_level}. ?>\n";
+ }
+
+ $mixin = new Mixin($params);
+ $this->_mixins[$name] = $mixin;
+
+ return $mixin;
+ }
+
+ protected function callMixin($name, $args)
+ {
+ return $this->$_mixins[$name]->apply($args);
+ }
+
+ public function parseToken ($matches)
+ {
+ ini_set('html_errors', 0);
+// var_dump($matches);
+// /* Get match offset and match length */
+// $match = $matches[0];
+// $match_start = $match[1];
+// $match_length = mb_strlen($match[0]);
+
+// /* Transform $matches to the format it is usually set as (without PREG_OFFSET_CAPTURE set) */
+// $matches = array_map(function ($m) {
+// return $m[0];
+// }, $matches);
+// $match = $matches[0];
+
+// if (defined('DEBUG') && DEBUG > 1)
+// {
+// echo print_r(array(
+// 'match_start' => $match_start,
+// 'match_length' => $match_length,
+// 'matches' => $matches,
+// 'in_mixin' => $in_mixin,
+// 'scope_level' => $this->_scope_level,
+// ), true) . "\n";
+// }
+
+// if (isset($matches['mixin_def']) && $matches['mixin_def'])
+// {
+// $this->_in_mixin = true;
+// $this->_current_mixin = $this->defineMixin($matches['mixin_def'], $matches['params']);
+// // $code = self::replace(mb_strlen($match), '', $code, $match_start);
+// // $match_length = 0;
+// }
+// else if (isset($matches['mixin_call']) && $matches['mixin_call'])
+// {
+// //return
+// $this->callMixin($matches['mixin_call'], $matches['args']);
+// //$code = self::replace(mb_strlen($match), '', $code, $match_start);
+// //$match_length = 0;
+// $mixin_start = $match_start;
+// }
+// else if ($match === '{')
+// {
+// ++$this->_scope_level;
+// $this->_vars[$this->_scope_level] = array();
+
+// if ($this->_in_mixin)
+// {
+// if ($mixin_body_start === 0)
+// {
+// $mixin_body_start = $match_start;
+// }
+
+// ++$this->_mixin_level;
+// }
+// }
+// else if ($match === '}')
+// {
+// --$this->_scope_level;
+// $this->_lookup_cache = $this->_vars[$this->_scope_level];
+
+// if ($this->_in_mixin)
+// {
+// --$this->_mixin_level;
+// if ($this->_mixin_level === 0)
+// {
+// $this->_in_mixin = false;
+// $this->_current_mixin->setBody(substr($code, $mixin_start + 1, $match_start - $mixin_start - 1));
+// echo '"'.print_r(substr($code, $mixin_start, $match_start - $mixin_start + 1), true) . "\"\n";
+// // $code = self::replace($match_start - $mixin_start + 1, '', $code, $mixin_start);
+// // $match_length = 0;
+// }
+// }
+// }
+// else if (isset($matches['name']) && $matches['name'])
+// {
+// $this->defineVar($matches['name'], $matches['value']);
+// $code = self::replace(mb_strlen($match), '', $code, $match_start);
+// $match_length = 0;
+// }
+// else if (isset($matches['ref']) && $matches['ref'])
+// {
+// $name = $matches['ref'];
+
+// if (!$in_mixin || !in_array($name, $mixin->params, true))
+// {
+// $value = $this->resolveVar($matches['ref']);
+// $this->_lexer->text = self::replace(mb_strlen($match), $value, $code, $match_start);
+// $this->_lexer->offset -= $match_length + mb_strlen($value);
+// }
+// }
+ }
+
+ public function getCompiled ()
+ {
+ return $this->_compiled;
+ }
+}
+
+class Mixin extends \de\pointedears\Base
+{
+ /**
+ * Parameters of the mixin
+ * @var array
+ */
+ protected $_params;
+
+ /**
+ * Body of the mixin
+ * @var string
+ */
+ protected $_body = '';
+
+ /**
+ * @param string $params
+ */
+ public function __construct ($params, $body = '')
+ {
+ $this->_params = is_array($params)
+ ? $params
+ : array_map(
+ function ($e) {
+ return preg_replace('/^\$/', '', $e);
+ },
+ preg_split('/\s*,\s*/', $params));
+
+ if ($body !== '')
+ {
+ $this->setBody($body);
+ }
+ }
+
+ public function setBody ($body)
+ {
+ $this->body = (string) $body;
+ }
+
+ /**
+ * @param string $arguments
+ */
+ public function apply ($arguments)
+ {
+ return '';
+ }
+}
\ No newline at end of file
Index: trunk/css/least/least-jit.php
===================================================================
--- trunk/css/least/least-jit.php (nonexistent)
+++ trunk/css/least/least-jit.php (revision 80)
@@ -0,0 +1,17 @@
+<?php
+
+$file = $_GET['src'];
+
+header('Last-Modified: ' . gmdate('D, d M Y H:i:s', @filemtime($file)) . ' GMT');
+
+/* Cached resource expires in HTTP/1.1 caches immediately after last retrieval */
+header('Cache-Control: max-age=0, s-maxage=0, must-revalidate, proxy-revalidate');
+
+/* Cached resource expires in HTTP/1.0 caches immediately after last retrieval */
+header('Expires: ' . gmdate('D, d M Y H:i:s', time() /*+ 86400*/) . ' GMT');
+
+header('Content-Type: text/plain; charset=UTF-8');
+
+require __DIR__ . '/LEAST.php';
+$least = new de\pointedears\css\least\LEAST();
+/*echo*/ $least->parse_file($file);
\ No newline at end of file
Index: trunk/css/least/LEAST.php
===================================================================
--- trunk/css/least/LEAST.php (nonexistent)
+++ trunk/css/least/LEAST.php (revision 80)
@@ -0,0 +1,138 @@
+<?php
+
+namespace de\pointedears\css\least;
+
+require_once 'Base.php';
+
+require_once 'Parser.php';
+
+define('DEBUG', 2);
+
+/**
+ * @author Thomas 'PointedEars' Lahn
+ */
+class LEAST
+{
+ /**
+ * Default name of parsed file
+ *
+ * @var string
+ */
+ protected $_filename;
+
+ /**
+ * Default name of parsed file
+ *
+ * @param string $filename
+ */
+ public function __construct ($code = null)
+ {
+ if ($code !== null)
+ {
+ $this->_code = $code;
+ }
+ }
+
+ /**
+ * Gets a template variable or a property
+ *
+ * @param string $name
+ * @throws InvalidArgumentException
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (property_exists($this, "_$name"))
+ {
+ return $this->{"_$name"};
+ }
+
+ if (array_key_exists($name, $this->_vars))
+ {
+ return $this->_vars[$name];
+ }
+
+ throw new InvalidArgumentException("no property '{$name}'");
+ }
+
+ /**
+ * Sets a template variable
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function __set($name, $value)
+ {
+ $this->_vars[$name] = $value;
+ }
+
+ /**
+ * Replaces characters in LEAST source code
+ *
+ * @param string $count
+ * Number of characters to replace
+ * @param string $replacement
+ * Replacement string
+ * @param string $haystack
+ * String to be searched
+ * @param int $start
+ * Start replace from here
+ * @return string
+ */
+ protected static function replace ($count, $replacement, $haystack, $start)
+ {
+ return substr($haystack, 0, $start) . $replacement . substr($haystack, $start + $count);
+ }
+
+ /**
+ * Parses LEAST source code, replacing special tokens, and returns the result
+ *
+ * @param string[optional] $code
+ * @return string
+ */
+ public function parse ($code = null)
+ {
+ if ($code === null)
+ {
+ $code = $this->_code;
+ }
+
+ static $nl = 'n';
+
+ \mb_internal_encoding(mb_detect_encoding($code));
+ $parser = new \de\pointedears\css\least\Parser($code);
+ $parser->parse();
+
+ return $parser->compiled;
+// return $code;
+ }
+
+ /**
+ * Parses a LEAST file and returns the result
+ *
+ * @param string $filename
+ * @return string
+ */
+ public function parse_file ($filename = null)
+ {
+ $contents = file_get_contents(
+ $filename === null ? $this->filename : $filename);
+ return $this->parse($contents);
+ }
+
+ /**
+ * Compiles a CSS file from a LEAST file
+ *
+ * @param string $source
+ * @param string $target
+ */
+ public static function compile ($source, $target)
+ {
+ if (@filemtime($target) < @filemtime($source))
+ {
+ $least = new self();
+ file_put_contents($target, $least->parse_file($source));
+ }
+ }
+}
+
Index: trunk/features.class.php
===================================================================
--- trunk/features.class.php (nonexistent)
+++ trunk/features.class.php (revision 80)
@@ -0,0 +1,571 @@
+<?php
+
+require_once __DIR__ . '/global.inc';
+
+$encoding = mb_detect_encoding(file_get_contents($_SERVER['SCRIPT_FILENAME']));
+if ($encoding === 'ASCII') $encoding = 'iso-8859-1';
+define('FEATURES_ENCODING', $encoding);
+
+/**
+ * A list of language features with URNs definitions
+ * for reference links.
+ */
+class FeatureList
+{
+ public $versions = array();
+
+ /**
+ * Versions of implementations that are considered safe.
+ * A feature is considered safe if it does not require
+ * an implementation version above these versions.
+ *
+ * @var Array[string=>string]
+ */
+ public $safeVersions = array();
+
+ /**
+ * URNs that can be used for reference links.
+ *
+ * @var Array[string=>string]
+ */
+ protected $urns = array();
+
+ /**
+ * <code>true</code> generates form controls for submitting test case
+ * results
+ *
+ * @var bool
+ */
+ protected $testcase = false;
+
+ /**
+ * The list of language features
+ *
+ * @var array[Features]
+ */
+ protected $items = array();
+
+ /**
+ * Determines the number of printed items the table headings should be repeated
+ *
+ * @var int
+ */
+ protected $headerRepeat = 25;
+
+ /**
+ * Initializes the FeatureList object
+ *
+ * @param array|Object $a
+ * @return FeatureList
+ */
+ public function __construct($a)
+ {
+ $aVars = get_class_vars(get_class($this));
+
+
+ foreach ($aVars as $key => $value)
+ {
+ if (isset($a[$key]))
+ {
+ $this->$key = $a[$key];
+ }
+ }
+
+ /* Inform items of ourself so that URNs can be used for links */
+ if (is_array($this->items))
+ {
+ foreach ($this->items as &$item)
+ {
+ $item->setList($this);
+ }
+ }
+
+ /* resolve URN references that are URNs */
+ if (is_array($this->urns))
+ {
+ foreach ($this->urns as &$urn)
+ {
+ if (($url = $this->resolveURN($urn)))
+ {
+ $urn = $url;
+ }
+ }
+ }
+ }
+
+ /*
+ * Protected properties may be read, but not written
+ */
+ public function __get($property)
+ {
+ if (property_exists(get_class($this), $property))
+ {
+ return $this->$property;
+ }
+ }
+
+ public function printHeaders()
+ {
+ foreach ($this->versions as $key => $ver)
+ {
+ if ($key || $this->testcase)
+ {
+?>
+ <th><?php echo $ver; ?></th>
+<?php
+ }
+ }
+ }
+
+ /**
+ * Prints the list of features.
+ *
+ * @see Feature::printMe()
+ */
+ public function printItems()
+ {
+ $counter = 0;
+ $headerRepeat = $this->headerRepeat;
+ $repeatHeaders = ($headerRepeat > 1);
+
+ foreach ($this->items as $feature)
+ {
+ if ($feature instanceof Feature)
+ {
+ /*
+ * TODO: Disabled header repetition until footnote ref. name/ID
+ * problem has been solved
+ */
+// if ($repeatHeaders
+// && $counter > 1
+// && $counter % $headerRepeat === 0)
+// {
+// echo <<<HTML
+// <tr class="header">
+// <th>Feature</th>
+// {$this->printHeaders()}
+// </tr>
+//HTML;
+// }
+
+ $feature->printMe();
+
+ $counter++;
+ }
+ }
+ }
+
+ /**
+ * Resolves a URN according to the value of the
+ * object's <code>$urn</code> property.
+ *
+ * @param string $urn
+ * URN to be resolved
+ * @return string|boolean
+ * The resolved URN if successful,
+ * <code>false</code> otherwise.
+ */
+ public function resolveURN($urn)
+ {
+ if (is_array($this->urns))
+ {
+ $reURN = '|^(.+?):(?!//)|';
+
+ if (preg_match($reURN, $urn, $m) && isset($this->urns[$m[1]]))
+ {
+ return preg_replace($reURN, $this->urns[$m[1]], $urn);
+ }
+ }
+
+ return $urn;
+ }
+}
+
+/**
+ * A language feature.
+ *
+ * @property-read Array[String] $anchors
+ * Fragment identifiers to be defined for quickly accessing
+ * the feature description.
+ * @property-read string $title
+ * Value of the explanatory <code>title</code> attribute for the feature.
+ * @property-read string $content
+ * Name or example code of the feature
+ * @property-read string $descr
+ * Description of the feature. Displayed directly if code is missing,
+ * otherwise used as `title' attribute value.
+ * @property-read Array $versions
+ * Versions that support this feature
+ * @property-read List $list
+ * Reference to the FeatureList that this feature belongs to
+ */
+class Feature
+{
+ /**
+ * Fragment identifiers to be defined for quickly accessing
+ * the feature description.
+ *
+ * @var Array[String]
+ */
+ protected $anchors = array();
+
+ /**
+ * Value of the explanatory <code>title</code> attribute for the feature.
+ *
+ * @var string
+ */
+ protected $title = '';
+
+ /**
+ * Name or example code of the feature
+ *
+ * @var string
+ */
+ protected $content = '';
+
+ /**
+ * Description of the feature. Displayed directly if code is missing,
+ * otherwise used as `title' attribute value.
+ *
+ * @var string
+ */
+ protected $descr = '';
+
+ /**
+ * Versions that support this feature
+ *
+ * @var Array
+ */
+ protected $versions = array();
+
+ /**
+ * Reference to the FeatureList that this feature belongs to
+ *
+ * @var FeatureList
+ */
+ protected $list = null;
+
+ public function setList(&$oList)
+ {
+ $this->list =& $oList;
+ }
+
+ /**
+ * Creates a new Feature object, using values from the passed parameters
+ * array.
+ *
+ * @param array|Object $params
+ * @return Feature
+ */
+ public function __construct($params = array())
+ {
+ $aVars = get_class_vars(__CLASS__);
+
+ foreach ($aVars as $key => $value)
+ {
+ if (isset($params[$key]))
+ {
+ $this->$key = $params[$key];
+ }
+ }
+ }
+
+ /*
+ * Protected properties may be read, but not written
+ */
+ public function __get($property)
+ {
+ if (property_exists(get_class($this), $property))
+ {
+ return $this->$property;
+ }
+ }
+
+ /**
+ * Determines whether one version is greater than another.
+ *
+ * @param string $v1 Version string #1
+ * @param string $v2 Version string #2
+ * @return bool
+ * <code>true</code> if the version <var>$v1</var> is greater than
+ * the version <var>$v2</var>, <code>false</code> otherwise
+ */
+ protected static function _versionIsGreater($v1, $v2)
+ {
+ $v1 = explode('.', $v1);
+ $v2 = explode('.', $v2);
+
+ foreach ($v1 as $key => $value)
+ {
+ if ((int)$value <= (int)$v2[$key])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns <code>' class="safe"'</code> if the feature
+ * can be considered safe. The required information
+ * is stored in the <code>safeVersions</code> property
+ * of the associated <code>FeatureList</code> object.
+ *
+ * @return string
+ * @see FeatureList::defaultSafeVersions
+ */
+ protected function getSafeStr()
+ {
+ if (!is_null($this->list))
+ {
+ foreach ($this->list->safeVersions as $impl => &$safeVer)
+ {
+ $thisImplVer =& $this->versions[$impl];
+ if (is_array($thisImplVer))
+ {
+ if (isset($thisImplVer['tested']) && !is_bool($thisImplVer['tested']))
+ {
+ $thisImplVer =& $thisImplVer['tested'];
+ }
+ else
+ {
+ $thisImplVer =& $thisImplVer[0];
+ }
+ }
+
+ /* DEBUG */
+ // echo " $impl=$thisImplVer ";
+
+ if (preg_match('/^-?$/', $thisImplVer) || self::_versionIsGreater($thisImplVer, $safeVer))
+ {
+ return '';
+ }
+ }
+
+ return ' class="safe"';
+ }
+ else
+ {
+ return '';
+ }
+ }
+
+ protected function getTitleStr()
+ {
+ if (!empty($this->title))
+ {
+ return " title=\"{$this->title}\"";
+ }
+ else
+ {
+ return '';
+ }
+ }
+
+ protected function getAnchors()
+ {
+ $result = array();
+
+ foreach ($this->anchors as $anchor)
+ {
+ $result[] = "<a name=\"{$anchor}\"";
+
+ if (preg_match('/^[a-z][a-z0-9_:.-]*/i', $anchor))
+ {
+ $result[] = " id=\"{$anchor}\"";
+ }
+
+ $result[] = '></a>';
+ }
+
+ return join('', $result);
+ }
+
+ protected function getAssumed($v)
+ {
+ if (is_array($v) && isset($v['assumed']) && $v['assumed'])
+ {
+ return ' class="assumed"';
+ }
+
+ return '';
+ }
+
+ protected function getTested($v)
+ {
+ if (is_array($v) && isset($v['tested']) && $v['tested'])
+ {
+ return ' class="tested"';
+ }
+
+ return '';
+ }
+
+ /**
+ * Returns the version of a feature.
+ *
+ * @param string|VersionInfo $vInfo
+ * @return mixed
+ */
+ protected function getVer($vInfo)
+ {
+ if (is_array($vInfo))
+ {
+ /* TODO: Return all versions: documented, assumed, and tested */
+ $vNumber = (isset($vInfo['tested'])
+ && gettype($vInfo['tested']) !== 'boolean')
+ ? $vInfo['tested']
+ : $vInfo[0];
+ $section = isset($vInfo['section'])
+ ? ' <span class="section" title="Specification section">['
+ . $vInfo['section'] . ']</span>'
+ : '';
+
+ if (isset($vInfo['urn']))
+ {
+ if ($this->list instanceof FeatureList)
+ {
+ $url = $this->list->resolveURN($vInfo['urn']);
+ $vNumber = '<a href="' . $url . '">' . $vNumber
+ . ($section ? $section : '') . '</a>';
+ }
+ }
+ else if ($section)
+ {
+ $vNumber .= $section;
+ }
+
+ $vInfo = $vNumber;
+ }
+
+ return ($vInfo === '-')
+ ? '<span title="Not supported">&#8722;</span>'
+ : $vInfo;
+ }
+
+ /**
+ * Returns a syntax-highlighted version of a string
+ *
+ * @param string $s
+ * @return string
+ */
+ protected static function shl($s)
+ {
+ /* stub */
+ return $s;
+ }
+
+ public function printMe()
+ {
+ ?>
+<tr<?php echo $this->getSafeStr(); ?>>
+ <th<?php echo $this->getTitleStr(); ?>><?php
+ echo $this->getAnchors();
+ echo /*preg_replace_callback(
+ '#(<code>)(.+?)(</code>)#',
+ array('self', 'shl'),*/
+ preg_replace('/&hellip;/', '&#8230;', $this->content)/*)*/;
+ ?></th>
+<?php
+ $versions = $this->versions;
+ $testcase = false;
+ if (!is_null($this->list))
+ {
+ $versions =& $this->list->versions;
+ $testcase = $this->list->testcase;
+ }
+
+ static $row = 0;
+ $row++;
+
+ $column = 0;
+ $thisVersions =& $this->versions;
+
+ foreach ($versions as $key => $value)
+ {
+ $column++;
+ $id = "td$row-$column";
+ $ver = isset($thisVersions[$key]) ? $thisVersions[$key] : '';
+ if ($key || $testcase)
+ {
+?>
+ <td<?php
+ if (!$key)
+ {
+ echo " id='$id'";
+ }
+
+ echo $this->getAssumed($ver) . $this->getTested($ver);
+
+ if (!$key)
+ {
+ if (!empty($ver))
+ {
+ echo ' title="Test code: '
+ . htmlspecialchars(
+ preg_replace('/\\\(["\'])/', '\1',
+ reduceWhitespace($ver)
+ ),
+ ENT_COMPAT,
+ FEATURES_ENCODING
+ )
+ . '"';
+ }
+ else
+ {
+ echo ' title="Not applicable: No automated test case'
+ . ' is available for this feature. If possible, please'
+ . ' click the feature code in the first column to run'
+ . ' a manual test."';
+ }
+ }
+ else
+ {
+ echo ' title="'
+ . htmlspecialchars(
+ preg_replace('/<.*?>/', '', $value),
+ ENT_COMPAT, FEATURES_ENCODING)
+ . '"';
+ }
+ ?>><?php
+ if ($key)
+ {
+ echo $this->getVer($ver);
+
+ /* General footnotes support: include footnotes.class.php to enable */
+ if (is_array($ver) && isset($ver['footnote']) && $ver['footnote'])
+ {
+ echo $ver['footnote'];
+ }
+ }
+ else
+ {
+ if (!empty($ver) && $testcase)
+ {
+ ?><script type="text/javascript">
+ // <![CDATA[
+ var s = test(<?php echo $ver; ?>, '<span title="Supported">+<\/span>',
+ '<span title="Not supported">&#8722;<\/span>');
+ jsx.tryThis("document.write(s);",
+ "document.getElementById('<?php echo $id; ?>').appendChild("
+ + "document.createTextNode(s));");
+ // ]]>
+</script><?php
+ }
+ else
+ {
+ echo '<abbr>N/A</abbr>';
+ }
+ }
+ ?></td>
+<?php
+ }
+ }
+?>
+ </tr>
+<?php
+ }
+}
+
+?>
Index: trunk/.settings/org.eclipse.php.core.prefs
===================================================================
--- trunk/.settings/org.eclipse.php.core.prefs (nonexistent)
+++ trunk/.settings/org.eclipse.php.core.prefs (revision 80)
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+include_path=0;/PHPX
/trunk/.settings/org.eclipse.php.core.prefs
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: trunk/global.inc
===================================================================
--- trunk/global.inc (nonexistent)
+++ trunk/global.inc (revision 80)
@@ -0,0 +1,533 @@
+<?php
+
+/* @section Helper functions */
+
+/**
+ * Retrieves the value of an element of an associative array.
+ *
+ * This is designed for $_GET and other HTTP variables but
+ * also works for common associative arrays.
+ *
+ * @param $key: string
+ * Key identifier. The default is the empty string.
+ * @param $array: string
+ * Array identifier. The default is '_GET'. If there is
+ * no such array, the identifier is prefixed with 'HTTP'
+ * and suffixed with '_VARS' (to support the deprecated
+ * HTTP_GET_VARS etc. arrays as of PHP &lt; 4.1). If
+ * there is still no array with that identifier, return
+ * the empty string.
+ * @param $default: string
+ * Default return value if the element specified with
+ * $key is not available in the array. The default
+ * is the empty string.
+ * @return
+ * The value of the element of that array with that key or
+ * the empty string if there is no such element or array.
+ * @author
+ * Copyright (C) 2004, 2005 Thomas Lahn &lt;php@PointedEars.de&gt;
+ */
+function getVars($key = '', $array = '_GET', $default = '', $noEntities = false)
+{
+ global ${$array};
+ if (!isset(${'HTTP'.$array.'_VARS'})) global ${'HTTP'.$array.'_VARS'};
+/*
+ echo "<pre>getVars: \$$array"."['$key']: return '"
+ .(isset(${$array}) && isset(${$array}[$key])
+ ? ${$array}[$key]
+ : (isset(${'HTTP'.$array.'_VARS'}) && isset(${'HTTP'.$array.'_VARS'}[$key])
+ ? ${'HTTP'.$array.'_VARS'}[$key]
+ : $default)) . "'</pre><br>\n";
+*/
+ $result = (isset(${$array}) && isset(${$array}[$key])
+ ? ${$array}[$key]
+ : (isset(${'HTTP'.$array.'_VARS'}) && isset(${'HTTP'.$array.'_VARS'}[$key])
+ ? ${'HTTP'.$array.'_VARS'}[$key]
+ : $default));
+
+// TODO: Escape HTML entities
+/*
+ if (!$noEntities)
+ {
+ $result = htmlentities($result);
+ }
+*/
+ return $result;
+}
+
+/**
+ * Converts the argument to a visible (X)HTML hyperlink
+ * where its URI target is created from the argument.
+ * Supported are e-mail addresses, domain names with
+ * optional paths, and valid URIs.
+ *
+ * @param $text
+ * Argument to be converted.
+ * @return
+ * The converted argument if it applies to a supported
+ * scheme, the unconverted argument otherwise.
+ *
+ * @author (C) 2001-04-04T02:03
+ * mark.young@vdhinc.com at http://php.net/manual/en/ref.strings.php
+ *
+ * Minor correction to my HTMLEncode function.
+ *
+ * @author 2002-08-29T09:00
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Added target="_blank"
+ * - Added support for ftp(s)-URIs: (ht|f)
+ * - Added support for search strings (?...=...&...=...), either
+ * with or without HTML entities: \?=(&\w;|&)
+ * - Removed enclosing nl2br call because of preformatted display
+ *
+ * @author 2003-12-30T14:18
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Removed target="_blank".
+ * - Added PHPdoc.
+ *
+ * @author 2004-01-12T12:45
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Added support for #fragment_identifiers in URIs.
+ * - Added support for bugs and comments.
+ *
+ * @author 2004-01-13T01:29
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Added support for bug aliases.
+ *
+ * @author 2004-01-26T20:43
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Do not convert URIs in A elements.
+ * - Do not allow URIs with "&" before search-string.
+ * - camelCased function identifier. Only classes
+ * and constructors should start with uppercase.
+ *
+ * @author 2004-01-27T14:07
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow to convert URIs preceded by "&gt;" but not followed by "&lt;/a&gt;".
+ * - Allow ";" to be part of the search string
+ *
+ * @author 2004-01-29T14:10
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Require valid domain name for "bar" on conversion of "foo@bar" and
+ * "www.bar".
+ * - Be case-insensitive except of bug aliases
+ * - Escaped "-" in character classes if not meant as range metacharacter.
+ * - Corrected year.
+ *
+ * @author 2004-02-14T17:37
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Accept only valid Internet domain names
+ * - Accept "%" within path and query part (to escape ASCII characters)
+ *
+ * @author 2004-02-27T19:21
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow unescaped ":" in URI components, since it apparently does not
+ * "conflict with the reserved purpose" (RFC 2396, section 2.2.; here:
+ * scheme component)
+ * - Allow slashes, dots and dashes in URI components
+ * - Removed invalid [...(...|...)...]
+ *
+ * @author 2004-03-01T21:48
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow IPv4 addresses
+ *
+ * @author 2004-03-08T02:20
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow "+" and "," in query part
+ * - Optimized character classes
+ *
+ * @author (C) 2004-04-23T10:03
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Rewrite to use RFC 2822 and 2369 syntax
+ */
+function htmlEncode($text)
+{
+ // see RFC 2822 "Internet Message Format"
+ $local_part = '[^()<>@,;:\\"\-\[\] \x00-\x1A\x7F]+';
+
+ // see RFC 2396 "Uniform Resource Identifiers (URI): Generic Syntax"
+ // $digit = '\d'; // 0-9; def. not required
+ // $hex = '\da-f'; // "hex" for case-insensitive match; def. N/R
+ $alpha = 'a-z'; // "alpha" for case-insensitive match
+ $alphanum = $alpha.'\d'; // "alphanum" for case-insensitive match
+ $mark = "\-_.!~*\'()";
+ $reserved = ';/?:@&=+$,';
+ $unreserved = $alphanum.$mark;
+ $escaped = '%[\da-f]'; // contains $hex
+
+ // added (?!gt;) to allow "&lt;URI&gt;"
+ $uric = '(['.$reserved.$alphanum.$mark.'](?!gt;)|'.$escaped.')';
+ $uric_no_slash = '(['.$unreserved.';\?:@&=+$,](?!gt;)|'.$escaped.')';
+ $pchar = '(['.$unreserved.':@&=+$,](?!gt;)|'.$escaped.')';
+ $param = $pchar;
+ $segment = $pchar.'*(;'.$param.')*';
+ $abs_path = '/' . $segment . '(/'.$segment.')*';
+ $userinfo = '(['.$unreserved.';:&=+$,](?!gt;)|'.$escaped.')*';
+ $domainlabel = '(['.$alphanum.']'
+ . '|['.$alphanum.'](['.$alphanum.']|-)*['.$alphanum.']'
+ . ')';
+ $toplabel = '(['.$alpha.']'
+ . '|['.$alpha.'](['.$alphanum.']|-)*['.$alphanum. '])';
+ $hostname = '('.$domainlabel. '\.)*' . $toplabel . '\.?';
+ $ipv4_address = '\d+\.\d+\.\d+\.\d+';
+ $host = '(' . $hostname . '|' . $ipv4_address . ')';
+ $port = '\d*';
+ $hostport = $host . '(:' . $port . ')?';
+ $server_req = '(' . $userinfo . ')?' . $hostport; // server is required
+ $reg_name = '([' . $unreserved . '$,;:@&=+](?!gt;)|' . $escaped . ')+';
+ $authority = '(' . $server_req . '|' . $reg_name . ')';
+ $net_path = '//' . $authority . '('.$abs_path.')?';
+ $query = $uric.'*';
+ $scheme = '(ht|f)tps?';
+ $hier_part = '(' . $net_path .'|' . $abs_path . ')(\?' . $query . ')?';
+ $opaque_part = $uric_no_slash . $uric.'*';
+ $absolute_uri = $scheme . ':(' . $hier_part . '|' . $opaque_part . ')';
+ $fragment = $uric.'*';
+
+ // absolute URIs only
+ $uri_reference = $absolute_uri . '(#' . $fragment . ')?';
+ // echo '<br>'.htmlentities($local_part . '@' . $host).'<br>';
+
+ $searcharray = array(
+ "'(?i)(" . $local_part . '@' . $host . ")'i",
+ "'(?i)((?:(?!://).{3}|^.{0,2}))(www\." . $hostname
+ . '|' . $ipv4_address . ")'i",
+ "'(?i)(?<!href=\")(" . $uri_reference . ")(?!</a>)'i",
+ "'(((?i)bug)\s+#?([\dA-Z_]+)\s+((?i)comment|Kommentar)\s+#?(\d+))'",
+ "'(((?i)bug)\s+#?([\dA-Z_]+))'",
+ "'(((?i)comment|Kommentar)\s+#?(\d+))'"
+ );
+
+ $replacearray = array(
+ "<a href=\"mailto:\\1\">\\1</a>",
+ "\\1http://\\2",
+ "<a href=\"\\1\">\\1</a>",
+ "<a href=\"./?bug=\\3#c\\5\">\\1</a>",
+ "<a href=\"./?bug=\\3#details\">\\1</a>",
+ "<a href=\"#c\\3\">\\1</a>"
+ );
+
+ return preg_replace($searcharray, $replacearray, $text);
+}
+
+/**
+ * Converts HTML entities to real characters using the detected
+ * or specified character encoding.
+ *
+ * @param string $s
+ * @param int[optional] $quote_style
+ * @return string
+ */
+function htmlEntityDecode($s, $quote_style=ENT_COMPAT, $encoding=null)
+{
+ $s = (string) $s;
+
+ if (is_null($encoding))
+ {
+ $encoding = mb_detect_encoding($s);
+ if ($encoding === 'ASCII')
+ {
+ $encoding = 'ISO-8859-1';
+ }
+ }
+
+ return html_entity_decode($s, $quote_style, $encoding);
+}
+
+
+/**
+ * Converts the argument into a visible (X)HTML hyperlink if a condition
+ * applies.
+ *
+ * @author
+ * (C) 2003, 2004 Thomas Lahn &lt;selfhtml.de@PointedEars.de&gt;
+ * @param $s
+ * Content to be converted. Required.
+ * @param $sCond
+ * Condition to be true for the content to be converted.
+ * The default is <code>true</code>.
+ * @param $sURI
+ * Target URI of the hyperlink. The default is the
+ * value of $s.
+ * @param $sName
+ * Value of the <code>name</code> attribute of the
+ * <code>a</code> element. Unused if not provided
+ * or empty.
+ * @param $sTitle
+ * Value of the <code>title</code> attribute of the
+ * <code>a</code> element. Unused if not provided
+ * or empty.
+ * @param $sClass
+ * Value of the <code>class</code> attribute of the
+ * <code>a</code> element. Unused if not provided
+ * or empty.
+ * @return
+ * The converted argument if the condition applies,
+ * the unconverted argument otherwise.
+ */
+function makeLinkIf(
+ $s,
+ $sCond = true,
+ $sURI = NULL,
+ $sTarget = '',
+ $sName = '',
+ $sTitle = '',
+ $sClass = '')
+{
+ return ($sCond || $sName
+ ? '<a' . ($sCond
+ ? " href=\"" . (is_null($sURI) ? $s : $sURI)
+ . "\"".($sTarget ? " target=\"$sTarget\"" : '')
+ : ''
+ )
+ . ($sName ? " name=\"$sName\"" : '')
+ . ($sTitle ? " title=\"$sTitle\"" : '')
+ . ($sClass ? " class=\"$sClass\"" : '')
+ . '>'
+ : ''
+ )
+ . $s
+ . ($sCond || $sName ? '</a>' : '');
+}
+
+/**
+ * Returns a visible (X)HTML hyperlink that uses the mailto: URI scheme.
+ *
+ * @author (C) 2003 Thomas Lahn &lt;selfhtml.de@PointedEars.de&gt;
+ *
+ * @author (C) 2003-12-30 Thomas Lahn &lt;selfhtml.de@PointedEars.de&gt;
+ * - Corrected `$email ($name)'.
+ * - Now uses rawurlencode(...).
+ * - Added PHPdoc.
+ *
+ * @author (C) 2004-11-30 Thomas Lahn &lt;selfhtml.de@PointedEars.de&gt;
+ * - Don't rawurlencode(...) parens for comments.
+ *
+ * @param $sAddress
+ * E-mail address. The default is <selfhtml.de@PointedEars.de>.
+ * @param $sTitle
+ * Value of the <code>title</code> attribute of the
+ * <code>a</code> element. Unused if not provided
+ * or empty. The default is 'PointedEars'.
+ * @param $sToName
+ * Name to be used in the To header of the e-mail.
+ * Note that @link{rawurlencode()} is used to escape
+ * special characters automatically.
+ * The default is "Thomas 'PointedEars' Lahn".
+ *
+ * @return
+ * The converted argument if the condition applies,
+ * the unconverted argument otherwise.
+ */
+function mailto_link(
+ $sAddress = 'selfhtml.de@PointedEars.de',
+ $sTitle = 'PointedEars',
+ $sToName = "Thomas 'PointedEars' Lahn",
+ $sSubject = 'SELFbug',
+ $sImgPath = '../../media/mail.gif'
+)
+{
+//width="14" height="15" // image size detection not yet implemented
+ return ($sImgPath != ''
+ ? '<img src="' . $sImgPath . '" border="0" alt="@">&nbsp;'
+ : '')
+ . '<a href="mailto:'
+ . ($sAddress != ''
+ ? $sAddress
+ : 'selfhtml.de@PointedEars.de')
+ . ($sToName != ''
+ ? ' (' . rawurlencode($sToName) . ')'
+ : '')
+ . ($sSubject != ''
+ ? '?subject=' . rawurlencode($sSubject)
+ : '')
+ . '">' . (($sTitle != '') ? $sTitle : $sAddress) . '</a>';
+}
+
+// map bug states to numbers so that they become comparable
+function array_values_to_keys($a)
+{
+ $aNew = array();
+ foreach ($a as $key => $value)
+ {
+ $aNew[$value] = count($aNew);
+ }
+ return $aNew;
+}
+
+/**
+ * Maps an array to another array using a callback function.
+ *
+ * Unlike array_map(), the callback is called with three parameters: the value
+ * to be processed, the key of that value, and the array processed. The return
+ * value of the callback defines the value for the same key of the returned
+ * array.
+ *
+ * Because of the way the callback is called, this method supports processing
+ * only one array at a time, unlike array_map(). Unlike array_walk(), this
+ * function does not modify the original array.
+ *
+ * @param string|array $callback
+ * The callback to be used for mapping array values. If an array, the first
+ * element of the array is supposed to be the class name, and the second
+ * element the method name of a static method to be used.
+ * @param array $array
+ * The array to process
+ * @return array
+ * @see array_map()
+ * @see array_walk()
+ */
+function array_map2($callback, $array)
+{
+ $a = array();
+
+ if (is_array($callback))
+ {
+ list($class, $method) = $callback;
+ }
+
+ foreach ($array as $key => &$value)
+ {
+ if (is_array($callback))
+ {
+ $a[$key] = $class::$method($value, $key, $array);
+ }
+ else
+ {
+ $a[$key] = $callback($value, $key, $array);
+ }
+ }
+
+ return $a;
+}
+
+/**
+ * Converts a string or an array of strings to an associative
+ * bitmask array with the string(s) as key(s).
+ *
+ * Converts the argument to a bitmask array where each member's
+ * value is a power of 2, so that arbitrary member values can be
+ * added to an integer on which bitwise operations with the member
+ * value or a combination of member values are possible.
+ *
+ * @author (c) 2003 Thomas Lahn &lt;SELFbug@PointedEars.de&gt;
+ * @param $aArray
+ * String or array of strings to be converted.
+ */
+function getBitmaskArray($aArray)
+{
+ $a = array();
+
+ if (is_array($aArray))
+ {
+ for ($i = 0; $i < count($aArray); $i++)
+ {
+ $a[$aArray[$i]] = pow(2, $i);
+ }
+ }
+ else
+ $a[$aArray] = 1;
+
+ return $a;
+}
+
+/**
+ * Returns the contents of a file as if include() was used.
+ *
+ * @param string $filename Path of the file to retrieve
+ * @return string File contents
+ */
+function get_include_content($filename)
+{
+ if (is_file($filename))
+ {
+ ob_start();
+ include $filename;
+ $contents = ob_get_contents();
+ ob_end_clean();
+ return $contents;
+ }
+
+ return '';
+}
+
+/**
+ * Replaces each group of expressions in a string with the same
+ * corresponding string.
+ *
+ * @param Array[Array[string] | string, string] $map
+ * @param string $subject
+ * @return string
+ * A copy of $subject with the provided mapping applied.
+ */
+function preg_replace_group($map = array(), $subject = '')
+{
+ if ($subject)
+ {
+ for ($i = 0, $len = count($map); $i < $len; $i++)
+ {
+ $subject = preg_replace($map[$i][0], $map[$i][1], $subject);
+ }
+ }
+
+ return $subject;
+}
+
+/**
+ * Randomly encodes a string of characters.
+ *
+ * @param string $s
+ * String to be encoded
+ * @param string $format = 'sgml'
+ * Encoding format. Currently only SGML-based encoding of
+ * ASCII characters with character references is supported.
+ * @return string
+ */
+function randomEsc($s = '', $format = 'sgml')
+{
+ $f = function_exists('mt_rand') ? 'mt_rand' : 'rand';
+
+ return preg_replace_callback('/[\\x00-\\x7F]/',
+ create_function('$m', "return $f(0, 1)" . '? $m[0] : "&#" . ord($m[0]) . ";";'),
+ $s);
+}
+
+/**
+ * Reduces sequences of two or more consecutive white-space characters
+ * in an input to a single space.
+ *
+ * @param string $s
+ * @return string
+ */
+function reduceWhitespace($s)
+{
+ return preg_replace('/\s{2,}/', ' ', $s);
+}
+
+function debug($x)
+{
+ echo '<pre>';
+
+// if (is_array($x))
+// {
+// print_r($x);
+// }
+// else
+// {
+ var_dump($x);
+// }
+
+ echo '</pre>';
+}
Index: trunk/.project
===================================================================
--- trunk/.project (nonexistent)
+++ trunk/.project (revision 80)
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>PHPX</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.wst.validation.validationbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.dltk.core.scriptbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.php.core.PHPNature</nature>
+ </natures>
+</projectDescription>
/trunk/.project
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: trunk/htmlencode.old.php
===================================================================
--- trunk/htmlencode.old.php (nonexistent)
+++ trunk/htmlencode.old.php (revision 80)
@@ -0,0 +1,117 @@
+/**
+ * Converts the argument to a visible (X)HTML hyperlink
+ * where its URI target is created from the argument.
+ * Supported are e-mail addresses, domain names with
+ * optional paths, and valid URIs.
+ *
+ * @param $text
+ * Argument to be converted.
+ * @return
+ * The converted argument if it applies to a supported
+ * scheme, the unconverted argument otherwise.
+ *
+ * @author (C) 2001-04-04T02:03
+ * mark.young@vdhinc.com at http://php.net/manual/en/ref.strings.php
+ *
+ * Minor correction to my HTMLEncode function.
+ *
+ * @author 2002-08-29T09:00
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Added target="_blank"
+ * - Added support for ftp(s)-URIs: (ht|f)
+ * - Added support for search strings (?...=...&...=...), either
+ * with or without HTML entities: \?=(&\w;|&)
+ * - Removed enclosing nl2br call because of preformatted display
+ *
+ * @author 2003-12-30T14:18
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Removed target="_blank".
+ * - Added PHPdoc.
+ *
+ * @author 2004-01-12T12:45
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Added support for #fragment_identifiers in URIs.
+ * - Added support for bugs and comments.
+ *
+ * @author 2004-01-13T01:29
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Added support for bug aliases.
+ *
+ * @author 2004-01-26T20:43
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Do not convert URIs in A elements.
+ * - Do not allow URIs with "&" before search-string.
+ * - camelCased function identifier. Only classes
+ * and constructors should start with uppercase.
+ *
+ * @author 2004-01-27T14:07
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow to convert URIs preceded by "&gt;" but not followed by "&lt;/a&gt;".
+ * - Allow ";" to be part of the search string
+ *
+ * @author 2004-01-29T14:10
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Require valid domain name for "bar" on conversion of "foo@bar" and
+ * "www.bar".
+ * - Be case-insensitive except of bug aliases
+ * - Escaped "-" in character classes if not meant as range metacharacter.
+ * - Corrected year.
+ *
+ * @author 2004-02-14T17:37
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Accept only valid Internet domain names
+ * - Accept "%" within path and query part (to escape ASCII characters)
+ *
+ * @author 2004-02-27T19:21
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow unescaped ":" in URI components, since it apparently does not
+ * "conflict with the reserved purpose" (RFC 2396, section 2.2.; here:
+ * scheme component)
+ * - Allow slashes, dots and dashes in URI components
+ * - Removed invalid [...(...|...)...]
+ *
+ * @author 2004-03-01T21:48
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow IPv4 addresses
+ *
+ * @author 2004-03-08T02:20
+ * Thomas Lahn &lt;PointedEars@selfhtml.de&gt; at localhost
+ *
+ * - Allow "+" and "," in query part
+ * - Optimized character classes
+ */
+function htmlEncode($text)
+{
+ $searcharray = array(
+ "'(?i)([\-_\w\d.]+@[\-\w\d.]+\.[A-Z]{2,})'i",
+ "'(?i)((?:(?!://).{3}|^.{0,2}))(www\.[\-\w\d.]+\.[A-Z]{2,}"
+ . "|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'i",
+ "'(?i)(?<!href=\")((ht|f)tps?:\/\/([\-\w\d.]+\.[A-Z]{2,}"
+ . "|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[#\-_~\w\d\.\/%]+"
+ . "(\?([%&+-;=\w])*)?(;[=\d\w])?)(?!</a>)'i",
+ "'(((?i)bug)\s+#?([\dA-Z_]+)\s+((?i)comment|Kommentar)\s+#?(\d+))'",
+ "'(((?i)bug)\s+#?([\dA-Z_]+))'",
+ "'(((?i)comment|Kommentar)\s+#?(\d+))'"
+ );
+
+ $replacearray = array(
+ "<a href=\"mailto:\\1\">\\1</a>",
+ "\\1http://\\2",
+ "<a href=\"\\1\">\\1</a>",
+ "<a href=\"./?bug=\\3#c\\5\">\\1</a>",
+ "<a href=\"./?bug=\\3#details\">\\1</a>",
+ "<a href=\"#c\\3\">\\1</a>"
+ );
+
+ return preg_replace($searcharray, $replacearray, $text);
+}
/trunk/htmlencode.old.php
Property changes:
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: trunk/.buildpath
===================================================================
--- trunk/.buildpath (nonexistent)
+++ trunk/.buildpath (revision 80)
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<buildpath>
+ <buildpathentry kind="src" path=""/>
+ <buildpathentry kind="con" path="org.eclipse.php.core.LANGUAGE"/>
+</buildpath>
Index: trunk
===================================================================
--- trunk (revision 1)
+++ trunk (revision 80)
/trunk
Property changes:
Added: svn:ignore
## -0,0 +1,5 ##
+Angi
+
+PHPeclipse
+
+PregCal