![]() |
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 */ 00031 import( 'Dataface/Application.php'); 00032 import('Dataface/Table.php'); 00033 00034 class Dataface_DB { 00035 var $_db; 00036 var $_fieldIndex = array(); 00037 var $_queryCache = array(); 00038 var $_parser = null; 00039 var $_compiler = null; 00040 var $_cache = null; 00041 var $_cacheDirtyFlag = false; 00042 var $app; 00043 var $_matchCount; 00044 var $_matches; 00045 var $_insert_id; 00046 var $count=0; 00047 var $_fcache_base_path= null; 00048 var $db_hits = 0; 00049 var $cache_hits = 0; 00050 var $cache_fails = 0; 00051 00052 var $blobs = array(); // Blobs. 00053 00054 function Dataface_DB($db=null){ 00055 if ( $db === null ){ 00056 $db = DATAFACE_DB_HANDLE; 00057 } 00058 $this->_db = $db; 00059 $this->app =& Dataface_Application::getInstance(); 00060 00061 if ( @$this->app->_conf['cache_queries'] and !$this->_fcache_base_path ){ 00062 if ( is_writable(DATAFACE_SITE_PATH.'/templates_c') ){ 00063 $this->_fcache_base_path = DATAFACE_SITE_PATH.'/templates_c/query_results'; 00064 } else { 00065 $this->_fcache_base_path = DATAFACE_PATH.'/Dataface/templates_c/query_results'; 00066 } 00067 00068 if ( !file_exists($this->_fcache_base_path) ){ 00069 00070 mkdir($this->_fcache_base_path, 0700); 00071 file_put_contents($this->_fcache_base_path.'/.htaccess', 'deny from all'); 00072 } 00073 } 00074 00075 00076 00077 00078 00079 } 00080 00086 function _loadCache(){ 00087 if ( !isset($this->_cache) ){ 00088 $filepath = DATAFACE_CACHE_PATH.'/Dataface_DB.cache'; 00089 //echo "Checking cache... $filepath"; 00090 00091 if ( is_readable($filepath) and filemtime($filepath) > time()-1 ){ 00092 //echo "Cache is readable"; 00093 include DATAFACE_CACHE_PATH.'/Dataface_DB.cache'; 00094 00095 } 00096 if ( isset( $cache ) ){ 00097 $this->_cache =& $cache; 00098 } else { 00099 $this->_cache = array(); 00100 } 00101 register_shutdown_function(array(&$this, 'writeCache')); 00102 00103 } 00104 } 00105 00109 function cache($key, $value, $lang=null){ 00110 if ( !isset($lang) ) $lang = $this->_app->_conf['lang']; 00111 // if the language isn't set, we use the default language from the database 00112 $this->_loadCache(); 00113 $this->_cache[$lang][$key] = $value; 00114 $this->_cacheDirtyFlag = true; 00115 } 00116 00117 00118 00122 function &_getParser(){ 00123 00124 if ( !isset($this->_parser)){ 00125 import('SQL/Parser.php'); 00126 $this->_parser = new SQL_Parser(null, 'MySQL'); 00127 } 00128 return $this->_parser; 00129 } 00130 00134 function &_getCompiler(){ 00135 if ( !isset($this->_compiler) ){ 00136 import('SQL/Compiler.php'); 00137 $this->_compiler = SQL_Compiler::newInstance('mysql'); 00138 } 00139 return $this->_compiler; 00140 } 00141 00142 00152 function prepareQuery($query){ 00153 //echo "Preparing query $query"; 00154 $len = strlen($query); 00155 $escaped = false; 00156 $dblquoted = false; 00157 $sglquoted = false; 00158 $output_query = ''; 00159 $buffer = ''; 00160 $output_args = array(); 00161 $count = 0; 00162 for ($i=0;$i<$len;$i++){ 00163 $skip = false; 00164 switch ($query{$i}){ 00165 case '\\': $escaped = !$escaped; 00166 break; 00167 case '"' : if ( !$escaped && !$sglquoted ){ 00168 $dblquoted = !$dblquoted; 00169 if (!$dblquoted ){ 00170 // double quotes are done, we can update the buffer. 00171 $count++; // increment the counter for number of found strings 00172 $output_args[] = $buffer; 00173 $buffer = ''; 00174 $output_query .= '"_'.$count.'_"'; 00175 } 00176 $skip = true; 00177 } 00178 00179 break; 00180 case '\'' : if (!$escaped && !$dblquoted) { 00181 $sglquoted = !$sglquoted; 00182 if ( !$sglquoted ){ 00183 // double quotes are done, we can update the buffer. 00184 $count++; // increment the counter for number of found strings 00185 $output_args[] = $buffer; 00186 $buffer = ''; 00187 $output_query .= '\'_'.$count.'_\''; 00188 } 00189 $skip = true; 00190 00191 } 00192 break; 00193 00194 00195 } 00196 00197 if ( $query{$i} != '\\' ) $escaped = false; 00198 if ( $skip ) continue; 00199 if ( $dblquoted || $sglquoted) { 00200 $buffer .= $query{$i}; 00201 } 00202 else $output_query .= $query{$i}; 00203 } 00204 00205 // Now to replace all numbers 00206 $this->_matchCount = 0; 00207 $this->_matches = array(); 00208 $output_query = preg_replace_callback('/\b(-{0,1})([0-9]*\.{0,1}[0-9]+)\b/', array(&$this, '_replacePrepareDigits'), $output_query); 00209 $output_args = array($output_query, $output_args, $this->_matches); 00210 00211 //print_r($output_args); 00212 //print_r($output_args); 00213 return $output_args; 00214 00215 } 00216 00217 function _replacePrepareDigits($matches){ 00218 $this->_matches[] = $matches[1].$matches[2]; 00219 return ++$this->_matchCount; 00220 } 00221 00222 function _replaceCompileStrings($matches){ 00223 return $matches[1].$this->_matches[intval($matches[2])-1].$matches[3]; 00224 } 00225 00226 function _replaceCompileDigits($matches){ 00227 return $this->_matches[intval($matches[1])-1]; 00228 } 00229 00230 function _replaceBlobs($matches){ 00231 $blob = $this->checkoutBlob($matches[1]); 00232 if ( !is_uploaded_file($blob) ) throw new Exception(df_translate('scripts.Dataface.DB._replaceBlobs.BLOB_NOT_UPLOADED',"Attempt to load blob that is not uploaded. "), E_USER_ERROR); 00233 if ( PEAR::isError($blob) ) throw new Exception($blob->toString(), E_USER_ERROR); 00234 00235 return mysql_real_escape_string(file_get_contents($blob)); 00236 } 00237 00238 00239 00240 function compilePreparedQuery($prepared_query){ 00241 $numArgs = count($prepared_query[1]); 00242 $buffer = $prepared_query[0]; 00243 $this->_matches = $prepared_query[2]; 00244 $buffer = preg_replace_callback('/\b([0-9]+)\b/', array(&$this, '_replaceCompileDigits'), $buffer); 00245 00246 $this->_matches = $prepared_query[1]; 00247 $buffer = preg_replace_callback('/([\'"])_(\d+)_([\'"])/', array(&$this, '_replaceCompileStrings'), $buffer); 00248 00249 $buffer = preg_replace_callback('/-=-=B(\d+)=-=-/', array(&$this, '_replaceBlobs'), $buffer); 00250 00251 return $buffer; 00252 } 00253 00254 00255 00264 function translate_query($query, $lang=null){ 00265 //echo "Dirty flag: ".$this->_cacheDirtyFlag; 00266 if ( $lang === null ){ 00267 // If no language is provided use the language in the conf.ini file 00268 $lang = $this->app->_conf['lang']; 00269 } 00270 $this->_loadCache(); 00271 00272 if ( strtolower(substr($query, 0, strlen('DELETE '))) == 'delete ' ){ 00273 return $query; 00274 } 00275 00276 $original_query = $query; 00277 $prepared_query = $this->prepareQuery($query); 00278 if ( isset( $this->_cache[$lang][$prepared_query[0]] )){ 00279 // we have already translated this select query and cached it! 00280 // just load the query from the cache and fill in the appropriate 00281 // values: 00282 $prepared_query[0] = $this->_cache[$lang][$prepared_query[0]]; 00283 return $this->compilePreparedQuery($prepared_query); 00284 } 00285 00286 $query = $prepared_query[0]; 00287 import('Dataface/QueryTranslator.php'); 00288 $translator = new Dataface_QueryTranslator($lang); 00289 $output = $translator->translateQuery($prepared_query[0]); 00290 if (PEAR::isError($output) ){ 00291 //echo $output->toString(); 00292 throw new Exception(df_translate('scripts.Dataface.DB.translate_query.FAILED_TO_TRANSLATE', "Failed to translate query: $query.: ",array('query'=>$query)).$output->toString(), E_USER_ERROR); 00293 } 00294 00295 $this->cache($prepared_query[0], $output, $lang); 00296 $prepared_query[0] = $output; 00297 return $this->compilePreparedQuery($prepared_query); 00298 } 00299 00307 function query($sql, $db=null, $lang=null, $as_array=false, $enumerated=false){ 00308 00309 $app =& Dataface_Application::getInstance(); 00310 00311 $refreshModTimes=false; 00312 if ( $as_array and ($isSelect = (strpos(strtolower(trim($sql)), 'select ') === 0)) ){ 00313 if ( ($results = $this->memcache_get($sql, $lang)) or is_array($results) ) { 00314 if ( @$this->app->_conf['cache_queries_log']){ 00315 $fp = fopen('/tmp/querylog.log', 'a'); 00316 fwrite($fp, "\n[".date('Y-m-d H:i:s')."] Cached: ".$sql); 00317 fclose($fp); 00318 } 00319 $this->cache_hits++; 00320 return $results; 00321 } else { 00322 if ( @$this->app->_conf['cache_queries_log']){ 00323 $fp = fopen('/tmp/querylog.log', 'a'); 00324 fwrite($fp, "\n[".date('Y-m-d H:i:s')."] Failed cached: ".$sql); 00325 fclose($fp); 00326 } 00327 $this->cache_fails++; 00328 $orig_sql = $sql; // save the original sql before it is translated 00329 } 00330 00331 } else if ( @$app->_conf['cache_queries'] ){ 00332 $refreshModTimes = true; 00333 } 00334 00335 00336 //$fp = fopen('/tmp/querylog.log', 'a'); 00337 //fwrite($fp, "\n[".date('Y-m-d H:i:s')."] Uncached: ".$sql); 00338 //fclose($fp); 00339 $this->count++; 00340 00341 if ( ( /*isset($lang) ||*/ $this->app->_conf['multilingual_content'])) { 00342 00343 $sql = $this->translate_query($sql,$lang ); 00344 if ( PEAR::isError($sql) ) return $sql; 00345 00346 00347 } 00348 if ( !isset($db) ){ 00349 $db = $this->app->db(); 00350 } 00351 $update_insert_id = true; 00352 if ( is_array($sql) ){ 00353 $loopctr = 0; 00354 00355 foreach ($sql as $q){ 00356 if ( $loopctr++ > 0 and mysql_insert_id($db) ){ 00357 $this->_insert_id = mysql_insert_id($db); 00358 $update_insert_id = false; 00359 $q = str_replace("'%%%%%__MYSQL_INSERT_ID__%%%%%'", mysql_insert_id($db), $q ); 00360 } 00361 if ( defined('DATAFACE_DEBUG_DB') ) echo "Performing query: '$q' <br>"; 00362 $res = mysql_query($q, $db); 00363 00364 } 00365 } else { 00366 if ( defined('DATAFACE_DEBUG_DB') ) echo "Performing query: '$sql' <br>"; 00367 $this->db_hits++; 00368 $res = mysql_query($sql, $db); 00369 00370 } 00371 if ( $update_insert_id ) $this->_insert_id = mysql_insert_id($db); 00372 if ( $res and $refreshModTimes) Dataface_Table::getTableModificationTimes(true); 00373 if ( $as_array and $isSelect ){ 00374 if ( !$res ) { 00375 00376 return $res; 00377 } 00378 // We want to return this as an array rather than a resource 00379 $out = array(); 00380 while ( $row = ($enumerated ? mysql_fetch_row($res) : mysql_fetch_assoc($res)) ){ 00381 $out[] = $row; 00382 } 00383 00384 $this->memcache_set($orig_sql, $lang, $out); 00385 @mysql_free_result($res); 00386 00387 return $out; 00388 00389 } 00390 00391 return $res; 00392 } 00393 00394 function insert_id(){ 00395 return $this->_insert_id; 00396 } 00397 00398 00399 public static function &getInstance(){ 00400 static $instance = null; 00401 if ( $instance === null ){ 00402 //echo "In get instance"; 00403 $instance = new Dataface_DB(); 00404 } 00405 00406 return $instance; 00407 } 00408 00409 00414 function writeCache(){ 00415 //echo "in write cache..."; 00416 //print_r($this); 00417 if ( $this->_cacheDirtyFlag ){ 00418 //echo "Dirty flag"; 00419 // The cache has been updated so we have to write it. 00420 ob_start(); 00421 echo '<?php 00422 $cache = array(); 00423 '; 00424 foreach ($this->_cache as $lang=>$values){ 00425 foreach ($values as $key=>$value){ 00426 if ( is_array($value) ){ 00427 foreach ($value as $innerValue){ 00428 echo '$cache[\''.$lang.'\'][\''.str_replace("'", "\\'", $key).'\'][] = \''.str_replace("'", "\\'", $innerValue).'\'; 00429 '; 00430 } 00431 } else { 00432 echo '$cache[\''.$lang.'\'][\''.str_replace("'", "\\'", $key).'\'] = \''.str_replace("'", "\\'", $value).'\'; 00433 '; 00434 } 00435 } 00436 } 00437 00438 echo ' 00439 ?>'; 00440 $contents = ob_get_contents(); 00441 ob_end_clean(); 00442 if ( !file_exists(DATAFACE_CACHE_PATH) ) @mkdir(DATAFACE_CACHE_PATH); 00443 $fh = @fopen(DATAFACE_CACHE_PATH.'/Dataface_DB.cache', 'w'); 00444 if ( !$fh or !fwrite($fh, $contents) ){ 00445 error_log("Failed to write DB cache", E_USER_ERROR); 00446 } 00447 @fclose($fh); 00448 } 00449 } 00450 00451 00452 function registerBlob($blobData){ 00453 static $id=1; 00454 $this->blobs[$id++] = $blobData; 00455 return $id-1; 00456 00457 00458 } 00459 00460 function checkoutBlob($blobID){ 00461 if ( !isset( $this->blobs[$blobID]) ) return PEAR::raiseError(df_translate('scripts.Dataface.DB.checkoutBlob.BLOB_DOESNT_EXIST', "Blob with ID $blobID doesn't exist. ",array('blobID'=>$blobID)), DATAFACE_E_ERROR); 00462 00463 $blob = $this->blobs[$blobID]; 00464 unset($this->blobs[$blobID]); 00465 return $blob; 00466 } 00467 00468 function startTransaction(){ return mysql_query('begin', df_db() ); } 00469 function commitTransaction(){ return mysql_query('commit', df_db() ); } 00470 function rollbackTransaction(){ return mysql_query('rollback', df_db() ); } 00471 00472 function memcache_get($sql, $lang=null){ 00473 00474 $app =& Dataface_Application::getInstance(); 00475 00476 $memcache =& $app->memcache; 00477 00478 if ( !@$app->_conf['cache_queries'] ) { 00479 00480 return null; 00481 } 00482 00483 $key = $this->memcache_get_key($sql, $lang); 00484 00485 00486 00487 $tables = $this->getTableDependencies($sql, $lang); 00488 if ( PEAR::isError($tables) ) return null; 00489 // This is a list of the tables that would cause the cache to be invalidated. 00490 00491 $modification_times = Dataface_Table::getTableModificationTimes(); 00492 $mtime = 0; 00493 foreach ( $tables as $table){ 00494 if ( isset($modification_times[$table]) ) $mtime = max($mtime, $modification_times[$table]); 00495 else { 00496 $t =& Dataface_Table::loadTable($table); 00497 if ( @$t->_atts['__source_tables__'] ){ 00498 $ts = explode(',', $t->_atts['__source_tables__']); 00499 foreach ($ts as $tst){ 00500 if ( isset($modification_times[trim($tst)]) ){ 00501 $mtime = max($mtime, $modification_times[trim($tst)]); 00502 } else { 00503 $mtime = time(); 00504 break; 00505 } 00506 } 00507 } else { 00508 //echo "$table no modified date"; 00509 $mtime = time(); 00510 break; 00511 } 00512 unset($t); 00513 } 00514 } 00515 00516 00517 00518 // Now we will get the cached value if it is newer than $mtime 00519 $cache_mtime = 0; 00520 if ( $memcache ) $cache_mtime = $this->memcache_mtime($key); 00521 else $cache_mtime = $this->fcache_mtime($key); 00522 //echo "Cache time for ".$this->fcache_path($key)." is $cache_mtime"; 00523 //echo "[$sql : $cache_mtime : $mtime]"; 00524 if ( $cache_mtime > $mtime ){ 00525 if ( $memcache ) { 00526 00527 if ( $result = $memcache->get($key) ) { 00528 return unserialize($result); 00529 } 00530 } 00531 else if (($result = $this->fcache_get($key)) !== null ){ 00532 00533 return unserialize($result); 00534 } 00535 } 00536 00537 00538 00539 return null; 00540 } 00541 00545 function memcache_mtime($key, $set=false){ 00546 00547 $key .= '&-action=mtime'; 00548 $key = md5($key); 00549 if ( DATAFACE_EXTENSION_LOADED_APC and !$set ){ 00550 return apc_fetch($key); 00551 } else if ( DATAFACE_EXTENSION_LOADED_APC and $set ){ 00552 apc_store($key, time()); 00553 } else if ( $set ){ 00554 00555 $_SESSION[$key] = time(); 00556 } else if ( !$set and isset($_SESSION[$key])){ 00557 00558 return $_SESSION[$key]; 00559 } 00560 return 0; 00561 00562 00563 } 00564 00565 function fcache_mtime($key){ 00566 if ( file_exists($this->fcache_path($key)) ){ 00567 //echo "Checking mtime for $key : ".$this->fcache_path($key); 00568 return filemtime($this->fcache_path($key)); 00569 } else { 00570 //echo "File does not exist : ".$this->fcache_path($key); 00571 return 0; 00572 } 00573 } 00574 00575 function fcache_get($key){ 00576 00577 if ( file_exists($this->fcache_path($key)) ){ 00578 return file_get_contents($this->fcache_path($key)); 00579 } 00580 return null; 00581 } 00582 00583 function fcache_set($key, $value){ 00584 00585 file_put_contents($this->fcache_path($key), serialize($value)); 00586 } 00587 00588 function fcache_path($key){ 00589 return $this->_fcache_base_path.'/'.md5($key); 00590 } 00591 00592 function memcache_get_key($sql, $lang){ 00593 00594 $app =& Dataface_Application::getInstance(); 00595 00596 $auth =& Dataface_AuthenticationTool::getInstance(); 00597 00598 00599 $dbname = $app->_conf['_database']['name']; 00600 00601 if ( !isset($lang) ) $lang = $app->_conf['lang']; 00602 00603 $key = urlencode($dbname).'?-query='.urlencode($sql).'&-lang='.urlencode($lang); 00604 00605 return md5($key); 00606 } 00607 00608 function memcache_set($sql, $lang, $value){ 00609 00610 $app =& Dataface_Application::getInstance(); 00611 $memcache =& $app->memcache; 00612 if ( !$memcache and !@$app->_conf['cache_queries'] ) return null; 00613 00614 $key = $this->memcache_get_key($sql, $lang); 00615 if ( $memcache ){ 00616 $memcache->set($key, serialize($value), false, 0); 00617 $this->memcache_mtime($key, true); 00618 } else if ( @$app->_conf['cache_queries'] ){ 00619 //echo "Setting $sql $key ".$this->fcache_path($key); 00620 $this->fcache_set($key, $value); 00621 } 00622 00623 00624 } 00625 00626 function getTableDependencies($sql, $lang=null){ 00627 $app =& Dataface_Application::getInstance(); 00628 $key = $this->memcache_get_key($sql, $lang); 00629 $key .= '&-action=deps'; 00630 $key = md5($key); 00631 if ( DATAFACE_EXTENSION_LOADED_APC && !isset($_GET['--clear-cache']) ){ 00632 $deps = apc_fetch($key); 00633 if ( is_array($deps) ) return $deps; 00634 } else if ( isset($_SESSION[$key]) && !isset($_GET['--clear-cache']) ){ 00635 $deps = $_SESSION[$key]; 00636 if ( is_array($deps) ) return $deps; 00637 } 00638 // We actually need to calculate the dependencies, so we will 00639 // parse the SQL query. 00640 import('SQL/Parser.php'); 00641 $parser = new SQL_Parser( null, 'MySQL'); 00642 $data =& $parser->parse($sql); 00643 if ( PEAR::isError($data) ){ 00644 return $data; 00645 } 00646 $tables = array_unique($data['all_tables']); 00647 00648 if ( @$app->_conf['cache_queries_log'] ){ 00649 $fp = fopen('/tmp/querylog.log', 'a'); 00650 fwrite($fp, "\n[".date('Y-m-d H:i:s')."] Dependencies: ".implode(',', $tables)." ".$sql); 00651 fclose($fp); 00652 } 00653 //import('SQL/Parser/wrapper.php'); 00654 00655 00656 //$wrapper = new SQL_Parser_wrapper($data); 00657 //$tables = $wrapper->getTableNames(); 00658 00659 foreach ($tables as $tid=>$table){ 00660 if ( preg_match('/^dataface__view_(.*)_[a-z0-9]{32}$/', $table, $matches) ){ 00661 $tables[$tid] = $table = $matches[1]; 00662 } 00663 $tobj =& Dataface_Table::loadTable($table,null,true); 00664 if ( is_a($tobj, 'Dataface_Table') and isset($tobj->_atts['__dependencies__']) ){ 00665 $deps = array_map('trim', explode(',', $tobj->_atts['__dependencies__'])); 00666 $tables = array_merge($tables, $deps); 00667 00668 } 00669 } 00670 00671 if ( isset($app->_conf['__dependencies__']) ){ 00672 $deps = array_map('trim',explode(',', $app->_conf['__dependencies'])); 00673 $tables = array_merge($tables, $deps); 00674 } 00675 00676 $deps = array_unique($tables); 00677 00678 if ( DATAFACE_EXTENSION_LOADED_APC ){ 00679 apc_store($key, $deps); 00680 } else { 00681 $_SESSION[$key] = $deps; 00682 } 00683 00684 00685 return $deps; 00686 } 00687 00688 00689 }