/** * Writes the values in the table to the database. * * @param tablename An optional tablename in case this record is not being placed in * the standard table. For example, the record could be placed into an import * table. * @param array $keys Optional array of keys to look up record to write to. * @param string $tablename The name of the table to write to, if not this table. * This is useful for writing to import tables or other * tables with identical schema. * @param boolean $secure Whether to check permissions or not. * @param boolean $forceNew If true, it forces an insert rather than an update. */ function write(&$record, $keys = null, $tablename = null, $secure = false, $forceNew = false) { // The vetoSecurity flag allows us to make changes to a record without // the fields being filtered for security checks when they are saved. // Since we may want to change or add values to a record in the // beforeSave type triggers, and we probably don't want these changes // checked by security, we should use this flag to make all changes // in these triggers immune to security checks. // We return the veto setting to its former state after this method // finishes. //$oldVeto = $record->vetoSecurity; //$record->vetoSecurity = true; //$parentRecord =& $record->getParentRecord(); $app =& Dataface_Application::getInstance(); //$parentIO =& $this->getParentIO(); if (!is_a($record, "Dataface_Record")) { throw new Exception(df_translate('scripts.Dataface.IO.write.ERROR_PARAMETER_1', "Dataface_IO::write() requires first parameter to be of type 'Dataface_Record' but received '" . get_class($record) . "\n<br>", array('class' => get_class($record))), E_USER_ERROR); } if ($tablename === null and $this->_altTablename !== null) { $tablename = $this->_altTablename; } if ($this->fireTriggers) { $res = $this->fireBeforeSave($record); if (PEAR::isError($res)) { //$record->vetoSecurity = $oldVeto; return $res; } } if (!$forceNew and $this->recordExists($record, $keys, $this->tablename($tablename))) { $res = $this->_update($record, $keys, $this->tablename($tablename), $secure); } else { $res = $this->_insert($record, $this->tablename($tablename), $secure); } if (PEAR::isError($res)) { if (Dataface_Error::isDuplicateEntry($res)) { /* * Duplicate entries we will propogate up so that the application can decide what to do. */ //$record->vetoSecurity = $oldVeto; return $res; } $res->addUserInfo(df_translate('scripts.Dataface.IO.write.ERROR_SAVING', "Error while saving record of table '" . $this->_table->tablename . "' in Dataface_IO::write() ", array('tablename' => $this->_table->tablename, 'line' => 0, 'file' => '_'))); //$record->vetoSecurity = $oldVeto; return $res; } $res = $this->saveTransients($record, $keys, $tablename, $secure); if (PEAR::isError($res)) { return $res; } if ($this->fireTriggers) { $res2 = $this->fireAfterSave($record); if (PEAR::isError($res2)) { //$record->vetoSecurity = $oldVeto; return $res2; } } if (isset($app->_conf['history']) and @$app->_conf['history']['enabled'] || !isset($app->_conf['history']['enabled'])) { // History is enabled ... let's save this record in our history. import('Dataface/HistoryTool.php'); $historyTool = new Dataface_HistoryTool(); $historyTool->logRecord($record, $this->getHistoryComments($record), $this->lang); } if (isset($app->_conf['_index']) and @$app->_conf['_index'][$record->table()->tablename]) { // If indexing is enabled, we index the record so that it is // searchable by natural language searching. // The Dataface_Index class takes care of whether or not this // record should be indexed. import('Dataface/Index.php'); $index = new Dataface_Index(); $index->indexRecord($record); } // It seems to me that we should be setting a new snapshot at this point. //$record->clearSnapshot(); $record->setSnapshot(); self::touchTable($this->_table->tablename); self::touchRecord($record); //$record->vetoSecurity = $oldVeto; return $res; }
/** * Writes the values in the table to the database. * * @param tablename An optional tablename in case this record is not being placed in * the standard table. For example, the record could be placed into an import * table. * @param array $keys Optional array of keys to look up record to write to. * @param string $tablename The name of the table to write to, if not this table. * This is useful for writing to import tables or other * tables with identical schema. * @param boolean $secure Whether to check permissions or not. */ function write(&$record, $keys = null, $tablename = null, $secure = false) { // The vetoSecurity flag allows us to make changes to a record without // the fields being filtered for security checks when they are saved. // Since we may want to change or add values to a record in the // beforeSave type triggers, and we probably don't want these changes // checked by security, we should use this flag to make all changes // in these triggers immune to security checks. // We return the veto setting to its former state after this method // finishes. //$oldVeto = $record->vetoSecurity; //$record->vetoSecurity = true; //$parentRecord =& $record->getParentRecord(); $app =& Dataface_Application::getInstance(); //$parentIO =& $this->getParentIO(); if (!is_a($record, "Dataface_Record")) { trigger_error(df_translate('scripts.Dataface.IO.write.ERROR_PARAMETER_1', "Dataface_IO::write() requires first parameter to be of type 'Dataface_Record' but received '" . get_class($record) . "\n<br>", array('class' => get_class($record))) . Dataface_Error::printStackTrace(), E_USER_ERROR); } if ($tablename === null and $this->_altTablename !== null) { $tablename = $this->_altTablename; } if ($this->fireTriggers) { $res = $this->fireBeforeSave($record); if (PEAR::isError($res)) { //$record->vetoSecurity = $oldVeto; return $res; } } if ($this->recordExists($record, $keys, $this->tablename($tablename))) { $res = $this->_update($record, $keys, $this->tablename($tablename), $secure); } else { $res = $this->_insert($record, $this->tablename($tablename), $secure); } if (PEAR::isError($res)) { if (Dataface_Error::isDuplicateEntry($res)) { /* * Duplicate entries we will propogate up so that the application can decide what to do. */ //$record->vetoSecurity = $oldVeto; return $res; } $res->addUserInfo(df_translate('scripts.Dataface.IO.write.ERROR_SAVING', "Error while saving record of table '" . $this->_table->tablename . "' in Dataface_IO::write() on line " . __LINE__ . " of file " . __FILE__, array('tablename' => $this->_table->tablename, 'line' => __LINE__, 'file' => __FILE__))); //$record->vetoSecurity = $oldVeto; return $res; } // Now we take care of the transient relationship fields. // Transient relationship fields aren't actually stored in the record // itself, they are stored as related records. foreach ($record->_table->transientFields() as $tfield) { if (!isset($tfield['relationship'])) { continue; } if (!$record->valueChanged($tfield['name'])) { continue; } $trelationship =& $record->_table->getRelationship($tfield['relationship']); if (!$trelationship or PEAR::isError($trelationship)) { // We couldn't find the specified relationship. //$record->vetoSecurity = $oldVeto; return $trelationship; } $orderCol = $trelationship->getOrderColumn(); if (PEAR::isError($orderCol)) { $orderCol = null; } $tval = $record->getValue($tfield['name']); if ($tfield['widget']['type'] == 'grid') { //echo "here";exit; $tval_existing = array(); $tval_new = array(); $tval_new_existing = array(); $torder = 0; foreach ($tval as $trow) { $trow['__order__'] = $torder++; if (isset($trow['__id__']) and preg_match('/^new:/', $trow['__id__'])) { $tval_new_existing[] = $trow; } else { if (isset($trow['__id__']) and $trow['__id__'] != 'new') { $tval_existing[$trow['__id__']] = $trow; } else { if (isset($trow['__id__']) and $trow['__id__'] == 'new') { $tval_new[] = $trow; } } } } // The transient field was loaded so we can go about saving the // changes/ $trecords =& $record->getRelatedRecordObjects($tfield['relationship'], 'all'); if (!is_array($trecords) or PEAR::isError($trecords)) { error_log('Failed to get related records for record ' . $record->getId() . ' in its relationship ' . $tfield['relationship']); unset($tval); unset($orderCol); unset($tval_new); unset($torder); unset($trelationship); unset($tval_existing); continue; } // Update the existing records in the relationship. // We use the __id__ parameter in each row for this. //echo "About to save related records"; foreach ($trecords as $trec) { $tid = $trec->getId(); if (isset($tval_existing[$tid])) { $tmp = new Dataface_RelatedRecord($trec->_record, $tfield['relationship'], $trec->getValues()); $tmp->setValues($tval_existing[$tid]); $changed = false; foreach ($tval_existing[$tid] as $k1 => $v1) { if ($tmp->isDirty($k1)) { $changed = true; break; } } if ($changed) { $trec->setValues($tval_existing[$tid]); if ($orderCol) { $trec->setValue($orderCol, $tval_existing[$tid]['__order__']); } //echo "Saving ";print_r($trec->vals()); $res_t = $trec->save($this->lang, $secure); if (PEAR::isError($res_t)) { return $res_t; 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()); } } else { if ($orderCol and $record->checkPermission('reorder_related_records', array('relationship' => $tfield['relationship']))) { $trec->setValue($orderCol, $tval_existing[$tid]['__order__']); $res_t = $trec->save($this->lang, false); // we don't need this to be secure if (PEAR::isError($res_t)) { return $res_t; 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()); } } } unset($tmp); } else { } unset($trec); unset($tid); unset($res_t); } //exit; // Now add new records (specified by __id__ field being 'new' foreach ($tval_new as $tval_to_add) { $temp_rrecord = new Dataface_RelatedRecord($record, $tfield['relationship'], array()); $temp_rrecord->setValues($tval_to_add); if ($orderCol) { $temp_rrecord->setValue($orderCol, $tval_to_add['__order__']); } $res_t = $this->addRelatedRecord($temp_rrecord, $secure); if (PEAR::isError($res_t)) { 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()); } unset($temp_rrecord); unset($res_t); } // Now add new existing records (specified by __id__ field being 'new:<recordid>' foreach ($tval_new_existing as $tval_to_add) { $tid = preg_replace('/^new:/', '', $tval_to_add['__id__']); $temp_record = df_get_record_by_id($tid); if (PEAR::isError($temp_record)) { return $temp_record; } if (!$temp_record) { return PEAR::raiseError("Failed to load existing record with ID {$tid}."); } $temp_rrecord = new Dataface_RelatedRecord($record, $tfield['relationship'], $temp_record->vals()); $temp_rrecord->setValues($tval_to_add); if ($orderCol) { $temp_rrecord->setValue($orderCol, $tval_to_add['__order__']); } $res_t = $this->addExistingRelatedRecord($temp_rrecord, $secure); if (PEAR::isError($res_t)) { 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()); } unset($temp_rrecord); unset($res_t); } // Now we delete the records that were deleted // we use the __deleted__ field. if (isset($tval['__deleted__']) and is_array($tval['__deleted__']) and $trelationship->supportsRemove()) { $tdelete_record = $trelationship->isOneToMany(); foreach ($tval['__deleted__'] as $del_id) { if ($del_id == 'new') { continue; } $drec = Dataface_IO::getByID($del_id); if (PEAR::isError($drec) or !$drec) { unset($drec); continue; } $this->removeRelatedRecord($drec, $tdelete_record, $secure); unset($drec); } } unset($trecords); } else { if ($tfield['widget']['type'] == 'checkbox') { // Load existing records in the relationship $texisting =& $record->getRelatedRecordObjects($tfield['relationship'], 'all'); if (!is_array($texisting) or PEAR::isError($texisting)) { error_log('Failed to get related records for record ' . $record->getId() . ' in its relationship ' . $tfield['relationship']); unset($tval); unset($orderCol); unset($tval_new); unset($torder); unset($trelationship); unset($tval_existing); continue; } $texistingIds = array(); foreach ($texisting as $terec) { $texistingIds[] = $terec->getId(); } // Load currently checked records $tchecked = array(); $tcheckedRecords = array(); $tcheckedIds = array(); $tcheckedId2ValsMap = array(); foreach ($tval as $trkey => $trval) { // $trval is in the form key1=val1&size=key2=val2 parse_str($trval, $trquery); $trRecord = new Dataface_RelatedRecord($record, $tfield['relationship'], $trquery); $trRecords[] =& $trRecord; $tcheckedIds[] = $tid = $trRecord->getId(); $checkedId2ValsMap[$tid] = $trquery; unset($trRecord); unset($trquery); } // Now we have existing ids in $texistingIds // and checked ids in $tcheckedIds // See which records we need to have removed $tremoves = array_diff($texistingIds, $tcheckedIds); $tadds = array_diff($tcheckedIds, $texistingIds); foreach ($tremoves as $tid) { $trec = df_get_record_by_id($tid); $res = $this->removeRelatedRecord($trec, false, $secure); if (PEAR::isError($res)) { return $res; } unset($trec); } foreach ($tadds as $tid) { $trecvals = $checkedId2ValsMap[$tid]; $trec = new Dataface_RelatedRecord($record, $tfield['relationship'], $trecvals); $res = $this->addExistingRelatedRecord($trec, $secure); if (PEAR::isError($res)) { return $res; } unset($trec, $trecvals); } unset($tadds); unset($tremoves); unset($tcheckedIds, $tcheckedId2ValsMap); unset($tcheckedRecords); unset($tchecked); unset($texistingIds); unset($texisting); } } unset($tval); unset($trelationship); } if ($this->fireTriggers) { $res2 = $this->fireAfterSave($record); if (PEAR::isError($res2)) { //$record->vetoSecurity = $oldVeto; return $res2; } } if (isset($app->_conf['history']) and @$app->_conf['history']['enabled'] || !isset($app->_conf['history']['enabled'])) { // History is enabled ... let's save this record in our history. import('Dataface/HistoryTool.php'); $historyTool = new Dataface_HistoryTool(); $historyTool->logRecord($record, $this->getHistoryComments($record), $this->lang); } if (isset($app->_conf['_index']) and @$app->_conf['_index'][$record->table()->tablename]) { // If indexing is enabled, we index the record so that it is // searchable by natural language searching. // The Dataface_Index class takes care of whether or not this // record should be indexed. import('Dataface/Index.php'); $index = new Dataface_Index(); $index->indexRecord($record); } // It seems to me that we should be setting a new snapshot at this point. //$record->clearSnapshot(); $record->setSnapshot(); self::touchTable($this->_table->tablename); //$record->vetoSecurity = $oldVeto; return $res; }
function test_restore_to_date() { $app =& Dataface_Application::getInstance(); $record = df_get_record('HistoryToolTest', array('name' => 'Johnny')); $this->assertEquals('john.gif', $record->val('container_field')); $record->setValue('container_field', 'john2.gif'); $record->save(); $ht = new Dataface_HistoryTool(); $hid = $ht->logRecord($record); $history1 = $ht->getRecordById('HistoryToolTest', $hid); $this->assertEquals(array('name' => 'Johnny', 'container_field' => 'john2.gif'), $history1->strvals(array('name', 'container_field'))); $record->setValue('container_field', 'john3.gif'); $record->save(); $hid2 = $ht->logRecord($record); $history2 = $ht->getRecordById('HistoryToolTest', $hid2); $this->assertEquals(array('name' => 'Johnny', 'container_field' => 'john3.gif'), $history2->strvals(array('name', 'container_field'))); $record2 = df_get_record('HistoryToolTest', array('name' => 'Johnny')); $this->assertEquals($record2->strvals(array('name', 'container_field')), $history2->strvals(array('name', 'container_field'))); $sql = array(); $sql[] = "update `HistoryToolTest__history` set `history__modified` = '2004-01-02' where `history__id` = '{$hid}'"; foreach ($sql as $q) { $res = xf_db_query($q, $app->db()); if (!$res) { trigger_error(xf_db_error($app->db()), E_USER_ERROR); } } $ht->restoreToDate($record, '2004-02-02'); $record3 = df_get_record('HistoryToolTest', array('name' => 'Johnny')); $this->assertEquals(array('name' => 'Johnny', 'container_field' => 'john2.gif'), $record3->strvals(array('name', 'container_field'))); }