![]() |
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 */ 00041 import( 'Dataface/QueryBuilder.php'); 00042 import('Dataface/DB.php'); 00043 define('Dataface_IO_READ_ERROR', 1001); 00044 define('Dataface_IO_WRITE_ERROR', 1002); 00045 define('Dataface_IO_NOT_FOUND_ERROR', 1003); 00046 define('Dataface_IO_TOO_MANY_ROWS', 1004); 00047 define('Dataface_IO_NO_TABLES_SELECTED', 1005); 00048 define('MYSQL_ER_DUP_KEY', 1022); 00049 define('MYSQL_ER_DUP_ENTRY', 1062); 00050 define('MYSQL_ER_ROW_IS_REFERENCED', 1217); 00051 define('MYSQL_ER_ROW_IS_REFERENCED_2', 1451); 00052 define('MYSQL_ER_NO_REFERENCED_ROW', 1216); 00053 define('MYSQL_ER_NO_REFERENCED_ROW_2', 1452); 00054 00055 00056 class Dataface_IO { 00057 var $_table; 00058 var $_serializer; 00059 var $insertIds = array(); 00060 var $lang; 00061 var $dbObj; 00062 var $parentIO=-1; 00063 var $fireTriggers=true; 00064 00070 var $_altTablename = null; 00071 00072 function Dataface_IO($tablename, $db=null, $altTablename=null){ 00073 $app =& Dataface_Application::getInstance(); 00074 $this->lang = $app->_conf['lang']; 00075 $this->_table =& Dataface_Table::loadTable($tablename, $db); 00076 $this->_serializer = new Dataface_Serializer($tablename); 00077 $this->_altTablename = $altTablename; 00078 $this->dbObj =& Dataface_DB::getInstance(); 00079 } 00080 00081 function __destruct(){ 00082 unset($this->_table); 00083 unset($this->dbObj); 00084 unset($this->_serializer); 00085 00086 if ( isset($this->parentIO) and $this->parentIO != -1 ){ 00087 $this->parentIO->__destruct(); 00088 unset($this->parentIO); 00089 } 00090 } 00091 00092 function &getParentIO(){ 00093 if ( $this->parentIO == -1 ){ 00094 if ( isset($this->_altTablename) and $this->_altTablename != $this->_table->tablename) { 00095 $null = null; 00096 return $null; 00097 } 00098 // There is no clear parent table if an alternate table name is set. 00099 00100 $parentTable =& $this->_table->getParent(); 00101 if ( isset($parentTable) ){ 00102 $this->parentIO = new Dataface_IO($parentTable->tablename, null, null); 00103 $this->parentIO->lang = $this->lang; 00104 $this->parentIO->fireTriggers = false; 00105 } else { 00106 $this->parentIO = null; 00107 } 00108 00109 } 00110 return $this->parentIO; 00111 } 00112 00125 function &loadRecordById($recordid){ 00126 $rec =& Dataface_IO::getByID($recordid); 00127 return $rec; 00128 00129 } 00130 00131 00139 function recordid2query($recordid){ 00140 $query = array(); 00141 list($base,$qstr) = explode('?', $recordid); 00142 if ( strpos($base,'/') !== false ){ 00143 list($query['-table'],$query['-relationship']) = explode('/',$base); 00144 } else { 00145 $query['-table'] = $base; 00146 } 00147 $params = explode('&', $qstr); 00148 foreach ( $params as $param){ 00149 list($key,$value) = explode('=', $param); 00150 $query[urldecode($key)] = '='.urldecode($value); 00151 } 00152 return $query; 00153 00154 } 00155 00156 00157 00171 function read($query='', &$record, $tablename=null){ 00172 $app =& Dataface_Application::getInstance(); 00173 if ( !is_a($record, "Dataface_Record") ){ 00174 throw new Exception( 00175 df_translate( 00176 'scripts.Dataface.IO.read.ERROR_PARAMETER_2', 00177 "Dataface_IO::read() requires second parameter to be of type 'Dataface_Record' but received '".get_class($record)."\n<br>", 00178 array('class'=>get_class($record)) 00179 ), E_USER_ERROR); 00180 } 00181 00182 if ( is_string($query) and !empty($query) ){ 00183 // If the query is actually a record id string, then we convert it 00184 // to a normal query. 00185 $query = $this->recordid2query($query); 00186 } 00187 00188 if ( $tablename === null and $this->_altTablename !== null ){ 00189 $tablename = $this->_altTablename; 00190 } 00191 00192 00193 $qb = new Dataface_QueryBuilder($this->_table->tablename); 00194 $qb->selectMetaData = true; 00195 $query['-limit'] = 1; 00196 if ( @$query['-cursor']>0 ) $query['-skip'] = $query['-cursor']; 00197 $sql = $qb->select('', $query, false, $this->tablename($tablename)); 00198 $res = $this->dbObj->query($sql, $this->_table->db, $this->lang, true /* as_array */); 00199 if ( (!is_array($res) and !$res) || PEAR::isError($res) ){ 00200 $app->refreshSchemas($this->_table->tablename); 00201 $res = $this->dbObj->query($sql, $this->_table->db, $this->lang, true /* as array */); 00202 if ( (!is_array($res) and !$res) || PEAR::isError($res) ){ 00203 if ( PEAR::isError($res) ) return $res; 00204 return PEAR::raiseError( 00205 Dataface_LanguageTool::translate( 00206 /* i18n id */ 00207 "Error reading record", 00208 /* default error message */ 00209 "Error reading table '". 00210 $this->_table->tablename. 00211 "' from the database: ". 00212 mysql_error($this->_table->db), 00213 /* i18n parameters */ 00214 00215 array('table'=>$this->_table->tablename, 00216 'mysql_error'=>mysql_error(), 00217 'line'=>0, 00218 'file'=>'_', 00219 'sql'=>$sql 00220 ) 00221 ), 00222 00223 DATAFACE_E_READ_FAILED 00224 ); 00225 } 00226 } 00227 00228 //if ( mysql_num_rows($res) == 0 ){ 00229 if ( count($res) == 0 ){ 00230 return PEAR::raiseError( 00231 Dataface_LanguageTool::translate( 00232 /* i18n id */ 00233 "No records found", 00234 /* default error message */ 00235 "Record for table '". 00236 $this->_table->tablename. 00237 "' could not be found.", 00238 /* i18n parameters */ 00239 array('table'=>$this->_table->tablename, 'sql'=>$sql) 00240 ), 00241 DATAFACE_E_READ_FAILED 00242 ); 00243 } 00244 00245 //$row = mysql_fetch_assoc($res); 00246 $row = $res[0]; 00247 //mysql_free_result($res); 00248 $record->setValues($row); 00249 $record->setSnapshot(); 00250 // clear all flags that may have been previously set to indicate that the data is old or needs to be updated. 00251 00252 00253 00254 } 00255 00256 00257 00258 00265 function delete(&$record, $secure=false){ 00266 if ( $secure && !$record->checkPermission('delete') ){ 00267 // Use security to check to see if we are allowed to delete this 00268 // record. 00269 return Dataface_Error::permissionDenied( 00270 df_translate( 00271 'scripts.Dataface.IO.delete.PERMISSION_DENIED', 00272 'Could not delete record "'.$record->getTitle().'" from table "'.$record->_table->tablename.'" because you have insufficient permissions.', 00273 array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename) 00274 ) 00275 ); 00276 } 00277 00278 00279 $builder = new Dataface_QueryBuilder($this->_table->tablename); 00280 00281 if ( $this->fireTriggers ){ 00282 $res = $this->fireBeforeDelete($record); 00283 if ( PEAR::isError($res) ) return $res; 00284 } 00285 00286 00287 00288 // do the deleting 00289 $keys =& $record->_table->keys(); 00290 if ( !$keys || count($keys) == 0 ){ 00291 throw new Exception( 00292 df_translate( 00293 'scripts.Dataface.IO.delete.ERROR_NO_PRIMARY_KEY', 00294 'Could not delete record from table "'.$record->_table->tablename.'" because no primary key was defined.', 00295 array('tablename'=>$record->_table->tablename) 00296 ) 00297 ); 00298 00299 } 00300 $query = array(); 00301 foreach ( array_keys($keys) as $key ){ 00302 if ( !$record->strval($key) ){ 00303 return PEAR::raiseError( 00304 Dataface_LanguageTool::translate( 00305 /* i18n id */ 00306 'Could not delete record because missing keys', 00307 /* default error message */ 00308 'Could not delete record '. 00309 $record->getTitle(). 00310 ' because not all of the keys were included.', 00311 /* i18n parameters */ 00312 array('title'=>$record->getTitle(), 'key'=>$key) 00313 ), 00314 DATAFACE_E_DELETE_FAILED 00315 ); 00316 } 00317 $query[$key] = '='.$record->strval($key); 00318 } 00319 00320 $sql = $builder->delete($query); 00321 if ( PEAR::isError($sql) ) return $sql; 00322 00323 //$res = mysql_query($sql); 00324 $res = $this->dbObj->query($sql, null, $this->lang); 00325 if ( !$res || PEAR::isError($res)){ 00326 if ( PEAR::isError($res) ) $msg = $res->getMessage(); 00327 else $msg = mysql_error(df_db()); 00328 return PEAR::raiseError( 00329 00330 Dataface_LanguageTool::translate( 00331 /* i18n id */ 00332 'Failed to delete record. SQL error', 00333 /* default error message */ 00334 'Failed to delete record '. 00335 $record->getTitle(). 00336 ' because of an sql error. '.mysql_error(df_db()), 00337 /* i18n parameters */ 00338 array('title'=>$record->getTitle(), 'sql'=>$sql, 'mysql_error'=>$msg) 00339 ), 00340 DATAFACE_E_DELETE_FAILED 00341 ); 00342 } 00343 00344 $parentIO =& $this->getParentIO(); 00345 if ( isset($parentIO) ){ 00346 $parentRecord =& $record->getParentRecord(); 00347 if ( isset($parentRecord) ){ 00348 $res = $parentIO->delete($parentRecord, $secure); 00349 if ( PEAR::isError($res) ) return $res; 00350 } 00351 } 00352 00353 if ( $this->fireTriggers ){ 00354 $res2 = $this->fireAfterDelete($record); 00355 if ( PEAR::isError($res2) ) return $res2; 00356 } 00357 self::touchTable($this->_table->tablename); 00358 return $res; 00359 00360 } 00361 00362 function saveTransients(Dataface_Record $record, $keys=null, $tablename=null, $secure=false){ 00363 $app = Dataface_Application::getInstance(); 00364 // Now we take care of the transient relationship fields. 00365 // Transient relationship fields aren't actually stored in the record 00366 // itself, they are stored as related records. 00367 foreach ( $record->_table->transientFields() as $tfield ){ 00368 if ( !isset($tfield['relationship']) ) continue; 00369 if ( !$record->valueChanged($tfield['name']) ) continue; 00370 00371 $trelationship =& $record->_table->getRelationship($tfield['relationship']); 00372 00373 if ( !$trelationship or PEAR::isError($trelationship) ){ 00374 // We couldn't find the specified relationship. 00375 //$record->vetoSecurity = $oldVeto; 00376 return $trelationship; 00377 } 00378 00379 $orderCol = $trelationship->getOrderColumn(); 00380 if ( PEAR::isError($orderCol) ) $orderCol = null; 00381 00382 $tval = $record->getValue($tfield['name']); 00383 if ( $tfield['widget']['type'] == 'grid' ){ 00384 00385 $tval_existing = array(); 00386 $tval_new = array(); 00387 $tval_new_existing = array(); 00388 $torder = 0; 00389 foreach ($tval as $trow){ 00390 if ( !is_array($trow) ) continue; 00391 $trow['__order__'] = $torder++; 00392 if ( isset($trow['__id__']) and preg_match('/^new:/', $trow['__id__']) ){ 00393 $tval_new_existing[] = $trow; 00394 } 00395 else if ( isset($trow['__id__']) and $trow['__id__'] != 'new' ){ 00396 $tval_existing[$trow['__id__']] = $trow; 00397 } else if ( isset($trow['__id__']) and $trow['__id__'] == 'new'){ 00398 $tval_new[] = $trow; 00399 } 00400 } 00401 00402 // The transient field was loaded so we can go about saving the 00403 // changes/ 00404 $trecords =& $record->getRelatedRecordObjects($tfield['relationship'], 'all'); 00405 if ( !is_array($trecords) or PEAR::isError($trecords) ){ 00406 error_log('Failed to get related records for record '.$record->getId().' in its relationship '.$tfield['relationship']); 00407 unset($tval); 00408 unset($orderCol); 00409 unset($tval_new); 00410 unset($torder); 00411 unset($trelationship); 00412 unset($tval_existing); 00413 continue; 00414 } 00415 00416 00417 // Update the existing records in the relationship. 00418 // We use the __id__ parameter in each row for this. 00419 //echo "About to save related records"; 00420 foreach ($trecords as $trec){ 00421 $tid = $trec->getId(); 00422 00423 if ( isset($tval_existing[$tid]) ){ 00424 $tmp = new Dataface_RelatedRecord($trec->_record, $tfield['relationship'], $trec->getValues()); 00425 00426 $tmp->setValues($tval_existing[$tid]); 00427 $changed = false; 00428 foreach ( $tval_existing[$tid] as $k1=>$v1 ){ 00429 if ( $tmp->isDirty($k1) ){ 00430 $changed = true; 00431 break; 00432 } 00433 } 00434 00435 if ( $changed ){ 00436 $trec->setValues($tval_existing[$tid]); 00437 if ( $orderCol ) $trec->setValue( $orderCol, $tval_existing[$tid]['__order__']); 00438 //echo "Saving ";print_r($trec->vals()); 00439 $res_t = $trec->save($this->lang, $secure); 00440 00441 if ( PEAR::isError($res_t) ){ 00442 return $res_t; 00443 error_log('Failed to save related record '.$trec->getId().' while saving transient field '.$tfield['name'].' in record '.$record->getId().'. The error returned was : '.$res_t->getMessage()); 00444 00445 } 00446 } else { 00447 if ( $orderCol and $record->checkPermission('reorder_related_records', array('relationship'=>$tfield['relationship'])) ){ 00448 $trec->setValue( $orderCol, $tval_existing[$tid]['__order__']); 00449 $res_t = $trec->save($this->lang, false); // we don't need this to be secure 00450 if ( PEAR::isError($res_t) ){ 00451 return $res_t; 00452 error_log('Failed to save related record '.$trec->getId().' while saving transient field '.$tfield['name'].' in record '.$record->getId().'. The error returned was : '.$res_t->getMessage()); 00453 00454 } 00455 } 00456 } 00457 00458 unset($tmp); 00459 } else { 00460 00461 00462 } 00463 unset($trec); 00464 unset($tid); 00465 unset($res_t); 00466 00467 } 00468 00469 00470 // Now add new records (specified by __id__ field being 'new' 00471 00472 foreach ($tval_new as $tval_to_add){ 00473 $temp_rrecord = new Dataface_RelatedRecord( $record, $tfield['relationship'], array()); 00474 00475 00476 $temp_rrecord->setValues($tval_to_add); 00477 if ( $orderCol ) $temp_rrecord->setValue( $orderCol, $tval_to_add['__order__']); 00478 $res_t = $this->addRelatedRecord($temp_rrecord, $secure); 00479 if ( PEAR::isError($res_t) ){ 00480 error_log('Failed to save related record '.$temp_rrecord->getId().' while saving transient field '.$tfield['name'].' in record '.$record->getId().'. The error returned was : '.$res_t->getMessage()); 00481 } 00482 unset($temp_rrecord); 00483 unset($res_t); 00484 00485 00486 } 00487 00488 // Now add new existing records (specified by __id__ field being 'new:<recordid>' 00489 00490 foreach ($tval_new_existing as $tval_to_add){ 00491 $tid = preg_replace('/^new:/', '', $tval_to_add['__id__']); 00492 $temp_record = df_get_record_by_id($tid); 00493 if ( PEAR::isError($temp_record) ){ 00494 return $temp_record; 00495 } 00496 if ( !$temp_record){ 00497 return PEAR::raiseError("Failed to load existing record with ID $tid."); 00498 } 00499 $temp_rrecord = new Dataface_RelatedRecord( $record, $tfield['relationship'], $temp_record->vals()); 00500 00501 00502 $temp_rrecord->setValues($tval_to_add); 00503 if ( $orderCol ) $temp_rrecord->setValue( $orderCol, $tval_to_add['__order__']); 00504 $res_t = $this->addExistingRelatedRecord($temp_rrecord, $secure); 00505 if ( PEAR::isError($res_t) ){ 00506 error_log('Failed to save related record '.$temp_rrecord->getId().' while saving transient field '.$tfield['name'].' in record '.$record->getId().'. The error returned was : '.$res_t->getMessage()); 00507 } 00508 unset($temp_rrecord); 00509 unset($res_t); 00510 00511 00512 } 00513 00514 // Now we delete the records that were deleted 00515 // we use the __deleted__ field. 00516 00517 if ( isset($tval['__deleted__']) and is_array($tval['__deleted__']) and $trelationship->supportsRemove() ){ 00518 $tdelete_record = ($trelationship->isOneToMany() and !$trelationship->supportsAddExisting()); 00519 // If it supports add existing, then we shouldn't delete the entire record. Just remove it 00520 // from the relationship. 00521 00522 foreach ( $tval['__deleted__'] as $del_id ){ 00523 if ($del_id == 'new' ) continue; 00524 $drec = Dataface_IO::getByID($del_id); 00525 if ( PEAR::isError($drec) or !$drec ){ 00526 unset($drec); 00527 continue; 00528 } 00529 00530 $mres = $this->removeRelatedRecord($drec, $tdelete_record, $secure); 00531 if ( PEAR::isError($mres) ){ 00532 throw new Exception($mres->getMessage()); 00533 } 00534 unset($drec); 00535 } 00536 } 00537 00538 unset($trecords); 00539 00540 } else if ( $tfield['widget']['type'] == 'checkbox' ){ 00541 00542 // Load existing records in the relationship 00543 $texisting =& $record->getRelatedRecordObjects($tfield['relationship'], 'all'); 00544 if ( !is_array($texisting) or PEAR::isError($texisting) ){ 00545 error_log('Failed to get related records for record '.$record->getId().' in its relationship '.$tfield['relationship']); 00546 unset($tval); 00547 unset($orderCol); 00548 unset($tval_new); 00549 unset($torder); 00550 unset($trelationship); 00551 unset($tval_existing); 00552 continue; 00553 } 00554 $texistingIds = array(); 00555 foreach ($texisting as $terec){ 00556 $texistingIds[] = $terec->getId(); 00557 } 00558 00559 // Load currently checked records 00560 $tchecked = array(); 00561 $tcheckedRecords = array(); 00562 $tcheckedIds = array(); 00563 $tcheckedId2ValsMap = array(); 00564 foreach ( $tval as $trkey=>$trval){ 00565 // $trval is in the form key1=val1&size=key2=val2 00566 parse_str($trval, $trquery); 00567 $trRecord = new Dataface_RelatedRecord($record, $tfield['relationship'],$trquery); 00568 $trRecords[] =& $trRecord; 00569 $tcheckedIds[] = $tid = $trRecord->getId(); 00570 $checkedId2ValsMap[$tid] = $trquery; 00571 unset($trRecord); 00572 unset($trquery); 00573 00574 } 00575 00576 // Now we have existing ids in $texistingIds 00577 // and checked ids in $tcheckedIds 00578 00579 // See which records we need to have removed 00580 $tremoves = array_diff($texistingIds, $tcheckedIds); 00581 $tadds = array_diff($tcheckedIds, $texistingIds); 00582 00583 foreach ($tremoves as $tid){ 00584 $trec = df_get_record_by_id($tid); 00585 $res = $this->removeRelatedRecord($trec, false, $secure); 00586 if ( PEAR::isError($res) ) return $res; 00587 unset($trec); 00588 } 00589 foreach ($tadds as $tid){ 00590 $trecvals = $checkedId2ValsMap[$tid]; 00591 $trec = new Dataface_RelatedRecord($record, $tfield['relationship'], $trecvals); 00592 00593 $res = $this->addExistingRelatedRecord($trec, $secure); 00594 if ( PEAR::isError($res) ) return $res; 00595 unset($trec, $trecvals); 00596 } 00597 00598 unset($tadds); 00599 unset($tremoves); 00600 unset($tcheckedIds, $tcheckedId2ValsMap); 00601 unset($tcheckedRecords); 00602 unset($tchecked); 00603 unset($texistingIds); 00604 unset($texisting); 00605 00606 00607 00608 } 00609 unset($tval); 00610 unset($trelationship); 00611 00612 } 00613 00614 } 00615 00616 00629 function write(&$record, $keys=null, $tablename=null, $secure=false){ 00630 // The vetoSecurity flag allows us to make changes to a record without 00631 // the fields being filtered for security checks when they are saved. 00632 // Since we may want to change or add values to a record in the 00633 // beforeSave type triggers, and we probably don't want these changes 00634 // checked by security, we should use this flag to make all changes 00635 // in these triggers immune to security checks. 00636 // We return the veto setting to its former state after this method 00637 // finishes. 00638 //$oldVeto = $record->vetoSecurity; 00639 //$record->vetoSecurity = true; 00640 //$parentRecord =& $record->getParentRecord(); 00641 $app =& Dataface_Application::getInstance(); 00642 //$parentIO =& $this->getParentIO(); 00643 00644 if ( !is_a($record, "Dataface_Record") ){ 00645 throw new Exception( 00646 df_translate( 00647 'scripts.Dataface.IO.write.ERROR_PARAMETER_1', 00648 "Dataface_IO::write() requires first parameter to be of type 'Dataface_Record' but received '".get_class($record)."\n<br>", 00649 array('class'=>get_class($record)) 00650 ), E_USER_ERROR); 00651 } 00652 if ( $tablename === null and $this->_altTablename !== null ){ 00653 $tablename = $this->_altTablename; 00654 } 00655 00656 if ( $this->fireTriggers ){ 00657 $res = $this->fireBeforeSave($record); 00658 if (PEAR::isError($res) ) { 00659 //$record->vetoSecurity = $oldVeto; 00660 return $res; 00661 } 00662 } 00663 00664 00665 00666 if ( $this->recordExists($record, $keys, $this->tablename($tablename)) ){ 00667 $res = $this->_update($record, $keys, $this->tablename($tablename), $secure); 00668 } else { 00669 00670 $res = $this->_insert($record, $this->tablename($tablename), $secure); 00671 00672 } 00673 00674 if ( PEAR::isError($res) ){ 00675 if ( Dataface_Error::isDuplicateEntry($res) ){ 00676 /* 00677 * Duplicate entries we will propogate up so that the application can decide what to do. 00678 */ 00679 //$record->vetoSecurity = $oldVeto; 00680 return $res; 00681 } 00682 $res->addUserInfo( 00683 df_translate( 00684 'scripts.Dataface.IO.write.ERROR_SAVING', 00685 "Error while saving record of table '".$this->_table->tablename."' in Dataface_IO::write() ", 00686 array('tablename'=>$this->_table->tablename,'line'=>0,'file'=>'_') 00687 ) 00688 ); 00689 //$record->vetoSecurity = $oldVeto; 00690 return $res; 00691 } 00692 00693 $res = $this->saveTransients($record, $keys, $tablename, $secure); 00694 if ( PEAR::isError($res) ){ 00695 return $res; 00696 } 00697 00698 00699 00700 if ( $this->fireTriggers ){ 00701 $res2 = $this->fireAfterSave($record); 00702 if ( PEAR::isError($res2) ){ 00703 //$record->vetoSecurity = $oldVeto; 00704 return $res2; 00705 } 00706 } 00707 if ( isset($app->_conf['history']) and ( @$app->_conf['history']['enabled'] || !isset($app->_conf['history']['enabled']))){ 00708 00709 // History is enabled ... let's save this record in our history. 00710 import('Dataface/HistoryTool.php'); 00711 $historyTool = new Dataface_HistoryTool(); 00712 $historyTool->logRecord($record, $this->getHistoryComments($record), $this->lang); 00713 } 00714 00715 00716 if ( isset($app->_conf['_index']) and @$app->_conf['_index'][$record->table()->tablename]){ 00717 // If indexing is enabled, we index the record so that it is 00718 // searchable by natural language searching. 00719 // The Dataface_Index class takes care of whether or not this 00720 // record should be indexed. 00721 import('Dataface/Index.php'); 00722 $index = new Dataface_Index(); 00723 $index->indexRecord($record); 00724 } 00725 00726 // It seems to me that we should be setting a new snapshot at this point. 00727 //$record->clearSnapshot(); 00728 $record->setSnapshot(); 00729 self::touchTable($this->_table->tablename); 00730 //$record->vetoSecurity = $oldVeto; 00731 return $res; 00732 } 00733 00734 00735 function getHistoryComments(&$record){ 00736 $del =& $this->_table->getDelegate(); 00737 if ( isset($del) and method_exists($del, 'getHistoryComments') ){ 00738 return $del->getHistoryComments($record); 00739 } 00740 $app =& Dataface_Application::getInstance(); 00741 $appdel =& $app->getDelegate(); 00742 if ( isset($appdel) and method_exists($appdel, 'getHistoryComments') ){ 00743 return $appdel->getHistoryComments($record); 00744 } 00745 return ''; 00746 } 00747 00748 00749 00757 function recordExists(&$record, $keys = null, $tablename=null){ 00758 if ( !is_a($record, "Dataface_Record") ){ 00759 throw new Exception( 00760 df_translate( 00761 'scripts.Dataface.IO.recordExists.ERROR_PARAMETER_1', 00762 "In Dataface_IO::recordExists() the first argument is expected to be either a 'Dataface_Record' object or an array of key values, but received neither.\n<br>" 00763 ), E_USER_ERROR); 00764 } 00765 if ( $tablename === null and $this->_altTablename !== null ){ 00766 $tablename = $this->_altTablename; 00767 } 00768 00769 $tempRecordCreated = false; 00770 if ( $record->snapshotExists() ){ 00771 $tempRecord = new Dataface_Record($record->_table->tablename, $record->getSnapshot()); 00772 $tempRecordCreated = true; 00773 } else { 00774 $tempRecord =& $record; 00775 } 00776 00777 if ( $keys == null ){ 00778 // Had to put in userialize(serialize(...)) because getValues() returns by reference 00779 // and we don't want to change actual values. 00780 $query = unserialize(serialize($tempRecord->getValues( array_keys($record->_table->keys())))); 00781 } else { 00782 $query = $keys; 00783 } 00784 00785 00786 $table_keys = array_keys($this->_table->keys()); 00787 00788 foreach ( $table_keys as $key){ 00789 if ( !isset( $query[$key] ) or !$query[$key] ) { 00790 00791 return false; 00792 } 00793 } 00794 00795 foreach ( array_keys($query) as $key){ 00796 $query[$key] = '='.$this->_serializer->serialize($key, $tempRecord->getValue($key) ); 00797 } 00798 00799 00800 $qb = new Dataface_QueryBuilder($this->_table->tablename, $query); 00801 $sql = $qb->select_num_rows(array(), $this->tablename($tablename)); 00802 00803 $res = mysql_query($sql, $this->_table->db); 00804 00805 // We just use regular mysql query to see if record exists because this should be sufficient 00806 //$res = $this->dbObj->query($sql, $this->_table->db, $this->lang); 00807 if ( !$res || PEAR::isError($res) ){ 00808 throw new Exception("SQL error in $sql : ".mysql_error($this->_table->db), E_USER_ERROR); 00809 } 00810 00811 list($rows) = mysql_fetch_row($res); 00812 mysql_free_result($res); 00813 if ( $rows > 1 ){ 00814 00815 $err = PEAR::raiseError( 00816 Dataface_LanguageTool::translate( 00817 /* i18n id */ 00818 'recordExists failure. Too many rows returned.', 00819 /* default error message */ 00820 "Test for existence of record in recordExists() returned $rows records. 00821 It should have max 1 record. 00822 The query must be incorrect. 00823 The query used was '$sql'. ", 00824 /* i18n parameters */ 00825 array('table'=>$this->_table->tablename, 'line'=>0, 'file'=>'_','sql'=>$sql) 00826 ), 00827 DATAFACE_E_IO_ERROR 00828 ); 00829 throw new Exception($err->toString(), E_USER_ERROR); 00830 } 00831 00832 00833 if ( $tempRecordCreated ) $tempRecord->__destruct(); 00834 return (intval($rows) === 1 ); 00835 00836 } 00837 00838 00842 function _update(&$record, $keys=null, $tablename=null, $secure=false ){ 00843 00844 00845 if ( $secure && !$record->checkPermission('edit') ){ 00846 // Use security to check to see if we are allowed to delete this 00847 // record. 00848 return Dataface_Error::permissionDenied( 00849 df_translate( 00850 'scripts.Dataface.IO._update.PERMISSION_DENIED', 00851 'Could not update record "'.$record->getTitle().'" from table "'.$record->_table->tablename.'" because you have insufficient permissions.', 00852 array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename) 00853 ) 00854 ); 00855 } 00856 if ( $secure ){ 00857 foreach ( array_keys($record->_table->fields()) as $fieldname ){ 00858 if ( $record->valueChanged($fieldname) and !@$record->vetoFields[$fieldname] and !$record->checkPermission('edit', array('field'=>$fieldname)) ){ 00859 // If this field's change doesn't have veto power and its value has changed, 00860 // we must make sure that the user has edit permission on this field. 00861 return Dataface_Error::permissionDenied( 00862 df_translate( 00863 'scripts.Dataface.IO._update.PERMISSION_DENIED_FIELD', 00864 'Could not update record "'.$record->getTitle().'" in table "'.$record->_table->tablename.'" because you do not have permission to modify the "'.$fieldname.'" column.', 00865 array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename, 'field'=>$fieldname) 00866 ) 00867 ); 00868 } 00869 } 00870 00871 } 00872 00873 // Step 1: Validate that the record already exists 00874 if ( !is_a($record, 'Dataface_Record') ){ 00875 throw new Exception( 00876 df_translate( 00877 'scripts.Dataface.IO._update.ERROR_PARAMETER_1', 00878 "In Dataface_IO::_update() the first argument is expected to be an object of type 'Dataface_Record' but received '".get_class($record)."'.\n<br>", 00879 array('class'=>get_class($record)) 00880 ), E_USER_ERROR); 00881 } 00882 if ( $tablename === null and $this->_altTablename !== null ){ 00883 $tablename = $this->_altTablename; 00884 } 00885 00886 $exists = $this->recordExists($record, $keys, $this->tablename($tablename)); 00887 if ( PEAR::isError($exists) ){ 00888 $exists->addUserInfo( 00889 df_translate( 00890 'scripts.Dataface.IO._update.ERROR_INCOMPLETE_INFORMATION', 00891 "Attempt to update record with incomplete information.", 00892 array('line'=>0,'file'=>'_') 00893 ) 00894 ); 00895 return $exists; 00896 } 00897 if ( !$exists ){ 00898 return PEAR::raiseError( 00899 df_translate( 00900 'scripts.Dataface.IO._update.ERROR_RECORD_DOESNT_EXIST', 00901 "Attempt to update record that doesn't exist in _update() ", 00902 array('line'=>0,'file'=>"_") 00903 ), DATAFACE_E_NO_RESULTS); 00904 } 00905 00906 // Step 2: Load objects that we will need 00907 $s =& $this->_table; 00908 $delegate =& $s->getDelegate(); 00909 $qb = new Dataface_QueryBuilder($this->_table->tablename, $keys); 00910 00911 if ( $record->recordChanged(true) ){ 00912 if ( $this->fireTriggers ){ 00913 $res = $this->fireBeforeUpdate($record); 00914 if ( PEAR::isError($res) ) return $res; 00915 } 00916 } 00917 00918 00919 $parentIO =& $this->getParentIO(); 00920 00921 if ( isset($parentIO) ){ 00922 00923 $parentRecord =& $record->getParentRecord(); 00924 00925 $res = $parentIO->write($parentRecord, $parentRecord->snapshotKeys()); 00926 if ( PEAR::isError($res) ) return $res; 00927 00928 } 00929 00930 00931 00932 // we only want to update changed values 00933 $sql = $qb->update($record,$keys, $this->tablename($tablename)); 00934 00935 if ( PEAR::isError($sql) ){ 00936 $sql->addUserInfo( 00937 df_translate( 00938 'scripts.Dataface.IO._update.ERROR_GENERATING_SQL', 00939 "Error generating sql for update in IO::_update()", 00940 array('line'=>0,'file'=>"_") 00941 ) 00942 ); 00943 return $sql; 00944 } 00945 if ( strlen($sql) > 0 ){ 00946 00947 00948 00949 //$res = mysql_query($sql, $s->db); 00950 $res =$this->dbObj->query($sql, $s->db, $this->lang); 00951 if ( !$res || PEAR::isError($res) ){ 00952 00953 if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_DUP_KEY,MYSQL_ER_DUP_ENTRY)) ){ 00954 /* 00955 * This is a duplicate entry. We will handle this as an exception rather than an error because 00956 * cases may arise in a database application when a duplicate entry will happen and the application 00957 * will want to handle it in a graceful way. Eg: If the user is entering a username that is the same 00958 * as an existing name. We don't want an ugle FATAL error to be thrown here. Rather we want to 00959 * notify the application that it is a duplicate entry. 00960 */ 00961 return Dataface_Error::duplicateEntry( 00962 df_translate( 00963 'scripts.Dataface.IO._update.ERROR_DUPLICATE_ENTRY', 00964 "Duplicate entry into table '".$s->tablename, 00965 array('tablename'=>$s->tablename) 00966 ) /* i18n parameters */ 00967 ); 00968 } 00969 throw new Exception( 00970 df_translate( 00971 'scripts.Dataface.IO._update.SQL_ERROR', 00972 "Failed to update due to sql error: ") 00973 .mysql_error($s->db), E_USER_ERROR); 00974 } 00975 00976 00977 if ( $this->fireTriggers ){ 00978 $res2 = $this->fireAfterUpdate($record); 00979 if ( PEAR::isError($res2) ) return $res2; 00980 } 00981 00982 00983 } 00984 00985 //$record->clearFlags(); 00986 return true; 00987 00988 00989 00990 } 00991 00995 function _insert(&$record, $tablename=null, $secure=false){ 00996 if ( $secure && !$record->checkPermission('new') ){ 00997 // Use security to check to see if we are allowed to delete this 00998 // record. 00999 return Dataface_Error::permissionDenied( 01000 df_translate( 01001 'scripts.Dataface.IO._insert.PERMISSION_DENIED', 01002 'Could not insert record "'.$record->getTitle().'" from table "'.$record->_table->tablename.'" because you have insufficient permissions.', 01003 array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename) 01004 ) 01005 ); 01006 } 01007 if ( $secure ){ 01008 foreach ( array_keys($record->_table->fields()) as $fieldname ){ 01009 if ( $record->valueChanged($fieldname) and !@$record->vetoFields[$fieldname] and !$record->checkPermission('new', array('field'=>$fieldname)) ){ 01010 // If this field was changed and the field doesn't have veto power, then 01011 // we must subject the change to a security check - the user must havce 01012 // edit permission to perform the change. 01013 return Dataface_Error::permissionDenied( 01014 df_translate( 01015 'scripts.Dataface.IO._insert.PERMISSION_DENIED_FIELD', 01016 'Could not insert record "'.$record->getTitle().'" into table "'.$record->_table->tablename.'" because you do not have permission to modify the "'.$fieldname.'" column.', 01017 array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename, 'field'=>$fieldname) 01018 ) 01019 ); 01020 } 01021 } 01022 01023 } 01024 01025 if ( $tablename === null and $this->_altTablename !== null ){ 01026 $tablename = $this->_altTablename; 01027 } 01028 $s =& $this->_table; 01029 $delegate =& $s->getDelegate(); 01030 01031 if ( $this->fireTriggers ){ 01032 $res = $this->fireBeforeInsert($record); 01033 if ( PEAR::isError($res) ) return $res; 01034 } 01035 01036 01037 01038 $parentIO =& $this->getParentIO(); 01039 if ( isset($parentIO) ){ 01040 $parentRecord =& $record->getParentRecord(); 01041 $res = $parentIO->write($parentRecord, $parentRecord->snapshotKeys()); 01042 if ( PEAR::isError($res) ) return $res; 01043 unset($parentRecord); 01044 } 01045 01046 $qb = new Dataface_QueryBuilder($s->tablename); 01047 $sql = $qb->insert($record, $this->tablename($tablename)); 01048 if ( PEAR::isError($sql) ){ 01049 01050 throw new Exception( 01051 df_translate( 01052 'scripts.Dataface.IO._insert.ERROR_GENERATING_SQL', 01053 "Error generating sql for insert in IO::_insert()") 01054 , E_USER_ERROR); 01055 //return $sql; 01056 } 01057 01058 01059 //$res = mysql_query($sql, $s->db); 01060 $res = $this->dbObj->query($sql, $s->db, $this->lang); 01061 if ( !$res || PEAR::isError($res)){ 01062 if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_DUP_KEY,MYSQL_ER_DUP_ENTRY)) ){ 01063 /* 01064 * This is a duplicate entry. We will handle this as an exception rather than an error because 01065 * cases may arise in a database application when a duplicate entry will happen and the application 01066 * will want to handle it in a graceful way. Eg: If the user is entering a username that is the same 01067 * as an existing name. We don't want an ugle FATAL error to be thrown here. Rather we want to 01068 * notify the application that it is a duplicate entry. 01069 */ 01070 return Dataface_Error::duplicateEntry( 01071 Dataface_LanguageTool::translate( 01072 /* i18n id */ 01073 "Failed to insert record because of duplicate entry", 01074 /* Default error message */ 01075 "Duplicate entry into table '".$s->tablename, 01076 /* i18n parameters */ 01077 array('table'=>$s->tablename) 01078 ) 01079 ); 01080 } 01081 throw new Exception( 01082 df_translate( 01083 'scripts.Dataface.IO._insert.ERROR_INSERTING_RECORD', 01084 "Error inserting record: ") 01085 .(PEAR::isError($res)?$res->getMessage():mysql_error(df_db())).": SQL: $sql", E_USER_ERROR); 01086 } 01087 $id = df_insert_id($s->db); 01088 $this->insertIds[$this->_table->tablename] = $id; 01089 01090 /* 01091 * Now update the record to contain the proper id. 01092 */ 01093 $autoIncrementField = $s->getAutoIncrementField(); 01094 if ( $autoIncrementField !== null ){ 01095 $record->setValue($autoIncrementField, $id); 01096 } 01097 01098 01099 if ( $this->fireTriggers ){ 01100 $res2 = $this->fireAfterInsert($record); 01101 if ( PEAR::isError($res2) ) return $res2; 01102 } 01103 01104 return true; 01105 01106 } 01107 01108 01109 function _writeRelationship($relname, $record){ 01110 $s =& $this->_table; 01111 $rel =& $s->getRelationship($relname); 01112 01113 if ( PEAR::isError($rel) ){ 01114 $rel->addUserInfo( 01115 df_translate( 01116 'scripts.Dataface.IO._writeRelationship.ERROR_OBTAINING_RELATIONSHIP', 01117 "Error obtaining relationship $relname in IO::_writeRelationship()", 01118 array('relname'=>$relname,'line'=>0,'file'=>"_") 01119 ) 01120 ); 01121 return $rel; 01122 } 01123 01124 $tables =& $rel['selected_tables']; 01125 $columns =& $rel['columns']; 01126 01127 if ( count($tables) == 0 ){ 01128 return PEAR::raiseError( 01129 Dataface_LanguageTool::translate( 01130 /* i18n id */ 01131 "Failed to write relationship because not table was selected", 01132 /* default error message */ 01133 "Error writing relationship '$relname'. No tables were selected", 01134 /* i18n parameters */ 01135 array('relationship'=>$relname) 01136 ), 01137 DATAFACE_E_NO_TABLE_SPECIFIED 01138 ); 01139 } 01140 01141 $records =& $record->getRelatedRecords($relname); 01142 $record_keys = array_keys($records); 01143 if ( PEAR::isError( $records) ){ 01144 $records->addUserInfo( 01145 df_translate( 01146 'scripts.Dataface.IO._writeRelationship.ERROR_GETTING_RELATED_RECORDS', 01147 "Error getting related records in IO::_writeRelationship()", 01148 array('line'=>0,'file'=>"_") 01149 ) 01150 ); 01151 return $records; 01152 } 01153 01154 01155 01156 foreach ($tables as $table){ 01157 01158 $rs =& Dataface_Table::loadTable($table, $s->db); 01159 $keys = array_keys($rs->keys()); 01160 $cols = array(); 01161 foreach ($columns as $column){ 01162 if ( preg_match('/^'.$table.'\.(\w+)/', $column, $matches) ){ 01163 $cols[] = $matches[1]; 01164 } 01165 } 01166 01167 01168 foreach ($record_keys as $record_key){ 01169 $changed = false; 01170 // flag whether this record has been changed 01171 $update_cols = array(); 01172 // store the columns that have been changed and require update 01173 01174 foreach ( $cols as $column ){ 01175 // check each column to see if it has been changed 01176 if ( $s->valueChanged($relname.'.'.$column, $record_key) ){ 01177 01178 $changed = true; 01179 $update_cols[] = $column; 01180 } else { 01181 01182 } 01183 } 01184 if ( !$changed ) continue; 01185 // if this record has not been changed with respect to the 01186 // columns of the current table, then we ignore it. 01187 01188 $sql = "UPDATE `$table` "; 01189 $set = ''; 01190 foreach ( $update_cols as $column ){ 01191 $set .= "SET $column = '".addslashes($rs->getSerializedValue($column, $records[$record_key][$column]) )."',"; 01192 } 01193 $set = trim(substr( $set, 0, strlen($set)-1)); 01194 01195 $where = 'WHERE '; 01196 foreach ($keys as $key){ 01197 $where .= "`$key` = '".addslashes($rs->getSerializedValue($key, $records[$record_key][$key]) )."' AND "; 01198 } 01199 $where = trim(substr($where, 0, strlen($where)-5)); 01200 01201 if ( strlen($where)>0 ) $where = ' '.$where; 01202 if ( strlen($set)>0 ) $set = ' '.$set; 01203 01204 $sql = $sql.$set.$where.' LIMIT 1'; 01205 01206 //$res = mysql_query($sql, $s->db); 01207 $res = $this->dbObj->query($sql, $s->db, $this->lang); 01208 if ( !$res || PEAR::isError($res) ){ 01209 throw new Exception( 01210 df_translate( 01211 'scripts.Dataface.IO._writeRelationship.ERROR_UPDATING_DATABASE', 01212 "Error updating database with query '$sql': ".mysql_error($s->db), 01213 array('sql'=>$sql,'mysql_error'=>mysql_error($s->db)) 01214 ) 01215 , E_USER_ERROR); 01216 } 01217 } 01218 01219 unset($rs); 01220 } 01221 } 01222 01223 01233 function performSQL($sql){ 01234 01235 $ids = array(); 01236 $queue = $sql; 01237 $names = array_keys($sql); 01238 $tables = implode('|', $names ); 01239 $skips = 0; // keep track of number of consecutive times we skip an iteration so we know when we have reached 01240 // a deadlock. 01241 01242 if ( func_num_args() >= 2 ){ 01243 $duplicates =& func_get_arg(1); 01244 if ( !is_array($duplicates) ){ 01245 throw new Exception( 01246 df_translate( 01247 'scripts.Dataface.IO.performSQL.ERROR_PARAMETER_2', 01248 "In Dataface_IO::performSQL() 2nd argument is expected to be an array but received '".get_class($duplicates)."'.", 01249 array('class'=>get_class($duplicates)) 01250 ) 01251 , E_USER_ERROR); 01252 } 01253 } else { 01254 $duplicates = array(); 01255 } 01256 $queryAttempts = array(); 01257 $numQueries = count($queue); 01258 while (count($queue) > 0 and $skips < $numQueries){ 01259 $current_query = array_shift($queue); 01260 $current_table = array_shift($names); 01261 if ( !isset($queryAttempts[$current_query]) ) $queryAttempts[$current_query] = 1; 01262 else $queryAttempts[$current_query]++; 01263 01264 $matches = array(); 01265 if ( preg_match('/__('.$tables.')__auto_increment__/', $current_query, $matches) ){ 01266 $table = $matches[1]; 01267 if ( isset($ids[$table]) ){ 01268 $current_query = preg_replace('/__'.$table.'__auto_increment__/', $ids[$table], $current_query); 01269 } else { 01270 array_push($queue, $current_query); 01271 array_push($names, $current_table); 01272 $skips++; 01273 continue; 01274 } 01275 } 01276 01277 01278 //$res = mysql_query($current_query, $this->_table->db); 01279 $res = $this->dbObj->query($current_query, $this->_table->db, $this->lang); 01280 if ( !$res || PEAR::isError($res) ){ 01281 if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_DUP_KEY,MYSQL_ER_DUP_ENTRY)) ){ 01282 /* 01283 * This is a duplicate record (ie: it already exists) 01284 */ 01285 $duplicates[] = $current_table; 01286 } else if ( $queryAttempts[$current_query] < 3 and in_array(mysql_errno($this->_table->db), array(MYSQL_ER_NO_REFERENCED_ROW, MYSQL_ER_NO_REFERENCED_ROW_2, MYSQL_ER_ROW_IS_REFERENCED_2)) ){ 01292 array_push($queue, $current_query); 01293 array_push($names, $current_table); 01294 01295 01296 } else { 01297 if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_NO_REFERENCED_ROW, MYSQL_ER_NO_REFERENCED_ROW_2)) ){ 01298 /* 01299 THis failed due to a foreign key constraint. 01300 */ 01301 $err = PEAR::raiseError( 01302 sprintf( 01303 df_translate( 01304 'scripts.Dataface.IO.performSQL.ERROR_FOREIGN_KEY', 01305 'Failed to save record because a foreign key constraint failed: %s' 01306 ), 01307 mysql_error(df_db()) 01308 ), 01309 01310 DATAFACE_E_NOTICE 01311 ); 01312 error_log($err->toString()); 01313 return $err; 01314 } 01315 01316 $err = PEAR::raiseError(DATAFACE_TABLE_SQL_ERROR, null,null,null, 01317 df_translate( 01318 'scripts.Dataface.IO.performSQL.ERROR_PERFORMING_QUERY', 01319 "Error performing query '$current_query'", 01320 array('line'=>0,'file'=>'_','current_query'=>$current_query) 01321 ) 01322 .mysql_errno($this->_table->db).': '.mysql_error($this->_table->db)); 01323 throw new Exception($err->toString(), E_USER_ERROR); 01324 } 01325 } 01326 $ids[$current_table] = df_insert_id(); 01327 self::touchTable($current_table); 01328 $skips = 0; 01329 } 01330 $this->insertids = $ids; 01331 01332 return true; 01333 01334 01335 } 01336 01337 01342 function addRelatedRecord(&$record, $secure=false){ 01343 if ( $secure && !$record->_record->checkPermission('add new related record', array('relationship'=>$record->_relationshipName) ) ){ 01344 // Use security to check to see if we are allowed to delete this 01345 // record. 01346 return Dataface_Error::permissionDenied( 01347 df_translate( 01348 'scripts.Dataface.IO.addRelatedRecord.PERMISSION_DENIED', 01349 'Could not add record "'.$record->getTitle().'" to relationship "'.$record->_relationshipName.'" of record "'.$record->_record->getTitle().'" because you have insufficient permissions.', 01350 array('title'=>$record->getTitle(), 'relationship'=>$record->_relationshipName, 'parent'=>$record->_record->getTitle()) 01351 ) 01352 ); 01353 } 01354 01355 01356 $queryBuilder = new Dataface_QueryBuilder($this->_table->tablename); 01357 01358 // Fire the "before events" 01359 if ( $this->fireTriggers ){ 01360 $res = $this->fireBeforeAddRelatedRecord($record); 01361 if ( PEAR::isError($res) ) return $res; 01362 } 01363 01364 if ( $this->fireTriggers ){ 01365 $res = $this->fireBeforeAddNewRelatedRecord($record); 01366 if ( PEAR::isError($res) ) return $res; 01367 } 01368 01369 01370 01371 01372 01373 01374 01375 // It makes sense for us to fire beforeSave, afterSave, beforeInsert, and afterInsert 01376 // events here for the records that are being inserted. To do this we will need to extract 01377 // Dataface_Record objects for all of the tables that will have records inserted. 01378 $drecords = $record->toRecords(); 01379 // $drecords is an array of Dataface_Record objects 01380 01381 foreach ( array_keys($drecords) as $recordIndex){ 01382 $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename); 01383 01384 $drec_snapshot = $drecords[$recordIndex]->strvals(); 01385 01386 $res = $rio->fireBeforeSave($drecords[$recordIndex]); 01387 if (PEAR::isError($res) ) return $res; 01388 $res = $rio->fireBeforeInsert($drecords[$recordIndex]); 01389 if ( PEAR::isError($res) ) return $res; 01390 01391 $drec_post_snapshot = $drecords[$recordIndex]->strvals(); 01392 01393 foreach ( $drec_snapshot as $ss_key=>$ss_val ){ 01394 01395 if ( $drec_post_snapshot[$ss_key] != $ss_val ){ 01396 01397 $record->setValue($ss_key,$drec_post_snapshot[$ss_key]); 01398 } 01399 } 01400 01401 unset($drec_snapshot); 01402 unset($drec_post_snapshot); 01403 unset($rio); 01404 } 01405 01406 //$sql = Dataface_QueryBuilder::addRelatedRecord($record); 01407 $sql = $queryBuilder->addRelatedRecord($record); 01408 if ( PEAR::isError($sql) ){ 01409 $sql->addUserInfo( 01410 df_translate( 01411 'scripts.Dataface.IO.addRelatedRecord.ERROR_GENERATING_SQL', 01412 "Error generating sql in ShortRelatedRecordForm::save()", 01413 array('line'=>0,'file'=>"_") 01414 ) 01415 ); 01416 return $sql; 01417 } 01418 01419 // Actually add the record 01420 $res = $this->performSQL($sql); 01421 if ( PEAR::isError($res) ){ 01422 return $res; 01423 } 01424 01425 $rfields = array_keys($record->vals()); 01426 // Just for completeness we will fire afterSave and afterInsert events for 01427 // all records being inserted. 01428 foreach ( array_keys($drecords) as $recordIndex){ 01429 $currentRecord =& $drecords[$recordIndex]; 01430 if ( isset($this->insertids[ $currentRecord->_table->tablename ] ) ){ 01431 $idfield = $currentRecord->_table->getAutoIncrementField(); 01432 if ( $idfield ){ 01433 $currentRecord->setValue($idfield, $this->insertids[ $currentRecord->_table->tablename ]); 01434 if ( in_array($idfield, $rfields) ){ 01435 $record->setValue($idfield, $this->insertids[ $currentRecord->_table->tablename ]); 01436 } 01437 } 01438 01439 unset($idfield); 01440 } 01441 unset($currentRecord); 01442 $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename); 01443 01444 $res = $rio->saveTransients($drecords[$recordIndex], null, null, true); 01445 if ( PEAR::isError($res) ){ 01446 return $res; 01447 } 01448 01449 $res = $rio->fireAfterInsert($drecords[$recordIndex]); 01450 if (PEAR::isError($res) ) return $res; 01451 $res = $rio->fireAfterSave($drecords[$recordIndex]); 01452 if ( PEAR::isError($res) ) return $res; 01453 01454 unset($rio); 01455 } 01456 01457 01458 // Fire the "after" events 01459 if ( $this->fireTriggers ){ 01460 $res2 = $this->fireAfterAddNewRelatedRecord($record); 01461 if ( PEAR::isError($res2) ) return $res2; 01462 01463 $res2 = $this->fireAfterAddRelatedRecord($record); 01464 if ( PEAR::isError($res2) ) return $res2; 01465 } 01466 01467 return $res; 01468 } 01469 01474 function addExistingRelatedRecord(&$record, $secure=false){ 01475 if ( $secure && !$record->_record->checkPermission('add existing related record', array('relationship'=>$record->_relationshipName) ) ){ 01476 // Use security to check to see if we are allowed to delete this 01477 // record. 01478 return Dataface_Error::permissionDenied( 01479 df_translate( 01480 'scripts.Dataface.IO.addExistingRelatedRecord.PERMISSION_DENIED', 01481 'Could not add record "'.$record->getTitle().'" to relationship "'.$record->_relationshipName.'" of record "'.$record->_record->getTitle().'" because you have insufficient permissions.', 01482 array('title'=>$record->getTitle(), 'relationship'=>$record->_relationshipName, 'parent'=>$record->_record->getTitle()) 01483 ) 01484 ); 01485 } 01486 01487 $builder = new Dataface_QueryBuilder($this->_table->tablename); 01488 01489 //We are often missing the values from the domain table so we will load them 01490 //here 01491 $domainRec = $record->toRecord($record->_relationship->getDomainTable()); 01492 $domainRec2 = df_get_record_by_id($domainRec->getId()); 01493 //$record->setValues(array_merge($domainRec2->vals(), $record->vals())); 01494 foreach ($domainRec2->vals() as $dreckey=>$drecval){ 01495 if ( !$record->val($dreckey) ) $record->setValue($dreckey, $drecval); 01496 } 01497 // fire the "before" events 01498 if ( $this->fireTriggers ){ 01499 $res =$this->fireBeforeAddRelatedRecord($record); 01500 if ( PEAR::isError($res) ) return $res; 01501 01502 $res = $this->fireBeforeAddExistingRelatedRecord($record); 01503 if ( PEAR::isError($res) ) return $res; 01504 } 01505 01506 01507 01508 01509 01510 // It makes sense for us to fire beforeSave, afterSave, beforeInsert, and afterInsert 01511 // events here for the records that are being inserted. To do this we will need to extract 01512 // Dataface_Record objects for all of the tables that will have records inserted. In this 01513 // case we are not updated any records because relationships are created by adding a record 01514 // to the join table. This means that we are also NOT adding a record to the domain table. 01515 // i.e., we should only fire these events for the join table. 01516 $drecords = & $record->toRecords(); 01517 // $drecords is an array of Dataface_Record objects 01518 01519 if ( count($drecords) > 1 ){ 01520 // If there is only one record then it is for the domain table - which we don't actually 01521 // change. 01522 foreach ( array_keys($drecords) as $recordIndex){ 01523 $currentRecord =& $drecords[$recordIndex]; 01524 if ( isset($this->insertids[ $currentRecord->_table->tablename ] ) ){ 01525 $idfield =& $currentRecord->_table->getAutoIncrementField(); 01526 if ( $idfield ){ 01527 $currentRecord->setValue($idfield, $this->insertids[ $currentRecord->_table->tablename ]); 01528 } 01529 unset($idfield); 01530 } 01531 unset($currentRecord); 01532 if ( $drecords[$recordIndex]->_table->tablename === $record->_relationship->getDomainTable() ) continue; 01533 // We don't do anything for the domain table because it is not being updated. 01534 01535 $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename); 01536 01537 $drec_snapshot = $drecords[$recordIndex]->strvals(); 01538 01539 $res = $rio->fireBeforeSave($drecords[$recordIndex]); 01540 if (PEAR::isError($res) ) return $res; 01541 $res = $rio->fireBeforeInsert($drecords[$recordIndex]); 01542 if ( PEAR::isError($res) ) return $res; 01543 01544 $drec_post_snapshot = $drecords[$recordIndex]->strvals(); 01545 01546 foreach ( $drec_post_snapshot as $ss_key=>$ss_val ){ 01547 if ( $drec_snapshot[$ss_key] != $ss_val ){ 01548 $drecords[$recordIndex]->setValue($ss_key,$ss_val); 01549 } 01550 } 01551 01552 unset($drec_post_snapshot); 01553 unset($drec_snapshot); 01554 unset($rio); 01555 } 01556 } 01557 01558 01559 if ( count($drecords) > 1 ){ 01560 $sql = $builder->addExistingRelatedRecord($record); 01561 if ( PEAR::isError($sql) ){ 01562 return $sql; 01563 } 01564 // Actually add the related record 01565 $res = $this->performSQL($sql); 01566 if ( PEAR::isError( $res) ) return $res; 01567 01568 // If there is only one record then it is for the domain table - which we don't actually 01569 // change. 01570 foreach ( array_keys($drecords) as $recordIndex){ 01571 01572 if ( $drecords[$recordIndex]->_table->tablename === $record->_relationship->getDomainTable() ) continue; 01573 // We don't do anything for the domain table because it is not being updated. 01574 01575 $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename); 01576 01577 $res = $rio->fireAfterInsert($drecords[$recordIndex]); 01578 if (PEAR::isError($res) ) return $res; 01579 $res = $rio->fireAfterSave($drecords[$recordIndex]); 01580 if ( PEAR::isError($res) ) return $res; 01581 01582 unset($rio); 01583 } 01584 } else { 01585 01586 01587 // This is a one to many relationship. We will handle this case 01588 // only when the foreign key is currently null. Otherwise we return 01589 // and error. 01590 $fkeys = $record->_relationship->getForeignKeyValues(); 01591 $fkeyvals = $record->getForeignKeyValues(); 01592 if ( isset($fkeys[$domainRec2->_table->tablename]) ){ 01593 $drecid = $domainRec2->getId(); 01594 unset($domainRec2); 01595 $domainRec2 = df_get_record_by_id($drecid); 01596 if ( !$domainRec2 ){ 01597 return PEAR::raiseError("Tried to get record with id $drecid but it doesn't exist"); 01598 01599 } else if ( PEAR::isError($domainRec2) ){ 01600 return $domainRec2; 01601 } 01602 foreach ( array_keys($fkeys[$domainRec2->_table->tablename]) as $fkey){ 01603 //echo $fkey; 01604 01605 if ( $domainRec2->val($fkey) ){ 01606 return PEAR::raiseError("Could not add existing related record '".$domainRec2->getTitle()."' because it can only belong to a single relationship and it already belongs to one."); 01607 01608 } else { 01609 01610 $domainRec2->setValue($fkey, $fkeyvals[$domainRec2->_table->tablename][$fkey]); 01611 } 01612 } 01613 01614 $res = $domainRec2->save($secure); 01615 if ( PEAR::raiseError($res) ) return $res; 01616 } else { 01617 return PEAR::raiseError("Failed to add existing record because the domain table doesn't have any foreign keys in it."); 01618 } 01619 01620 01621 } 01622 01623 // Fire the "after" events 01624 if ( $this->fireTriggers ){ 01625 $res2 = $this->fireAfterAddExistingRelatedRecord($record); 01626 if ( PEAR::isError( $res2 ) ) return $res2; 01627 01628 $res2 = $this->fireAfterAddRelatedRecord($record); 01629 if ( PEAR::isError( $res2 ) ) return $res2; 01630 } 01631 01632 return $res; 01633 01634 } 01635 01644 function removeRelatedRecord(&$related_record, $delete=false, $secure=false){ 01645 if ( $secure && !$related_record->_record->checkPermission('remove related record', array('relationship'=>$related_record->_relationshipName) ) ){ 01646 // Use security to check to see if we are allowed to delete this 01647 // record. 01648 01649 return Dataface_Error::permissionDenied( 01650 df_translate( 01651 'scripts.Dataface.IO.removeRelatedRecord.PERMISSION_DENIED', 01652 'Could not remove record "'.$related_record->getTitle().'" from relationship "'.$related_record->_relationshipName.'" of record "'.$related_record->_record->getTitle().'" because you have insufficient permissions.', 01653 array('title'=>$related_record->getTitle(), 'relationship'=>$related_record->_relationshipName, 'parent'=>$related_record->_record->getTitle()) 01654 ) 01655 ); 01656 } 01657 01658 $res = $this->fireEvent('beforeRemoveRelatedRecord', $related_record); 01659 if ( PEAR::isError($res) ) return $res; 01660 /* 01661 * First we need to find out which table is the domain table. The domain table 01662 * is the table that actually contains the records of interest. The rest of 01663 * the tables are referred to as 'join' tables. 01664 */ 01665 $domainTable = $related_record->_relationship->getDomainTable(); 01666 if ( PEAR::isError($domainTable) ){ 01667 /* 01668 * Dataface_Relationship::getDomainTable() throws an error if there are 01669 * no join tables. We account for that by explicitly setting the domain 01670 * table to the first table in the list. 01671 */ 01672 $domainTable = $related_record->_relationship->_schema['selected_tables'][0]; 01673 } 01674 /* 01675 * Next we construct an IO object to write to the domain table. 01676 */ 01677 $domainIO = new Dataface_IO($domainTable); 01678 01679 $domainTable =& Dataface_Table::loadTable($domainTable); 01680 // reference to the Domain table Dataface_Table object. 01681 01682 /* 01683 * Begin building queries. 01684 */ 01685 $query = array(); 01686 // query array to build the query to delete the record. 01687 $absVals = array(); 01688 // same as query array except the keys are absolute field names (ie: Tablename.Fieldname) 01689 $currKeyNames = array_keys($domainTable->keys()); 01690 // Names of key fields in the domain table 01691 foreach ($currKeyNames as $keyName){ 01692 $query[$keyName] = $related_record->val($keyName); 01693 $absVals[$domainTable->tablename.'.'.$keyName] = $query[$keyName]; 01694 } 01695 01696 01697 $fkeys = $related_record->_relationship->getForeignKeyValues($absVals, null, $related_record->_record); 01698 $warnings = array(); 01699 $confirmations = array(); 01700 foreach ( array_keys($fkeys) as $currTable){ 01701 // For each table in the relationship we go through and delete its record. 01702 $io = new Dataface_IO($currTable); 01703 01704 $record = new Dataface_Record($currTable, array()); 01705 $res = $io->read($fkeys[$currTable], $record); 01706 //patch for Innodb foreign keys with ON DELELE CASCADE 01707 // Contributed by Optik 01708 if (!$io->recordExists($record,null,$currTable)){ 01709 $warnings[] = df_translate( 01710 'scripts.Dataface.IO.removeRelatedRecord.ERROR_RECORD_DOESNT_EXIST', 01711 "Failed to delete entry for record '".$record->getTitle()."' in table '$currTable' because record doesn't exist.", 01712 array('title'=>$record->getTitle(), 'currTable'=>$currTable) 01713 ); 01714 unset($record); 01715 unset($io); 01716 continue; 01717 } 01718 // -- end patch for Innodb foreign keys 01719 if ( $currTable == $domainTable->tablename and !$delete ){ 01720 // Unless we have specified that we want the domain table record 01721 // deleted, we leave it alone! 01722 01723 01724 01725 // If this is a one to many we'll try to just set the foreign key to null 01726 if ( count($fkeys) == 1 ){ 01727 01728 if (($currTable == $domainTable->tablename) and $secure and !$related_record->_record->checkPermission('remove related record', array('relationship'=>$related_record->_relationshipName)) ){ 01729 $useSecurity = true; 01730 01731 } else { 01732 $useSecurity = false; 01733 } 01734 01735 $myfkeys = $related_record->_relationship->getForeignKeyValues(); 01736 foreach ( $myfkeys[$currTable] as $colName=>$colVal ){ 01737 $record->setValue($colName, null); 01738 01739 } 01740 //exit; 01741 01742 $res = $record->save(null, $useSecurity); 01743 if ( PEAR::isError($res) && Dataface_Error::isError($res) ){ 01744 //$this->logError($res); 01745 return $res; 01746 } else if ( PEAR::isError($res) ){ 01747 $warnings[] = $res; 01748 01749 } else { 01750 01751 $confirmations[] = df_translate( 01752 'Successfully removed record', 01753 "Successfully removed entry for record '".$record->getTitle()."' in table '$currTable'", 01754 array('title'=>$record->getTitle(), 'table'=>$currTable) 01755 ); 01756 01757 } 01758 01759 01760 } 01761 01762 unset($record); 01763 unset($io); 01764 continue; 01765 } 01766 01767 // Let's figure out whether we need to use security for deleting this 01768 // record. 01769 // If security is on, and it is the domain table, and the user doesn't 01770 // have the 'delete related record' permission then we need to use 01771 // security 01772 if (($currTable == $domainTable->tablename) and $secure and !$related_record->_record->checkPermission('delete related record', array('relationship'=>$related_record->_relationshipName)) ){ 01773 $useSecurity = true; 01774 01775 } else { 01776 $useSecurity = false; 01777 } 01778 01779 $res = $io->delete($record, $useSecurity); 01780 01781 if ( PEAR::isError($res) && Dataface_Error::isError($res) ){ 01782 //$this->logError($res); 01783 return $res; 01784 } else if ( PEAR::isError($res) ){ 01785 $warnings[] = $res; 01786 } 01787 else { 01788 $confirmations[] = df_translate( 01789 'Successfully deleted record', 01790 "Successfully deleted entry for record '".$record->getTitle()."' in table '$currTable'", 01791 array('title'=>$record->getTitle(), 'table'=>$currTable) 01792 ); 01793 } 01794 $record->__destruct(); 01795 unset($record); 01796 unset($b); 01797 unset($io); 01798 01799 } 01800 $res = $this->fireEvent('afterRemoveRelatedRecord', $related_record); 01801 if ( PEAR::isError($res) ) return $res; 01802 if (count($warnings)>0 ) return PEAR::raiseError(@implode("\n",$warnings), DATAFACE_E_WARNING); 01803 if (count($confirmations)==0) return false; 01804 return true; 01805 01806 } 01807 01834 function copy(&$sourceRecord, &$destParent, $destRelationship=null, $deepCopy=false){ 01835 throw new Exception("The method ".__METHOD__." is not implemented yet.", E_USER_ERROR); 01836 } 01837 01838 01839 01840 // Event handlers. 01841 01846 function fireBeforeSave(&$record){ 01847 return $this->fireEvent('beforeSave', $record); 01848 } 01849 01854 function fireAfterSave(&$record){ 01855 return $this->fireEvent('afterSave', $record); 01856 } 01857 01862 function fireBeforeUpdate(&$record){ 01863 return $this->fireEvent('beforeUpdate', $record); 01864 } 01865 01866 01871 function fireAfterUpdate(&$record){ 01872 return $this->fireEvent('afterUpdate', $record); 01873 } 01874 01879 function fireBeforeInsert(&$record){ 01880 return $this->fireEvent('beforeInsert', $record); 01881 } 01882 01887 function fireAfterInsert(&$record){ 01888 return $this->fireEvent('afterInsert', $record); 01889 } 01890 01895 function fireBeforeAddRelatedRecord(&$record){ 01896 return $this->fireEvent('beforeAddRelatedRecord', $record); 01897 } 01898 01903 function fireAfterAddRelatedRecord(&$record){ 01904 return $this->fireEvent('afterAddRelatedRecord', $record); 01905 01906 } 01907 01912 function fireBeforeAddNewRelatedRecord(&$record){ 01913 return $this->fireEvent('beforeAddNewRelatedRecord', $record); 01914 } 01915 01920 function fireAfterAddNewRelatedRecord(&$record){ 01921 return $this->fireEvent('afterAddNewRelatedRecord', $record); 01922 } 01923 01928 function fireBeforeAddExistingRelatedRecord(&$record){ 01929 return $this->fireEvent('beforeAddExistingRelatedRecord', $record); 01930 } 01931 01936 function fireAfterAddExistingRelatedRecord(&$record){ 01937 return $this->fireEvent('afterAddExistingRelatedRecord', $record); 01938 } 01939 01944 function fireBeforeDelete(&$record){ 01945 return $this->fireEvent('beforeDelete', $record); 01946 } 01947 01952 function fireAfterDelete(&$record){ 01953 return $this->fireEvent('afterDelete', $record); 01954 } 01955 01961 function fireEvent($name, &$record, $bubble=true){ 01962 $oldVeto = $record->vetoSecurity; 01963 $record->vetoSecurity = true; 01964 $delegate =& $this->_table->getDelegate(); 01965 if ( $delegate !== null and method_exists($delegate,$name) ){ 01966 $res =& $delegate->$name($record); 01967 if ( PEAR::isError( $res ) ){ 01968 $res->addUserInfo( 01969 df_translate( 01970 'scripts.Dataface.IO.fireEvent.ERROR_WHILE_FIRING', 01971 "Error while firing event '$name' on table '".$this->_table->tablename."' in Dataface_IO::write() ", 01972 array('name'=>$name,'tablename'=>$this->_table->tablename, 'line'=>0,'file'=>"_") 01973 ) 01974 ); 01975 $record->vetoSecurity = $oldVeto; 01976 return $res; 01977 } 01978 } 01979 01980 $parentIO =& $this->getParentIO(); 01981 if ( isset($parentIO) ){ 01982 $parentIO->fireEvent($name, $record, false); 01983 } 01984 01985 if ( $bubble ){ 01986 $app =& Dataface_Application::getInstance(); 01987 $res = $app->fireEvent($name, array(&$record, &$this)); 01988 if ( PEAR::isError($res) ) { 01989 $record->vetoSecurity = $oldVeto; 01990 return $res; 01991 } 01992 } 01993 01994 return true; 01995 01996 } 01997 01998 01999 02000 02001 02008 function tablename($tablename=null){ 02009 if ( $tablename !== null ) return $tablename; 02010 return $this->_table->tablename; 02011 02012 } 02013 02014 02090 function importData( &$record, $data, $importFilter=null, $relationshipName=null, $commit=false, $defaultValues=array()){ 02091 if ( $relationshipName === null ){ 02092 02093 /* 02094 * No relationship is specified so our import table is just the current table. 02095 */ 02096 $table =& $this->_table; 02097 02098 } else { 02099 /* 02100 * A relationship is specified so we are actually importing the records into the 02101 * domain table of the relationship. 02102 */ 02103 02104 $relationship =& $this->_table->getRelationship($relationshipName); 02105 $tablename = $relationship->getDomainTable(); 02106 if ( PEAR::isError($tablename) ){ 02107 /* 02108 * This relationship does not have a domain table.. so we will just take the destination table. 02109 */ 02110 $destinationTables =& $relationship->getDestinationTables(); 02111 if ( count($destinationTables) <= 0 ){ 02112 throw new Exception( 02113 df_translate( 02114 'scripts.Dataface.IO.importData.ERROR_NO_DESTINATION_TABLES', 02115 "Error occurred while attempting to parse import data into a table. The relationship '".$relationship->getName()."' of table '".$this->_table->tablename."' has not destination tables listed. It should have at least one.\n", 02116 array('relationship'=>$relationship->getName(), 'table'=>$this->_table->tablename) 02117 ) 02118 , E_USER_ERROR); 02119 } 02120 $tablename = $destinationTables[0]->tablename; 02121 02122 } 02123 02124 if ( PEAR::isError($tablename) ){ 02125 throw new Exception($tablename->toString(), E_USER_ERROR); 02126 } 02127 $table =& Dataface_Table::loadTable($tablename); 02128 $rel_io = new Dataface_IO($tablename); 02129 $io =& $rel_io; 02130 } 02131 02132 if ( !$commit ){ 02133 // If data is provided, we must parse it and prepare it for 02134 // import 02135 $records = $table->parseImportData($data, $importFilter, $defaultValues); 02136 if ( PEAR::isError($records) ){ 02137 /* 02138 * The import didn't work with the specified import filter, so we will 02139 * try the other filters. 02140 */ 02141 $records = $table->parseImportData($data, null, $defaultValues); 02142 } 02143 02144 if ( PEAR::isError($records) ){ 02145 /* 02146 * Apparently we have failed to import the data, so let's just 02147 * return the errors. 02148 */ 02149 return $records; 02150 } 02151 02152 // Now we will load the values of the records into an array 02153 // so that we can store it in the session 02154 $importData = array( 02155 'table' => $table->tablename, 02156 'relationship' => $relationshipName, 02157 'defaults' => $defaultValues, 02158 'importFilter' => $importFilter, 02159 'record' => null, 02160 'rows' => array() 02161 ); 02162 if ( isset($record) ) $importData['record'] = $record->getId(); 02163 02164 foreach ($records as $r){ 02165 if ( is_a($r, 'Dataface_ImportRecord') ){ 02166 // The current record is actually an ImportRecord 02167 $importData['rows'][] = $r->toArray(); 02168 } else { 02169 $importData['rows'][] = $r->vals(array_keys($r->_table->fields(false,true))); 02170 unset($r); 02171 } 02172 } 02173 02174 $dumpFile = tempnam(sys_get_temp_dir(), 'dataface_import'); 02175 $handle = fopen($dumpFile, "w"); 02176 if ( !$handle ){ 02177 throw new Exception("Could not write import data to dump file $dumpFile", E_USER_ERROR); 02178 } 02179 fwrite($handle, serialize($importData)); 02180 fclose($handle); 02181 02182 $_SESSION['__dataface__import_data__'] = $dumpFile; 02183 02184 return $dumpFile; 02185 02186 } 02187 02188 if ( !@$_SESSION['__dataface__import_data__'] ){ 02189 throw new Exception("No import data to import", E_USER_ERROR); 02190 } 02191 02192 $dumpFile = $_SESSION['__dataface__import_data__']; 02193 $importData = unserialize(file_get_contents($dumpFile)); 02194 02195 02196 if ( $importData['table'] != $table->tablename ){ 02197 return PEAR::raiseError("Unexpected table name in import data. Expected ".$table->tablename." but received ".$importData['table']); 02198 02199 } 02200 02201 $inserted = array(); 02202 $i=0; 02203 foreach ( $importData['rows'] as $row ){ 02204 if ( isset($row['__CLASS__']) and isset($row['__CLASSPATH__']) ){ 02205 // This row is an import record - not merely a Dataface_Record 02206 // object so it provides its own logic to import the records. 02207 import($row['__CLASSPATH__']); 02208 $class = $row['__CLASS__']; 02209 $importRecord = new $class($row); 02210 $res = $importRecord->commit($record, $relationshipName); 02211 if ( PEAR::isError($res) ){ 02212 return $res; 02213 } 02214 } else { 02215 $values = array(); 02216 foreach (array_keys($row) as $key){ 02217 if ( !is_int($key) ){ 02218 $values[$key] = $row[$key]; 02219 } 02220 } 02221 if ( $relationshipName === null ){ 02222 /* 02223 * These records are not being added to a relationship. They are just being added directly 02224 * into the table. 02225 */ 02226 02227 $defaults = array(); 02228 // for absolute field name keys for default values, we will strip out the table name. 02229 foreach (array_keys($defaultValues) as $key){ 02230 if ( strpos($key,'.') !== false ){ 02231 list($tablename, $fieldname) = explode('.', $key); 02232 if ( $tablename == $this->_table->tablename ){ 02233 $defaults[$fieldname] = $defaultValues[$key]; 02234 } else { 02235 continue; 02236 } 02237 } else { 02238 $defaults[$key] = $defaultValues[$key]; 02239 } 02240 } 02241 02242 $values = array_merge($defaults, $values); 02243 $insrecord = new Dataface_Record($this->_table->tablename, $values); 02244 $inserted[] =& $insrecord; 02245 $this->write($insrecord); 02246 $insrecord->__destruct(); 02247 unset($insrecord); 02248 } else { 02249 /* 02250 * The records are being added to a relationship so we need to make sure that we add the appropriate 02251 * entries to the "join" tables as well. 02252 */ 02253 foreach (array_keys($values) as $key){ 02254 $values[$table->tablename.'.'.$key] = $values[$key]; 02255 unset($values[$key]); 02256 } 02257 02258 $values = array_merge( $defaultValues, $values); 02259 02260 /* 02261 * Let's check if all of the keys are set. If they are then the record already exists.. we 02262 * just need to update the record. 02263 * 02264 */ 02265 $rvalues = array(); 02266 foreach ( $values as $valkey=>$valval){ 02267 if ( strpos($valkey,'.') !== false ){ 02268 list($tablename,$fieldname) = explode('.',$valkey); 02269 if ( $tablename == $table->tablename ){ 02270 $rvalues[$fieldname] = $valval; 02271 } 02272 } 02273 } 02274 $rrecord = new Dataface_Record( $table->tablename, array()); 02275 02276 $rrecord->setValues($rvalues); 02277 // we set the values in a separate call because we want to be able to do an update 02278 // and setting values in the constructer sets the snapshot (ie: it will think that 02279 // no values have changed. 02280 02281 if ( $io->recordExists($rrecord)){ 02282 /* 02283 * The record already exists, so we update it and then add it to the relationship. 02284 * 02285 */ 02286 if ( Dataface_PermissionsTool::edit($rrecord) ){ 02287 /* 02288 * We only edit the record if we have permission to do so. 02289 */ 02290 02291 $result = $io->write($rrecord); 02292 if ( PEAR::isError($result) ){ 02293 throw new Exception($result->toString(), E_USER_ERROR); 02294 } 02295 } 02296 $relatedRecord = new Dataface_RelatedRecord( $record, $relationshipName, $values); 02297 $inserted[] =& $relatedRecord; 02298 $qb = new Dataface_QueryBuilder($this->_table->tablename); 02299 $sql = $qb->addExistingRelatedRecord($relatedRecord); 02300 02301 $res2 = $this->performSQL($sql); 02302 02303 unset($relatedRecord); 02304 02305 02306 } else { 02307 02308 $relatedRecord = new Dataface_RelatedRecord( $record, $relationshipName, $values); 02309 $inserted[] =& $relatedRecord; 02310 $qb = new Dataface_QueryBuilder($this->_table->tablename); 02311 $sql = $qb->addRelatedRecord($relatedRecord); 02312 02313 $res2 = $this->performSQL($sql); 02314 02315 unset($relatedRecord); 02316 } 02317 02318 unset($rrecord); 02319 02320 02321 } 02322 } 02323 02324 unset($row); 02325 } 02326 02327 02328 @unlink($dumpFile); 02329 unset($_SESSION['__dataface__import_data__']); 02330 02331 return $inserted; 02332 02333 02334 02335 02336 } 02337 02338 02390 static function &getByID($uri, $filter=null){ 02391 if ( strpos($uri, '?') === false ) return PEAR::raiseError("Invalid record id: ".$uri); 02392 $uri_parts = df_parse_uri($uri); 02393 if ( PEAR::isError($uri_parts) ) return $uri_parts; 02394 if ( !isset($uri_parts['relationship']) ){ 02395 // This is just requesting a normal record. 02396 02397 // Check to see if this is to be a new record or an existing record 02398 if ( @$uri_parts['action'] and ( $uri_parts['action'] == 'new' ) ){ 02399 $record = new Dataface_Record($uri_parts['table'], array()); 02400 $record->setValues($uri_parts['query']); 02401 return $record; 02402 } 02403 02404 foreach ($uri_parts['query'] as $ukey=>$uval){ 02405 if ( $uval and $uval{0}!='=' ) $uval = '='.$uval; 02406 $uri_parts['query'][$ukey]=$uval; 02407 } 02408 // At this point we are sure that this is requesting an existing record 02409 $record =& df_get_record($uri_parts['table'], $uri_parts['query']); 02410 02411 if ( isset($uri_parts['field']) ){ 02412 if ( isset($filter) and method_exists($record, $filter) ){ 02413 $val =& $record->$filter($uri_parts['field']); 02414 return $val; 02415 } else { 02416 $val =& $record->val($uri_parts['field']); 02417 return $val; 02418 } 02419 } 02420 else return $record; 02421 02422 } else { 02423 // This is requesting a related record. 02424 02425 $record =& df_get_record($uri_parts['table'], $uri_parts['query']); 02426 if ( !$record ) return PEAR::raiseError("Could not find any records matching the query"); 02427 02428 // Check to see if we are creating a new record 02429 if ( @$uri_parts['action'] and ( $uri_parts['action'] == 'new' ) ){ 02430 $related_record = new Dataface_RelatedRecord($record, $uri_parts['relationship']); 02431 $related_record->setValues( $uri_parts['query']); 02432 return $related_record; 02433 } 02434 02435 02436 // At this point we can be sure that we are requesting an existing record. 02437 $related_records =& $record->getRelatedRecordObjects($uri_parts['relationship'], 0,1, $uri_parts['related_where']); 02438 if ( count($related_records) == 0 ){ 02439 02440 return PEAR::raiseError("Could not find any related records matching the query: ".$uri_parts['related_where']); 02441 } 02442 if ( isset($uri_parts['field']) ) { 02443 if ( isset($filter) and method_exists($related_records[0], $filter) ){ 02444 $val =& $related_records[0]->$filter($uri_parts['field']); 02445 return $val; 02446 } else { 02447 $val =& $related_records[0]->val($uri_parts['field']); 02448 return $val; 02449 } 02450 } 02451 else return $related_records[0]; 02452 02453 } 02454 } 02455 02456 02460 static function setByID($uri, $value){ 02461 02462 @list($uri, $fieldname) = explode('#', $uri); 02463 $record =& Dataface_IO::getByID($uri); 02464 02465 if ( PEAR::isError($record) ) return $record; 02466 if ( !is_object($record) ) return PEAR::raiseError("Could not find record matching '$uri'."); 02467 02468 if ( isset($fieldname) ){ 02469 $res = $record->setValue($fieldname, $value); 02470 } else { 02471 $res = $record->setValues($value); 02472 } 02473 if ( PEAR::isError($res) ) return $res; 02474 02475 $res = $record->save(); 02476 return $res; 02477 } 02478 02479 02480 static function createModificationTimesTable(){ 02481 $sql = "create table dataface__mtimes ( 02482 `name` varchar(255) not null primary key, 02483 `mtime` int(11) 02484 )"; 02485 $res = mysql_query($sql, df_db()); 02486 if ( !$res ) throw new Exception(mysql_error(df_db())); 02487 } 02488 02489 static function touchTable($table){ 02490 $sql = "replace into dataface__mtimes (`name`,`mtime`) values ('".addslashes($table)."','".addslashes(time())."')"; 02491 $res = mysql_query($sql, df_db()); 02492 if ( !$res ){ 02493 self::createModificationTimesTable(); 02494 $res = mysql_query($sql, df_db()); 02495 if ( !$res ) throw new Exception(mysql_error(df_db())); 02496 } 02497 } 02498 02499 02500 02501 02502 02503 02504 }