# PLUGIN PREVIEW BY TEXTPATTERN.INFO
/// Generic abstract base class, with a few low-level utility methods.
abstract class soo_obj
{
/** Generic getter.
* Allow calls in the form $obj->property for protected properties.
*/
public function __get( $property )
{
return isset($this->$property) ? $this->$property : null;
}
/** Method overloading for generic setters.
* Allow calls in the form $obj->property($value).
* Returns $this to allow method chaining.
*/
public function __call( $request, $args )
{
if ( isset($this->$request) )
$this->$request = array_shift($args);
return $this;
}
/** Return an object's class name.
* Allows object to be used in a string context.
* @return string Class name
*/
public function __toString()
{
return get_class($this);
}
/** Return an object's properties as an associative array.
* Note: includes protected and private properties.
* For public properties only, use get_object_vars($obj) directly.
* @return array
*/
public function properties()
{
return get_object_vars($this);
}
/** Return an object's properties as an indexed array.
* @return array
*/
public function property_names()
{
return array_keys($this->properties());
}
}
/// Abstract base class for SQL queries.
abstract class soo_txp_query extends soo_obj
{
/// Database table name.
protected $table = '';
/// SQL WHERE expressions.
protected $where = array();
/// SQL ORDER BY expressions.
protected $order_by = array();
/// SQL LIMIT.
protected $limit = 0;
/// SQL OFFSET.
protected $offset = 0;
/** Constructor.
* Use $key to match a single row matching on appropriate key column
* If $key is an array, current key is the column and current value
* is the value. Otherwise column will be taken from self::key_column().
* @param table Table name
* @param key Key column value (or array) for WHERE expression
*/
public function __construct( $table, $key = null )
{
$this->table = trim($table);
if ( is_array($key) )
$this->where(key($key), current($key));
elseif ( $key )
$this->where($this->key_column($key), $key);
}
/** Add expression to WHERE clause.
* @param column Column name
* @param value Column value(s) (string, array, or soo_txp_select)
* @param operator Comparison operator
* @param join AND or OR
*/
public function where( $column, $value, $operator = '=', $join = '' )
{
$join = $this->andor($join);
if ( is_array($value) )
$value = '(' . implode(',', quote_list(doSlash($value))) . ')';
elseif ( $value instanceof soo_txp_select )
$value = '(' . $value->sql() . ')';
else
$value = "'$value'";
$this->where[] = ( $join ? "$join " : '' ) .
self::quote($column) . " $operator $value";
return $this;
}
/** Add a raw expression to WHERE clause.
* Use instead of {@link where()} for complex (e.g. nested) expressions
* @param clause WHERE Expression
* @param join AND or OR
*/
public function where_clause( $clause, $join = '' )
{
$join = $this->andor($join);
$this->where[] = ( $join ? "$join " : '' ) . $clause;
return $this;
}
/** Add an IN expression to the WHERE clause.
* @param column Column name
* @param list Items to compare against
* @param join AND or OR
* @param in true = IN, false = NOT IN
*/
public function in( $column, $list, $join = '', $in = true )
{
$in = ( $in ? '' : ' not' ) . ' in';
if ( is_string($list) )
$list = do_list($list);
return $this->where($column, $list, $in, $join);
}
/** Alias of in() with $in = false (add a NOT IN expression).
* @param column Column name
* @param list Items to compare against
* @param join AND or OR
*/
public function not_in( $column, $list, $join = '' )
{
return $this->in( $column , $list , $join , false );
}
/** Add a MySQL REGEXP expression to the WHERE clause.
* @param pattern MySQL REGEXP pattern
* @param subject Column name or string to match
* @param join AND or OR
*/
public function regexp( $pattern, $subject, $join = '' )
{
return $this->where($subject, $pattern, 'regexp', $join);
}
protected function andor( $join = 'and' )
{
$join = strtolower($join);
return count($this->where) ?
( in_list($join, 'and,or') ? $join : 'and' ) : '';
}
/** Quote with backticks.
* Only quote items consisting of alphanumerics, $, and/or _
* @param identifier Item to quote
* @return string
*/
public static function quote( $identifier )
{
return preg_match('/^[a-z_$]+$/i', $identifier) ?
"`$identifier`" : $identifier;
}
/** Add an expression to the ORDER BY array.
* Example: $query->order_by('foo ASC, bar DESC');
* @param expr Comma-separated list, or array, of expressions
* @param direction ASC or DESC
*/
public function order_by( $expr, $direction = '' )
{
if ( $expr )
{
if ( ! is_array($expr) ) $expr = do_list($expr);
foreach ( $expr as $x )
{
if ( preg_match('/(\S+)\s+(\S+)/', $x, $match) )
list( , $column, $direction) = $match;
else
$column = $x;
if ( in_array(strtolower($column), array('random', 'rand', 'rand()')) )
$column = 'rand()';
else
$direction = in_array(strtolower($direction), array('asc', 'desc')) ? $direction : '';
$this->order_by[] = $column . ( $direction ? ' ' . $direction : '');
}
}
return $this;
}
/** Alias of order_by() for a single column ASC.
* @param col Column name
*/
public function asc( $col )
{
return $this->order_by($col, 'asc');
}
/** Alias of order_by() for a single column DESC.
* @param col Column name
*/
public function desc( $col )
{
return $this->order_by($col, 'desc');
}
/** Add a FIELD() expression to the ORDER BY array.
* For preserving an arbitrary sort order, e.g. '7,5,12,1'
* Note that FIELD() is a MySQL-specific function (not standard SQL)
* @param field Column name
* @param list Comma-separated list, or array, of values in order
*/
public function order_by_field( $field, $list )
{
if ( is_string($list) ) $list = do_list($list);
if ( count($list) )
$this->order_by[] = 'field(' . self::quote($field) . ', ' .
implode(', ', quote_list(doSlash($list))) . ')';
return $this;
}
/** Add a LIMIT to the query.
* @param limit Maximum number of items to return
*/
public function limit( $limit )
{
if ( $limit = intval($limit) )
$this->limit = ' limit ' . $limit;
return $this;
}
/** Add an OFFSET to the query.
* @param offset Number of items to skip
*/
public function offset( $offset )
{
if ( $offset = intval($offset) )
$this->offset = ' offset ' . $offset;
return $this;
}
/** Assemble and return the query clauses as a string.
* @return string
*/
protected function clause_string()
{
return implode(' ', $this->where) .
( count($this->order_by) ? ' order by ' . implode(', ', $this->order_by) : '' ) .
( $this->limit ? $this->limit : '' ) . ( $this->offset ? $this->offset : '' );
}
/** Number of items the query will return.
* Runs the query with COUNT() as the select expression
* @return int|false
*/
public function count()
{
return getCount($this->table, $this->clause_string() ? $this->clause_string() : '1=1');
}
/** Return the key column name for the current table.
* Some Txp tables have multiple indexes.
* If $key_value is provided, column of the matching type will be returned.
* Otherwise the numeric index will be returned in preference to the string index.
* @param key_value
* @return string
*/
public function key_column( $key_value = null )
{
$numeric_index = array(
'textpattern' => 'ID',
'txp_category' => 'id',
'txp_discuss' => 'discussid',
'txp_file' => 'id',
'txp_image' => 'id',
'txp_lang' => 'id',
'txp_link' => 'id',
'txp_log' => 'id',
'txp_users' => 'user_id',
);
$string_index = array(
'textpattern' => 'Title',
'txp_category' => 'name',
'txp_css' => 'name',
'txp_discuss_ipban' => 'ip',
'txp_discuss_nonce' => 'nonce',
'txp_file' => 'filename',
'txp_form' => 'name',
'txp_image' => 'name',
'txp_lang' => 'lang',
'txp_page' => 'name',
'txp_plugin' => 'name',
'txp_prefs' => 'name',
'txp_section' => 'name',
'txp_users' => 'name',
);
if ( isset($numeric_index[$this->table]) )
$nx = $numeric_index[$this->table];
if ( isset($string_index[$this->table]) )
$sx = $string_index[$this->table];
if ( is_numeric($key_value) )
return isset($nx) ? $nx : null;
if ( is_string($key_value) )
return isset($sx) ? $sx : null;
return isset($nx) ? $nx : ( isset($sx) ? $sx : null );
}
}
/// Class for SELECT queries.
class soo_txp_select extends soo_txp_query
{
/// SQL SELECT expressions.
protected $select = array();
/// Whether to add DISTINCT
protected $distinct = false;
/** Constructor.
* @param table Table name
* @param key Optional key for selecting a single record
* @param select item(s) to select
*/
public function __construct( $table, $key = null, $select = null )
{
parent::__construct($table, $key);
if ( $select ) $this->select($select);
}
/** Add items to the SELECT array.
* @param list comma-separated list, or array, of items to select
*/
public function select( $list = '*' )
{
if ( is_string($list) ) $list = do_list($list);
foreach ( $list as $col ) $this->select[] = parent::quote($col);
return $this;
}
/** Add the DISTINCT keyword to the query
* @return $this to allow method chaining
*/
public function distinct( )
{
$this->distinct = true;
return $this;
}
protected function init_query()
{
if ( ! count($this->select) ) $this->select();
if ( ! count($this->where) ) $this->where[] = '1 = 1';
}
/** Return a single record, or empty array if no matching records.
* @return array
*/
public function row()
{
$this->init_query();
return safe_row(implode(',', $this->select), $this->table,
$this->clause_string());
}
/** Return all records, or empty array if no matching records.
* @return array
*/
public function rows()
{
$this->init_query();
return safe_rows( ( $this->distinct ? 'distinct ' : '') .
implode(',', $this->select), $this->table, $this->clause_string());
}
/** Return the query as a string.
* @return string
*/
public function sql()
{
$this->init_query();
return 'select ' . implode(',', $this->select) . ' from ' . safe_pfx($this->table) . ' where ' . $this->clause_string();
}
/** Return result of a SELECT COUNT(*) query
* @int
*/
public function count()
{
$select = $this->select;
$this->select = array('count(*)');
$r = safe_query($this->sql());
$this->select = $select;
if ( $r && $row = mysqli_fetch_row($r) )
return $row[0];
}
}
/// Class for SELECT ... LEFT JOIN queries.
/// Currently very incomplete; needs to override most parent methods
/// to specify which table each expression refers to.
class soo_txp_left_join extends soo_txp_select
{
/// Join table name
protected $left_join;
/// ON expression for join
protected $join_on;
/// Left table alias
const t1 = 't1';
/// Join table alias
const t2 = 't2';
/** Constructor.
* @param table Left table
* @param left_join Join table
* @param col1 Key column name for left table
* @param col2 Key column name for join table
*/
public function __construct ( $table, $left_join, $col1, $col2 )
{
parent::__construct($table);
$this->left_join = $left_join;
$this->join_on = self::t1 . '.' . self::quote($col1) . ' = ' . self::t2 . '.' . self::quote($col2);
}
/** Like parent::quote, optionally prepending table name/alias.
* @param identifier Column name or alias
* @param prefix Table name or alias
*/
public static function quote( $identifier, $prefix = '' )
{
return ( $prefix ? $prefix . '.' : '' ) . parent::quote($identifier);
}
/** Add items to the SELECT array from the left table.
* @param list comma-separated list, or array, of items to select
*/
public function select( $list = '*' )
{
return self::select_from($list, self::t1);
}
/** Add items to the SELECT array from the join table.
* @param list comma-separated list, or array, of items to select
*/
public function select_join( $list = '*' )
{
return self::select_from($list, self::t2);
}
private function select_from( $list, $table )
{
if ( is_string($list) ) $list = do_list($list);
foreach ( $list as $col ) $this->select[] = self::quote($col, $table);
return $this;
}
/** Add expression to WHERE clause, referring to the left table.
* @param column Column name
* @param value Column value
* @param operator Comparison operator
* @param join AND or OR
*/
public function where( $column, $value, $operator = '=', $join = '' )
{
return parent::where(self::quote($column, self::t1), $value, $operator, $join);
}
/** Add expression to WHERE clause, referring to the join table.
* @param column Column name
* @param value Column value
* @param operator Comparison operator
* @param join AND or OR
*/
public function where_join( $column, $value, $operator = '=', $join = '' )
{
return parent::where(self::quote($column, self::t2), $value, $operator, $join);
}
/** Add an IS NULL expression, for selecting only items not in the join table
* @param column Key column in join table
*/
public function where_join_null( $column )
{
$join = parent::andor('');
$this->where[] = ( $join ? $join . ' ' : '' ) . self::quote($column, self::t2) . ' is null';
return $this;
}
/** Add a column to the ORDER BY array.
* @param col Column name from left table
* @param direction ASC or DESC
*/
public function order_by( $cols, $direction = '' )
{
$direction = in_array(strtolower($direction), array('asc', 'desc')) ? $direction : '';
if ( is_string($cols) ) $cols = do_list($cols);
foreach ( $cols as $col )
{
if ( $col == 'random' or $col == 'rand' or $col == 'rand()' )
{
$col = 'rand()';
$direction = '';
}
$this->order_by[] = self::quote($col, self::t1) . ( $direction ? ' ' . $direction : '');
}
return $this;
}
/** Override parent function; return a single record.
* @return array
*/
public function row()
{
return getRow($this->sql());
}
/** Override parent function; return all records
* @return array
*/
public function rows()
{
return getRows($this->sql());
}
/** Assemble query.
* @return string
*/
public function sql()
{
parent::init_query();
return 'select ' . implode(',', $this->select) . ' from ' . self::quote(safe_pfx($this->table)) . ' as ' . self::t1 . ' left join ' . self::quote(safe_pfx($this->left_join)) . ' as ' . self::t2 . ' on ' . $this->join_on . ' where ' . $this->clause_string();
}
}
/// Class for INSERT and UPDATE queries.
class soo_txp_upsert extends soo_txp_query
{
// For use with VALUES() syntax
/// Columns to be explicitly set.
public $columns = array();
/// VALUES() values.
public $values = array();
/// VALUES() clause
protected $values_clause = '';
// For use with SET col_name=value syntax
/// SET columns and values.
public $set = array();
/// SET clause.
protected $set_clause = '';
/** Constructor.
* Use $col to update a single row matching on appropriate key column
* (usually `name` or `id`)
* @param init Table name, soo_txp_rowset, or soo_txp_row
* @param col Key column value for WHERE expression
*/
public function __construct( $init, $col = null )
{
if ( is_scalar($init) )
parent::__construct($init, $col);
elseif ( $init instanceof soo_txp_rowset )
{
$this->table = $init->table;
if ( $col )
$this->columns = is_array($col) ? $col : do_list($col);
else
$this->columns = array_keys(current($init->data));
foreach ( $init->rows as $r )
$this->values[] = $r->data;
}
elseif ( $init instanceof soo_txp_row )
{
$this->table = $init->table;
if ( $col )
$this->columns = is_array($col) ? $col : do_list($col);
else
$this->columns = array_keys($init->data);
$this->values[] = $init->data;
}
}
/** Add a column:value pair to the $set array.
* @param column Column name
* @param value Column value
*/
public function set( $column, $value )
{
$this->set[$column] = $value;
return $this;
}
private function init_query()
{
if ( count($this->set) )
{
foreach ( $this->set as $col => $val )
{
$val = is_numeric($val) ? $val : "'$val'";
$set_pairs[] = "$col = $val";
}
$this->set_clause = implode(',', $set_pairs);
}
elseif ( count($this->values) )
{
if ( count($this->columns) )
$this->values_clause = '(`' . implode('`,`', $this->columns) . '`) ';
$this->values_clause .= ' values ';
foreach ( $this->values as $vs )
{
$this->values_clause .= '(';
foreach ( $vs as $v )
$this->values_clause .= ( is_numeric($v) ? $v : "'$v'" ) . ',';
$this->values_clause = rtrim($this->values_clause, ',') . '),';
}
$this->values_clause = rtrim($this->values_clause, ',') . ';';
}
}
/** Run the query.
* Runs an UPDATE query if $where is set, otherwise INSERT
* @return bool success or failure
*/
public function upsert()
{
$this->init_query();
if ( count($this->where) )
return safe_upsert($this->table, $this->set_clause, $this->clause_string());
if ( $this->set_clause )
return safe_insert($this->table, $this->set_clause);
if ( $this->values_clause )
return safe_query('insert into ' . safe_pfx($this->table) . $this->values_clause);
}
}
/// Class for DELETE queries.
class soo_txp_delete extends soo_txp_query
{
/** Execute the DELETE query.
* @return bool Query success or failure
*/
public function delete()
{
if ( count($this->where) )
return safe_delete($this->table, $this->clause_string());
}
}
/// Class for data results sets.
class soo_txp_rowset extends soo_obj
{
/// Database table name.
protected $table = '';
/// Array of soo_txp_row objects.
public $rows = array();
/** Constructor.
* $init can be a soo_txp_select object, mysql result resource,
* or an array of records.
* If $index is provided or if $init is a soo_txp_select object,
* the $rows array will be indexed by key column values.
* @param init Data array or query object to initialize rowset
* @param table Txp table name
*/
public function __construct( $init = array(), $table = '', $index = null )
{
if ( $init instanceof soo_txp_select )
{
$table = $init->table;
$index = $init->key_column();
$init = $init->rows();
}
if ( is_resource($init) and mysql_num_rows($init) )
{
while ( $r = mysql_fetch_assoc($init) )
$data[] = $r;
mysql_free_result($init);
$init = $data;
}
$this->table = $table;
if ( is_array($init) and count($init) )
{
foreach ( $init as $r )
if ( $index )
$this->add_row($r, $table, $r[$index]);
else
$this->add_row($r, $table);
}
}
/** Generic getter, overriding parent method.
* If $property is not a property name, look for row object
* matching this index value
* @param property Property name, or rowset index
*/
public function __get( $property )
{
if ( property_exists($this, $property) )
return $this->$property;
if ( array_key_exists($property, $this->rows) )
return $this->rows[$property];
}
/** Return an array of all values for a particular column (field).
* If $key is set, make it an associative array, using the value
* of the key column as the array index
* @param field Column (field) name
* @param key Key column name
*/
public function field_vals( $field, $key = null )
{
foreach ( $this->rows as $r )
if ( ! is_null($key) )
$out[$r->$key] = $r->$field;
else
$out[] = $r->$field;
return isset($out) ? $out : array();
}
/** Add a soo_txp_row object to $rows.
* @param data soo_txp_row object or key value
* @param table Txp table name
* @param i index value for new row in $rows array
*/
public function add_row( $data, $table = null, $i = null )
{
$table = is_null($table) ? $this->table : $table;
$r = $data instanceof soo_txp_row ?
$data : ( $table == 'txp_image' ?
new soo_txp_img($data) : new soo_txp_row($data, $table) );
if ( is_null($i) )
$this->rows[] = $r;
else
$this->rows[$i] = $r;
return $this;
}
/** Split off a subset of rows as a new soo_txp_rowset object
* @param key array key for finding rows for the new set
* @param value key column value to match for rows for the new set
* @param index array index for new rowset rows
* @return soo_txp_rowset
*/
public function subset( $key, $value, $index = null )
{
$out = new self;
foreach ( $this->rows as $row )
if ( $row->$key == $value )
$out->add_row($row, null, is_null($index) ? null : $row->$index);
return $out;
}
}
/// Class for Joe Celko nested sets, aka modified preorder tree
class soo_nested_set extends soo_txp_rowset
{
/** Constructor.
* $init can be a soo_txp_rowset object,
* otherwise see parent::__construct()
* @param init Data array or query object to initialize rowset
* @param table Txp table name
*/
public function __construct( $init = array(), $table = '', $index = null )
{
if ( $init instanceof soo_txp_rowset )
{
$this->table = $init->table;
$this->rows = $init->rows;
}
else
parent::__construct($init, $table);
}
/** Return all rows as a nested array of row objects.
* Each array item is either a soo_txp_row object,
* or an array of such. If an array, it is the children of the
* immediately preceding item.
* This is a recursive function.
* @param rows Internal use only.
* @param rgt Internal use only.
*/
public function as_object_array( &$rows = null, $rgt = null )
{
if ( is_null($rows) )
{
$rows = $this->rows;
$root = current($rows);
$rgt = $root->rgt;
}
while ( $out[] = $node = array_shift($rows) and $node->rgt <= $rgt )
if ( $node->rgt > $node->lft + 1 )
$out[] = $this->as_object_array($rows, $node->rgt);
if ( $node and $node->rgt > $rgt )
array_unshift($rows, array_pop($out));
if ( is_null($out[count($out) - 1]) )
array_pop($out);
return $out;
}
/** Return all rows as a nested array of values.
* Each array item is either a node, as $index_column => $value_column,
* or an array of such. If an array, it is the children of the
* immediately preceding item, and has the key 'x_c' where 'x' is
* the parent node's index.
* This is a recursive function.
* @param index_column Column for node index value
* @param index_column Column for node value
* @param rows Internal use only.
* @param rgt Internal use only.
*/
public function as_array( $index_column, $value_column, &$rows = null, $rgt = null )
{
if ( is_null($rows) )
{
$rows = $this->rows;
$root = current($rows);
$rgt = $root->rgt;
}
while ( $node = array_shift($rows) and $node->rgt <= $rgt )
{
$out[$node->$index_column] = $node->$value_column;
if ( $node->rgt > $node->lft + 1 )
$out[$node->$index_column . '_c'] = $this->as_array($index_column, $value_column, $rows, $node->rgt);
}
if ( $node and $node->rgt > $rgt )
array_unshift($rows, $node);
return $out;
}
/** Split off a subtree of rows as a new soo_nested_set object
* @param root id of subtree root node
* @return soo_txp_rowset
*/
public function subtree( $root, $index = null )
{
$out = new self;
$root = $this->rows[$root];
foreach ( $this->rows as $row )
if ( $row->lft >= $root->lft and $row->rgt <= $root->rgt )
$out->add_row($row, null, is_null($index) ? null : $row->$index);
return $out;
}
}
/// Class for single data records.
class soo_txp_row extends soo_obj
{
/// Database table name.
protected $table = '';
/// Database record.
protected $data = array();
/** Constructor.
* @param init Key value, soo_txp_select object, or data array
* @param table Txp table name
*/
public function __construct( $init = array(), $table = '' )
{
if ( is_scalar($init) and $table )
$init = new soo_txp_select($table, $init);
if ( $init instanceof soo_txp_select )
{
$table = $init->table;
$init = $init->row();
}
if ( is_array($init) )
foreach ( $init as $k => $v )
$this->data[$k] = $v;
$this->table = $table;
}
/** Generic getter, overriding parent method.
* Look for $property in the $data array first
* @param property Column or property name
* @return mixed Data field or object property
*/
public function __get( $property )
{
return isset($this->data[$property]) ? $this->data[$property]
: parent::__get($property);
}
/// Override parent method, to keep $data protected.
public function data( )
{
return;
}
/// @return array Database record (column:value array)
public function properties( )
{
return $this->data;
}
}
/// Class for Txp image records.
class soo_txp_img extends soo_txp_row
{
/// URL of full-size image.
protected $full_url = '';
/// URL of thumbnail image.
protected $thumb_url = '';
/** Constructor.
* @param init Txp image id
*/
public function __construct( $init )
{
global $img_dir;
parent::__construct($init, 'txp_image');
$this->full_url = hu . $img_dir . '/' . $this->id . $this->ext;
if ( $this->thumbnail )
$this->thumb_url = hu . $img_dir . '/' . $this->id . 't' . $this->ext;
}
}
/// Abstact base class for (X)HTML elements.
abstract class soo_html extends soo_obj
{
// HTML element class. Instantiation takes a required 'name' argument and an
// optional 'atts' array: items with keys matching HTML attributes
// will be transferred to the new object.
//
// See the soo_html_img class for an example of how to extend this class.
/// @name Inherent properties
//@{
/// (X)HTML element name
protected $element_name = '';
/// container (false) or empty element (true)
protected $is_empty = 0;
/// Element content array (strings or soo_html objects)
protected $contents = array();
//@}
/// @name Common (X)HTML attributes
//@{
protected $class = '';
protected $dir = '';
protected $id = '';
protected $lang = '';
protected $onclick = '';
protected $ondblclick = '';
protected $onkeydown = '';
protected $onkeypress = '';
protected $onkeyup = '';
protected $onmousedown = '';
protected $onmousemove = '';
protected $onmouseout = '';
protected $onmouseover = '';
protected $onmouseup = '';
protected $style = '';
protected $title = '';
//@}
/** Constructor.
* @param element_name (X)HTML element name
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct($element_name, $atts, $content = null, $is_empty = 0)
{
$this->element_name($element_name)->is_empty($is_empty);
if ( empty($atts) )
$atts = array();
foreach ( $this as $property => $value )
if ( in_array($property, array_keys($atts)) )
$this->$property($atts[$property]);
if ( $content )
$this->contents($content);
}
/** Validate and set id attribute.
* Important: (X)HTML attribute, NOT a database ID!
* @param id (must begin with a letter)
*/
public function id($id)
{
if ( $id and !preg_match('/^[a-z]/', strtolower(trim($id))) )
{
$this->id = 'invalid_HTML_ID_value_from_Soo_Txp_Obj';
return false;
}
$this->id = $id;
return $this;
}
/** Add string|object|array to $contents array.
* @param content
*/
public function contents($content)
{
if ( ! $this->is_empty )
{
$content = is_array($content) ? $content : array($content);
foreach ( $content as $i => $item )
if ( is_null($item) )
unset($content[$i]);
$this->contents = array_merge($this->contents, $content);
}
return $this;
}
/** Return an attribute:value array of all (X)HTML attributes.
* Hard-coded list of excluded properties is rather lame,
* but I haven't thought of anything better yet.
* @return array
*/
private function html_attributes()
{
return array_diff_key($this->properties(), array_flip(array('element_name', 'is_empty', 'contents')));
}
/** Create (X)HTML tag(s) string for this element.
* Recursively tags contained elements
* @return string
*/
public function tag()
{
$out = '<' . $this->element_name;
foreach ( $this->html_attributes() as $property => $value )
if ( $value or $property == 'alt' )
$out .= " $property=\"$value\"";
if ( $this->is_empty )
return $out . ' />';
$out .= '>' . $this->newline();
foreach ( $this->contents as $item )
if ( $item instanceof soo_html )
$out .= $item->tag();
else
$out .= $item;
return $out . $this->newline() . "</$this->element_name>" . $this->newline();
}
/** Convert $this->$property with htmlspecialchars().
* @param property Attribute name
* @return string (X)HTML-escaped attribute value
*/
protected function html_escape( $property )
{
$this->$property = htmlspecialchars($this->$property);
return $this;
}
private function newline()
{
return ( ! $this->is_empty and count($this->contents) > 1 ) ? n : '';
}
}
/// Class for (X)HTML anchor elements.
class soo_html_anchor extends soo_html
{
/// @name (X)HTML attributes
//@{
protected $href = '';
protected $name = '';
protected $rel = '';
protected $rev = '';
protected $type = '';
protected $hreflang = '';
protected $charset = '';
protected $accesskey = '';
protected $tabindex = '';
protected $shape = '';
protected $coords = '';
protected $onfocus = '';
protected $onblur = '';
//@}
/** @param atts URI string (href value) or attribute array.
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
if ( ! is_array($atts) )
$atts = array('href' => $atts);
parent::__construct( 'a', $atts, $content );
}
}
/// Class for (X)HTML br elements.
class soo_html_br extends soo_html
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
*/
public function __construct ( $atts = array() )
{
parent::__construct('br', $atts, null, true);
}
}
/// Class for (X)HTML form elements
class soo_html_form extends soo_html
{
/// @name (X)HTML attributes
//@{
protected $action = '';
protected $method = '';
protected $enctype = '';
protected $accept_charset = '';
protected $onsubmit = '';
protected $onreset = '';
//@}
/** Constructor.
* @param init Form action or attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $init = array(), $content = '' )
{
$atts = is_string($init) ? array('action' => $init) : $init;
if ( ! isset($atts['method']) )
$atts['method'] = 'post';
if ( is_array($atts['action']) )
{
foreach ( $atts['action'] as $k => $v )
$atts['action'][$k] = "$k=$v";
$atts['action'] = '?' . implode(a, $atts['action']);
}
parent::__construct('form', $atts, $content );
}
}
/// Class for (X)HTML label elements
class soo_html_label extends soo_html
{
/// @name (X)HTML attributes
//@{
protected $for = '';
protected $onfocus = '';
protected $onblur = '';
//@}
/** Constructor.
* @param init 'for' attribute or array of name=>value pairs
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $init = array(), $content = '' )
{
if ( is_string($init) )
$init = array('for' => $init);
parent::__construct('label', $init, $content);
}
}
/// Abstract base class for (X)HTML form controls
abstract class soo_html_form_control extends soo_html
{
/// @name (X)HTML attributes
//@{
protected $name = '';
protected $disabled = '';
protected $tabindex = '';
protected $onfocus = '';
protected $onblur = '';
//@}
}
/// Class for (X)HTML input elements
class soo_html_input extends soo_html_form_control
{
/// @name (X)HTML attributes
//@{
protected $type = '';
protected $value = '';
protected $checked = '';
protected $size = '';
protected $maxlength = '';
protected $src = '';
protected $alt = '';
protected $usemap = '';
protected $readonly = '';
protected $accept = '';
protected $onselect = '';
protected $onchange = '';
//@}
/** Constructor.
* @param type Input type (text|checkbox|radio etc.)
* @param atts Attributes (array of name=>value pairs)
*/
public function __construct ( $type = 'text', $atts = array() )
{
$this->type($type);
parent::__construct('input', $atts, null, true);
}
}
/// Class for (X)HTML select elements
class soo_html_select extends soo_html_form_control
{
/// @name (X)HTML attributes
//@{
protected $multiple = '';
protected $size = '';
protected $onchange = '';
//@}
/** Constructor.
* If $content is an array, each item will be added as
* a soo_html_option element (assumes value=>text array)
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (soo_html_option objects)
*/
public function __construct ( $atts = array(), $content = array() )
{
parent::__construct('select', $atts);
if ( ! is_array($content) ) $content = array($content);
foreach ( $content as $i => $item )
{
if ( $item instanceof soo_html_option )
$this->contents($item);
else
$this->contents(new soo_html_option(array('value' => $i), $item));
}
}
}
/// Class for (X)HTML option elements
class soo_html_option extends soo_html_form_control
{
/// @name (X)HTML attributes
//@{
protected $value = '';
protected $selected = '';
protected $label = '';
//@}
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (displayed option text)
*/
public function __construct ( $atts = array(), $content = array() )
{
parent::__construct('option', $atts, $content);
}
}
/// Class for (X)HTML textarea elements
class soo_html_textarea extends soo_html_form_control
{
/// @name (X)HTML attributes
//@{
protected $rows = '';
protected $cols = '';
protected $readonly = '';
protected $onselect = '';
protected $onchange = '';
//@}
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct('textarea', $atts, $content);
}
}
/// Class for (X)HTML img elements
class soo_html_img extends soo_html
{
/// @name (X)HTML attributes
//@{
protected $alt = '';
protected $src = '';
protected $width = '';
protected $height = '';
//@}
/** Constructor.
* @param init soo_txp_img object, attribute array, or src value
* @param thumbnail Thumbnail or full image?
* @param escape HTML-escape title and alt attributes?
*/
public function __construct ( $init = array(), $thumbnail = false, $escape = true )
{
if ( $init instanceof soo_txp_img )
{
$src = $thumbnail ? $init->thumb_url : $init->full_url;
$init = $init->properties();
if ( $thumbnail )
{
if ( ! empty($init['thumb_h']) )
{ // pre Txp 4.2 compatibility
$init['h'] = $init['thumb_h'];
$init['w'] = $init['thumb_w'];
}
else $init['h'] = ( $init['w'] = 0 );
}
$init['height'] = $init['h'];
$init['width'] = $init['w'];
$init['title'] = $init['caption'];
$init['src'] = $src;
unset($init['id']); // don't want database id as HTML id!
}
elseif ( ! is_array($init) )
$init['src'] = $init;
parent::__construct('img', $init, null, true);
if ( $escape )
$this->html_escape('title')->html_escape('alt');
}
}
/// Class for (X)HTML p elements
class soo_html_p extends soo_html
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct('p', $atts, $content);
}
}
/// Class for (X)HTML table elements
class soo_html_table extends soo_html
{
/// @name (X)HTML attributes
//@{
protected $summary = '';
protected $width = '';
protected $border = '';
protected $frame = '';
protected $rules = '';
protected $cellspacing = '';
protected $cellpadding = '';
//@}
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
* @see contents() for $content options
*/
public function __construct ( $atts = array(), $content = null )
{
$this->contents($content);
parent::__construct( 'table', $atts );
}
/** Add string|object|array to $contents array.
* If this is a soo_html_table_component object, or an array of such,
* it will be added directly. Otherwise it is assumed to be a single
* item or 2-dimensional array of such, and will be formed into a grid
* of table cells/rows.
* @param content
*/
public function contents($content)
{
if ( is_null($content) ) return $this;
$content = is_array($content) ? $content : array($content);
foreach ( $content as $item )
{
if ( is_object($item) and ( $item instanceof soo_html_table_component or $item instanceof soo_html_caption) )
$this->contents[] = $item;
else
{
$item = is_array($item) ? $item : array($item);
foreach ( $item as $i => $cell )
if ( ! $cell instanceof soo_html_table_component )
$item[$i] = new soo_html_td(array(), $cell);
$this->contents[] = new soo_html_tr(array(), $item);
}
}
return $this;
}
}
/// Class for (X)HTML caption elements
class soo_html_caption extends soo_html_table_component
{
public function __construct ( $atts = array(), $content )
{
parent::__construct( 'caption', $atts, $content );
}
}
/// Abstract base class for (X)HTML table components
abstract class soo_html_table_component extends soo_html
{
/// @name (X)HTML attributes
//@{
protected $align = '';
protected $char = '';
protected $charoff = '';
protected $valign = '';
//@}
/** Constructor.
* @param component (X)HTML element name
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $component, $atts = array(), $content = '' )
{
parent::__construct( $component, $atts, $content );
}
}
/// Class for (X)HTML thead elements
class soo_html_thead extends soo_html_table_component
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct( 'thead', $atts, $content );
}
}
/// Class for (X)HTML tbody elements
class soo_html_tbody extends soo_html_table_component
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct( 'tbody', $atts, $content );
}
}
/// Class for (X)HTML tfoot elements
class soo_html_tfoot extends soo_html_table_component
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct( 'tfoot', $atts, $content );
}
}
/// Class for (X)HTML tr elements
class soo_html_tr extends soo_html_table_component
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct( 'tr', $atts, $content );
}
}
/// Abstract base class for (X)HTML table cells
abstract class soo_html_table_cell extends soo_html_table_component
{
/// @name (X)HTML attributes
//@{
protected $rowspan = '';
protected $colspan = '';
protected $headers = '';
protected $abbr = '';
protected $scope = '';
protected $axis = '';
//@}
/** Constructor.
* @param cell_type Element name (td, th)
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $cell_type, $atts = array(), $content = '' )
{
parent::__construct( $cell_type, $atts, $content );
}
}
/// Class for (X)HTML th elements
class soo_html_th extends soo_html_table_cell
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct( 'th', $atts, $content );
}
}
/// Class for (X)HTML td elements
class soo_html_td extends soo_html_table_cell
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct( 'td', $atts, $content );
}
}
/// Base class for (X)HTML ol and ul elements
abstract class soo_html_list extends soo_html
{
/** Constructor.
* If $content is an array, each item that is not an array will be added
* as a soo_html_li object; each item that is an array will be added
* to the previous soo_html_li object as a new list of the same class
* (hence this is a recursive function).
* Any other content will be added as a soo_html_li object.
* @param element_name Element namd (ol or ul)
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $element_name, $atts, $content, $class )
{
if ( ! is_array($content) )
$content = array($content);
$prev = null;
foreach ( $content as $i => &$item )
{
if ( is_array($item) )
{
if ( ! is_null($prev) and $content[$prev] instanceof soo_html_li )
{
$content[$prev]->contents(new $class($atts, $item));
unset($content[$i]);
}
else foreach ( $item as &$li )
$li = new soo_html_li(array(), $li);
}
elseif ( ! $item instanceof soo_html_li )
$item = new soo_html_li(array(), $item);
$prev = $i;
}
parent::__construct($element_name, $atts, $content);
}
}
/// Class for (X)HTML ol elements
class soo_html_ol extends soo_html_list
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct('ol', $atts, $content, __CLASS__);
}
}
/// Class for (X)HTML ul elements
class soo_html_ul extends soo_html_list
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct('ul', $atts, $content, __CLASS__);
}
}
/// Class for (X)HTML li elements
class soo_html_li extends soo_html
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct('li', $atts, $content);
}
}
/// Class for (X)HTML span elements
class soo_html_span extends soo_html
{
/** Constructor.
* @param atts Attributes (array of name=>value pairs)
* @param content Element content (string, soo_html object, or array thereof)
*/
public function __construct ( $atts = array(), $content = '' )
{
parent::__construct('span', $atts, $content);
}
}
/////////////////////// MLP Pack compatibility //////////////////////////
// MLP Pack manipulates $_SERVER['REQUEST_URI'], so grab it first
global $plugin_callback;
if( is_array($plugin_callback)
and $plugin_callback[0]['function'] == '_l10n_pretext' )
array_unshift($plugin_callback, array(
'function' => 'soo_uri_mlp',
'event' => 'pretext',
'step' => '',
'pre' => 0 )
);
function soo_uri_mlp()
{
global $soo_request_uri;
$soo_request_uri = $_SERVER['REQUEST_URI'];
}
/////////////////////// end MLP Pack compatibility //////////////////////
/// Class for URI query string manipulation
class soo_uri extends soo_obj
{
/// Full URI
protected $full;
/// $_SERVER['REQUEST_URI'] value
protected $request_uri;
/// $_SERVER['QUERY_STRING'] value
protected $query_string;
/// URI query parameters
protected $query_params;
/** Constructor.
* Extract REQUEST_URI and QUERY_STRING from $_SERVER,
* and parse into query params and full URI.
*/
public function __construct ( )
{
global $soo_request_uri; // MLP Pack compatibility
$this->request_uri = $soo_request_uri ? $soo_request_uri :
$_SERVER['REQUEST_URI'];
$this->query_string = $_SERVER['QUERY_STRING'];
$this->full = preg_replace('/\/$/', '', hu) . $this->request_uri();
parse_str($this->query_string, $this->query_params);
}
/// Override parent method to prevent direct property manipulation
public function __call( $request, $args )
{
return false;
}
/** Add, remove, or update a query parameter
* Then run update_from_params() to update $query_string and
* $request_uri (and the corresponding $_SERVER values) accordingly
* @param name Parameter name
* @param value Parameter value
*/
public function set_query_param ( $name, $value = null )
{
if ( is_null($value) )
unset($this->query_params[$name]);
else
$this->query_params[$name] = $value;
$this->update_from_params();
return $this;
}
/** Rebuild $query_string and $request_uri (and the corresponding
* $_SERVER values) based on the current $params array
*/
private function update_from_params ( )
{
// htmlencode ampersands now, because some servers will anyway
$this->query_string = http_build_query($this->query_params, '', '&');
$this->request_uri = self::strip_query($this->request_uri) .
( $this->query_string ? '?' . $this->query_string : '' );
$this->full = preg_replace('/\/$/', '', hu) . $this->request_uri();
// then htmldecode before updating the $_SERVER array
$_SERVER['QUERY_STRING'] = html_entity_decode($this->query_string);
$_SERVER['REQUEST_URI'] = html_entity_decode($this->request_uri);
}
/** Remove the query string from a URI
* @return string
*/
public function strip_query ( $uri )
{
return preg_replace ('/(.+)\?.+/', '$1', $uri);
}
/** Return the $request_uri after stripping any subdir
* (for Txp subdir installations)
* @return string
*/
private function request_uri ( )
{
if ( preg_match('&://[^/]+(/.+)/$&', hu, $match) )
{
$sub_dir = $match[1];
return substr($this->request_uri, strlen($sub_dir));
}
return $this->request_uri;
}
}
/// Class for static utility methods
class soo_util
{
/** Build a Txp tag string.
* @param func Txp tag name (e.g. 'article_custom')
* @param atts Tag attributes
* @param thing Tag contents
* @return string Txp tag
*/
public static function txp_tag ( $func, $atts = array(), $thing = null )
{
$a = '';
foreach ( $atts as $k => $v )
$a .= " $k=\"$v\"";
return "<txp:$func$a" . ( is_null($thing) ? ' />' : ">$thing</txp:$func>" );
}
/** Return a Txp tag string, if it's still the first parse() pass.
* Allows placing a tag with dependencies before its associated controller,
* deferring parsing to the second parse() pass.
* E.g. placing a pagination tag before its associated article tag.
* @param func Txp tag name (e.g. 'article_custom')
* @param atts Tag attributes
* @param thing Tag contents
* @return string Txp tag
*/
public static function secondpass ( $func, $atts = array(), $thing = null )
{
global $pretext;
if ( $pretext['secondpass'] ) return; // you only live twice
return self::txp_tag($func, $atts, $thing);
}
}