DIR : /home/kozerus/public_html/dgs/include/std_classes.php
/home/kozerus/public_html/dgs/include
<?php
/*
Dragon Go Server
Copyright (C) 2001- Erik Ouchterlony, Rod Ival, Jens-Uwe Gaspar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Author: Jens-Uwe Gaspar */
// NOTE: don't use translation-texts in this file
// used as valid query with an empty result, since mysql4.1 use -> 'SELECT 1 FROM DUAL'
//define('EMPTY_SQL_QUERY', 'SELECT 1 FROM Forums WHERE 1=0');
define('EMPTY_SQL_QUERY', 'SELECT 1 FROM DUAL');
/*!
* \class RequestParameters
*
* \brief Class to store request-parameters, which can be attached to Table- or Form-class.
* This class provides two interface-methods:
* \see get_hiddens()
* \see get_url_parts()
* \see use_hidden()
*
* see also SearchFilter->get_req_params(..)
*/
class RequestParameters
{
/*! \brief array holding pairs of data: ( key => val ), val can be an array too representing multi-values. */
private $values;
/*! \brief false, if hidden-vars should not be exported (default: true). */
private $use_hidden;
/*!
* \brief Constructs RequestParameters( [array( key => val)] )
* \param $arr_src Copies values from optional passed source-arry.
*/
public function __construct( array $arr_src = null, $use_hidden = true )
{
$this->values = array();
if ( is_array($arr_src) )
{
foreach ( $arr_src as $key => $val )
$this->values[$key] = $val;
}
$this->use_hidden = $use_hidden;
}
/*! \brief Sets var to determine if hiddens should be exported or not (default: true). */
public function use_hidden( $use_hidden=true )
{
$this->use_hidden = (bool)$use_hidden;
}
/*!
* \brief Adds entry.
* signature: add_entry( string key, string|array value );
*/
public function add_entry( $key, $value )
{
$this->values[$key] = $value;
}
/*!
* \brief Returns a string containing hidden-form-elements containing all the data
* and storing all key-value-pairs within $hiddens-referenced-array.
* \param $hiddens may be overwritten (if not set)
* signature: interface string hidden_str = get_hiddens( arr-ref hiddens );
* note: used as interface for Form- or Table-class
* note: returning elements don't handle multi-values correctly (if val is array), though not needed IMHO
*/
public function get_hiddens( array &$hiddens = null )
{
if ( !$this->use_hidden)
return ''; // don't export hiddens
if ( is_array($hiddens) )
$hiddens = array_merge( $hiddens, $this->values);
else
$hiddens = $this->values;
return build_hidden( $this->values);
}//get_hiddens
/*!
* \brief Returns an URL-encoded URL-parts-string for all the key-value-pairs of this object.
* signature: interface string url_parts = get_url_parts();
* note: used as interface for Form- or Table-class
* note: also handle multi-values using 'varname[]'-notation
*/
public function get_url_parts( $end_sep=false)
{
return build_url( $this->values, $end_sep);
}
/*! \brief Return entries as key-value array. */
public function get_entries()
{
return $this->values;
}
} // end of 'RequestParameters'
// signature: string fill_sql_template( string sql_template, string operator, string value )
// Example:
// fill_sql_template( "(f #OP #VAL or f2 #OP #VAL)", ">=", "'val'") gives "(f >= 'val' or f2 >= 'val')
function fill_sql_template( $tmpl, $op, $val )
{
return preg_replace( array( "/#OP/", "/#VAL/" ), array( $op, $val ), $tmpl );
}
/*!
* \class QuerySQL
*
* \brief Class to build SQL-statements (mainly to ease handling SQL with Filters).
* for Resulting SQL-Statement \see get_select()
*
* supported: sub-clauses (though those are since mysql4.1).
*
* SQL-parts of type:
* SQLP_OPTS: [DISTINCT] [STRAIGHT_JOIN] [SQL_SMALL_RESULT | SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
* [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
* SQLP_GROUP, SQLP_LIMIT: can be only set once with same value
* SQLP_FIELDS, SQLP_FIELDS: mandatory
* SQLP_FNAMES, SQLP_WHERETMPL are not directly included for query, but fieldnames to be used for Filters
*
* Examples:
* \see code_examples/query_sql.php
*/
// parts of SQL-select-statement:
// NOTE: if adding new: handle ARR_SQL_STATEMENTS, add_part, get_part
define('SQLP_FIELDS', 'fields'); // mandatory
define('SQLP_OPTS', 'options');
define('SQLP_FROM', 'from'); // mandatory
define('SQLP_WHERE', 'where');
define('SQLP_GROUP', 'group'); // only once
define('SQLP_HAVING', 'having');
define('SQLP_ORDER', 'order');
define('SQLP_LIMIT', 'limit'); // only once
// special types for Filters (must be handled manually): \see include/filters.php
// note: be careful when merging 2 QuerySQLs
define('SQLP_FNAMES', 'fieldnames'); // field-names used to build "real" SQLP_WHERE
define('SQLP_WHERETMPL', 'where_tmpl'); // SQL-template used to build "real" SQLP_WHERE; only used to store templates along with other parts
define('SQLP_UNION_WHERE', 'union_where');
define('SQLOPT_CALC_ROWS', 'SQL_CALC_FOUND_ROWS'); // for SQLP_OPTS
class QuerySQL
{
/*! \brief array( type => array( part, ...), ...) */
private $parts;
private $use_union_all;
// sql-statements for part-types
private static $ARR_SQL_STATEMENTS = array(
SQLP_FIELDS => 'SELECT',
SQLP_OPTS => '',
SQLP_FROM => 'FROM',
SQLP_WHERE => 'WHERE',
SQLP_GROUP => 'GROUP BY',
SQLP_HAVING => 'HAVING',
SQLP_ORDER => 'ORDER BY',
SQLP_LIMIT => 'LIMIT',
SQLP_FNAMES => NULL,
SQLP_WHERETMPL => NULL,
SQLP_UNION_WHERE => 'UNION',
);
/*!
* \brief Constructs QuerySQL( part_type, part1, part2, ... part_type, ...) with var-args.
* \param $part_type: one of SQLP_...-consts
*/
public function __construct()
{
$this->parts = array();
foreach ( array_keys(self::$ARR_SQL_STATEMENTS) as $type )
$this->parts[$type] = array();
$this->use_union_all = false;
// skip arg #0=type-arg to add var-args: parts
$type = '';
$cnt_args = func_num_args();
for ( $i=0; $i < $cnt_args; $i++)
{
$arg = trim(func_get_arg($i));
if ( isset($this->parts[$arg]) )
$type = $arg;
else
{
if ( $type == '')
error('assert', "QuerySQL.construct.miss_type($arg)"); // missing part-type for part
if ( $arg != '' )
$this->parts[$type][] = $arg;
}
}
}//__construct
public function useUnionAll( $use=true )
{
$this->use_union_all = $use;
}
/*!
* \brief Adds SQL-part
* signature: add_part( part_type, part1, part2, ...); var-args for one part-type
* \param $type sql-part-type to add to QuerySQL: one of SQLP_...-consts
* \param $parts allow variable number of parts (1..n); if part is an array its items are merged as if being parts
*
* \note empty parts are skipped
* \note Best practice is to use only ONE SQL-varname per SQLP_FIELDS-part
*
* Examples:
* add_part( SQLP_FIELDS, 'P.Name', 'P.Handle', 'P.ID' );
* add_part( SQLP_OPTS, "DISTINCT" );
* add_part( SQLP_FROM, "Games" );
* add_part( SQLP_FROM, "INNER JOIN ..." );
* add_part( SQLP_WHERE, "G.ID=G2.ID AND G.Moves>10" ); // joined with 'AND'
* add_part( SQLP_GROUP, "Games.To_MoveID" );
* add_part( SQLP_HAVING, 'haverating AND goodrating' );
* add_part( SQLP_ORDER, 'M.ID DESC' );
* add_part( SQLP_LIMIT, '0,10' );
* add_part( SQLP_WHERETMPL, "(G.ID #OP #VAL OR G.ID2 #OP #VAL)" );
* add_part( SQLP_UNION_WHERE, 'Black_ID=4711', 'White_ID=4711' );
*/
public function add_part( $type ) // var-args for parts
{
// check
if ( !isset($this->parts[$type]) )
error('assert', "QuerySQL.add_part.unknown_type($type)");
if ( ($type === SQLP_GROUP || $type === SQLP_LIMIT) && $this->has_part($type) )
{
// allow only once, except same part-value
$arg = ( func_num_args() > 1 ) ? func_get_arg(1) : null;
if ( !is_null($arg) && !empty($arg) && $this->get_part($type) != $arg )
{
// type can only be set once (except the same value), part1=$arg
error('assert', "QuerySQL.add_part.set_type_once($type,$arg)");
}
return; // ignore same-value
}
// skip arg #0=type-arg to add var-args: parts
$cnt_args = func_num_args();
for ( $i=1; $i < $cnt_args; $i++)
{
$part = trim(func_get_arg($i));
if ( (string)$part != '' )
$this->parts[$type][] = $part;
}
}//add_part
/*!
* \brief Adds SQL-part, only for SQLP_FIELDS
* signature: add_part_array( part_type, array( part1, part2, ...) );
* \param $part_arr parts (1..n)
*
* \see also func add_part(type, ...)
*/
public function add_part_fields( array $part_arr )
{
foreach ( $part_arr as $part )
{
$part = trim($part);
if ( $part != '' )
$this->parts[SQLP_FIELDS][] = $part;
}
}//add_part_fields
/*!
* \brief Clears specified part-types (var-args)
* \param $part_type SQLP_...
*/
public function clear_parts() // var-args
{
$args = func_get_args();
foreach ( $args as $type )
{
if ( !isset($this->parts[$type]) )
error('assert', "QuerySQL.clear_parts.unknown_type($type)");
if ( isset($this->parts[$type]) )
$this->parts[$type] = array();
}
}//clear_parts
/*! \brief Returns true, if sql-part existing */
public function has_part( $type )
{
if ( !isset($this->parts[$type]) )
error('assert', "QuerySQL.has_part.unknown_type($type)");
return ( count($this->parts[$type]) > 0 );
}
/*! \brief Returns true, if query has a UNION-part. */
public function has_union()
{
return $this->has_part(SQLP_UNION_WHERE);
}
/*! \brief Returns parts-array for specified part_type */
public function get_parts( $type )
{
if ( !isset($this->parts[$type]) )
error('assert', "QuerySQL.get_parts.unknown_type($type)");
return $this->parts[$type];
}
/*!
* \brief Returns typed part of select-statement; '' for none set.
* \param $incl_prefix: if true, preprend sql-part with according SQL-keyword, e.g. 'SELECT' for SQLP_FIELDS
* \param $union_part -1 (no union = default), 0..n = part of union;
* for some union-parts certain SQL-options are forbidden
*/
public function get_part( $type, $incl_prefix = false, $union_part=-1 )
{
if ( !$this->has_part($type) )
return '';
// make unique: SQLP_FIELDS|OPTS|FROM|WHERE|HAVING|ORDER
$arr = array_unique( $this->parts[$type] );
if ( $type === SQLP_FIELDS || $type === SQLP_ORDER || $type === SQLP_GROUP )
$part = implode(', ', $arr);
elseif ( $type === SQLP_WHERE || $type === SQLP_HAVING || $type === SQLP_WHERETMPL )
$part = implode(' AND ', $arr);
elseif ( $type === SQLP_FROM )
$part = $this->merge_from_parts( $arr );
elseif ( $type === SQLP_LIMIT )
$part = (count($arr) > 0) ? $arr[0] : '';
elseif ( $type === SQLP_FNAMES )
$part = implode(',', $arr);
elseif ( $type === SQLP_OPTS )
{
$skiparr = array(
'HIGH_PRIORITY' => ($union_part >= 0), // forbidden with UNION
SQLOPT_CALC_ROWS => ($union_part > 0), // only allowed in 1st SELECT of UNION-clause
);
$part = '';
foreach ( $arr as $val )
{
if ( !@$skiparr[$val] )
$part .= ' ' . $val;
}
}
elseif ( $type === SQLP_UNION_WHERE )
$part = implode(' OR ', $arr);
$prefix = ($incl_prefix) ? self::$ARR_SQL_STATEMENTS[$type] . ' ' : '';
return $prefix . $part;
}//get_part
/*!
* \brief Merges FROM-parts (could be Tables or JOIN-parts).
* \note "{ OJ ..}" not supported (mysql-specialty)
*/
public function merge_from_parts( array $arr )
{
if ( count($arr) == 0 )
return '';
$result = array_shift($arr);
foreach ( $arr as $part )
{
// valid JOIN-Syntax (mysql 4.0):
// STRAIGHT_JOIN, [INNER | CROSS] JOIN, [NATURAL] LEFT|RIGHT [OUTER] JOIN
// NOTE about mysql5.0-compatibility for JOINs:
// - parentheses can be omitted if all other tables stands before a JOIN
// (whatever join you use): "FROM (...) JOIN",
// see also http://dev.mysql.com/doc/refman/5.0/en/join.html
// - fix: -> replace ','-join with 'INNER JOIN' (could also use CROSS JOIN),
// because join has higher precedence than ','-join
// see referenced URL above(!)
if ( !preg_match( "/^(STRAIGHT_JOIN|((INNER|CROSS)\s+)?JOIN|(NATURAL\s+)?(LEFT|RIGHT)\s+(OUTER\s+)?JOIN)\s/i", $part ) )
$result .= ' INNER JOIN';
$result .= " $part";
}
return $result;
}//merge_from_parts
/*!
* \brief Returns SQL-statement with current SQL-parts as one string.
* \return query-string:
* output: "SELECT [options] fields FROM from [WHERE where] [GROUP BY group] [HAVING having] [ORDER BY order] [LIMIT limit]"
* output: "(SELECT ...) UNION (SELECT ...) [ORDER BY order] [LIMIT limit]" if UNION_WHERE-part set
*/
public function get_select()
{
if ( !$this->has_union() )
return $this->get_select_normal();
// handle UNION-syntax
$arr_union = array();
$union_parts = $this->get_parts(SQLP_UNION_WHERE);
$cnt_uparts = count($union_parts);
for ( $idx=0; $idx < $cnt_uparts; $idx++)
{
$arr_union[]= $this->get_select_normal($idx);
}
$arrsql = array();
$union_cmd = ($this->use_union_all) ? 'UNION ALL' : 'UNION DISTINCT';
$arrsql[]= '(' . implode(") $union_cmd (", $arr_union) . ')';
if ( $this->has_part(SQLP_ORDER) )
$arrsql[]= $this->get_part(SQLP_ORDER, true);
if ( $this->has_part(SQLP_LIMIT) )
$arrsql[]= $this->get_part(SQLP_LIMIT, true);
$sql = implode(' ', $arrsql);
return $sql;
}//get_select
/*!
* \brief Returns SQL-statement with current SQL-parts as one string.
* \internal
* \param $union_part -1 (no union = default), 0..n = union-part to add;
* then order + limit not added
* \return output: "SELECT [options] fields FROM from [WHERE where] [GROUP BY group] [HAVING having] [ORDER BY order] [LIMIT limit]"
*/
private function get_select_normal( $union_part = -1 )
{
$arrsql = array();
$has_opts = $this->has_part(SQLP_OPTS);
if ( $this->has_part(SQLP_FIELDS) || $has_opts )
$arrsql[]= 'SELECT';
if ( $has_opts )
$arrsql[]= $this->get_part(SQLP_OPTS, false, $union_part );
$arrsql[]= $this->get_part(SQLP_FIELDS);
$arrsql[]= $this->get_part(SQLP_FROM, true);
// handle UNION-WHERE and WHERE
if ( $union_part < 0 )
{
if ( $this->has_part(SQLP_WHERE) )
$arrsql[]= $this->get_part(SQLP_WHERE, true);
}
else
{
$union_parts = $this->get_parts(SQLP_UNION_WHERE); // non-empty
$arrsql[]= 'WHERE ' . $union_parts[$union_part];
if ( $this->has_part(SQLP_WHERE) )
$arrsql[]= 'AND ' . $this->get_part(SQLP_WHERE);
}
if ( $this->has_part(SQLP_GROUP) )
$arrsql[]= $this->get_part(SQLP_GROUP, true);
if ( $this->has_part(SQLP_HAVING) )
$arrsql[]= $this->get_part(SQLP_HAVING, true);
// ORDER and LIMIT only for non-union-select
if ( $union_part < 0 )
{
if ( $this->has_part(SQLP_ORDER) )
$arrsql[]= $this->get_part(SQLP_ORDER, true);
if ( $this->has_part(SQLP_LIMIT) )
$arrsql[]= $this->get_part(SQLP_LIMIT, true);
}
$sql = implode(' ', $arrsql);
return $sql;
}//get_select_normal
/*!
* \brief Merges passed QuerySQL in current one.
* \param $qsql may be null (-> then skipped and true returned)
* \return true if merging is possible; false otherwise and merge hasn't been started.
*
* \note SQLP_GROUP|LIMIT are set, if current unset or the same part is used; otherwise error
*
* Example: bool success = merge( QuerySQL );
*/
public function merge( QuerySQL $qsql = null )
{
// checks
if ( is_null($qsql) )
return true;
// eventually add_part throws error
$this->add_part( SQLP_GROUP, $qsql->get_part(SQLP_GROUP) );
$this->add_part( SQLP_LIMIT, $qsql->get_part(SQLP_LIMIT) );
foreach ( array_keys( $this->parts ) as $type )
{
if ( $qsql->has_part($type) )
if ( $type != SQLP_GROUP && $type != SQLP_LIMIT )
$this->parts[$type] = array_merge( $this->parts[$type], $qsql->get_parts($type) );
}
return true;
}//merge
/*!
* \brief Merges passed QuerySQL with current one returning _NEW_ QuerySQL, if merging possible.
* signature: QuerySQL merge_or( QuerySQL );
* \return NULL otherwise.
* \note merge WHERE and HAVING parts using OR-operation
*/
public function merge_or( QuerySQL $qsql )
{
// normal merge into new one
$query = $this->duplicate();
if ( !$query->merge($qsql) )
return NULL;
// manual merge of WHERE and HAVING
$query->clear_parts( SQLP_WHERE, SQLP_HAVING );
foreach ( array( SQLP_WHERE, SQLP_HAVING ) as $type )
{
$arr = array();
if ( $this->has_part($type) )
$arr[]= $this->get_part($type);
if ( $qsql->has_part($type) )
$arr[]= $qsql->get_part($type);
if ( count($arr) > 0 )
$query->add_part( $type, '(' . implode(') OR (', $arr) . ')' );
}
return $query;
}//merge_or
/*! \brief Returns copy of this object */
public function duplicate()
{
$q = new QuerySQL();
$q->merge($this);
return $q;
}
/*! \brief Returns String-representation of this object. */
public function to_string()
{
$arr = array();
foreach ( array_keys($this->parts) as $type )
{
if ( $this->has_part($type) )
$arr[]= "$type={[" . implode( '], [', $this->parts[$type] ) . "]}";
}
return "QuerySQL: " . implode(', ', $arr);
}//to_string
} // end of 'QuerySQL'
/*!
* \class ListIterator
*
* \brief Class to help with iterating over lists, especially loaded from database.
*
* \note Also help to keep track and build query, supportive for debugging query
*/
class ListIterator
{
/*! \brief Name of list-iterator (type of items). */
private $Name;
/*! \brief main QuerySQL | null */
private $QuerySQL;
/*! \brief List of QuerySQL to be merged into main QuerySQL. */
private $QuerySQLMerge = array();
/*! \brief optional order string to be appended to query. */
private $QueryOrder;
/*! \brief optional limit string to be appended to query. */
private $QueryLimit;
/*! \brief field-name for comparison-function _compare_items_...() for sortListIterator(). */
private $SortField = null;
/*! \brief (internal) QuerySQL built from merging QuerySQL with list of QuerySQLMerge. */
private $MergedQuerySQL = null;
/*! \brief query-string for db-query (for debugging). */
private $Query = '';
/*! \brief Number of rows resulting from db-query. */
private $ResultRows = -1;
/*! \brief List of array with objects and original row read from db-query: array( array( Obj, row), ...). */
private $Items;
/*! \brief optional index mapping index-fields from query-result to items: array( field => [ val => (Obj,row) ], ...). */
private $Index = array();
/*!
* \brief Constructs ListIterator with name
* \param $qsql will be added as merge-QuerySQL
*/
public function __construct( $name, QuerySQL $qsql = null, $order = '', $limit = '' )
{
$this->Name = $name;
$this->QuerySQL = new QuerySQL();
$this->addQuerySQLMerge( $qsql );
$this->QueryOrder = $order;
$this->QueryLimit = $limit;
$this->clearItems();
}
/*! \brief Clears list of items in this list-iterator. */
public function clearItems()
{
$this->Items = array();
}
/*! \brief Returns number of stored items. */
public function getItemCount()
{
return count( $this->Items );
}
/*! \brief Returns each() from items-list of this ListIterator. */
public function getListIterator()
{
return each( $this->Items );
}
/*! \brief Resets iterating with getListIterator-func. */
public function resetListIterator()
{
reset( $this->Items );
}
public function getItem( $row_idx )
{
if ( !is_numeric($row_idx) || !isset($this->Items[$row_idx]) )
error('invalid_args', "ListIterator.getItem.check_idx($row_idx)");
return $this->Items[$row_idx];
}
public function setItemRawValue( $row_idx, $key, $value )
{
if ( !is_numeric($row_idx) || $row_idx < 0 || $row_idx >= $this->getItemCount() )
error('invalid_args', "ListIterator.setItemRawValue.check_idx($row_idx)");
$this->Items[$row_idx][1][$key] = $value;
}
/*! \brief Sorts internal items-list by given compare-function $cmp_func. */
public function sortListIteratorCustom( callable $cmp_func )
{
return usort( $this->Items, $cmp_func );
}
/*!
* \brief Sorts internal items-list by given $field (from row-data).
* \param $sort_flags SORT_NUMERIC, SORT_STRING (case-sensitive), SORT_STRING|SORT_FLAG_CASE (case-insensitive)
*/
public function sortListIterator( $field, $sort_flags=SORT_REGULAR )
{
if ( count($this->Items) == 0 )
return true;
if ( !isset($this->Items[0][1][$field]) ) // 1st item, row[field]
error('invalid_args', "ListIterator.sortListIterator.unknown_field($field,$sort_flags)");
$this->SortField = $field;
$cmp_func = array( $this );
if ( $sort_flags == SORT_NUMERIC )
$cmp_func[] = '_compare_items_numeric';
elseif ( $sort_flags & SORT_STRING )
$cmp_func[] = '_compare_items_string';
elseif ( ($sort_flags & (SORT_STRING|SORT_FLAG_CASE)) == (SORT_STRING|SORT_FLAG_CASE) )
$cmp_func[] = '_compare_items_string_nocase';
else
error('invalid_args', "ListIterator.sortListIterator.bad_sort_flags($field,$sort_flags)");
return usort( $this->Items, $cmp_func );
}//sortListIterator
// \internal, see sortListIterator()
private function _compare_items_numeric( array $item1, array $item2 )
{
$a = $item1[1][$this->SortField]; // row[field]
$b = $item2[1][$this->SortField];
// could use cmp_int(), but inline is faster
if ($a == $b)
return 0;
else
return ($a < $b) ? -1 : 1;
}
// \internal, see sortListIterator()
private function _compare_items_string( array $item1, array $item2 )
{
$a = $item1[1][$this->SortField]; // row[field]
$b = $item2[1][$this->SortField];
return strcmp($a, $b);
}
// \internal, see sortListIterator()
private function _compare_items_string_nocase( array $item1, array $item2 )
{
$a = $item1[1][$this->SortField]; // row[field]
$b = $item2[1][$this->SortField];
return strcasecmp($a, $b);
}
/*! \brief Sets main QuerySQL. */
public function setQuerySQL( QuerySQL $qsql )
{
$this->QuerySQL = $qsql;
}
/*! \brief Adds QuerySQL for merging. */
public function addQuerySQLMerge( QuerySQL $qsql = null )
{
if ( !is_null($qsql) )
$this->QuerySQLMerge[] = $qsql;
}//addQuerySQLMerge
/*! \brief Sets ORDER-string appended to resulting query built from merging QuerySQLs. */
public function setQueryOrder( $order='' )
{
$this->QueryOrder = $order;
}
/*! \brief Sets LIMIT-string appended to resulting query built from merging QuerySQLs. */
public function setQueryLimit( $limit='' )
{
$this->QueryLimit = $limit;
}
/*!
* \brief Sets SQL-query-string (built from main and merge-list QuerySQLs,
* order and limit query-parts). Should contain the final query used
* to query database.
*/
public function setQuery( $query_str )
{
$this->Query = $query_str;
}
public function getResultRows()
{
return $this->ResultRows;
}
/*! \brief Sets number of rows from resulting db-query. */
public function setResultRows( $result_rows )
{
$this->ResultRows = $result_rows;
}
/*! \brief Clears list of indexes in this list-iterator. */
public function clearIndex()
{
$this->Index = array();
}
/*! \brief Adds index-field(s) to be generated on iterator. */
public function addIndex( /*var-args*/ )
{
$cnt_args = func_num_args();
for ( $i=0; $i < $cnt_args; $i++)
{
$field = func_get_arg($i);
$this->Index[$field] = array();
}
}//addIndex
/*! \brief Returns true if field has filled index. */
public function hasIndex( $field )
{
return ( count(@$this->Index[$field]) > 0 );
}
/*! \brief Returns index-map for indexed field; die with error on unknown field. */
public function getIndexMap( $field )
{
if ( !isset($this->Index[$field]) )
error('invalid_args', "ListIterator.getIndexMap({$this->Name},$field)");
return $this->Index[$field];
}
/*!
* \brief Returns index-map-value for indexed field and key; null if undefined.
* \param $ret_val -1 = return [item,row]; 0=return item, 1=return row
*/
public function getIndexValue( $field, $key, $ret_val=-1 )
{
if ( !isset($this->Index[$field][$key]) )
return null;
$arr_item = $this->Index[$field][$key];
if ( $ret_val < 0 )
return $arr_item;
elseif ( $ret_val == 0 )
return $arr_item[0];
else
return $arr_item[1];
}//getIndexValue
/*! \brief Returns String-representation of this object. */
public function to_string()
{
$arr = array();
if ( !is_null($this->QuerySQL) )
$arr[] = "QuerySQL={" . $this->QuerySQL->to_string() . '}';
$idx = 1;
foreach ( $this->QuerySQLMerge as $qsql )
$arr[] = sprintf( "QuerySQLMerge.%d=[%s]", $idx++, $qsql->to_string() );
$arr[] = "QueryOrder=[{$this->QueryOrder}]";
$arr[] = "QueryLimit=[{$this->QueryLimit}]";
$arr[] = "Query=[{$this->Query}]";
$arr[] = "ResultRows=[{$this->ResultRows}]";
$arr[] = '#Items=[' . count($this->Items) . ']';
$idx = 1;
foreach ( $this->Items as $arr_item )
{
list( $item, $row ) = $arr_item;
$arr[] = sprintf( "Item.%d=[%s]", $idx++,
( method_exists($item, 'to_string') ? $item->to_string() : print_r($item,true) ));
}
$this->resetListIterator();
$arr[] = 'Index=[' . print_r( $this->Index, true ) . ']';
return "ListIterator({$this->Name}): " . implode(', ', $arr);
}//to_string
/*!
* \brief Builds SQL-query from main QuerySQL and merge-list of QuerySQLs,
* appending optional order and limit query parts.
* \note Sets QuerySQL if unset.
* \note Sets MergedQuerySQL and Query with finalized SQL-query-string.
*/
public function buildQuery()
{
if ( is_null($this->QuerySQL) )
$this->QuerySQL = new QuerySQL();
// merge all QuerySQLs
$merged_qsql = $this->QuerySQL;
foreach ( $this->QuerySQLMerge as $m_qsql )
$merged_qsql->merge( $m_qsql );
$this->MergedQuerySQL = $merged_qsql;
$query = $merged_qsql->get_select() . ' ' . $this->QueryOrder . ' ' . $this->QueryLimit;
$this->setQuery( $query );
return $query;
}//buildQuery
/*! \brief Adds item to item-list. */
public function addItem( $item, $row=null )
{
$arr_item = array( $item, $row );
$this->Items[] = (is_array($row)) ? $arr_item : $item;
if ( is_array($row) )
{
foreach ( $this->Index as $field => $map )
$this->Index[$field][$row[$field]] = $arr_item;
}
}//addItem
/*! \brief Return array with list of objects (without extra-row). */
public function getItems( $with_extra=false )
{
if ( $with_extra )
return $this->Items;
$out = array();
foreach ( $this->Items as $item )
$out[] = $item[0];
return $out;
}//getItems
/*! \brief Return array with list of raw rows only. */
public function getItemRows()
{
$out = array();
foreach ( $this->Items as $arr_item )
$out[] = $arr_item[1];
$this->resetListIterator();
return $out;
}
/*! \brief Rescans items and re-filling index. */
public function rescanIndex()
{
foreach ( array_keys($this->Index) as $field )
$this->Index[$field] = array();
foreach ( $this->Items as $arr_item )
{
$row = $arr_item[1];
foreach ( $this->Index as $field => $map )
$this->Index[$field][$row[$field]] = $arr_item;
}
$this->resetListIterator();
}//rescanIndex
} // end of 'ListIterator'
/*!
* \class ThreadList
*
* \brief Class to help with representing threads
*/
class ThreadList
{
/*! \brief Thread item of certain type equal for whole thread-list. */
private $item;
/*! \brief Level within thread, starting at 0. */
private $level;
/*! \brief Parent ThreadList, null if root-item. */
private $parent;
/*! \brief List of ThreadList-objects being the children of current thread. */
private $children = array();
public function __construct( $item, $level=0, $parent=null )
{
$this->item = $item;
$this->level = $level;
$this->parent = $parent;
}
/*! \brief Returns current item. */
public function getItem()
{
return $this->item;
}
/*! \brief Returns level of current item. */
public function getLevel()
{
return $this->level;
}
/*! \brief Sets level for current item. */
public function setLevel( $level )
{
$this->level = $level;
}
/*! \brief Returns true, if current item has a parent item. */
public function hasParent()
{
return !is_null($this->parent);
}
/*! \brief Returns true, if current item has children items. */
public function hasChildren()
{
return (count($this->children) > 0);
}
/*! \brief Returns children items. */
public function getChildren()
{
return $this->children;
}
/*! \brief Adds child to current item. */
public function addChild( $item )
{
$thread = new ThreadList( $item, $this->level + 1, $this );
$this->children[] = $thread;
return $thread;
}
/*!
* \brief Deep-traverse current ThreadList applying given function on each
* ThreadList-part and given $result reference.
* \param $function Function with signature: function_name( ThreadList, &$result )
* \param $result passed in as reference to function when traversing ThreadList;
* can be used as storage
* \note Function is applied on each item first before applying it on its children.
*/
public function traverse( callable $function, &$result )
{
$function( $this, $result );
foreach ( $this->children as $child_item )
{
$child_item->traverse( $function, $result );
}
}//traverse
} // end of 'ThreadList'
?>
//
koh5_pano
Drag mouse to navigate.
Navigation
- Left/Right Mouse drag: Changes camera heading.
- Up/Sown Mouse drag: Changes camera pitch.
- Scroll wheel: Changes camera field of view.
- I-Key: Displays Info panel with canvas size, image size and FPS.
17.Aug.2010, Martin Wengenmayer