Xataface 2.0
Xataface Application Framework
Dataface/IO.php
Go to the documentation of this file.
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 }
 All Data Structures Namespaces Files Functions Variables Enumerations