![]() |
Xataface 2.0
Xataface Application Framework
|
00001 <?php 00002 /*------------------------------------------------------------------------------- 00003 * Xataface Web Application Framework 00004 * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca) 00005 * 00006 * This program is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU General Public License 00008 * as published by the Free Software Foundation; either version 2 00009 * of the License, or (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with this program; if not, write to the Free Software 00018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 *------------------------------------------------------------------------------- 00020 */ 00021 00022 /******************************************************************************* 00023 * File: Dataface/QueryBuilder.php 00024 * Author: Steve Hannah <shannah@sfu.ca> 00025 * Created: Sept. 2, 2005 00026 * Description: 00027 * Builds SQL queries based on key-value pair queries. 00028 * 00029 ******************************************************************************/ 00030 00031 import( 'PEAR.php'); 00032 import( 'Dataface/Table.php'); 00033 import( 'Dataface/Error.php'); 00034 import( 'Dataface/Serializer.php'); 00035 import('Dataface/DB.php'); // for Blob registry. 00036 00037 define('QUERYBUILDER_ERROR_EMPTY_SELECT', 1); 00038 00039 class Dataface_QueryBuilder { 00040 00050 var $_query; 00051 00055 var $_tablename; 00056 00060 var $_table; 00061 00065 var $_fields; 00066 00067 var $_mutableFields; 00068 00072 var $_exactMatches = false; 00073 00077 var $_omitBlobs = true; 00078 00082 var $_omitPasswords = true; 00083 00090 var $_security = array(); 00091 00092 00093 var $_serializer; 00094 00095 var $errors = array(); 00096 00097 /* 00098 * Whether or not meta data should be selected along with select statements. 00099 * Meta data includes calculations of the "lengths" of the data in particular 00100 * fields. This is particularly handy for blob fields where we are interested 00101 * in the size of the blob. 00102 */ 00103 var $selectMetaData = false; 00104 00108 var $metadata = false; 00109 00110 00117 var $action = null; 00118 00119 00120 00121 00122 00130 function Dataface_QueryBuilder($tablename, $query=''){ 00131 $this->_tablename = $tablename; 00132 $this->_table =& Dataface_Table::loadTable($tablename); 00133 $this->_query = is_array($query) ? $query : array(); 00134 $this->_fields =& $this->_table->fields(false, true); 00135 $this->_mutableFields =& $this->_table->fields(); 00136 $this->_serializer = new Dataface_Serializer($tablename); 00137 $this->action = null; 00138 00139 $app =& Dataface_Application::getInstance(); 00140 if ( @$app->_conf['metadata_enabled'] ){ 00141 $this->metadata = true; 00142 } 00143 00144 $keys = array_keys( $this->_query ); 00145 foreach ($keys as $key){ 00146 if ( $this->_query[$key] === ''){ 00147 unset( $this->_query[$key] ); 00148 } 00149 } 00150 00151 00152 if ( isset( $GLOBALS['DATAFACE_QUERYBUILDER_SECURITY_CONSTRAINTS'][$tablename]) ){ 00153 $this->_security = $GLOBALS['DATAFACE_QUERYBUILDER_SECURITY_CONSTRAINTS'][$tablename]; 00154 } 00155 00156 00157 } 00158 00159 function _opt($code, $isText=true){ 00160 switch ( $code ){ 00161 case '=': 00162 case '>': 00163 case '<': 00164 case '>=': 00165 case '<=': 00166 return $code; 00167 default: 00168 return 'LIKE'; 00169 } 00170 } 00171 00172 00173 00192 function search($queryStr, $columns='', $queryParams=array(), $nolimit=false){ 00193 $this->action='search'; 00194 $ret = $this->_select($columns); 00195 $from = trim($this->_from($this->_table->tablename)); 00196 $indexOnly = ( (isset($queryParams['-ignoreIndex']) and $queryParams['-useIndex']) ? false : true ); 00197 $where = "WHERE ".$this->_match($queryStr, $indexOnly); 00198 $from = trim( $this->_from($this->_table->tablename)); 00199 $order = trim($this->_orderby($queryParams)); 00200 $limit = trim($this->_limit($queryParams, $nolimit)); 00201 if ( strlen($from)>0 ) $ret .= ' '.$from; 00202 if ( strlen($where)>0 ) $ret .= ' '.$where; 00203 if ( strlen($order)>0 ) $ret .= ' '.$order; 00204 if ( strlen($limit)>0 ) $ret .= ' '.$limit; 00205 $this->action = null; 00206 return $ret; 00207 00208 00209 } 00215 function select($columns='', $query=array(), $nolimit=false, $tablename=null, $preview=false){ 00216 $this->action="select"; 00217 if ( !is_array($query) ){ 00218 $query = array(); 00219 } 00220 $query = array_merge( $this->_query, $query); 00221 00222 $ret = $this->_select($columns, array(), $preview); 00223 if ( PEAR::isError($ret) ){ 00224 $ret->addUserInfo("Failed to select columns in select() "); 00225 00226 return $ret; 00227 } 00228 $from = trim($this->_from($tablename)); 00229 $where = trim($this->_where($query)); 00230 $where = $this->_secure($where); 00231 //$having = $this->_having($query); 00232 00233 $order = trim($this->_orderby($query)); 00234 $limit = trim($this->_limit($query, $nolimit)); 00235 00236 if ( strlen($from)>0 ) $ret .= ' '.$from; 00237 if ( strlen($where)>0 ) $ret .= ' '.$where; 00238 //if ( strlen($having)>0 ) $ret .= ' '.$having; 00239 if ( strlen($order)>0 ) $ret .= ' '.$order; 00240 if ( strlen($limit)>0 ) $ret .= ' '.$limit; 00241 $this->action = null; 00242 //echo $ret; 00243 return $ret; 00244 } 00245 00252 function select_num_rows($query=array(), $tablename=null){ 00253 00254 $this->action='select_num_rows'; 00255 $query = array_merge( $this->_query, $query); 00256 $ret = 'SELECT COUNT(*) as num'; 00257 $from = $this->_from($this->tablename($tablename)); 00258 $where = $this->_where($query); 00259 $where = $this->_secure($where); 00260 00261 if ( strlen($from)>0 ) $ret .= ' '.$from; 00262 if ( strlen($where)>0 ) $ret .= ' '.$where; 00263 $this->action = null; 00264 return trim($ret); 00265 00266 00267 } 00268 00269 00276 function update(&$record, $key_vals=null, $tablename=null){ 00277 $app =& Dataface_Application::getInstance(); 00278 $this->action='update'; 00279 // Step 1: Make sure that the input is valid 00280 if ( !is_a($record, "Dataface_Record") ){ 00281 throw new Exception("Attempt to use QueryBuilder::update() where something other than a Dataface_Record object is defined.", E_USER_ERROR); 00282 } 00283 00284 // Step 2: Start building the sql query. We use an array to build up the query, but 00285 // string concatenation would work just as well. We switched to arrays in an attempt to 00286 // fix a bug related to updating BLOB fields, but it turned out that the problem was different. 00287 $tableObj =& Dataface_Table::loadTable($this->tablename($tablename)); 00288 $dbObj =& Dataface_DB::getInstance(); 00289 $sql = array(); 00290 $sql[] = "UPDATE `".$this->tablename($tablename)."` SET "; 00291 $fieldnames = array_keys($this->_mutableFields); 00292 // Get all of the field names in this table. 00293 $keys = array_keys($this->_table->keys()); 00294 // Get the names of the key fields in this table 00295 $changed = false; 00296 // A flag to indicate whether the record has been changed. 00297 foreach ($fieldnames as $fieldname){ 00298 if ( isset($fieldArr) ) unset($fieldArr); 00299 $fieldArr =& $tableObj->getField($fieldname); 00300 if ( @$fieldArr['ignore'] ) continue; 00301 if ( !$record->valueChanged($fieldname) and !(isset($fieldArr['timestamp']) and strtolower($fieldArr['timestamp']) == 'update') ) { 00302 // If this field has not been changed then we don't need to update it. 00303 // Note that in order for valueChanged() to work properly, the Dataface_Record::setSnapshot() 00304 // method must be called at somepoint to indicate that that snapshot represents the last unchanged 00305 // state of the record. 00306 continue; 00307 } 00308 //echo "$fieldname changed\n"; 00309 00310 // If we made it this far, then the current field has indeed changed 00311 $changed = true; 00312 00313 $sval = $this->_serializer->serialize($fieldname, $record->getValue($fieldname)); 00314 // Serialize the field's value to prepare it for the database 00315 if ( !isset($sval) and @$fieldArr['timestamp'] != 'update' ){ 00316 $sql[] = "`$fieldname` = NULL, "; 00317 } else if ( $tableObj->isBlob($fieldname) and @$app->_conf['multilingual_content']){ 00318 // This is a blob column... we don't place the data directly in the String because it would take 00319 // too long to parse when Dataface_DB needs to parse it. 00320 // Instead we register the BLOB and store its id number. 00321 $blobID = $dbObj->registerBlob($sval); 00322 $sql[] = "`$fieldname` = '-=-=B".$blobID."=-=-', "; 00323 } else if ( $tableObj->isDate($fieldname) and 00324 isset($fieldArr['timestamp']) and 00325 strtolower($fieldArr['timestamp']) == 'update'){ 00326 $sql[] = "`$fieldname` = NOW(), "; 00327 00328 } else { 00329 $sql[] = "`$fieldname` = ".$this->prepareValue($fieldname, $sval).", "; 00330 } 00331 } 00332 if ( !$changed ) return ''; 00333 // If no fields have changed, then we will just return an empty string for the query. 00334 00335 00336 $sql[count($sql)-1] = substr($sql[count($sql)-1], 0, strlen($sql[count($sql)-1])-2); 00337 // chop off the trailing comma from the update clause 00338 $vals = $record->snapshotExists() ? $record->snapshotKeys() : $record->getValues( array_keys($this->_table->keys())); 00339 // If a snapshot has been set we will use its key values in the where clause 00340 00341 if ( $key_vals === null ){ 00342 $query = unserialize(serialize($vals)); 00343 foreach ( array_keys($query) as $qkey){ 00344 $query[$qkey] = "=".$this->_serializer->serialize($qkey, $query[$qkey]); 00345 } 00346 } else { 00347 $query = $key_vals; 00348 foreach (array_keys($query) as $qkey){ 00349 $query[$qkey] = "=".$query[$qkey]; 00350 } 00351 } 00352 00353 $sql[] = " ".$this->_where($query); 00354 $sql[] = " LIMIT 1"; 00355 00356 00357 00358 $sql = implode($sql); 00359 //echo $sql; 00360 $this->action = null; 00361 return $sql; 00362 00363 } 00364 00365 00366 00367 function insert(&$record, $tablename=null){ 00368 $app =& Dataface_Application::getInstance(); 00369 $this->action = 'insert'; 00370 if ( !is_a($record, "Dataface_Record") ){ 00371 throw new Exception("First argument to QueryBuilder::insert() must be of type Dataface_Record, but received ".get_class($record), E_USER_ERROR); 00372 } 00373 // the keys are not complete... so this item does not exist.. create new record. 00374 $tableObj =& Dataface_Table::loadTable($this->tablename($tablename)); 00375 $dbObj =& Dataface_DB::getInstance(); 00376 $fields = array_keys($this->_mutableFields); 00377 $keys =& $this->_table->keys(); 00378 $insertedKeys = array(); 00379 $insertedValues = array(); 00380 00381 foreach ($this->_mutableFields as $key=>$field){ 00382 if ( @$field['ignore'] ) continue; 00383 if ( $tableObj->isDate($key) ){ 00384 // We must take special care for dates. 00385 if (isset($fieldArr)) unset($fieldArr); 00386 $fieldArr =& $tableObj->getField($key); 00387 if ( isset($fieldArr['timestamp']) and in_array(strtolower($fieldArr['timestamp']), array('insert','update')) ){ 00388 $insertedKeys[] = '`'.$key.'`'; 00389 $insertedValues[] = 'NOW()'; 00390 00391 continue; 00392 } 00393 } 00394 if ( !$record->hasValue($key) ) continue; 00395 $val = $record->getValue($key); 00396 if ( strtolower($this->_mutableFields[$key]['Extra']) == 'auto_increment' && !$val ){ 00397 // This is a MySQL 5 fix. In MySQL 5 it doesn't like it when you put blank values into 00398 // auto increment fields. 00399 continue; 00400 } 00401 if ( !isset($val)) continue; 00402 $sval = $this->_serializer->serialize($key, $record->getValue($key) ); 00403 //if ( !$field['value'] && in_array($key, array_keys($keys)) ) continue; 00404 if ( $tableObj->isBlob($key) and @$app->_conf['multilingual_content'] ){ 00405 $blobID = $dbObj->registerBlob($sval); 00406 $sval2 = "-=-=B".$blobID."=-=-"; 00407 } else { 00408 $sval2 = $sval; 00409 } 00410 00411 if ( strlen(strval($sval2)) == 0 and strtolower($this->_mutableFields[$key]['Null']) == 'yes' ){ 00412 $insertedKeys[] = '`'.$key.'`'; 00413 $insertedValues[] = 'NULL'; 00414 //$sql .= 'NULL,'; 00415 } else { 00416 $insertedKeys[] = '`'.$key.'`'; 00417 00418 $insertedValues[] = $this->prepareValue($key,$sval2); 00419 //$sql .= "'".addslashes($sval2)."',"; 00420 } 00421 } 00422 $sql = "INSERT INTO `".$this->tablename($tablename)."` (". 00423 implode(',', $insertedKeys).') VALUES ('. 00424 implode(',', $insertedValues).')'; 00425 $this->action = null; 00426 return $sql; 00427 } 00428 00429 00430 function prepareValue($fieldname, $value,$serialize=false){ 00431 $quotes = true; 00432 if ( $serialize ) $value = $this->_serializer->serialize($fieldname, $value); 00433 if ( in_array( strtolower($this->_table->getType($fieldname)), array('timestamp','datetime')) ){ 00434 $value = "convert_tz('".addslashes($value)."','".addslashes(df_utc_offset())."','SYSTEM')"; 00435 $quotes = false; 00436 } 00437 if ( $quotes ) $value = "'".addslashes($value)."'"; 00438 $value = $this->_serializer->encrypt($fieldname,$value); 00439 return $value; 00440 00441 } 00442 00443 00444 00445 function delete($query=array(), $nolimit=false, $tablename=null){ 00446 $this->action = 'delete'; 00447 if ( !isset($tablename) ) $tablename = $this->_table->tablename; 00448 $table =& Dataface_Table::loadTable($tablename); 00449 00450 $query = array_merge($this->_query, $query); 00451 $tsql=$table->sql(); 00452 $parent =& $table->getParent(); 00453 if ( isset($tsql) or isset($parent)){ 00454 $talias = $tablename.'__dforiginal__'; 00455 $joinclause = array(); 00456 foreach ( array_keys($table->keys() ) as $tkey){ 00457 $joinclause[] = "`$talias`.`$tkey`=`$tablename`.`$tkey`"; 00458 } 00459 $joinclause = implode(' AND ', $joinclause); 00460 $from = "FROM `{$talias}` USING `{$tablename}` as `{$talias}` left join ".substr($this->_from($tablename), 5)." on ($joinclause)"; 00461 00462 } else { 00463 $from = $this->_from($tablename); 00464 00465 } 00466 $where = $this->_where($query); 00467 $limit = $this->_limit($query, $nolimit); 00468 $ret = "DELETE ".$from; 00469 00470 if ( strlen($where)>0 ) $ret .= ' '.$where; 00471 if ( strlen($limit)>0 ) $ret .= ' '.$limit; 00472 $this->action = null; 00473 return trim($ret); 00474 00475 00476 } 00477 00478 function wc($tablename, $colname){ 00479 if ( in_array($this->action, array('select','delete', 'select_num_rows')) ){ 00480 return "`{$tablename}`.`{$colname}`"; 00481 } else { 00482 return "`{$colname}`"; 00483 } 00484 } 00485 00486 function _fieldWhereClause(&$field, $value, &$use_where, $tableAlias=null){ 00487 $key = $field['Field']; 00488 $where = ''; 00489 $table =& Dataface_Table::loadTable($field['tablename']); 00490 $changeTable = false; 00491 if ( $this->_table->tablename != $table->tablename ){ 00492 $changeTable = true; 00493 $oldTable =& $this->_table; 00494 unset($this->_table); 00495 $this->_table =& $table; 00496 $oldSerializer =& $this->_serializer; 00497 unset($this->_serializer); 00498 $this->_serializer = new Dataface_Serializer($table->tablename); 00499 } 00500 if ( !isset($tableAlias) ) $tableAlias = $table->tablename; 00501 if ( is_array($value) ){ 00502 throw new Exception("Attempt to use array in query clause"); 00503 } 00504 $words = explode(' OR ', $value); 00505 if ( count($words) > 1){ 00506 $where .= '('; 00507 $conj = 'OR'; 00508 } else { 00509 $conj = 'AND'; 00510 } 00511 00512 // A value with a prefix of '<>' indicates we are searching for values NOT equal to... 00513 if ( isset($field['repeat']) and $field['repeat']){ 00514 $repeat = true; 00515 00516 } else { 00517 $repeat = false; 00518 } 00519 foreach ($words as $value){ 00520 if ( $value === '' ) continue; 00521 // A value with a prefix of '=' indicates that this is an exact match 00522 if ( $value{0}=='=' ){ 00523 $exact = true; 00524 $value = substr($value,1); 00525 } else { 00526 $exact = false; 00527 } 00528 $factors = explode(' AND ', $value); 00529 if ( count($factors) > 1 ){ 00530 $where .= '('; 00531 } 00532 foreach ($factors as $value){ 00533 if ( !$exact and (strpos($value, '!=')===0 or strpos($value, '<>') === 0)){ 00534 $value = substr($value, 2); 00535 $value = $this->prepareValue( $key, $table->parse($key, $value), true ); 00536 if ( $repeat ){ 00537 $where .= $this->wc($tableAlias, $key)." NOT RLIKE CONCAT('[[:<:]]',$value,'[[:>:]]') AND "; 00538 } else { 00539 $where .= $this->wc($tableAlias, $key)." <> $value AND "; 00540 } 00541 00542 // A value with a prefix of '<' indicates that we are searching for values less than 00543 // a field. 00544 } else if ( !$exact and strpos($value,'<')===0){ 00545 if ( strpos($value,'=') === 1 ){ 00546 $value = substr($value,2); 00547 $op = '<='; 00548 } else { 00549 $value = substr($value, 1); 00550 $op = '<'; 00551 } 00552 $value = $this->prepareValue( $key, $table->parse($key, $value), true ); 00553 $where .= $this->wc($tableAlias, $key)." $op $value AND "; 00554 00555 // A value with a prefix of '>' indicates a greater than search 00556 } else if ( !$exact and strpos($value, '>')===0 ) { 00557 if ( strpos($value,'=') === 1 ){ 00558 $value = substr($value,2); 00559 $op = '>='; 00560 } else { 00561 $value = substr($value, 1); 00562 $op = '>'; 00563 } 00564 $value = $this->prepareValue( $key, $table->parse($key, $value), true ); 00565 $where .= $this->wc($tableAlias, $key)." $op $value AND "; 00566 00567 00568 // If the query term has '..' any where it is interpreted as a range search 00569 } else if ( !$exact and strpos($value, '..')> 0 ){ 00570 list($low,$high) = explode('..',$value); 00571 $low = trim($low); $high = trim($high); 00572 $low = $this->prepareValue( $key, $table->parse($key, $low), true); 00573 $high = $this->prepareValue( $key, $table->parse($key, $high), true); 00574 $where .= $this->wc($tableAlias, $key)." >= $low AND ".$this->wc($tableAlias, $key)." <= $high AND "; 00575 } else if ( !$exact and strpos($value, '~') === 0 ){ 00576 $value = substr($value,1); 00577 $oldval = $value; 00578 $oper = 'LIKE'; 00579 $value = $this->prepareValue( $key, $table->parse($key, $value), true); 00580 if ( strlen($oldval) > 0 ){ 00581 $where .= $this->wc($tableAlias,$key)." $oper $value AND "; 00582 } else { 00583 $where .= '('.$this->wc($tableAlias,$key)." $oper '' OR ".$this->wc($tableAlias,$key)." IS NULL) AND "; 00584 } 00585 00586 00587 } else if ( $repeat ){ 00588 $value = $this->prepareValue( $key, $table->parse($key, $value), true); 00589 $where .= $this->wc($tableAlias, $key)." RLIKE CONCAT('[[:<:]]',$value,'[[:>:]]') AND "; 00590 } 00591 00592 else if ( $this->_exactMatches || preg_match( '/int/i', $field['Type']) || $exact ){ 00593 $oldval = $value; 00594 $oper = '='; 00595 $value = $this->prepareValue( $key, $table->parse($key, $value), true); 00596 if ( strlen($oldval) > 0 ){ 00597 $where .= $this->wc($tableAlias,$key)." $oper $value AND "; 00598 } else { 00599 $where .= '('.$this->wc($tableAlias,$key)." $oper '' OR ".$this->wc($tableAlias,$key)." IS NULL) AND "; 00600 } 00601 } else { 00602 $value = $this->prepareValue( $key, $table->parse($key, $value), true); 00603 $where .= $this->wc($tableAlias, $key)." LIKE CONCAT('%',$value,'%') AND "; 00604 } 00605 $use_where = true; 00606 } 00607 $where = substr($where, 0, strlen($where)-5); 00608 if (count($factors) > 1){ 00609 00610 $where .= ')'; 00611 } 00612 $where .= ' OR '; 00613 00614 00615 } 00616 $where = substr($where, 0, strlen($where)-4); 00617 if ( count($words) > 1){ 00618 00619 $where .= ')'; 00620 } 00621 00622 if ($changeTable){ 00623 unset($this->_table); 00624 $this->_table =& $oldTable; 00625 unset($this->_serializer); 00626 $this->_serializer =& $oldSerializer; 00627 } 00628 return $where; 00629 00630 } 00631 00632 00638 function _where($query=array(), $merge=true){ 00639 if ( $merge ){ 00640 $query = array_merge( $this->_query, $query); 00641 00642 } 00643 foreach ($query as $key=>$value) { 00644 if ( $value === null or $value === '' ){ 00645 unset($query[$key]); 00646 } 00647 } 00648 00649 if ( isset($query['__id__']) ){ 00650 $keys = array_keys($this->_table->keys()); 00651 if ( $keys ){ 00652 $query[$keys[0]] = $query['__id__']; 00653 unset($query['__id__']); 00654 } 00655 } 00656 00657 00658 00659 $where = "WHERE "; 00660 $missing_key = false; 00661 $limit = ''; 00662 $use_where = false; 00663 00664 $fields = array(); 00665 //print_r($query); 00666 foreach ($query as $key=>$value){ 00667 if ( strpos($key,'-') !== 0 ) $fields[$key] = $value; 00668 } 00669 foreach ($fields as $key=>$value){ 00670 if ( isset($this->_fields[$key]) ){ 00671 $field =& $this->_fields[$key]; 00672 if ( !@$field['not_findable'] ){ 00673 $where .= $this->_fieldWhereClause($field, $value, $use_where, $this->_tablename).' AND '; 00674 } 00675 unset($field); 00676 } 00677 00678 } 00679 $charFields = $this->_table->getCharFields(true, true); 00680 if ( isset( $query['-search'] ) and strlen($query['-search']) and count($charFields)>0 ){ 00681 $status = $this->_table->getStatus(); 00682 //if ( $status['Engine'] == 'MyISAM' ){ 00683 // // MyISAM has a match clause. that works quite well. 00684 // $where .= $this->_match($query['-search'])." AND "; 00685 //} else { 00686 // // If the table type is not MyISAM, then we need to manually do the multi-field search. 00687 $words = explode(' ', $query['-search']); 00688 foreach ( $words as $word ){ 00689 $where .= '(`'.implode('` LIKE \'%'.addslashes($word).'%\' OR `', $charFields).'` LIKE \'%'.addslashes($word).'%\') AND '; 00690 // } 00691 } 00692 00693 $use_where = true; 00694 } 00695 00696 if ( $this->metadata ){ 00697 $wfkeys = preg_grep('/^_metadata::/', array_keys($query)); 00698 $clause = array(); 00699 foreach ($wfkeys as $wfkey){ 00700 $wf_col = substr($wfkey,11); 00701 if ( !$this->_table->fieldExists($wf_col) ) continue; 00702 $wf_col = $this->_tablename."__metadata.__{$wf_col}"; 00703 $clause[] = "`{$wf_col}`='".addslashes($query[$wfkey])."'"; 00704 } 00705 if ( count($clause)>0 ){ 00706 $use_where = true; 00707 $where .= implode(' AND ', $clause).' AND '; 00708 } 00709 } 00710 00711 // Now we will search related fields 00712 $rkeys = preg_grep('/^[^\-].*\/.*$/', array_keys($query)); 00713 00714 $rquery = array(); 00715 foreach ($rkeys as $rkey ){ 00716 list($relationship, $rfield) = explode('/', $rkey); 00717 $rquery[$relationship][] = $rfield; 00718 00719 } 00720 00721 foreach ( $rquery as $rname=>$rfields){ 00722 $r =& $this->_table->getRelationship($rname); 00723 if ( PEAR::isError($r) ){ 00724 unset($r); 00725 continue; 00726 } 00727 00728 00729 $pairs=array(); 00730 foreach ( $rfields as $rfield ){ 00731 $rfieldDef =& $r->getField($rfield); 00732 $q = $query[$rname.'/'.$rfield]; 00733 $ralias = $r->getTableAlias($rfieldDef['tablename']); 00734 if ( !$ralias ) $ralias = null; 00735 $pairs[] = $this->_fieldWhereClause($rfieldDef, $q, $use_where, $ralias ); 00736 unset($rfieldDef); 00737 00738 //$pairs[] = '`'.str_replace('`','',$rfield).'` LIKE \'%'.addslashes($query[$rname.'/'.$rfield]).'%\''; 00739 } 00740 if ( $pairs ){ 00741 $subwhere = ' AND '.implode(' AND ',$pairs); 00742 } 00743 00744 $sql = $r->getSQL(); 00745 00746 $fkeys =& $r->getForeignKeyValues(); 00747 foreach ( $fkeys as $tname=>$tfields ){ 00748 foreach ( $tfields as $tval ){ 00749 if ( !is_scalar($tval) ) continue; 00750 if ( strlen($tval) > 0 ) $tval = substr($tval,1); 00751 $sql = preg_replace('/[\'"]?\$('.preg_quote($tval).')[\'"]?/', '`'.str_replace('`','',$this->_table->tablename).'`.`\1`', $sql); 00752 } 00753 } 00754 $where .= 'EXISTS ('.$sql.$subwhere.') AND '; 00755 $use_where = true; 00756 unset($r); 00757 unset($fkeys); 00758 } 00759 00760 00761 00762 if ( $use_where ){ 00763 00764 $where = substr($where,0, strlen($where)-5); 00765 } else { 00766 $where = ''; 00767 } 00768 00769 return $where; 00770 } 00771 00772 00773 00778 function _from($tablename=null){ 00779 $app =& Dataface_Application::getInstance(); 00780 if ( !isset($tablename) ) $tablename = $this->_table->tablename; 00781 00782 $table =& Dataface_Table::loadTable($tablename); 00783 $proxyView = $table->getProxyView(); 00784 $tsql = $table->sql(); 00785 $fromq = ''; 00786 if ( $proxyView ){ 00787 $fromq = "`".$proxyView."`"; 00788 } else if ( isset($tsql) ){ 00789 $fromq = "(".$tsql.")"; 00790 } else { 00791 $fromq = "`".$this->tablename($tablename)."`"; 00792 } 00793 00794 $parent =& $table->getParent(); 00795 if ( isset($parent) ){ 00796 $qb2 = new Dataface_QueryBuilder($parent->tablename); 00797 $pfrom = $qb2->_from(); 00798 $as_pos = ( ( strpos(strtolower($pfrom), ' as ') !== false ) ? (strlen($pfrom) - strpos(strrev(strtolower($pfrom)), ' sa ' )-3) : false); 00799 if ( $as_pos !== false ){ 00800 $pfrom = substr($pfrom, strlen('FROM '), $as_pos-strlen('FROM ')); 00801 } else { 00802 $pfrom = substr($pfrom, strlen('FROM ')); 00803 } 00804 $pkeys = array_keys($parent->keys()); 00805 $ckeys = array_keys($table->keys()); 00806 $joinq = array(); 00807 for ($i=0; $i<count($pkeys); $i++){ 00808 $joinq[] = '`t___child`.`'.$ckeys[$i].'`=`t___parent`.`'.$pkeys[$i].'`'; 00809 } 00810 $joinq = implode(' and ', $joinq); 00811 00812 00813 $out = "FROM (select * from ".$fromq." as `t___child` left join ".$pfrom." as `t___parent` on ($joinq)) as `".$this->tablename($tablename)."`" ; 00814 00815 00816 } else if ( isset($tsql) or isset($proxyView) ){ 00817 $out = "FROM ".$fromq." as `".$this->tablename($tablename)."`"; 00818 } else { 00819 $out = "FROM ".$fromq; 00820 } 00821 00822 00823 00824 00825 if ( $this->metadata and $this->action == 'select') { 00826 $out .= " LEFT JOIN `{$tablename}__metadata` ON "; 00827 $keys = array_keys($table->keys()); 00828 if ( count($keys) == 0 ) throw new Exception("The table '".$tablename."' has no primary key.", E_USER_ERROR); 00829 00830 $clauses = array(); 00831 foreach ( $keys as $key ){ 00832 $clauses[] = "`{$tablename}`.`{$key}`=`{$tablename}__metadata`.`{$key}`"; 00833 } 00834 $out .= "(".implode(' and ', $clauses).")"; 00835 } 00836 return $out; 00837 } 00838 00839 00845 function _select($columns='', $query=array(), $preview=false, $previewLen=null){ 00846 if ( !isset($previewLen) and defined('XATAFACE_DEFAULT_PREVIEW_LENGTH') and is_int(XATAFACE_DEFAULT_PREVIEW_LENGTH) ){ 00847 $previewLen = XATAFACE_DEFAULT_PREVIEW_LENGTH; 00848 } 00849 if ( !is_int($previewLen) ) $previewLen = 255; 00850 $app =& Dataface_Application::getInstance(); 00851 $query = array_merge( $this->_query, $query); 00852 foreach ($query as $key=>$value) { 00853 if ( $value === null ){ 00854 unset($query[$key]); 00855 } 00856 } 00857 $select = "SELECT "; 00858 $colcount = 0; 00859 foreach ($this->_fields as $key=>$field){ 00860 if ( $this->selectMetaData ){ 00861 $select .= "length(`{$this->_tablename}`.`".$key."`) as `__".$key."_length`,"; 00862 00863 } 00864 if ( is_array($columns) and !in_array($key, $columns) ) continue; 00865 // if the columns array is set then we only return the columns listed in that array. 00866 00867 00868 00869 if ( $this->_omitBlobs and $this->_table->isBlob($field['name']) ) continue; 00870 // if the omitBlobs flag is set then we don't select blob columns 00871 if ( $this->_omitPasswords and $this->_table->isPassword($field['name']) ) continue; 00872 // if the omitPasswords flag is set then we don't select password columns 00873 if ( $preview and $this->_table->isText($field['name']) and !@$field['struct'] and !$this->_table->isXML($field['name'])) 00874 $select .= "SUBSTRING(`{$this->_tablename}`.`$key`, 1, ".$previewLen.") as `$key`,"; 00875 else if ( in_array(strtolower($this->_table->getType($key)),array('datetime','timestamp')) ) 00876 $select .= "convert_tz(`".$this->_tablename."`.`".$key."`, 'SYSTEM', '".df_utc_offset()."') as `$key`,"; 00877 else 00878 $select .= "`{$this->_tablename}`.`$key`,"; 00879 $colcount++; 00880 00881 } 00882 if ( $this->metadata) { 00883 $clauses = array(); 00884 foreach ( $this->_table->getMetadataColumns() as $mdc ){ 00885 $clauses[] = "`{$this->_tablename}__metadata`.`{$mdc}`"; 00886 } 00887 $select .= implode(',',$clauses).','; 00888 00889 } 00890 00891 if ( $colcount == 0 ){ 00892 return PEAR::raiseError(QUERYBUILDER_ERROR_EMPTY_SELECT, null,null,null, "No columns were selected in select statement. Make sure that _omitBlobs property is disabled in QueryBuilder object if you are only wanting to return Blob columns."); 00893 } 00894 $select = substr($select, 0, strlen($select) -1); 00895 return $select; 00896 } 00897 00902 function _limit($query=array(), $nolimit=false){ 00903 if ( $nolimit ) return ''; 00904 $query = array_merge( $this->_query, $query); 00905 foreach ($query as $key=>$value) { 00906 if ( $value === null ){ 00907 unset($query[$key]); 00908 } 00909 } 00910 00911 $limit = ''; 00912 if ( isset( $query['-limit']) && isset($query['-skip'] ) ){ 00913 if ( preg_match('/^[0-9]+$/',$query['-limit']) && 00914 preg_match('/^[0-9]+$/',$query['-skip']) ){ 00915 $limit = "LIMIT ".$query['-skip'].",".$query['-limit']; 00916 } 00917 } else if ( isset( $query['-limit'] ) ){ 00918 if ( preg_match('/^[0-9]+$/', $query['-limit']) ){ 00919 $limit = "LIMIT ".$query['-limit']; 00920 } 00921 } else if ( isset( $query['-skip']) ){ 00922 if ( preg_match('/^[0-9]+$/', $query['-skip']) ){ 00923 $limit = "LIMIT ".$query['-skip'].", 100"; 00924 } 00925 } 00926 return $limit; 00927 } 00928 00929 00933 function _orderby($query = array()){ 00934 $query = array_merge( $this->_query, $query); 00935 foreach ($query as $key=>$value) { 00936 if ( $value === null ){ 00937 unset($query[$key]); 00938 } 00939 } 00940 00941 if ( isset($query['-sort']) ){ 00942 00943 return 'ORDER BY '.preg_replace_callback('/\b(\w+?)\b/',array(&$this, '_mysql_quote_idents'), $query['-sort']); 00944 } 00945 return ''; 00946 00947 } 00948 00949 function _mysql_quote_idents($matches){ 00950 if (!in_array(strtolower($matches[1]), array('asc','desc') ) ){ 00951 return '`'.((strpos($matches[1],'.') === false) ?"{$this->_tablename}`.`":'').$matches[1].'`'; 00952 } else { 00953 return $matches[1]; 00954 } 00955 } 00956 00974 function _match($queryStr){ 00975 $version = mysql_get_server_info(); 00976 $matches = array(); 00977 preg_match('/(\d+)\.(\d)+\.(\d)+/', $version, $matches); 00978 $majorVersion = intval($matches[1]); 00979 00980 // We want to escape illegal characters, but in a boolean search 00981 // double quotes are allowed so we much unescape them. 00982 $queryStr = addslashes($queryStr); 00983 $queryStr = str_replace('\"', '"', $queryStr); 00984 00985 $out = 'MATCH ('; 00986 00987 // We have at least version 4 so we can do boolean searches 00988 $indexedFields =& $this->_table->getFullTextIndexedFields(); 00989 if ( count($indexedFields)>0){ 00990 $fields =& $indexedFields; 00991 } else { 00992 // There are no indexed fields so we will just do a search on all character fields. 00993 $fields =& $this->_table->getCharFields(); 00994 } 00995 00996 00997 $empty = true; 00998 // flag to indicate if the query will be empty 00999 foreach ($fields as $field){ 01000 $out .= "`{$this->_tablename}`.`$field`,"; 01001 $empty = false; 01002 // the query is NOT empty 01003 } 01004 01005 if ( $empty ){ 01006 throw new Exception("Query attempted when no queryable columns are available in table '".$this->_table->tablename."'. Only tables with a full-text search defined on at least one column are eligiblle to be searched in this way.", E_USER_ERROR); 01007 } 01008 01009 $out = substr($out, 0, strlen($out)-1); 01010 01011 $out .= ") AGAINST ('$queryStr'"; 01012 if ( $majorVersion >= 4 ) { 01013 $out .= " IN BOOLEAN MODE"; 01014 } 01015 $out .= ")"; 01016 01017 return $out; 01018 01019 } 01020 01021 01022 function omitBlobs(){ 01023 01024 $this->_omitBlobs = true; 01025 } 01026 01027 function includeBlobs(){ 01028 $this->_omitBlobs = false; 01029 } 01030 01031 01032 function addSecurityConstraint($key, $value){ 01033 01034 $this->_security[$key] = $value; 01035 01036 } 01037 01038 function addSecurityConstraints($constraints){ 01039 01040 $this->_security = array_merge($this->_security, $constraints); 01041 } 01042 01043 01044 function removeSecurityConstraint($key){ 01045 unset( $this->_security[$key] ); 01046 } 01047 01048 function setSecurityConstraints( $constraints ){ 01049 $this->_security = $constraints; 01050 } 01051 01052 function _secure($where){ 01053 01054 $swhere = $this->_where($this->_table->getSecurityFilter($this->_security), false); 01055 // get rid of the leading "where" 01056 $swhere = trim(substr($swhere, 5, strlen($swhere)-5)); 01057 01058 $where = trim($where); 01059 if ( strlen($where)>0 ){ 01060 if (strlen($swhere)>0) { 01061 $where .= " AND ".$swhere; 01062 } 01063 } else if ( strlen($swhere)>0){ 01064 $where = "WHERE $swhere"; 01065 } 01066 return $where; 01067 01068 01069 01070 } 01071 01072 01076 function addRelatedRecord(&$relatedRecord, $sql=null){ 01077 if ( !is_a($relatedRecord, 'Dataface_RelatedRecord') ){ 01078 throw new Exception("In QueryBuilder::addRelatedRecord() expecting first argument to be type 'Dataface_RelatedRecord' but received '".get_class($relatedRecord)."'\n<br>", E_USER_ERROR); 01079 } 01080 $relationship =& $relatedRecord->_relationship; 01081 $table_cols = $relatedRecord->getForeignKeyValues( $sql); 01082 if ( count($this->errors) > 0 ){ 01083 $error = array_pop($this->errors); 01084 $error->addUserInfo("Error getting foreign key values for relationship '$relationship_name'"); 01085 throw new Exception($error->toString()); 01086 } 01087 01088 01089 $sql = array(); 01090 01091 // now generate the sql 01092 // We will go through each table and insert the record for that 01093 // table separately. 01094 foreach ( $table_cols as $table=>$cols ){ 01095 if ( isset($recordObj) ) unset($recordObj); 01096 $recordObj = new Dataface_Record($table, $cols); 01097 $recordVals =& $recordObj->vals(); 01098 if ( isset( $recordVals[ $recordObj->_table->getAutoIncrementField() ] ) ){ 01099 // We don't want the auto-increment field to be inserted - though it may 01100 // have a placeholder value. 01101 $recordObj->setValue($recordObj->_table->getAutoIncrementField(), null); 01102 } 01103 $qb = new Dataface_QueryBuilder($table); 01104 $sql[$table] = $qb->insert($recordObj); 01105 01106 } 01107 01108 return $sql; 01109 01110 } 01111 01112 01113 function addExistingRelatedRecord(&$relatedRecord){ 01114 $record =& $relatedRecord->_record; 01115 $relationshipName =& $relatedRecord->_relationshipName; 01116 $values =& $relatedRecord->getAbsoluteValues(true); 01117 if ( !is_a($record, 'Dataface_Record') ){ 01118 throw new Exception("In Dataface_QueryBuilder::addExistingRelatedRecord() expected first argument to be of type 'Dataface_Record' but received '".get_class($record)."'.\n<br>", E_USER_ERROR); 01119 } 01120 if ( !is_array($values) ){ 01121 throw new Exception("In Dataface_QueryBuilder::addExistingRelatedRecord() expected third argument to be an array but received a scalar.", E_USER_ERROR); 01122 } 01123 $relationship =& $record->_table->getRelationship($relationshipName); 01124 $foreignKeys = $relationship->getForeignKeyValues(); 01125 $foreignKeys_withValues = $relatedRecord->getForeignKeyValues(); 01126 01127 if ( count($this->errors) > 0 ){ 01128 $error = array_pop($this->errors); 01129 $error->addUserInfo("Error getting foreign key values for relationship '$relationship_name'"); 01130 throw new Exception($error->toString()); 01131 } 01132 01133 $sql = array(); 01134 foreach ($foreignKeys as $table=>$cols){ 01135 $skip = true; 01136 foreach ($cols as $field_name=>$field_value){ 01137 if ( $field_value != "__".$table."__auto_increment__" ) { 01138 $skip = false; 01139 break; 01140 } 01141 } 01142 if ( $skip ) continue; 01143 $cols = $foreignKeys_withValues[$table]; 01144 if ( isset($recordObj) ) unset($recordObj); 01145 $recordObj = new Dataface_Record($table, $cols); 01146 $recordVals =& $recordObj->vals(); 01147 if ( isset( $recordVals[ $recordObj->_table->getAutoIncrementField() ] ) ){ 01148 // We don't want the auto-increment field to be inserted - though it may 01149 // have a placeholder value. 01150 $recordObj->setValue($recordObj->_table->getAutoIncrementField(), null); 01151 } 01152 $qb = new Dataface_QueryBuilder($table); 01153 $sql[$table] = $qb->insert($recordObj); 01154 /* 01155 $skip = true; 01156 // indicator to say whether or not to skip this table 01157 // we skip the table if it contains an unresolved autoincrement value 01158 01159 foreach ($cols as $field_name=>$field_value){ 01160 if ( $field_value != "__".$table."__auto_increment__" ) { 01161 $skip = false; 01162 break; 01163 } 01164 } 01165 01166 if ( $skip == true ) continue; 01167 01168 01169 $cols = $foreignKeys_withValues[$table]; 01170 01171 01172 $query = "INSERT INTO `$table`"; 01173 $colnames = ""; 01174 $colvals = ""; 01175 01176 foreach ( $cols as $colname=>$colval){ 01177 $colnames .= $colname.','; 01178 $colvals .= "'".addslashes($colval)."',"; 01179 } 01180 01181 $colnames = substr($colnames, 0, strlen($colnames)-1); 01182 $colvals = substr($colvals, 0, strlen($colvals)-1); 01183 01184 $query .= " ($colnames) VALUES ($colvals)"; 01185 01186 $sql[$table] = $query; 01187 */ 01188 01189 } 01190 01191 return $sql; 01192 01193 01194 01195 } 01196 01197 function tablename($tablename=null){ 01198 if ( $tablename === null ) return $this->_tablename; 01199 return $tablename; 01200 01201 } 01202 01203 01204 01205 01206 01207 }