fields = array(); } /** * Class member get overloading * * @param string $name * @return mixed */ function __get($name) { return $this->fields[$name]; } /** * Class member set overloading * * @param string $name * @param mixed $value * @return void */ function __set($name, $value) { $this->fields[$name] = $value; return true; } } /** * @class SelectFieldQueryComponent Class representing a select field. * This class represents a select field component. * @author Marcus Povey */ class SelectFieldQueryComponent extends QueryComponent { /** * Construct a select field component * * @param string $table The table containing the field. * @param string $field The field or "*" */ function __construct($table, $field) { global $CONFIG; $this->table = $CONFIG->dbprefix . sanitise_string($table); $this->field = sanitise_string($field); } function __toString() { return "{$this->table}.{$this->field}"; } } /** * @class LimitOffsetQueryComponent * Limit and offset clauses of a query. * @author Marcus Povey */ class LimitOffsetQueryComponent extends QueryComponent { /** * Specify a limit and an offset. * * @param int $limit The limit. * @param int $offset The offset. */ function __construct($limit = 25, $offset = 0) { $this->limit = (int)$limit; $this->offset = (int)$offset; } function __toString() { return "limit {$this->offset}, {$this->limit}"; } } /** * @class OrderQueryComponent * Order the query results. * @author Marcus Povey */ class OrderQueryComponent extends QueryComponent { function __construct($table, $field, $order = "asc") { global $CONFIG; $this->table = $CONFIG->dbprefix . sanitise_string($table); $this->field = sanitise_string($field); $this->order = sanitise_string($order); } function __toString() { return "order by {$this->table}.{$this->field} {$this->order}"; } } /** * @class TableQueryComponent * List of tables to select from or insert into. * @author Marcus Povey */ class TableQueryComponent extends QueryComponent { function __construct($table) { global $CONFIG; $this->table = $CONFIG->dbprefix . sanitise_string($table); } function __toString() { return $this->table; } } /** * @class AccessControlQueryComponent * Access control component. * @author Marcus Povey */ class AccessControlQueryComponent extends QueryComponent { /** * Construct the ACL. * * @param string $acl_table The table where the access control field is. * @param string $acl_field The field containing the access control. * @param string $object_owner_table The table containing the owner information for the stuff you're retrieving. * @param string $object_owner_id_field The field in $object_owner_table containing */ function __construct($acl_table = "entities", $acl_field = "access_id", $object_owner_table = "entities", $object_owner_id_field = "owner_guid") { global $CONFIG; $this->acl_table = $CONFIG->dbprefix . sanitise_string($acl_table); $this->acl_field = sanitise_string($acl_field); $this->object_owner_table = $CONFIG->dbprefix . sanitise_string($object_owner_table); $this->object_owner_id_field = sanitise_string($object_owner_id_field); } function __toString() { $access = get_access_list(); return "and ({$this->acl_table}.{$this->acl_field} in {$access} or ({$this->acl_table}.{$this->acl_field} = 0 and {$this->object_owner_table}.{$this->object_owner_id_field} = {$_SESSION['id']}))"; } } /** * @class JoinQueryComponent Join query. * Represents a join query. * @author Marcus Povey */ class JoinQueryComponent extends QueryComponent { /** * Construct a join query. * @param string $table Table one to join... * @param string $field Field 1 with... * @param string $table2 Table 2 ... * @param string $field2 Field... * @param string $operator Using this operator */ function __construct($table1, $field1, $table2, $field2, $operator = "=") { global $CONFIG; $this->table1 = $CONFIG->dbprefix . sanitise_string($table1); $this->field1 = sanitise_string($field1); $this->table2 = $CONFIG->dbprefix . sanitise_string($table2); $this->field2 = sanitise_string($field2); $this->operator = sanitise_string($operator); } function __toString() { return "join {$this->table2} on {$this->$table}.{$this->$field} {$this->$operator} {$this->$table2}.{$this->$field2}"; } } /** * @class SetQueryComponent Set query. * Represents an update set query. * @author Marcus Povey */ class SetQueryComponent extends QueryComponent { /** * Construct a setting query * * @param string $table The table to modify * @param string $field The field to modify * @param mixed $value The value to set it to */ function __construct($table, $field, $value) { global $CONFIG; $this->table = $CONFIG->dbprefix . sanitise_string($table); $this->field = sanitise_string($field); if (is_numeric($value)) $this->value = (int)$value; else $this->value = "'".sanitise_string($value)."'"; } function __toString() { return "{$this->table}.{$this->field}={$this->value}"; } } /** * @class WhereQueryComponent * A component of a where query. * @author Marcus Povey */ class WhereQueryComponent extends QueryComponent { /** * A where query. * * @param string $left_table The table on the left of the operator * @param string $left_field The left field * @param string $operator The operator eg "=" or "<" * @param string $right_table The table on the right of the operator * @param string $right_field The right field * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" */ function __construct($left_table, $left_field, $operator, $right_table, $right_field, $link_operator = "and") { global $CONFIG; $this->link_operator = sanitise_string($link_operator); $this->left_table = $CONFIG->dbprefix . sanitise_string($left_table); $this->left_field = sanitise_string($left_field); $this->operator = sanitise_string($operator); $this->right_table = $CONFIG->dbprefix . sanitise_string($right_table); $this->right_field = sanitise_string($right_field); } public function toStringNoLink() { return "{$this->left_table }.{$this->left_field} {$this->operator} {$this->right_table}.{$this->right_field}"; } function __toString() { return "{$this->link_operator} " . $this->toStringNoLink(); } } /** * @class WhereStaticQueryComponent * A component of a where query where there is no right hand table, rather a static value. * @author Marcus Povey */ class WhereStaticQueryComponent extends WhereQueryComponent { /** * A where query. * * @param string $left_table The table on the left of the operator * @param string $left_field The left field * @param string $operator The operator eg "=" or "<" * @param string $value The value * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" */ function __construct($left_table, $left_field, $operator, $value, $link_operator = "and") { global $CONFIG; $this->link_operator = sanitise_string($link_operator); $this->left_table = $CONFIG->dbprefix . sanitise_string($left_table); $this->left_field = sanitise_string($left_field); $this->operator = sanitise_string($operator); if (is_numeric($value)) $this->value = (int)$value; else $this->value = "'".sanitise_string($value)."'"; } public function toStringNoLink() { return "{$this->left_table }.{$this->left_field} {$this->operator} {$this->value}"; } } /** * @class WhereSetQueryComponent * A where query that may contain other where queries (in brackets). * @author Marcus Povey */ class WhereSetQueryComponent extends WhereQueryComponent { /** * Construct a subset of wheres. * * @param array $wheres An array of WhereQueryComponent * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" */ function __construct(array $wheres, $link_operator = "and") { $this->link_operator = sanitise_string($link_operator); $this->wheres = $wheres; } public function toStringNoLink() { $cnt = 0; $string = " ("; foreach ($this->wheres as $where) { if (!($where instanceof WhereQueryComponent)) throw new DatabaseException("Where set contains non WhereQueryComponent"); if (!$cnt) $string.= $where->toStringNoLink(); else $string.=" $where "; $cnt ++; } $string .= ")"; return $string; } } /** * @class QueryTypeQueryComponent * What type of query is this? * @author Marcus Povey */ abstract class QueryTypeQueryComponent extends QueryComponent { function __toString() { return $this->query_type; } } /** * @class SelectQueryTypeQueryComponent * A select query. * @author Marcus Povey */ class SelectQueryTypeQueryComponent extends QueryTypeQueryComponent { function __construct() { $this->query_type = "select"; } } /** * @class InsertQueryTypeQueryComponent * An insert query. * @author Marcus Povey */ class InsertQueryTypeQueryComponent extends QueryTypeQueryComponent { function __construct() { $this->query_type = "insert into"; } } /** * @class DeleteQueryTypeQueryComponent * A delete query. * @author Marcus Povey */ class DeleteQueryTypeQueryComponent extends QueryTypeQueryComponent { function __construct() { $this->query_type = "delete from"; } } /** * @class UpdateQueryTypeQueryComponent * An update query. * @author Marcus Povey */ class UpdateQueryTypeQueryComponent extends QueryTypeQueryComponent { function __construct() { $this->query_type = "update"; } } /** * @class Query * This class provides a framework to construct complex queries in a safe environment. * * @author Marcus Povey */ class Query { /// The limit of the query private $limit_and_offset; /// Fields to return on a query private $fields; /// Tables to use in a from query private $tables; /// Join tables private $joins; /// Set values private $sets; /// Where query private $where; /// Order by private $order; /// The query type private $query_type; /// ACL private $access_control; /** * Construct query & initialise variables */ function __construct() { $this->fields = array(); $this->tables = array(); $this->joins = array(); $this->where = array(); $this->sets = array(); $this->setQueryType(new SelectQueryTypeQueryComponent()); } public function setLimitAndOffset(LimitOffsetQueryComponent $component) { $this->limit_and_offset = $component; } public function setSelectField(SelectFieldQueryComponent $component) { $this->fields = array(); return $this->addSelectField($component); } public function addSelectField(SelectFieldQueryComponent $component) { $this->fields[] = $component; } public function addJoin(JoinQueryComponent $component) { $this->joins[] = $component; } public function addSet(SetQueryComponent $component) { $this->sets[] = $component; } public function setQueryType(QueryTypeQueryComponent $component) { $this->query_type = $component; } public function setOrder(OrderQueryComponent $component) { $this->order = $component; } public function addTable(TableQueryComponent $component) { $this->tables[] = $component; } public function addWhere(WhereQueryComponent $component) { $this->where[] = $component; } public function setAccessControl(AccessControlQueryComponent $component) { $this->access_control = $component; } public function __toString() { global $CONFIG; $sql = ""; try { // Query prefix & fields if (!empty($this->query_type)) { $sql .= "{$this->query_type} "; if (!empty($this->fields)) { $fields = ""; foreach ($this->fields as $field) $fields .= "$field"; $sql .= " $fields from "; } else throw new DatabaseException("Fields missing on a select style query"); } else throw new DatabaseException("Unrecognised or unspecified query type."); // Tables if (!empty($this->tables)) { foreach($this->tables as $table) $sql .= "$table, "; $sql = trim($sql, ", "); } else throw new DatabaseException("No tables specified for query."); // Joins on select queries if ($this->query_type->query_type == 'select') { if (!empty($this->joins)) { foreach($this->joins as $join) $sql .= "$join "; } } // Setting values if ( ($this->query_type->query_type == 'update') || ($this->query_type->query_type == 'insert') ) { $sql .= "set "; foreach ($this->sets as $set) $sql .= "$set, "; $sql = trim($sql, ", ") . " "; } // Where if (!empty($this->where)) { $sql .= " where 1 "; foreach ($this->where as $where) $sql .= "$where "; } // Access control if (!empty($this->access_control)) { // Catch missing Where if (empty($this->where)) $sql .= " where 1 "; $sql .= "{$this->access_control} "; } else throw new DatabaseException("No access control was provided on query"); // Order by if (!empty($this->order)) $sql .= "{$this->order} "; // Limits if (!empty($this->limit_and_offset)) $sql .= "{$this->limit_and_offset} "; } catch (Exception $e) { trigger_error($e, E_USER_WARNING); } return $sql; } } /** * Connect to the database server and use the Elgg database for a particular database link * * @param string $dblinkname Default "readwrite"; you can change this to set up additional global database links, eg "read" and "write" */ function establish_db_link($dblinkname = "readwrite") { // Get configuration, and globalise database link global $CONFIG, $dblink; if (!isset($dblink)) { $dblink = array(); } if ($dblinkname != "readwrite" && isset($CONFIG->db[$dblinkname])) { if (is_array($CONFIG->db[$dblinkname])) { $index = rand(0,sizeof($CONFIG->db[$dblinkname])); $dbhost = $CONFIG->db[$dblinkname][$index]->dbhost; $dbuser = $CONFIG->db[$dblinkname][$index]->dbuser; $dbpass = $CONFIG->db[$dblinkname][$index]->dbpass; $dbname = $CONFIG->db[$dblinkname][$index]->dbname; } else { $dbhost = $CONFIG->db[$dblinkname]->dbhost; $dbuser = $CONFIG->db[$dblinkname]->dbuser; $dbpass = $CONFIG->db[$dblinkname]->dbpass; $dbname = $CONFIG->db[$dblinkname]->dbname; } } else { $dbhost = $CONFIG->dbhost; $dbuser = $CONFIG->dbuser; $dbpass = $CONFIG->dbpass; $dbname = $CONFIG->dbname; } // Connect to database if (!$dblink[$dblinkname] = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, true)) throw new DatabaseException("Elgg couldn't connect to the database using the given credentials."); if (!mysql_select_db($CONFIG->dbname, $dblink[$dblinkname])) throw new DatabaseException("Elgg couldn't select the database {$CONFIG->dbname}."); } /** * Establish all database connections * * If the configuration has been set up for multiple read/write databases, set those * links up separately; otherwise just create the one database link * */ function setup_db_connections() { // Get configuration and globalise database link global $CONFIG, $dblink; if (!empty($CONFIG->db->split)) { establish_db_link('read'); establish_db_link('write'); } else { establish_db_link('readwrite'); } } /** * Alias to setup_db_connections, for use in the event handler * * @param string $event The event type * @param string $object_type The object type * @param mixed $object Used for nothing in this context */ function init_db($event, $object_type, $object = null) { setup_db_connections(); return true; } /** * Gets the appropriate db link for the operation mode requested * * @param string $dblinktype The type of link we want - "read", "write" or "readwrite" (the default) * @return object Database link */ function get_db_link($dblinktype) { global $dblink; if (isset($dblink[$dblinktype])) { return $dblink[$dblinktype]; } else { return $dblink['readwrite']; } } /** * Explain a given query, useful for debug. */ function explain_query($query, $link) { if ($result = mysql_query("explain " . $query, $link)) { return mysql_fetch_object($result); } return false; } /** * Use this function to get data from the database * @param mixed $query The query being passed. * @param string $call Optionally, the name of a function to call back to on each row (which takes $row as a single parameter) * @return array An array of database result objects */ function get_data($query, $callback = "") { global $CONFIG, $dbcalls; if (!callpath_gatekeeper($CONFIG->path . "engine/", true, true)) throw new DatabaseException("Access to privileged function 'get_data()' is denied."); $dblink = get_db_link('read'); $resultarray = array(); $dbcalls++; if ((isset($CONFIG->debug)) && ($CONFIG->debug==true)) { error_log("--- DB QUERY --- $query"); error_log("--- EXPLAINATION --- " . print_r(explain_query($query,$dblink), true)); } if ($result = mysql_query("$query", $dblink)) { while ($row = mysql_fetch_object($result)) { if (!empty($callback) && is_callable($callback)) { $row = $callback($row); } $resultarray[] = $row; } } if (mysql_errno($dblink)) throw new DatabaseException(mysql_error($dblink) . " QUERY: " . $query); if (empty($resultarray)) { if ((isset($CONFIG->debug)) && ($CONFIG->debug==true)) error_log("WARNING: DB query \"$query\" returned no results."); return false; } return $resultarray; } /** * Use this function to get a single data row from the database * @param mixed $query The query to run. * @return object A single database result object */ function get_data_row($query) { global $CONFIG, $dbcalls; if (!callpath_gatekeeper($CONFIG->path . "engine/", true, true)) throw new DatabaseException("Access to privileged function 'get_data_row()' is denied."); $dblink = get_db_link('read'); $dbcalls++; if ((isset($CONFIG->debug)) && ($CONFIG->debug==true)) { error_log("--- DB QUERY --- $query"); error_log("--- EXPLAINATION --- " . print_r(explain_query($query,$dblink), true)); } if ($result = mysql_query("$query", $dblink)) { while ($row = mysql_fetch_object($result)) { return $row; } } if (mysql_errno($dblink)) throw new DatabaseException(mysql_error($dblink) . " QUERY: " . $query); if ((isset($CONFIG->debug)) && ($CONFIG->debug==true)) error_log("WARNING: DB query \"$query\" returned no results."); return false; } /** * Use this function to insert database data; returns id or false * * @param mixed $query The query to run. * @return int $id the database id of the inserted row. */ function insert_data($query) { global $CONFIG, $dbcalls; if (!callpath_gatekeeper($CONFIG->path . "engine/", true, true)) throw new DatabaseException("Access to privileged function 'insert_data()' is denied."); $dblink = get_db_link('write'); $dbcalls++; if (mysql_query("$query", $dblink)) return mysql_insert_id($dblink); if (mysql_errno($dblink)) throw new DatabaseException(mysql_error($dblink) . " QUERY: " . $query); return false; } /** * Update database data * * @param mixed $query The query to run. * @return int|false Either the number of affected rows, or false on failure */ function update_data($query) { global $dbcalls, $CONFIG; if (!callpath_gatekeeper($CONFIG->path . "engine/", true, true)) throw new DatabaseException("Access to privileged function 'update_data()' is denied."); $dblink = get_db_link('write'); $dbcalls++; if (mysql_query("$query", $dblink)) return mysql_affected_rows(); if (mysql_errno($dblink)) throw new DatabaseException(mysql_error($dblink) . " QUERY: " . $query); return false; } /** * Use this function to delete data * * @param mixed $query The SQL query to run * @return int|false Either the number of affected rows, or false on failure */ function delete_data($query) { global $dbcalls, $CONFIG; if (!callpath_gatekeeper($CONFIG->path . "engine/", true, true)) throw new DatabaseException("Access to privileged function 'delete_data()' is denied."); $dblink = get_db_link('write'); $dbcalls++; if (mysql_query("$query", $dblink)) return mysql_affected_rows(); if (mysql_errno($dblink)) throw new DatabaseException(mysql_error($dblink) . " QUERY: " . $query); return false; } /** * Returns the number of rows returned by the last select statement, without the need to re-execute the query. * * CANDIDATE FOR DELETION? * * @return int The number of rows returned by the last statement */ function count_last_select() { $row = get_data_row("SELECT found_rows() as count"); if ($row) return $row->count; return 0; } /** * Get the tables currently installed in the Elgg database * * @return array List of tables */ function get_db_tables() { global $CONFIG; $result = get_data("show tables"); $tables = array(); if (is_array($result) && !empty($result)) { foreach($result as $row) { $row = (array) $row; if (is_array($row) && !empty($row)) foreach($row as $element) { $tables[] = $element; } } } else return false; return $tables; } /** * Get the last database error for a particular database link * * @param database link $dblink * @return string Database error message */ function get_db_error($dblink) { return mysql_error($dblink); } /** * Runs a full database script from disk * * @uses $CONFIG * @param string $scriptlocation The full path to the script */ function run_sql_script($scriptlocation) { if ($script = file_get_contents($scriptlocation)) { global $CONFIG; $errors = array(); $script = preg_replace('/\-\-.*\n/', '', $script); $sql_statements = preg_split('/;[\n\r]+/', $script); foreach($sql_statements as $statement) { $statement = trim($statement); $statement = str_replace("prefix_",$CONFIG->dbprefix,$statement); if (!empty($statement)) { $result = update_data($statement); } } if (!empty($errors)) { $errortxt = ""; foreach($errors as $error) $errortxt .= " {$error};"; throw new DatabaseException("There were a number of issues: " . $errortxt); } } else { throw new DatabaseException("Elgg couldn't find the requested database script at {$scriptlocation}."); } } /** * Sanitise a string for database use * * @param string $string The string to sanitise * @return string Sanitised string */ function sanitise_string($string) { return mysql_real_escape_string(trim($string)); } /** * Wrapper function for Americans * * @param string $string The string to sanitise * @return string Sanitised string * @uses sanitise_string */ function sanitize_string($string) { return sanitise_string($string); } /** * Sanitises an integer for database use * * @param int $int * @return int Sanitised integer */ function sanitise_int($int) { return (int) $int; } /** * Wrapper function for Americans * * @param int $int * @return int Sanitised integer * @uses sanitise_string */ function sanitize_int($int) { return (int) $int; } // Stuff for initialisation register_event_handler('boot','system','init_db',0); ?>