Xataface  2.0alpha2
Xataface Application Framework
 All Data Structures Namespaces Files Functions Variables Groups Pages
IO.php
Go to the documentation of this file.
1 <?php
2 /*-------------------------------------------------------------------------------
3  * Xataface Web Application Framework
4  * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *-------------------------------------------------------------------------------
20  */
41 import( 'Dataface/QueryBuilder.php');
42 import('Dataface/DB.php');
43 define('Dataface_IO_READ_ERROR', 1001);
44 define('Dataface_IO_WRITE_ERROR', 1002);
45 define('Dataface_IO_NOT_FOUND_ERROR', 1003);
46 define('Dataface_IO_TOO_MANY_ROWS', 1004);
47 define('Dataface_IO_NO_TABLES_SELECTED', 1005);
48 define('MYSQL_ER_DUP_KEY', 1022);
49 define('MYSQL_ER_DUP_ENTRY', 1062);
50 define('MYSQL_ER_ROW_IS_REFERENCED', 1217);
51 define('MYSQL_ER_ROW_IS_REFERENCED_2', 1451);
52 define('MYSQL_ER_NO_REFERENCED_ROW', 1216);
53 define('MYSQL_ER_NO_REFERENCED_ROW_2', 1452);
54 
55 
56 class Dataface_IO {
57  var $_table;
59  var $insertIds = array();
60  var $lang;
61  var $dbObj;
62  var $parentIO=-1;
63  var $fireTriggers=true;
64 
65  // Placeholder for the version number when recordExists is called
66  // it will place the version number of the existing record in this
67  // placeholder.
68  var $lastVersionNumber = null;
69 
75  var $_altTablename = null;
76 
77  function Dataface_IO($tablename, $db=null, $altTablename=null){
79  $this->lang = $app->_conf['lang'];
80  $this->_table =& Dataface_Table::loadTable($tablename, $db);
81  $this->_serializer = new Dataface_Serializer($tablename);
82  $this->_altTablename = $altTablename;
83  $this->dbObj =& Dataface_DB::getInstance();
84  }
85 
86  function __destruct(){
87  unset($this->_table);
88  unset($this->dbObj);
89  unset($this->_serializer);
90 
91  if ( isset($this->parentIO) and $this->parentIO != -1 ){
92  $this->parentIO->__destruct();
93  unset($this->parentIO);
94  }
95  }
96 
97  function &getParentIO(){
98  if ( $this->parentIO == -1 ){
99  if ( isset($this->_altTablename) and $this->_altTablename != $this->_table->tablename) {
100  $null = null;
101  return $null;
102  }
103  // There is no clear parent table if an alternate table name is set.
104 
105  $parentTable =& $this->_table->getParent();
106  if ( isset($parentTable) ){
107  $this->parentIO = new Dataface_IO($parentTable->tablename, null, null);
108  $this->parentIO->lang = $this->lang;
109  $this->parentIO->fireTriggers = false;
110  } else {
111  $this->parentIO = null;
112  }
113 
114  }
115  return $this->parentIO;
116  }
117 
130  function &loadRecordById($recordid){
131  $rec =& Dataface_IO::getByID($recordid);
132  return $rec;
133 
134  }
135 
136 
144  function recordid2query($recordid){
145  $query = array();
146  list($base,$qstr) = explode('?', $recordid);
147  if ( strpos($base,'/') !== false ){
148  list($query['-table'],$query['-relationship']) = explode('/',$base);
149  } else {
150  $query['-table'] = $base;
151  }
152  $params = explode('&', $qstr);
153  foreach ( $params as $param){
154  list($key,$value) = explode('=', $param);
155  $query[urldecode($key)] = '='.urldecode($value);
156  }
157  return $query;
158 
159  }
160 
161 
162 
176  function read($query='', &$record, $tablename=null){
178  if ( !is_a($record, "Dataface_Record") ){
179  throw new Exception(
180  df_translate(
181  'scripts.Dataface.IO.read.ERROR_PARAMETER_2',
182  "Dataface_IO::read() requires second parameter to be of type 'Dataface_Record' but received '".get_class($record)."\n<br>",
183  array('class'=>get_class($record))
184  ), E_USER_ERROR);
185  }
186 
187  if ( is_string($query) and !empty($query) ){
188  // If the query is actually a record id string, then we convert it
189  // to a normal query.
190  $query = $this->recordid2query($query);
191  }
192 
193  if ( $tablename === null and $this->_altTablename !== null ){
195  }
196 
197 
198  $qb = new Dataface_QueryBuilder($this->_table->tablename);
199  $qb->selectMetaData = true;
200  $query['-limit'] = 1;
201  if ( @$query['-cursor']>0 ) $query['-skip'] = $query['-cursor'];
202  $sql = $qb->select('', $query, false, $this->tablename($tablename));
203  $res = $this->dbObj->query($sql, $this->_table->db, $this->lang, true /* as_array */);
204  if ( (!is_array($res) and !$res) || PEAR::isError($res) ){
205  $app->refreshSchemas($this->_table->tablename);
206  $res = $this->dbObj->query($sql, $this->_table->db, $this->lang, true /* as array */);
207  if ( (!is_array($res) and !$res) || PEAR::isError($res) ){
208  if ( PEAR::isError($res) ) return $res;
209  return PEAR::raiseError(
211  /* i18n id */
212  "Error reading record",
213  /* default error message */
214  "Error reading table '".
215  $this->_table->tablename.
216  "' from the database: ".
217  mysql_error($this->_table->db),
218  /* i18n parameters */
219 
220  array('table'=>$this->_table->tablename,
221  'mysql_error'=>mysql_error(),
222  'line'=>0,
223  'file'=>'_',
224  'sql'=>$sql
225  )
226  ),
227 
229  );
230  }
231  }
232 
233  //if ( mysql_num_rows($res) == 0 ){
234  if ( count($res) == 0 ){
235  return PEAR::raiseError(
237  /* i18n id */
238  "No records found",
239  /* default error message */
240  "Record for table '".
241  $this->_table->tablename.
242  "' could not be found.",
243  /* i18n parameters */
244  array('table'=>$this->_table->tablename, 'sql'=>$sql)
245  ),
247  );
248  }
249 
250  //$row = mysql_fetch_assoc($res);
251  $row = $res[0];
252  //mysql_free_result($res);
253  $record->setValues($row);
254  $record->setSnapshot();
255  // clear all flags that may have been previously set to indicate that the data is old or needs to be updated.
256 
257 
258 
259  }
260 
261 
262 
263 
270  function delete(&$record, $secure=false){
271  if ( $secure && !$record->checkPermission('delete') ){
272  // Use security to check to see if we are allowed to delete this
273  // record.
275  df_translate(
276  'scripts.Dataface.IO.delete.PERMISSION_DENIED',
277  'Could not delete record "'.$record->getTitle().'" from table "'.$record->_table->tablename.'" because you have insufficient permissions.',
278  array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename)
279  )
280  );
281  }
282 
283 
284  $builder = new Dataface_QueryBuilder($this->_table->tablename);
285 
286  if ( $this->fireTriggers ){
287  $res = $this->fireBeforeDelete($record);
288  if ( PEAR::isError($res) ) return $res;
289  }
290 
291 
292 
293  // do the deleting
294  $keys =& $record->_table->keys();
295  if ( !$keys || count($keys) == 0 ){
296  throw new Exception(
297  df_translate(
298  'scripts.Dataface.IO.delete.ERROR_NO_PRIMARY_KEY',
299  'Could not delete record from table "'.$record->_table->tablename.'" because no primary key was defined.',
300  array('tablename'=>$record->_table->tablename)
301  )
302  );
303 
304  }
305  $query = array();
306  foreach ( array_keys($keys) as $key ){
307  if ( !$record->strval($key) ){
308  return PEAR::raiseError(
310  /* i18n id */
311  'Could not delete record because missing keys',
312  /* default error message */
313  'Could not delete record '.
314  $record->getTitle().
315  ' because not all of the keys were included.',
316  /* i18n parameters */
317  array('title'=>$record->getTitle(), 'key'=>$key)
318  ),
320  );
321  }
322  $query[$key] = '='.$record->strval($key);
323  }
324 
325  $sql = $builder->delete($query);
326  if ( PEAR::isError($sql) ) return $sql;
327 
328  //$res = mysql_query($sql);
329  $res = $this->dbObj->query($sql, null, $this->lang);
330  if ( !$res || PEAR::isError($res)){
331  if ( PEAR::isError($res) ) $msg = $res->getMessage();
332  else $msg = mysql_error(df_db());
333  return PEAR::raiseError(
334 
336  /* i18n id */
337  'Failed to delete record. SQL error',
338  /* default error message */
339  'Failed to delete record '.
340  $record->getTitle().
341  ' because of an sql error. '.mysql_error(df_db()),
342  /* i18n parameters */
343  array('title'=>$record->getTitle(), 'sql'=>$sql, 'mysql_error'=>$msg)
344  ),
346  );
347  }
348 
349  $parentIO =& $this->getParentIO();
350  if ( isset($parentIO) ){
351  $parentRecord =& $record->getParentRecord();
352  if ( isset($parentRecord) ){
353  $res = $parentIO->delete($parentRecord, $secure);
354  if ( PEAR::isError($res) ) return $res;
355  }
356  }
357 
358  if ( $this->fireTriggers ){
359  $res2 = $this->fireAfterDelete($record);
360  if ( PEAR::isError($res2) ) return $res2;
361  }
362  self::touchTable($this->_table->tablename);
363  return $res;
364 
365  }
366 
367  function saveTransients(Dataface_Record $record, $keys=null, $tablename=null, $secure=false){
369  // Now we take care of the transient relationship fields.
370  // Transient relationship fields aren't actually stored in the record
371  // itself, they are stored as related records.
372  foreach ( $record->_table->transientFields() as $tfield ){
373  if ( !isset($tfield['relationship']) ) continue;
374  if ( !$record->valueChanged($tfield['name']) ) continue;
375 
376  $trelationship =& $record->_table->getRelationship($tfield['relationship']);
377 
378  if ( !$trelationship or PEAR::isError($trelationship) ){
379  // We couldn't find the specified relationship.
380  //$record->vetoSecurity = $oldVeto;
381  return $trelationship;
382  }
383 
384  $orderCol = $trelationship->getOrderColumn();
385  if ( PEAR::isError($orderCol) ) $orderCol = null;
386 
387  $tval = $record->getValue($tfield['name']);
388  if ( $tfield['widget']['type'] == 'grid' ){
389 
390  $tval_existing = array();
391  $tval_new = array();
392  $tval_new_existing = array();
393  $torder = 0;
394  foreach ($tval as $trow){
395  if ( !is_array($trow) ) continue;
396  $trow['__order__'] = $torder++;
397  if ( isset($trow['__id__']) and preg_match('/^new:/', $trow['__id__']) ){
398  $tval_new_existing[] = $trow;
399  }
400  else if ( isset($trow['__id__']) and $trow['__id__'] != 'new' ){
401  $tval_existing[$trow['__id__']] = $trow;
402  } else if ( isset($trow['__id__']) and $trow['__id__'] == 'new'){
403  $tval_new[] = $trow;
404  }
405  }
406 
407  // The transient field was loaded so we can go about saving the
408  // changes/
409  $trecords =& $record->getRelatedRecordObjects($tfield['relationship'], 'all');
410  if ( !is_array($trecords) or PEAR::isError($trecords) ){
411  error_log('Failed to get related records for record '.$record->getId().' in its relationship '.$tfield['relationship']);
412  unset($tval);
413  unset($orderCol);
414  unset($tval_new);
415  unset($torder);
416  unset($trelationship);
417  unset($tval_existing);
418  continue;
419  }
420 
421 
422  // Update the existing records in the relationship.
423  // We use the __id__ parameter in each row for this.
424  //echo "About to save related records";
425  foreach ($trecords as $trec){
426  $tid = $trec->getId();
427 
428  if ( isset($tval_existing[$tid]) ){
429  $tmp = new Dataface_RelatedRecord($trec->_record, $tfield['relationship'], $trec->getValues());
430 
431  $tmp->setValues($tval_existing[$tid]);
432  $changed = false;
433  foreach ( $tval_existing[$tid] as $k1=>$v1 ){
434  if ( $tmp->isDirty($k1) ){
435  $changed = true;
436  break;
437  }
438  }
439 
440  if ( $changed ){
441  $trec->setValues($tval_existing[$tid]);
442  if ( $orderCol ) $trec->setValue( $orderCol, $tval_existing[$tid]['__order__']);
443  //echo "Saving ";print_r($trec->vals());
444  $res_t = $trec->save($this->lang, $secure);
445 
446  if ( PEAR::isError($res_t) ){
447  return $res_t;
448  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());
449 
450  }
451  } else {
452  if ( $orderCol and $record->checkPermission('reorder_related_records', array('relationship'=>$tfield['relationship'])) ){
453  $trec->setValue( $orderCol, $tval_existing[$tid]['__order__']);
454  $res_t = $trec->save($this->lang, false); // we don't need this to be secure
455  if ( PEAR::isError($res_t) ){
456  return $res_t;
457  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());
458 
459  }
460  }
461  }
462 
463  unset($tmp);
464  } else {
465 
466 
467  }
468  unset($trec);
469  unset($tid);
470  unset($res_t);
471 
472  }
473 
474 
475  // Now add new records (specified by __id__ field being 'new'
476 
477  foreach ($tval_new as $tval_to_add){
478  $temp_rrecord = new Dataface_RelatedRecord( $record, $tfield['relationship'], array());
479 
480 
481  $temp_rrecord->setValues($tval_to_add);
482  if ( $orderCol ) $temp_rrecord->setValue( $orderCol, $tval_to_add['__order__']);
483  $res_t = $this->addRelatedRecord($temp_rrecord, $secure);
484  if ( PEAR::isError($res_t) ){
485  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());
486  }
487  unset($temp_rrecord);
488  unset($res_t);
489 
490 
491  }
492 
493  // Now add new existing records (specified by __id__ field being 'new:<recordid>'
494 
495  foreach ($tval_new_existing as $tval_to_add){
496  $tid = preg_replace('/^new:/', '', $tval_to_add['__id__']);
497  $temp_record = df_get_record_by_id($tid);
498  if ( PEAR::isError($temp_record) ){
499  return $temp_record;
500  }
501  if ( !$temp_record){
502  return PEAR::raiseError("Failed to load existing record with ID $tid.");
503  }
504  $temp_rrecord = new Dataface_RelatedRecord( $record, $tfield['relationship'], $temp_record->vals());
505 
506 
507  $temp_rrecord->setValues($tval_to_add);
508  if ( $orderCol ) $temp_rrecord->setValue( $orderCol, $tval_to_add['__order__']);
509  $res_t = $this->addExistingRelatedRecord($temp_rrecord, $secure);
510  if ( PEAR::isError($res_t) ){
511  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());
512  }
513  unset($temp_rrecord);
514  unset($res_t);
515 
516 
517  }
518 
519  // Now we delete the records that were deleted
520  // we use the __deleted__ field.
521 
522  if ( isset($tval['__deleted__']) and is_array($tval['__deleted__']) and $trelationship->supportsRemove() ){
523  $tdelete_record = ($trelationship->isOneToMany() and !$trelationship->supportsAddExisting());
524  // If it supports add existing, then we shouldn't delete the entire record. Just remove it
525  // from the relationship.
526 
527  foreach ( $tval['__deleted__'] as $del_id ){
528  if ($del_id == 'new' ) continue;
529  $drec = Dataface_IO::getByID($del_id);
530  if ( PEAR::isError($drec) or !$drec ){
531  unset($drec);
532  continue;
533  }
534 
535  $mres = $this->removeRelatedRecord($drec, $tdelete_record, $secure);
536  if ( PEAR::isError($mres) ){
537  throw new Exception($mres->getMessage());
538  }
539  unset($drec);
540  }
541  }
542 
543  unset($trecords);
544 
545  } else if ( $tfield['widget']['type'] == 'checkbox' ){
546 
547  // Load existing records in the relationship
548  $texisting =& $record->getRelatedRecordObjects($tfield['relationship'], 'all');
549  if ( !is_array($texisting) or PEAR::isError($texisting) ){
550  error_log('Failed to get related records for record '.$record->getId().' in its relationship '.$tfield['relationship']);
551  unset($tval);
552  unset($orderCol);
553  unset($tval_new);
554  unset($torder);
555  unset($trelationship);
556  unset($tval_existing);
557  continue;
558  }
559  $texistingIds = array();
560  foreach ($texisting as $terec){
561  $texistingIds[] = $terec->getId();
562  }
563 
564  // Load currently checked records
565  $tchecked = array();
566  $tcheckedRecords = array();
567  $tcheckedIds = array();
568  $tcheckedId2ValsMap = array();
569  foreach ( $tval as $trkey=>$trval){
570  // $trval is in the form key1=val1&size=key2=val2
571  parse_str($trval, $trquery);
572  $trRecord = new Dataface_RelatedRecord($record, $tfield['relationship'],$trquery);
573  $trRecords[] =& $trRecord;
574  $tcheckedIds[] = $tid = $trRecord->getId();
575  $checkedId2ValsMap[$tid] = $trquery;
576  unset($trRecord);
577  unset($trquery);
578 
579  }
580 
581  // Now we have existing ids in $texistingIds
582  // and checked ids in $tcheckedIds
583 
584  // See which records we need to have removed
585  $tremoves = array_diff($texistingIds, $tcheckedIds);
586  $tadds = array_diff($tcheckedIds, $texistingIds);
587 
588  foreach ($tremoves as $tid){
589  $trec = df_get_record_by_id($tid);
590  $res = $this->removeRelatedRecord($trec, false, $secure);
591  if ( PEAR::isError($res) ) return $res;
592  unset($trec);
593  }
594  foreach ($tadds as $tid){
595  $trecvals = $checkedId2ValsMap[$tid];
596  $trec = new Dataface_RelatedRecord($record, $tfield['relationship'], $trecvals);
597 
598  $res = $this->addExistingRelatedRecord($trec, $secure);
599  if ( PEAR::isError($res) ) return $res;
600  unset($trec, $trecvals);
601  }
602 
603  unset($tadds);
604  unset($tremoves);
605  unset($tcheckedIds, $tcheckedId2ValsMap);
606  unset($tcheckedRecords);
607  unset($tchecked);
608  unset($texistingIds);
609  unset($texisting);
610 
611 
612 
613  }
614  unset($tval);
615  unset($trelationship);
616 
617  }
618 
619  }
620 
621 
634  function write(&$record, $keys=null, $tablename=null, $secure=false){
635  // The vetoSecurity flag allows us to make changes to a record without
636  // the fields being filtered for security checks when they are saved.
637  // Since we may want to change or add values to a record in the
638  // beforeSave type triggers, and we probably don't want these changes
639  // checked by security, we should use this flag to make all changes
640  // in these triggers immune to security checks.
641  // We return the veto setting to its former state after this method
642  // finishes.
643  //$oldVeto = $record->vetoSecurity;
644  //$record->vetoSecurity = true;
645  //$parentRecord =& $record->getParentRecord();
647  //$parentIO =& $this->getParentIO();
648 
649  if ( !is_a($record, "Dataface_Record") ){
650  throw new Exception(
651  df_translate(
652  'scripts.Dataface.IO.write.ERROR_PARAMETER_1',
653  "Dataface_IO::write() requires first parameter to be of type 'Dataface_Record' but received '".get_class($record)."\n<br>",
654  array('class'=>get_class($record))
655  ), E_USER_ERROR);
656  }
657  if ( $tablename === null and $this->_altTablename !== null ){
659  }
660 
661  if ( $this->fireTriggers ){
662  $res = $this->fireBeforeSave($record);
663  if (PEAR::isError($res) ) {
664  //$record->vetoSecurity = $oldVeto;
665  return $res;
666  }
667  }
668 
669 
670  if ( $this->recordExists($record, $keys, $this->tablename($tablename)) ){
671  $res = $this->_update($record, $keys, $this->tablename($tablename), $secure);
672  } else {
673 
674  $res = $this->_insert($record, $this->tablename($tablename), $secure);
675 
676  }
677 
678  if ( PEAR::isError($res) ){
680  /*
681  * Duplicate entries we will propogate up so that the application can decide what to do.
682  */
683  //$record->vetoSecurity = $oldVeto;
684  return $res;
685  }
686  $res->addUserInfo(
687  df_translate(
688  'scripts.Dataface.IO.write.ERROR_SAVING',
689  "Error while saving record of table '".$this->_table->tablename."' in Dataface_IO::write() ",
690  array('tablename'=>$this->_table->tablename,'line'=>0,'file'=>'_')
691  )
692  );
693  //$record->vetoSecurity = $oldVeto;
694  return $res;
695  }
696 
697  $res = $this->saveTransients($record, $keys, $tablename, $secure);
698  if ( PEAR::isError($res) ){
699  return $res;
700  }
701 
702 
703 
704  if ( $this->fireTriggers ){
705  $res2 = $this->fireAfterSave($record);
706  if ( PEAR::isError($res2) ){
707  //$record->vetoSecurity = $oldVeto;
708  return $res2;
709  }
710  }
711  if ( isset($app->_conf['history']) and ( @$app->_conf['history']['enabled'] || !isset($app->_conf['history']['enabled']))){
712 
713  // History is enabled ... let's save this record in our history.
714  import('Dataface/HistoryTool.php');
715  $historyTool = new Dataface_HistoryTool();
716  $historyTool->logRecord($record, $this->getHistoryComments($record), $this->lang);
717  }
718 
719 
720  if ( isset($app->_conf['_index']) and @$app->_conf['_index'][$record->table()->tablename]){
721  // If indexing is enabled, we index the record so that it is
722  // searchable by natural language searching.
723  // The Dataface_Index class takes care of whether or not this
724  // record should be indexed.
725  import('Dataface/Index.php');
726  $index = new Dataface_Index();
727  $index->indexRecord($record);
728  }
729  // It seems to me that we should be setting a new snapshot at this point.
730  //$record->clearSnapshot();
731  $record->setSnapshot();
732  self::touchTable($this->_table->tablename);
733  self::touchRecord($record);
734  //$record->vetoSecurity = $oldVeto;
735  return $res;
736  }
737 
738 
739  static function touchRecord(Dataface_Record $record=null){
740  if ( !isset($record) ) return;
741  $id = $record->getId();
742  $hash = md5($id);
743  $sql = "replace into dataface__record_mtimes
744  (recordhash, recordid, mtime) values
745  ('".addslashes($hash)."','".addslashes($id)."','".time()."')";
746  try {
747  $res = df_q($sql);
748  } catch ( Exception $ex){
750  }
751  }
752 
753  static function createRecordMtimes(){
754  $res = df_q("create table dataface__record_mtimes (
755  recordhash varchar(32) not null primary key,
756  recordid varchar(255) not null,
757  mtime int(11) not null)");
758  $res = df_q($sql);
759  }
760 
761  function getHistoryComments(&$record){
762  $del =& $this->_table->getDelegate();
763  if ( isset($del) and method_exists($del, 'getHistoryComments') ){
764  return $del->getHistoryComments($record);
765  }
767  $appdel =& $app->getDelegate();
768  if ( isset($appdel) and method_exists($appdel, 'getHistoryComments') ){
769  return $appdel->getHistoryComments($record);
770  }
771  return '';
772  }
773 
774 
775 
783  function recordExists(&$record, $keys = null, $tablename=null){
784  $this->lastVersionNumber = null;
785  if ( !is_a($record, "Dataface_Record") ){
786  throw new Exception(
787  df_translate(
788  'scripts.Dataface.IO.recordExists.ERROR_PARAMETER_1',
789  "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>"
790  ), E_USER_ERROR);
791  }
792  if ( $tablename === null and $this->_altTablename !== null ){
794  }
795 
796  $tempRecordCreated = false;
797  if ( $record->snapshotExists() ){
798  $tempRecord = new Dataface_Record($record->_table->tablename, $record->getSnapshot());
799  $tempRecordCreated = true;
800  } else {
801  $tempRecord =& $record;
802  }
803 
804  if ( $keys == null ){
805  // Had to put in userialize(serialize(...)) because getValues() returns by reference
806  // and we don't want to change actual values.
807  $query = unserialize(serialize($tempRecord->getValues( array_keys($record->_table->keys()))));
808  } else {
809  $query = $keys;
810  }
811 
812 
813  $table_keys = array_keys($this->_table->keys());
814 
815  foreach ( $table_keys as $key){
816  if ( !isset( $query[$key] ) or !$query[$key] ) {
817 
818  return false;
819  }
820  }
821 
822  foreach ( array_keys($query) as $key){
823  //$query[$key] = '='.$this->_serializer->serialize($key, $tempRecord->getValue($key) );
824  $query[$key] = $this->_serializer->serialize($key, $tempRecord->getValue($key) );
825 
826  }
827  if ( $tempRecordCreated ) $tempRecord->__destruct();
828 
829  //$qb = new Dataface_QueryBuilder($this->_table->tablename, $query);
830  //$sql = $qb->select_num_rows(array(), $this->tablename($tablename));
831  if ( $record->table()->isVersioned() ){
832  $versionField = "`".$record->table()->getVersionField()."`";
833  } else {
834  $versionField = "NULL";
835  }
836  $sql = "select `".$table_keys[0]."`, $versionField from `".$this->tablename($tablename)."` where ";
837  $where = array();
838  foreach ($query as $key=>$val){
839  $where[] = '`'.$key.'`="'.addslashes($val).'"';
840  }
841  $sql .= implode(' AND ', $where).' limit 1';
842 
843  $res = df_q($sql, $this->_table->db);
844  $num = mysql_num_rows($res);
845  $row = mysql_fetch_row($res);
846  @mysql_free_result($res);
847  if ( $num === 1 ){
848  // We have the correct number...
849  // let's check the version
850  $this->lastVersionNumber = intval($row[1]);
851  return true;
852  }
853  if ( $num > 1 ){
854 
855  $err = PEAR::raiseError(
857  /* i18n id */
858  'recordExists failure. Too many rows returned.',
859  /* default error message */
860  "Test for existence of record in recordExists() returned $rows records.
861  It should have max 1 record.
862  The query must be incorrect.
863  The query used was '$sql'. ",
864  /* i18n parameters */
865  array('table'=>$this->_table->tablename, 'line'=>0, 'file'=>'_','sql'=>$sql)
866  ),
868  );
869  throw new Exception($err->toString(), E_USER_ERROR);
870  }
871  return false;
872 
873 
874  }
875 
876 
880  function _update(&$record, $keys=null, $tablename=null, $secure=false ){
881 
882 
883  if ( $secure && !$record->checkPermission('edit') ){
884  // Use security to check to see if we are allowed to delete this
885  // record.
887  df_translate(
888  'scripts.Dataface.IO._update.PERMISSION_DENIED',
889  'Could not update record "'.$record->getTitle().'" from table "'.$record->_table->tablename.'" because you have insufficient permissions.',
890  array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename)
891  )
892  );
893  }
894  if ( $secure ){
895  foreach ( array_keys($record->_table->fields()) as $fieldname ){
896  if ( $record->valueChanged($fieldname) and !@$record->vetoFields[$fieldname] and !$record->checkPermission('edit', array('field'=>$fieldname)) ){
897  $field = $record->table()->getField($fieldname);
898  if ( @$field['timestamp'] and $field['timestamp'] == 'update' ){
899  // Since timestamps are just updated automatically,
900  // we don't need to perform any permissions on it
901  continue;
902  }
903  // If this field's change doesn't have veto power and its value has changed,
904  // we must make sure that the user has edit permission on this field.
906  df_translate(
907  'scripts.Dataface.IO._update.PERMISSION_DENIED_FIELD',
908  'Could not update record "'.$record->getTitle().'" in table "'.$record->_table->tablename.'" because you do not have permission to modify the "'.$fieldname.'" column.',
909  array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename, 'field'=>$fieldname)
910  )
911  );
912  }
913  }
914 
915  }
916 
917  // Step 1: Validate that the record already exists
918  if ( !is_a($record, 'Dataface_Record') ){
919  throw new Exception(
920  df_translate(
921  'scripts.Dataface.IO._update.ERROR_PARAMETER_1',
922  "In Dataface_IO::_update() the first argument is expected to be an object of type 'Dataface_Record' but received '".get_class($record)."'.\n<br>",
923  array('class'=>get_class($record))
924  ), E_USER_ERROR);
925  }
926  if ( $tablename === null and $this->_altTablename !== null ){
928  }
929 
930  $exists = $this->recordExists($record, $keys, $this->tablename($tablename));
931  if ( PEAR::isError($exists) ){
932  $exists->addUserInfo(
933  df_translate(
934  'scripts.Dataface.IO._update.ERROR_INCOMPLETE_INFORMATION',
935  "Attempt to update record with incomplete information.",
936  array('line'=>0,'file'=>'_')
937  )
938  );
939  return $exists;
940  }
941  if ( !$exists ){
942  return PEAR::raiseError(
943  df_translate(
944  'scripts.Dataface.IO._update.ERROR_RECORD_DOESNT_EXIST',
945  "Attempt to update record that doesn't exist in _update() ",
946  array('line'=>0,'file'=>"_")
948  }
949 
950  if ( $record->table()->isVersioned()){
951  $currVersion = intval($record->getVersion());
952  $dbVersion = intval($this->lastVersionNumber);
953  if ( $currVersion !== $dbVersion ){
954  return PEAR::raiseError(
955  df_translate(
956  'scripts.Dataface.IO._update.ERROR_RECORD_VERSION_MISMATCH',
957  "Attempt to update record with a different version than the database version. Current version is $currVersion. DB Version is $dbVersion",
958  array()
960  );
961  }
962  }
963  // Step 2: Load objects that we will need
964  $s =& $this->_table;
965  $delegate =& $s->getDelegate();
966  $qb = new Dataface_QueryBuilder($this->_table->tablename, $keys);
967 
968  if ( $record->recordChanged(true) ){
969  if ( $this->fireTriggers ){
970  $res = $this->fireBeforeUpdate($record);
971  if ( PEAR::isError($res) ) return $res;
972  }
973  }
974 
975 
976  $parentIO =& $this->getParentIO();
977 
978  if ( isset($parentIO) ){
979 
980  $parentRecord =& $record->getParentRecord();
981 
982  $res = $parentIO->write($parentRecord, $parentRecord->snapshotKeys());
983  if ( PEAR::isError($res) ) return $res;
984 
985  }
986 
987 
988 
989  // we only want to update changed values
990  $sql = $qb->update($record,$keys, $this->tablename($tablename));
991 
992  if ( PEAR::isError($sql) ){
993  $sql->addUserInfo(
994  df_translate(
995  'scripts.Dataface.IO._update.ERROR_GENERATING_SQL',
996  "Error generating sql for update in IO::_update()",
997  array('line'=>0,'file'=>"_")
998  )
999  );
1000  return $sql;
1001  }
1002  if ( strlen($sql) > 0 ){
1003 
1004 
1005 
1006  //$res = mysql_query($sql, $s->db);
1007  $res =$this->dbObj->query($sql, $s->db, $this->lang);
1008  if ( !$res || PEAR::isError($res) ){
1009 
1010  if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_DUP_KEY,MYSQL_ER_DUP_ENTRY)) ){
1011  /*
1012  * This is a duplicate entry. We will handle this as an exception rather than an error because
1013  * cases may arise in a database application when a duplicate entry will happen and the application
1014  * will want to handle it in a graceful way. Eg: If the user is entering a username that is the same
1015  * as an existing name. We don't want an ugle FATAL error to be thrown here. Rather we want to
1016  * notify the application that it is a duplicate entry.
1017  */
1019  df_translate(
1020  'scripts.Dataface.IO._update.ERROR_DUPLICATE_ENTRY',
1021  "Duplicate entry into table '".$s->tablename,
1022  array('tablename'=>$s->tablename)
1023  ) /* i18n parameters */
1024  );
1025  }
1026  throw new Exception(
1027  df_translate(
1028  'scripts.Dataface.IO._update.SQL_ERROR',
1029  "Failed to update due to sql error: ")
1030  .mysql_error($s->db), E_USER_ERROR);
1031  }
1032 
1033  //$record->clearFlags();
1034  if ( $record->table()->isVersioned() ){
1035  $versionField = $record->table()->getVersionField();
1036  $record->setValue($versionField, $record->getVersion()+1);
1037  }
1038 
1039  if ( $this->fireTriggers ){
1040  $res2 = $this->fireAfterUpdate($record);
1041  if ( PEAR::isError($res2) ) return $res2;
1042  }
1043 
1044 
1045  }
1046 
1047 
1048  return true;
1049 
1050 
1051 
1052  }
1053 
1057  function _insert(&$record, $tablename=null, $secure=false){
1058  if ( $secure && !$record->checkPermission('new') ){
1059  // Use security to check to see if we are allowed to delete this
1060  // record.
1062  df_translate(
1063  'scripts.Dataface.IO._insert.PERMISSION_DENIED',
1064  'Could not insert record "'.$record->getTitle().'" from table "'.$record->_table->tablename.'" because you have insufficient permissions.',
1065  array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename)
1066  )
1067  );
1068  }
1069  if ( $secure ){
1070  foreach ( array_keys($record->_table->fields()) as $fieldname ){
1071  if ( $record->valueChanged($fieldname) and !@$record->vetoFields[$fieldname] and !$record->checkPermission('new', array('field'=>$fieldname)) ){
1072  // If this field was changed and the field doesn't have veto power, then
1073  // we must subject the change to a security check - the user must havce
1074  // edit permission to perform the change.
1075 
1076  if ( @$field['timestamp'] ){
1077  // Since timestamps are just updated automatically,
1078  // we don't need to perform any permissions on it
1079  continue;
1080  }
1081 
1083  df_translate(
1084  'scripts.Dataface.IO._insert.PERMISSION_DENIED_FIELD',
1085  'Could not insert record "'.$record->getTitle().'" into table "'.$record->_table->tablename.'" because you do not have permission to modify the "'.$fieldname.'" column.',
1086  array('title'=>$record->getTitle(), 'table'=>$record->_table->tablename, 'field'=>$fieldname)
1087  )
1088  );
1089  }
1090  }
1091 
1092  }
1093 
1094  if ( $tablename === null and $this->_altTablename !== null ){
1096  }
1097  $s =& $this->_table;
1098  $delegate =& $s->getDelegate();
1099 
1100  if ( $this->fireTriggers ){
1101  $res = $this->fireBeforeInsert($record);
1102  if ( PEAR::isError($res) ) return $res;
1103  }
1104 
1105 
1106 
1107  $parentIO =& $this->getParentIO();
1108  if ( isset($parentIO) ){
1109  $parentRecord =& $record->getParentRecord();
1110  $res = $parentIO->write($parentRecord, $parentRecord->snapshotKeys());
1111  if ( PEAR::isError($res) ) return $res;
1112  unset($parentRecord);
1113  }
1114 
1115  $qb = new Dataface_QueryBuilder($s->tablename);
1116  $sql = $qb->insert($record, $this->tablename($tablename));
1117  if ( PEAR::isError($sql) ){
1118 
1119  throw new Exception(
1120  df_translate(
1121  'scripts.Dataface.IO._insert.ERROR_GENERATING_SQL',
1122  "Error generating sql for insert in IO::_insert()")
1123  , E_USER_ERROR);
1124  //return $sql;
1125  }
1126 
1127 
1128  //$res = mysql_query($sql, $s->db);
1129  $res = $this->dbObj->query($sql, $s->db, $this->lang);
1130  if ( !$res || PEAR::isError($res)){
1131  if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_DUP_KEY,MYSQL_ER_DUP_ENTRY)) ){
1132  /*
1133  * This is a duplicate entry. We will handle this as an exception rather than an error because
1134  * cases may arise in a database application when a duplicate entry will happen and the application
1135  * will want to handle it in a graceful way. Eg: If the user is entering a username that is the same
1136  * as an existing name. We don't want an ugle FATAL error to be thrown here. Rather we want to
1137  * notify the application that it is a duplicate entry.
1138  */
1141  /* i18n id */
1142  "Failed to insert record because of duplicate entry",
1143  /* Default error message */
1144  "Duplicate entry into table '".$s->tablename,
1145  /* i18n parameters */
1146  array('table'=>$s->tablename)
1147  )
1148  );
1149  }
1150  throw new Exception(
1151  df_translate(
1152  'scripts.Dataface.IO._insert.ERROR_INSERTING_RECORD',
1153  "Error inserting record: ")
1154  .(PEAR::isError($res)?$res->getMessage():mysql_error(df_db())).": SQL: $sql", E_USER_ERROR);
1155  }
1156  $id = df_insert_id($s->db);
1157  $this->insertIds[$this->_table->tablename] = $id;
1158 
1159  /*
1160  * Now update the record to contain the proper id.
1161  */
1162  $autoIncrementField = $s->getAutoIncrementField();
1163  if ( $autoIncrementField !== null ){
1164  $record->setValue($autoIncrementField, $id);
1165  }
1166 
1167 
1168  if ( $this->fireTriggers ){
1169  $res2 = $this->fireAfterInsert($record);
1170  if ( PEAR::isError($res2) ) return $res2;
1171  }
1172 
1173  return true;
1174 
1175  }
1176 
1177 
1178  function _writeRelationship($relname, $record){
1179  $s =& $this->_table;
1180  $rel =& $s->getRelationship($relname);
1181 
1182  if ( PEAR::isError($rel) ){
1183  $rel->addUserInfo(
1184  df_translate(
1185  'scripts.Dataface.IO._writeRelationship.ERROR_OBTAINING_RELATIONSHIP',
1186  "Error obtaining relationship $relname in IO::_writeRelationship()",
1187  array('relname'=>$relname,'line'=>0,'file'=>"_")
1188  )
1189  );
1190  return $rel;
1191  }
1192 
1193  $tables =& $rel['selected_tables'];
1194  $columns =& $rel['columns'];
1195 
1196  if ( count($tables) == 0 ){
1197  return PEAR::raiseError(
1199  /* i18n id */
1200  "Failed to write relationship because not table was selected",
1201  /* default error message */
1202  "Error writing relationship '$relname'. No tables were selected",
1203  /* i18n parameters */
1204  array('relationship'=>$relname)
1205  ),
1207  );
1208  }
1209 
1210  $records =& $record->getRelatedRecords($relname);
1211  $record_keys = array_keys($records);
1212  if ( PEAR::isError( $records) ){
1213  $records->addUserInfo(
1214  df_translate(
1215  'scripts.Dataface.IO._writeRelationship.ERROR_GETTING_RELATED_RECORDS',
1216  "Error getting related records in IO::_writeRelationship()",
1217  array('line'=>0,'file'=>"_")
1218  )
1219  );
1220  return $records;
1221  }
1222 
1223 
1224 
1225  foreach ($tables as $table){
1226 
1227  $rs =& Dataface_Table::loadTable($table, $s->db);
1228  $keys = array_keys($rs->keys());
1229  $cols = array();
1230  foreach ($columns as $column){
1231  if ( preg_match('/^'.$table.'\.(\w+)/', $column, $matches) ){
1232  $cols[] = $matches[1];
1233  }
1234  }
1235 
1236 
1237  foreach ($record_keys as $record_key){
1238  $changed = false;
1239  // flag whether this record has been changed
1240  $update_cols = array();
1241  // store the columns that have been changed and require update
1242 
1243  foreach ( $cols as $column ){
1244  // check each column to see if it has been changed
1245  if ( $s->valueChanged($relname.'.'.$column, $record_key) ){
1246 
1247  $changed = true;
1248  $update_cols[] = $column;
1249  } else {
1250 
1251  }
1252  }
1253  if ( !$changed ) continue;
1254  // if this record has not been changed with respect to the
1255  // columns of the current table, then we ignore it.
1256 
1257  $sql = "UPDATE `$table` ";
1258  $set = '';
1259  foreach ( $update_cols as $column ){
1260  $set .= "SET $column = '".addslashes($rs->getSerializedValue($column, $records[$record_key][$column]) )."',";
1261  }
1262  $set = trim(substr( $set, 0, strlen($set)-1));
1263 
1264  $where = 'WHERE ';
1265  foreach ($keys as $key){
1266  $where .= "`$key` = '".addslashes($rs->getSerializedValue($key, $records[$record_key][$key]) )."' AND ";
1267  }
1268  $where = trim(substr($where, 0, strlen($where)-5));
1269 
1270  if ( strlen($where)>0 ) $where = ' '.$where;
1271  if ( strlen($set)>0 ) $set = ' '.$set;
1272 
1273  $sql = $sql.$set.$where.' LIMIT 1';
1274 
1275  //$res = mysql_query($sql, $s->db);
1276  $res = $this->dbObj->query($sql, $s->db, $this->lang);
1277  if ( !$res || PEAR::isError($res) ){
1278  throw new Exception(
1279  df_translate(
1280  'scripts.Dataface.IO._writeRelationship.ERROR_UPDATING_DATABASE',
1281  "Error updating database with query '$sql': ".mysql_error($s->db),
1282  array('sql'=>$sql,'mysql_error'=>mysql_error($s->db))
1283  )
1284  , E_USER_ERROR);
1285  }
1286  }
1287 
1288  unset($rs);
1289  }
1290  }
1291 
1292 
1302  function performSQL($sql){
1303 
1304  $ids = array();
1305  $queue = $sql;
1306  $names = array_keys($sql);
1307  $tables = implode('|', $names );
1308  $skips = 0; // keep track of number of consecutive times we skip an iteration so we know when we have reached
1309  // a deadlock.
1310 
1311  if ( func_num_args() >= 2 ){
1312  $duplicates =& func_get_arg(1);
1313  if ( !is_array($duplicates) ){
1314  throw new Exception(
1315  df_translate(
1316  'scripts.Dataface.IO.performSQL.ERROR_PARAMETER_2',
1317  "In Dataface_IO::performSQL() 2nd argument is expected to be an array but received '".get_class($duplicates)."'.",
1318  array('class'=>get_class($duplicates))
1319  )
1320  , E_USER_ERROR);
1321  }
1322  } else {
1323  $duplicates = array();
1324  }
1325  $queryAttempts = array();
1326  $numQueries = count($queue);
1327  while (count($queue) > 0 and $skips < $numQueries){
1328  $current_query = array_shift($queue);
1329  $current_table = array_shift($names);
1330  if ( !isset($queryAttempts[$current_query]) ) $queryAttempts[$current_query] = 1;
1331  else $queryAttempts[$current_query]++;
1332 
1333  $matches = array();
1334  if ( preg_match('/__('.$tables.')__auto_increment__/', $current_query, $matches) ){
1335  $table = $matches[1];
1336  if ( isset($ids[$table]) ){
1337  $current_query = preg_replace('/__'.$table.'__auto_increment__/', $ids[$table], $current_query);
1338  } else {
1339  array_push($queue, $current_query);
1340  array_push($names, $current_table);
1341  $skips++;
1342  continue;
1343  }
1344  }
1345 
1346 
1347  //$res = mysql_query($current_query, $this->_table->db);
1348  $res = $this->dbObj->query($current_query, $this->_table->db, $this->lang);
1349  if ( !$res || PEAR::isError($res) ){
1350  if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_DUP_KEY,MYSQL_ER_DUP_ENTRY)) ){
1351  /*
1352  * This is a duplicate record (ie: it already exists)
1353  */
1354  $duplicates[] = $current_table;
1355  } 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)) ){
1361  array_push($queue, $current_query);
1362  array_push($names, $current_table);
1363 
1364 
1365  } else {
1366  if ( in_array(mysql_errno($this->_table->db), array(MYSQL_ER_NO_REFERENCED_ROW, MYSQL_ER_NO_REFERENCED_ROW_2)) ){
1367  /*
1368  THis failed due to a foreign key constraint.
1369  */
1370  $err = PEAR::raiseError(
1371  sprintf(
1372  df_translate(
1373  'scripts.Dataface.IO.performSQL.ERROR_FOREIGN_KEY',
1374  'Failed to save record because a foreign key constraint failed: %s'
1375  ),
1376  mysql_error(df_db())
1377  ),
1378 
1380  );
1381  error_log($err->toString());
1382  return $err;
1383  }
1384 
1385  $err = PEAR::raiseError(DATAFACE_TABLE_SQL_ERROR, null,null,null,
1386  df_translate(
1387  'scripts.Dataface.IO.performSQL.ERROR_PERFORMING_QUERY',
1388  "Error performing query '$current_query'",
1389  array('line'=>0,'file'=>'_','current_query'=>$current_query)
1390  )
1391  .mysql_errno($this->_table->db).': '.mysql_error($this->_table->db));
1392  throw new Exception($err->toString(), E_USER_ERROR);
1393  }
1394  }
1395  $ids[$current_table] = df_insert_id();
1396  self::touchTable($current_table);
1397  $skips = 0;
1398  }
1399  $this->insertids = $ids;
1400 
1401  return true;
1402 
1403 
1404  }
1405 
1406 
1411  function addRelatedRecord(&$record, $secure=false){
1412  if ( $secure && !$record->_record->checkPermission('add new related record', array('relationship'=>$record->_relationshipName) ) ){
1413  // Use security to check to see if we are allowed to delete this
1414  // record.
1416  df_translate(
1417  'scripts.Dataface.IO.addRelatedRecord.PERMISSION_DENIED',
1418  'Could not add record "'.$record->getTitle().'" to relationship "'.$record->_relationshipName.'" of record "'.$record->_record->getTitle().'" because you have insufficient permissions.',
1419  array('title'=>$record->getTitle(), 'relationship'=>$record->_relationshipName, 'parent'=>$record->_record->getTitle())
1420  )
1421  );
1422  }
1423 
1424 
1425  $queryBuilder = new Dataface_QueryBuilder($this->_table->tablename);
1426 
1427  // Fire the "before events"
1428  if ( $this->fireTriggers ){
1429  $res = $this->fireBeforeAddRelatedRecord($record);
1430  if ( PEAR::isError($res) ) return $res;
1431  }
1432 
1433  if ( $this->fireTriggers ){
1434  $res = $this->fireBeforeAddNewRelatedRecord($record);
1435  if ( PEAR::isError($res) ) return $res;
1436  }
1437 
1438 
1439 
1440 
1441 
1442 
1443 
1444  // It makes sense for us to fire beforeSave, afterSave, beforeInsert, and afterInsert
1445  // events here for the records that are being inserted. To do this we will need to extract
1446  // Dataface_Record objects for all of the tables that will have records inserted.
1447  $drecords = $record->toRecords();
1448  // $drecords is an array of Dataface_Record objects
1449 
1450  foreach ( array_keys($drecords) as $recordIndex){
1451  $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename);
1452 
1453  $drec_snapshot = $drecords[$recordIndex]->strvals();
1454 
1455  $res = $rio->fireBeforeSave($drecords[$recordIndex]);
1456  if (PEAR::isError($res) ) return $res;
1457  $res = $rio->fireBeforeInsert($drecords[$recordIndex]);
1458  if ( PEAR::isError($res) ) return $res;
1459 
1460  $drec_post_snapshot = $drecords[$recordIndex]->strvals();
1461 
1462  foreach ( $drec_snapshot as $ss_key=>$ss_val ){
1463 
1464  if ( $drec_post_snapshot[$ss_key] != $ss_val ){
1465 
1466  $record->setValue($ss_key,$drec_post_snapshot[$ss_key]);
1467  }
1468  }
1469 
1470  unset($drec_snapshot);
1471  unset($drec_post_snapshot);
1472  unset($rio);
1473  }
1474 
1475  //$sql = Dataface_QueryBuilder::addRelatedRecord($record);
1476  $sql = $queryBuilder->addRelatedRecord($record);
1477  if ( PEAR::isError($sql) ){
1478  $sql->addUserInfo(
1479  df_translate(
1480  'scripts.Dataface.IO.addRelatedRecord.ERROR_GENERATING_SQL',
1481  "Error generating sql in ShortRelatedRecordForm::save()",
1482  array('line'=>0,'file'=>"_")
1483  )
1484  );
1485  return $sql;
1486  }
1487 
1488  // Actually add the record
1489  $res = $this->performSQL($sql);
1490  if ( PEAR::isError($res) ){
1491  return $res;
1492  }
1493 
1494  $rfields = array_keys($record->vals());
1495  // Just for completeness we will fire afterSave and afterInsert events for
1496  // all records being inserted.
1497  foreach ( array_keys($drecords) as $recordIndex){
1498  $currentRecord =& $drecords[$recordIndex];
1499  if ( isset($this->insertids[ $currentRecord->_table->tablename ] ) ){
1500  $idfield = $currentRecord->_table->getAutoIncrementField();
1501  if ( $idfield ){
1502  $currentRecord->setValue($idfield, $this->insertids[ $currentRecord->_table->tablename ]);
1503  if ( in_array($idfield, $rfields) ){
1504  $record->setValue($idfield, $this->insertids[ $currentRecord->_table->tablename ]);
1505  }
1506  }
1507 
1508  unset($idfield);
1509  }
1510  unset($currentRecord);
1511  $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename);
1512 
1513  $res = $rio->saveTransients($drecords[$recordIndex], null, null, true);
1514  if ( PEAR::isError($res) ){
1515  return $res;
1516  }
1517 
1518  $res = $rio->fireAfterInsert($drecords[$recordIndex]);
1519  if (PEAR::isError($res) ) return $res;
1520  $res = $rio->fireAfterSave($drecords[$recordIndex]);
1521  if ( PEAR::isError($res) ) return $res;
1522 
1523  unset($rio);
1524  }
1525 
1526 
1527  // Fire the "after" events
1528  if ( $this->fireTriggers ){
1529  $res2 = $this->fireAfterAddNewRelatedRecord($record);
1530  if ( PEAR::isError($res2) ) return $res2;
1531 
1532  $res2 = $this->fireAfterAddRelatedRecord($record);
1533  if ( PEAR::isError($res2) ) return $res2;
1534  }
1535 
1536  return $res;
1537  }
1538 
1543  function addExistingRelatedRecord(&$record, $secure=false){
1544  if ( $secure && !$record->_record->checkPermission('add existing related record', array('relationship'=>$record->_relationshipName) ) ){
1545  // Use security to check to see if we are allowed to delete this
1546  // record.
1548  df_translate(
1549  'scripts.Dataface.IO.addExistingRelatedRecord.PERMISSION_DENIED',
1550  'Could not add record "'.$record->getTitle().'" to relationship "'.$record->_relationshipName.'" of record "'.$record->_record->getTitle().'" because you have insufficient permissions.',
1551  array('title'=>$record->getTitle(), 'relationship'=>$record->_relationshipName, 'parent'=>$record->_record->getTitle())
1552  )
1553  );
1554  }
1555 
1556  $builder = new Dataface_QueryBuilder($this->_table->tablename);
1557 
1558  //We are often missing the values from the domain table so we will load them
1559  //here
1560  $domainRec = $record->toRecord($record->_relationship->getDomainTable());
1561  $domainRec2 = df_get_record_by_id($domainRec->getId());
1562  //$record->setValues(array_merge($domainRec2->vals(), $record->vals()));
1563  foreach ($domainRec2->vals() as $dreckey=>$drecval){
1564  if ( !$record->val($dreckey) ) $record->setValue($dreckey, $drecval);
1565  }
1566  // fire the "before" events
1567  if ( $this->fireTriggers ){
1568  $res =$this->fireBeforeAddRelatedRecord($record);
1569  if ( PEAR::isError($res) ) return $res;
1570 
1571  $res = $this->fireBeforeAddExistingRelatedRecord($record);
1572  if ( PEAR::isError($res) ) return $res;
1573  }
1574 
1575 
1576 
1577 
1578 
1579  // It makes sense for us to fire beforeSave, afterSave, beforeInsert, and afterInsert
1580  // events here for the records that are being inserted. To do this we will need to extract
1581  // Dataface_Record objects for all of the tables that will have records inserted. In this
1582  // case we are not updated any records because relationships are created by adding a record
1583  // to the join table. This means that we are also NOT adding a record to the domain table.
1584  // i.e., we should only fire these events for the join table.
1585  $drecords = $record->toRecords();
1586  // $drecords is an array of Dataface_Record objects
1587 
1588  if ( count($drecords) > 1 ){
1589  // If there is only one record then it is for the domain table - which we don't actually
1590  // change.
1591  foreach ( array_keys($drecords) as $recordIndex){
1592  $currentRecord =& $drecords[$recordIndex];
1593  if ( isset($this->insertids[ $currentRecord->_table->tablename ] ) ){
1594  $idfield =& $currentRecord->_table->getAutoIncrementField();
1595  if ( $idfield ){
1596  $currentRecord->setValue($idfield, $this->insertids[ $currentRecord->_table->tablename ]);
1597  }
1598  unset($idfield);
1599  }
1600  unset($currentRecord);
1601  if ( $drecords[$recordIndex]->_table->tablename === $record->_relationship->getDomainTable() ) continue;
1602  // We don't do anything for the domain table because it is not being updated.
1603 
1604  $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename);
1605 
1606  $drec_snapshot = $drecords[$recordIndex]->strvals();
1607 
1608  $res = $rio->fireBeforeSave($drecords[$recordIndex]);
1609  if (PEAR::isError($res) ) return $res;
1610  $res = $rio->fireBeforeInsert($drecords[$recordIndex]);
1611  if ( PEAR::isError($res) ) return $res;
1612 
1613  $drec_post_snapshot = $drecords[$recordIndex]->strvals();
1614 
1615  foreach ( $drec_post_snapshot as $ss_key=>$ss_val ){
1616  if ( $drec_snapshot[$ss_key] != $ss_val ){
1617  $drecords[$recordIndex]->setValue($ss_key,$ss_val);
1618  }
1619  }
1620 
1621  unset($drec_post_snapshot);
1622  unset($drec_snapshot);
1623  unset($rio);
1624  }
1625  }
1626 
1627 
1628  if ( count($drecords) > 1 ){
1629  $sql = $builder->addExistingRelatedRecord($record);
1630  if ( PEAR::isError($sql) ){
1631  return $sql;
1632  }
1633  // Actually add the related record
1634  $res = $this->performSQL($sql);
1635  if ( PEAR::isError( $res) ) return $res;
1636 
1637  // If there is only one record then it is for the domain table - which we don't actually
1638  // change.
1639  foreach ( array_keys($drecords) as $recordIndex){
1640 
1641  if ( $drecords[$recordIndex]->_table->tablename === $record->_relationship->getDomainTable() ) continue;
1642  // We don't do anything for the domain table because it is not being updated.
1643 
1644  $rio = new Dataface_IO($drecords[$recordIndex]->_table->tablename);
1645 
1646  $res = $rio->fireAfterInsert($drecords[$recordIndex]);
1647  if (PEAR::isError($res) ) return $res;
1648  $res = $rio->fireAfterSave($drecords[$recordIndex]);
1649  if ( PEAR::isError($res) ) return $res;
1650 
1651  unset($rio);
1652  }
1653  } else {
1654 
1655 
1656  // This is a one to many relationship. We will handle this case
1657  // only when the foreign key is currently null. Otherwise we return
1658  // and error.
1659  $fkeys = $record->_relationship->getForeignKeyValues();
1660  $fkeyvals = $record->getForeignKeyValues();
1661  if ( isset($fkeys[$domainRec2->_table->tablename]) ){
1662  $drecid = $domainRec2->getId();
1663  unset($domainRec2);
1664  $domainRec2 = df_get_record_by_id($drecid);
1665  if ( !$domainRec2 ){
1666  return PEAR::raiseError("Tried to get record with id $drecid but it doesn't exist");
1667 
1668  } else if ( PEAR::isError($domainRec2) ){
1669  return $domainRec2;
1670  }
1671  foreach ( array_keys($fkeys[$domainRec2->_table->tablename]) as $fkey){
1672  //echo $fkey;
1673 
1674  if ( $domainRec2->val($fkey) ){
1675  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.");
1676 
1677  } else {
1678 
1679  $domainRec2->setValue($fkey, $fkeyvals[$domainRec2->_table->tablename][$fkey]);
1680  }
1681  }
1682 
1683  $res = $domainRec2->save($secure);
1684  if ( PEAR::raiseError($res) ) return $res;
1685  } else {
1686  return PEAR::raiseError("Failed to add existing record because the domain table doesn't have any foreign keys in it.");
1687  }
1688 
1689 
1690  }
1691 
1692  // Fire the "after" events
1693  if ( $this->fireTriggers ){
1694  $res2 = $this->fireAfterAddExistingRelatedRecord($record);
1695  if ( PEAR::isError( $res2 ) ) return $res2;
1696 
1697  $res2 = $this->fireAfterAddRelatedRecord($record);
1698  if ( PEAR::isError( $res2 ) ) return $res2;
1699  }
1700 
1701  return $res;
1702 
1703  }
1704 
1713  function removeRelatedRecord(&$related_record, $delete=false, $secure=false){
1714  if ( $secure && !$related_record->_record->checkPermission('remove related record', array('relationship'=>$related_record->_relationshipName) ) ){
1715  // Use security to check to see if we are allowed to delete this
1716  // record.
1717 
1719  df_translate(
1720  'scripts.Dataface.IO.removeRelatedRecord.PERMISSION_DENIED',
1721  'Could not remove record "'.$related_record->getTitle().'" from relationship "'.$related_record->_relationshipName.'" of record "'.$related_record->_record->getTitle().'" because you have insufficient permissions.',
1722  array('title'=>$related_record->getTitle(), 'relationship'=>$related_record->_relationshipName, 'parent'=>$related_record->_record->getTitle())
1723  )
1724  );
1725  }
1726 
1727  $res = $this->fireEvent('beforeRemoveRelatedRecord', $related_record);
1728  if ( PEAR::isError($res) ) return $res;
1729  /*
1730  * First we need to find out which table is the domain table. The domain table
1731  * is the table that actually contains the records of interest. The rest of
1732  * the tables are referred to as 'join' tables.
1733  */
1734  $domainTable = $related_record->_relationship->getDomainTable();
1735  if ( PEAR::isError($domainTable) ){
1736  /*
1737  * Dataface_Relationship::getDomainTable() throws an error if there are
1738  * no join tables. We account for that by explicitly setting the domain
1739  * table to the first table in the list.
1740  */
1741  $domainTable = $related_record->_relationship->_schema['selected_tables'][0];
1742  }
1743  /*
1744  * Next we construct an IO object to write to the domain table.
1745  */
1746  $domainIO = new Dataface_IO($domainTable);
1747 
1748  $domainTable =& Dataface_Table::loadTable($domainTable);
1749  // reference to the Domain table Dataface_Table object.
1750 
1751  /*
1752  * Begin building queries.
1753  */
1754  $query = array();
1755  // query array to build the query to delete the record.
1756  $absVals = array();
1757  // same as query array except the keys are absolute field names (ie: Tablename.Fieldname)
1758  $currKeyNames = array_keys($domainTable->keys());
1759  // Names of key fields in the domain table
1760  foreach ($currKeyNames as $keyName){
1761  $query[$keyName] = $related_record->strval($keyName);
1762  $absVals[$domainTable->tablename.'.'.$keyName] = $query[$keyName];
1763  }
1764 
1765 
1766  $fkeys = $related_record->_relationship->getForeignKeyValues($absVals, null, $related_record->_record);
1767  $warnings = array();
1768  $confirmations = array();
1769  foreach ( array_keys($fkeys) as $currTable){
1770  // For each table in the relationship we go through and delete its record.
1771  $io = new Dataface_IO($currTable);
1772 
1773  $record = new Dataface_Record($currTable, array());
1774  $res = $io->read($fkeys[$currTable], $record);
1775  //patch for Innodb foreign keys with ON DELELE CASCADE
1776  // Contributed by Optik
1777  if (!$io->recordExists($record,null,$currTable)){
1778  $warnings[] = df_translate(
1779  'scripts.Dataface.IO.removeRelatedRecord.ERROR_RECORD_DOESNT_EXIST',
1780  "Failed to delete entry for record '".$record->getTitle()."' in table '$currTable' because record doesn't exist.",
1781  array('title'=>$record->getTitle(), 'currTable'=>$currTable)
1782  );
1783  unset($record);
1784  unset($io);
1785  continue;
1786  }
1787  // -- end patch for Innodb foreign keys
1788  if ( $currTable == $domainTable->tablename and !$delete ){
1789  // Unless we have specified that we want the domain table record
1790  // deleted, we leave it alone!
1791 
1792 
1793 
1794  // If this is a one to many we'll try to just set the foreign key to null
1795  if ( count($fkeys) == 1 ){
1796 
1797  if (($currTable == $domainTable->tablename) and $secure and !$related_record->_record->checkPermission('remove related record', array('relationship'=>$related_record->_relationshipName)) ){
1798  $useSecurity = true;
1799 
1800  } else {
1801  $useSecurity = false;
1802  }
1803 
1804  $myfkeys = $related_record->_relationship->getForeignKeyValues();
1805  foreach ( $myfkeys[$currTable] as $colName=>$colVal ){
1806  $record->setValue($colName, null);
1807 
1808  }
1809  //exit;
1810 
1811  $res = $record->save(null, $useSecurity);
1812  if ( PEAR::isError($res) && Dataface_Error::isError($res) ){
1813  //$this->logError($res);
1814  return $res;
1815  } else if ( PEAR::isError($res) ){
1816  $warnings[] = $res;
1817 
1818  } else {
1819 
1820  $confirmations[] = df_translate(
1821  'Successfully removed record',
1822  "Successfully removed entry for record '".$record->getTitle()."' in table '$currTable'",
1823  array('title'=>$record->getTitle(), 'table'=>$currTable)
1824  );
1825 
1826  }
1827 
1828 
1829  }
1830 
1831  unset($record);
1832  unset($io);
1833  continue;
1834  }
1835 
1836  // Let's figure out whether we need to use security for deleting this
1837  // record.
1838  // If security is on, and it is the domain table, and the user doesn't
1839  // have the 'delete related record' permission then we need to use
1840  // security
1841  if (($currTable == $domainTable->tablename) and $secure and !$related_record->_record->checkPermission('delete related record', array('relationship'=>$related_record->_relationshipName)) ){
1842  $useSecurity = true;
1843 
1844  } else {
1845  $useSecurity = false;
1846  }
1847 
1848  $res = $io->delete($record, $useSecurity);
1849 
1850  if ( PEAR::isError($res) && Dataface_Error::isError($res) ){
1851  //$this->logError($res);
1852  return $res;
1853  } else if ( PEAR::isError($res) ){
1854  $warnings[] = $res;
1855  }
1856  else {
1857  $confirmations[] = df_translate(
1858  'Successfully deleted record',
1859  "Successfully deleted entry for record '".$record->getTitle()."' in table '$currTable'",
1860  array('title'=>$record->getTitle(), 'table'=>$currTable)
1861  );
1862  }
1863  $record->__destruct();
1864  unset($record);
1865  unset($b);
1866  unset($io);
1867 
1868  }
1869  $res = $this->fireEvent('afterRemoveRelatedRecord', $related_record);
1870  if ( PEAR::isError($res) ) return $res;
1871  if (count($warnings)>0 ) return PEAR::raiseError(@implode("\n",$warnings), DATAFACE_E_WARNING);
1872  if (count($confirmations)==0) return false;
1873  return true;
1874 
1875  }
1876 
1903  function copy(&$sourceRecord, &$destParent, $destRelationship=null, $deepCopy=false){
1904  throw new Exception("The method ".__METHOD__." is not implemented yet.", E_USER_ERROR);
1905  }
1906 
1907 
1908 
1909  // Event handlers.
1910 
1915  function fireBeforeSave(&$record){
1916  return $this->fireEvent('beforeSave', $record);
1917  }
1918 
1923  function fireAfterSave(&$record){
1924  return $this->fireEvent('afterSave', $record);
1925  }
1926 
1931  function fireBeforeUpdate(&$record){
1932  return $this->fireEvent('beforeUpdate', $record);
1933  }
1934 
1935 
1940  function fireAfterUpdate(&$record){
1941  return $this->fireEvent('afterUpdate', $record);
1942  }
1943 
1948  function fireBeforeInsert(&$record){
1949  return $this->fireEvent('beforeInsert', $record);
1950  }
1951 
1956  function fireAfterInsert(&$record){
1957  return $this->fireEvent('afterInsert', $record);
1958  }
1959 
1964  function fireBeforeAddRelatedRecord(&$record){
1965  return $this->fireEvent('beforeAddRelatedRecord', $record);
1966  }
1967 
1972  function fireAfterAddRelatedRecord(&$record){
1973  return $this->fireEvent('afterAddRelatedRecord', $record);
1974 
1975  }
1976 
1981  function fireBeforeAddNewRelatedRecord(&$record){
1982  return $this->fireEvent('beforeAddNewRelatedRecord', $record);
1983  }
1984 
1989  function fireAfterAddNewRelatedRecord(&$record){
1990  return $this->fireEvent('afterAddNewRelatedRecord', $record);
1991  }
1992 
1998  return $this->fireEvent('beforeAddExistingRelatedRecord', $record);
1999  }
2000 
2006  return $this->fireEvent('afterAddExistingRelatedRecord', $record);
2007  }
2008 
2013  function fireBeforeDelete(&$record){
2014  return $this->fireEvent('beforeDelete', $record);
2015  }
2016 
2021  function fireAfterDelete(&$record){
2022  return $this->fireEvent('afterDelete', $record);
2023  }
2024 
2030  function fireEvent($name, &$record, $bubble=true){
2031  $oldVeto = $record->vetoSecurity;
2032  $record->vetoSecurity = true;
2033  $delegate =& $this->_table->getDelegate();
2034  if ( $delegate !== null and method_exists($delegate,$name) ){
2035  $res = $delegate->$name($record);
2036  if ( PEAR::isError( $res ) ){
2037  $res->addUserInfo(
2038  df_translate(
2039  'scripts.Dataface.IO.fireEvent.ERROR_WHILE_FIRING',
2040  "Error while firing event '$name' on table '".$this->_table->tablename."' in Dataface_IO::write() ",
2041  array('name'=>$name,'tablename'=>$this->_table->tablename, 'line'=>0,'file'=>"_")
2042  )
2043  );
2044  $record->vetoSecurity = $oldVeto;
2045  return $res;
2046  }
2047  }
2048 
2049  $parentIO =& $this->getParentIO();
2050  if ( isset($parentIO) ){
2051  $parentIO->fireEvent($name, $record, false);
2052  }
2053 
2054  if ( $bubble ){
2056  $res = $app->fireEvent($name, array(&$record, &$this));
2057  if ( PEAR::isError($res) ) {
2058  $record->vetoSecurity = $oldVeto;
2059  return $res;
2060  }
2061  }
2062 
2063  return true;
2064 
2065  }
2066 
2067 
2068 
2069 
2070 
2077  function tablename($tablename=null){
2078  if ( $tablename !== null ) return $tablename;
2079  return $this->_table->tablename;
2080 
2081  }
2082 
2083 
2159  function importData( &$record, $data, $importFilter=null, $relationshipName=null, $commit=false, $defaultValues=array()){
2160  if ( $relationshipName === null ){
2161 
2162  /*
2163  * No relationship is specified so our import table is just the current table.
2164  */
2165  $table =& $this->_table;
2166 
2167  } else {
2168  /*
2169  * A relationship is specified so we are actually importing the records into the
2170  * domain table of the relationship.
2171  */
2172 
2173  $relationship =& $this->_table->getRelationship($relationshipName);
2174  $tablename = $relationship->getDomainTable();
2175  if ( PEAR::isError($tablename) ){
2176  /*
2177  * This relationship does not have a domain table.. so we will just take the destination table.
2178  */
2179  $destinationTables =& $relationship->getDestinationTables();
2180  if ( count($destinationTables) <= 0 ){
2181  throw new Exception(
2182  df_translate(
2183  'scripts.Dataface.IO.importData.ERROR_NO_DESTINATION_TABLES',
2184  "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",
2185  array('relationship'=>$relationship->getName(), 'table'=>$this->_table->tablename)
2186  )
2187  , E_USER_ERROR);
2188  }
2189  $tablename = $destinationTables[0]->tablename;
2190 
2191  }
2192 
2193  if ( PEAR::isError($tablename) ){
2194  throw new Exception($tablename->toString(), E_USER_ERROR);
2195  }
2197  $rel_io = new Dataface_IO($tablename);
2198  $io =& $rel_io;
2199  }
2200 
2201  if ( !$commit ){
2202  // If data is provided, we must parse it and prepare it for
2203  // import
2204  $records = $table->parseImportData($data, $importFilter, $defaultValues);
2205  if ( PEAR::isError($records) ){
2206  /*
2207  * The import didn't work with the specified import filter, so we will
2208  * try the other filters.
2209  */
2210  $records = $table->parseImportData($data, null, $defaultValues);
2211  }
2212 
2213  if ( PEAR::isError($records) ){
2214  /*
2215  * Apparently we have failed to import the data, so let's just
2216  * return the errors.
2217  */
2218  return $records;
2219  }
2220 
2221  // Now we will load the values of the records into an array
2222  // so that we can store it in the session
2223  $importData = array(
2224  'table' => $table->tablename,
2225  'relationship' => $relationshipName,
2226  'defaults' => $defaultValues,
2227  'importFilter' => $importFilter,
2228  'record' => null,
2229  'rows' => array()
2230  );
2231  if ( isset($record) ) $importData['record'] = $record->getId();
2232 
2233  foreach ($records as $r){
2234  if ( is_a($r, 'Dataface_ImportRecord') ){
2235  // The current record is actually an ImportRecord
2236  $importData['rows'][] = $r->toArray();
2237  } else {
2238  $importData['rows'][] = $r->vals(array_keys($r->_table->fields(false,true)));
2239  unset($r);
2240  }
2241  }
2242 
2243  $dumpFile = tempnam(sys_get_temp_dir(), 'dataface_import');
2244  $handle = fopen($dumpFile, "w");
2245  if ( !$handle ){
2246  throw new Exception("Could not write import data to dump file $dumpFile", E_USER_ERROR);
2247  }
2248  fwrite($handle, serialize($importData));
2249  fclose($handle);
2250 
2251  $_SESSION['__dataface__import_data__'] = $dumpFile;
2252 
2253  return $dumpFile;
2254 
2255  }
2256 
2257  if ( !@$_SESSION['__dataface__import_data__'] ){
2258  throw new Exception("No import data to import", E_USER_ERROR);
2259  }
2260 
2261  $dumpFile = $_SESSION['__dataface__import_data__'];
2262  $importData = unserialize(file_get_contents($dumpFile));
2263 
2264 
2265  if ( $importData['table'] != $table->tablename ){
2266  return PEAR::raiseError("Unexpected table name in import data. Expected ".$table->tablename." but received ".$importData['table']);
2267 
2268  }
2269 
2270  $inserted = array();
2271  $i=0;
2272  foreach ( $importData['rows'] as $row ){
2273  if ( isset($row['__CLASS__']) and isset($row['__CLASSPATH__']) ){
2274  // This row is an import record - not merely a Dataface_Record
2275  // object so it provides its own logic to import the records.
2276  import($row['__CLASSPATH__']);
2277  $class = $row['__CLASS__'];
2278  $importRecord = new $class($row);
2279  $res = $importRecord->commit($record, $relationshipName);
2280  if ( PEAR::isError($res) ){
2281  return $res;
2282  }
2283  } else {
2284  $values = array();
2285  foreach (array_keys($row) as $key){
2286  if ( !is_int($key) ){
2287  $values[$key] = $row[$key];
2288  }
2289  }
2290  if ( $relationshipName === null ){
2291  /*
2292  * These records are not being added to a relationship. They are just being added directly
2293  * into the table.
2294  */
2295 
2296  $defaults = array();
2297  // for absolute field name keys for default values, we will strip out the table name.
2298  foreach (array_keys($defaultValues) as $key){
2299  if ( strpos($key,'.') !== false ){
2300  list($tablename, $fieldname) = explode('.', $key);
2301  if ( $tablename == $this->_table->tablename ){
2302  $defaults[$fieldname] = $defaultValues[$key];
2303  } else {
2304  continue;
2305  }
2306  } else {
2307  $defaults[$key] = $defaultValues[$key];
2308  }
2309  }
2310 
2311  $values = array_merge($defaults, $values);
2312  $insrecord = new Dataface_Record($this->_table->tablename, $values);
2313  $inserted[] =& $insrecord;
2314  $this->write($insrecord);
2315  $insrecord->__destruct();
2316  unset($insrecord);
2317  } else {
2318  /*
2319  * The records are being added to a relationship so we need to make sure that we add the appropriate
2320  * entries to the "join" tables as well.
2321  */
2322  foreach (array_keys($values) as $key){
2323  $values[$table->tablename.'.'.$key] = $values[$key];
2324  unset($values[$key]);
2325  }
2326 
2327  $values = array_merge( $defaultValues, $values);
2328 
2329  /*
2330  * Let's check if all of the keys are set. If they are then the record already exists.. we
2331  * just need to update the record.
2332  *
2333  */
2334  $rvalues = array();
2335  foreach ( $values as $valkey=>$valval){
2336  if ( strpos($valkey,'.') !== false ){
2337  list($tablename,$fieldname) = explode('.',$valkey);
2338  if ( $tablename == $table->tablename ){
2339  $rvalues[$fieldname] = $valval;
2340  }
2341  }
2342  }
2343  $rrecord = new Dataface_Record( $table->tablename, array());
2344 
2345  $rrecord->setValues($rvalues);
2346  // we set the values in a separate call because we want to be able to do an update
2347  // and setting values in the constructer sets the snapshot (ie: it will think that
2348  // no values have changed.
2349 
2350  if ( $io->recordExists($rrecord)){
2351  /*
2352  * The record already exists, so we update it and then add it to the relationship.
2353  *
2354  */
2355  if ( Dataface_PermissionsTool::edit($rrecord) ){
2356  /*
2357  * We only edit the record if we have permission to do so.
2358  */
2359 
2360  $result = $io->write($rrecord);
2361  if ( PEAR::isError($result) ){
2362  throw new Exception($result->toString(), E_USER_ERROR);
2363  }
2364  }
2365  $relatedRecord = new Dataface_RelatedRecord( $record, $relationshipName, $values);
2366  $inserted[] =& $relatedRecord;
2367  $qb = new Dataface_QueryBuilder($this->_table->tablename);
2368  $sql = $qb->addExistingRelatedRecord($relatedRecord);
2369 
2370  $res2 = $this->performSQL($sql);
2371 
2372  unset($relatedRecord);
2373 
2374 
2375  } else {
2376 
2377  $relatedRecord = new Dataface_RelatedRecord( $record, $relationshipName, $values);
2378  $inserted[] =& $relatedRecord;
2379  $qb = new Dataface_QueryBuilder($this->_table->tablename);
2380  $sql = $qb->addRelatedRecord($relatedRecord);
2381 
2382  $res2 = $this->performSQL($sql);
2383 
2384  unset($relatedRecord);
2385  }
2386 
2387  unset($rrecord);
2388 
2389 
2390  }
2391  }
2392 
2393  unset($row);
2394  }
2395 
2396 
2397  @unlink($dumpFile);
2398  unset($_SESSION['__dataface__import_data__']);
2399 
2400  return $inserted;
2401 
2402 
2403 
2404 
2405  }
2406 
2407 
2459  static function &getByID($uri, $filter=null){
2460  if ( strpos($uri, '?') === false ) return PEAR::raiseError("Invalid record id: ".$uri);
2461  $uri_parts = df_parse_uri($uri);
2462  if ( PEAR::isError($uri_parts) ) return $uri_parts;
2463  if ( !isset($uri_parts['relationship']) ){
2464  // This is just requesting a normal record.
2465 
2466  // Check to see if this is to be a new record or an existing record
2467  if ( @$uri_parts['action'] and ( $uri_parts['action'] == 'new' ) ){
2468  $record = new Dataface_Record($uri_parts['table'], array());
2469  $record->setValues($uri_parts['query']);
2470  return $record;
2471  }
2472 
2473  foreach ($uri_parts['query'] as $ukey=>$uval){
2474  if ( $uval and $uval{0}!='=' ) $uval = '='.$uval;
2475  $uri_parts['query'][$ukey]=$uval;
2476  }
2477  // At this point we are sure that this is requesting an existing record
2478  $record =& df_get_record($uri_parts['table'], $uri_parts['query']);
2479 
2480  if ( isset($uri_parts['field']) ){
2481  if ( isset($filter) and method_exists($record, $filter) ){
2482  $val =& $record->$filter($uri_parts['field']);
2483  return $val;
2484  } else {
2485  $val =& $record->val($uri_parts['field']);
2486  return $val;
2487  }
2488  }
2489  else return $record;
2490 
2491  } else {
2492  // This is requesting a related record.
2493 
2494  $record =& df_get_record($uri_parts['table'], $uri_parts['query']);
2495  if ( !$record ) return PEAR::raiseError("Could not find any records matching the query");
2496 
2497  // Check to see if we are creating a new record
2498  if ( @$uri_parts['action'] and ( $uri_parts['action'] == 'new' ) ){
2499  $related_record = new Dataface_RelatedRecord($record, $uri_parts['relationship']);
2500  $related_record->setValues( $uri_parts['query']);
2501  return $related_record;
2502  }
2503 
2504 
2505  // At this point we can be sure that we are requesting an existing record.
2506  $related_records =& $record->getRelatedRecordObjects($uri_parts['relationship'], 0,1, $uri_parts['related_where']);
2507  if ( count($related_records) == 0 ){
2508 
2509  return PEAR::raiseError("Could not find any related records matching the query: ".$uri_parts['related_where']);
2510  }
2511  if ( isset($uri_parts['field']) ) {
2512  if ( isset($filter) and method_exists($related_records[0], $filter) ){
2513  $val =& $related_records[0]->$filter($uri_parts['field']);
2514  return $val;
2515  } else {
2516  $val =& $related_records[0]->val($uri_parts['field']);
2517  return $val;
2518  }
2519  }
2520  else return $related_records[0];
2521 
2522  }
2523  }
2524 
2525 
2529  static function setByID($uri, $value){
2530 
2531  @list($uri, $fieldname) = explode('#', $uri);
2532  $record =& Dataface_IO::getByID($uri);
2533 
2534  if ( PEAR::isError($record) ) return $record;
2535  if ( !is_object($record) ) return PEAR::raiseError("Could not find record matching '$uri'.");
2536 
2537  if ( isset($fieldname) ){
2538  $res = $record->setValue($fieldname, $value);
2539  } else {
2540  $res = $record->setValues($value);
2541  }
2542  if ( PEAR::isError($res) ) return $res;
2543 
2544  $res = $record->save();
2545  return $res;
2546  }
2547 
2548 
2549  static function createModificationTimesTable(){
2550  $sql = "create table dataface__mtimes (
2551  `name` varchar(255) not null primary key,
2552  `mtime` int(11)
2553  )";
2554  $res = mysql_query($sql, df_db());
2555  if ( !$res ) throw new Exception(mysql_error(df_db()));
2556  }
2557 
2558  static function touchTable($table){
2559  $sql = "replace into dataface__mtimes (`name`,`mtime`) values ('".addslashes($table)."','".addslashes(time())."')";
2560  $res = mysql_query($sql, df_db());
2561  if ( !$res ){
2563  $res = mysql_query($sql, df_db());
2564  if ( !$res ) throw new Exception(mysql_error(df_db()));
2565  }
2566  }
2567 
2568 
2569 
2570 
2571 
2572 
2573 }