예제 #1
0
 /**
  * Delete record represented by this object. Uses the Datamodel object
  * to generate possible dependencies and relationships.
  *
  * @param bool $pb_delete_related delete stuff related to the record? pass non-zero value if you want to.
  * @param array $pa_options Options for delete process. Options are:
  *		hard = if true records which can support "soft" delete are really deleted rather than simply being marked as deleted
  *		queueIndexing =
  * @param array $pa_fields instead of deleting the record represented by this object instance you can
  * pass an array of field => value assignments which is used in a SQL-DELETE-WHERE clause.
  * @param array $pa_table_list this is your possibility to pass an array of table name => true assignments
  * to specify which tables to omit when deleting related stuff
  */
 public function delete($pb_delete_related = false, $pa_options = null, $pa_fields = null, $pa_table_list = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     $pb_queue_indexing = caGetOption('queueIndexing', $pa_options, false);
     $vn_id = $this->getPrimaryKey();
     if ($this->hasField('deleted') && (!isset($pa_options['hard']) || !$pa_options['hard'])) {
         if ($this->getMode() == ACCESS_WRITE) {
             $vb_we_set_transaction = false;
             if (!$this->inTransaction()) {
                 $o_t = new Transaction($this->getDb());
                 $this->setTransaction($o_t);
                 $vb_we_set_transaction = true;
             }
             $this->setMode(ACCESS_WRITE);
             $this->set('deleted', 1);
             if ($vn_rc = $this->update(array('force' => true))) {
                 if (!defined('__CA_DONT_DO_SEARCH_INDEXING__') || !__CA_DONT_DO_SEARCH_INDEXING__) {
                     $o_indexer = $this->getSearchIndexer();
                     $o_indexer->startRowUnIndexing($this->tableNum(), $vn_id);
                     $o_indexer->commitRowUnIndexing($this->tableNum(), $vn_id, array('queueIndexing' => $pb_queue_indexing));
                 }
             }
             $this->logChange("D");
             if ($vb_we_set_transaction) {
                 $this->removeTransaction(true);
             }
             return $vn_rc;
         } else {
             $this->postError(400, _t("Mode was %1; must be write", $this->getMode(true)), "BaseModel->delete()");
             return false;
         }
     }
     $this->clearErrors();
     if (!$this->getPrimaryKey() && !is_array($pa_fields)) {
         # is there a record loaded?
         $this->postError(770, _t("No record loaded"), "BaseModel->delete()");
         return false;
     }
     if (!is_array($pa_table_list)) {
         $pa_table_list = array();
     }
     $pa_table_list[$this->tableName()] = true;
     if ($this->getMode() == ACCESS_WRITE) {
         $vb_we_set_transaction = false;
         if (!$this->inTransaction()) {
             $o_t = new Transaction($this->getDb());
             $this->setTransaction($o_t);
             $vb_we_set_transaction = true;
         }
         $o_db = $this->getDb();
         if (is_array($pa_fields)) {
             $vs_sql = "DELETE FROM " . $this->tableName() . " WHERE ";
             $vs_wheres = "";
             while (list($vs_field, $vm_val) = each($pa_fields)) {
                 $vn_datatype = $this->_getFieldTypeType($vs_field);
                 switch ($vn_datatype) {
                     # -----------------------------
                     case 0:
                         # number
                         if ($vm_val == "") {
                             $vm_val = 0;
                         }
                         break;
                         # -----------------------------
                     # -----------------------------
                     case 1:
                         # string
                         $vm_val = $this->quote($vm_val);
                         break;
                         # -----------------------------
                 }
                 if ($vs_wheres) {
                     $vs_wheres .= " AND ";
                 }
                 $vs_wheres .= "({$vs_field} = {$vm_val})";
             }
             $vs_sql .= $vs_wheres;
         } else {
             $vs_sql = "DELETE FROM " . $this->tableName() . " WHERE " . $this->primaryKey() . " = " . $this->getPrimaryKey(1);
         }
         if ($this->isHierarchical()) {
             // TODO: implement delete of children records
             $vs_parent_id_fld = $this->getProperty('HIERARCHY_PARENT_ID_FLD');
             $qr_res = $o_db->query("\n\t\t\t\t\tSELECT " . $this->primaryKey() . "\n\t\t\t\t\tFROM " . $this->tableName() . "\n\t\t\t\t\tWHERE\n\t\t\t\t\t\t{$vs_parent_id_fld} = ?\n\t\t\t\t", $this->getPrimaryKey());
             if ($qr_res->nextRow()) {
                 $this->postError(780, _t("Can't delete item because it has sub-records"), "BaseModel->delete()");
                 if ($vb_we_set_transaction) {
                     $this->removeTransaction(false);
                 }
                 return false;
             }
         }
         #
         # --- begin delete search index entries
         #
         if (!defined('__CA_DONT_DO_SEARCH_INDEXING__')) {
             $o_indexer = $this->getSearchIndexer();
             $o_indexer->startRowUnIndexing($this->tableNum(), $vn_id);
             // records dependencies but does not actually delete indexing
         }
         # --- Check ->many and many<->many relations
         $va_one_to_many_relations = $this->_DATAMODEL->getOneToManyRelations($this->tableName());
         #
         # Note: cascading delete code is very slow when used
         # on a record with a large number of related records as
         # each record in check individually for cascading deletes...
         # it is possible to make this *much* faster by crafting clever-er queries
         #
         if (is_array($va_one_to_many_relations)) {
             foreach ($va_one_to_many_relations as $vs_many_table => $va_info) {
                 foreach ($va_info as $va_relationship) {
                     if (isset($pa_table_list[$vs_many_table . '/' . $va_relationship["many_table_field"]]) && $pa_table_list[$vs_many_table . '/' . $va_relationship["many_table_field"]]) {
                         continue;
                     }
                     # do any records exist?
                     $t_related = $this->_DATAMODEL->getTableInstance($vs_many_table);
                     $o_trans = $this->getTransaction();
                     $t_related->setTransaction($o_trans);
                     $qr_record_check = $o_db->query("\n\t\t\t\t\t\t\tSELECT " . $t_related->primaryKey() . "\n\t\t\t\t\t\t\tFROM " . $vs_many_table . "\n\t\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\t\t(" . $va_relationship["many_table_field"] . " = " . $this->getPrimaryKey(1) . ")\n\t\t\t\t\t\t");
                     $pa_table_list[$vs_many_table . '/' . $va_relationship["many_table_field"]] = true;
                     //print "FOR ".$vs_many_table.'/'.$va_relationship["many_table_field"].":".$qr_record_check->numRows()."<br>\n";
                     if ($qr_record_check->numRows() > 0) {
                         if ($pb_delete_related) {
                             while ($qr_record_check->nextRow()) {
                                 if ($t_related->load($qr_record_check->get($t_related->primaryKey()))) {
                                     $t_related->setMode(ACCESS_WRITE);
                                     $t_related->delete($pb_delete_related, array_merge($pa_options, array('hard' => true)), null, $pa_table_list);
                                     if ($t_related->numErrors()) {
                                         $this->postError(790, _t("Can't delete item because items related to it have sub-records (%1)", $vs_many_table), "BaseModel->delete()");
                                         if ($vb_we_set_transaction) {
                                             $this->removeTransaction(false);
                                         }
                                         return false;
                                     }
                                 }
                             }
                         } else {
                             $this->postError(780, _t("Can't delete item because it is in use (%1)", $vs_many_table), "BaseModel->delete()");
                             if ($vb_we_set_transaction) {
                                 $this->removeTransaction(false);
                             }
                             return false;
                         }
                     }
                 }
             }
         }
         # --- do deletion
         if ($this->debug) {
             echo $vs_sql;
         }
         $o_db->query($vs_sql);
         if ($o_db->numErrors() > 0) {
             $this->errors = $o_db->errors();
             if ($vb_we_set_transaction) {
                 $this->removeTransaction(false);
             }
             return false;
         }
         #
         # --- complete delete of search index entries
         #
         if (!defined('__CA_DONT_DO_SEARCH_INDEXING__')) {
             $o_indexer->commitRowUnIndexing($this->tableNum(), $vn_id, array('queueIndexing' => $pb_queue_indexing));
         }
         # cancel and pending queued tasks against this record
         $tq = new TaskQueue();
         $tq->cancelPendingTasksForRow(join("/", array($this->tableName(), $vn_id)));
         $this->_FILES_CLEAR = array();
         # --- delete media and file field files
         foreach ($this->FIELDS as $f => $attr) {
             switch ($attr['FIELD_TYPE']) {
                 case FT_MEDIA:
                     $versions = $this->getMediaVersions($f);
                     foreach ($versions as $v) {
                         $this->_removeMedia($f, $v);
                     }
                     $this->_removeMedia($f, '_undo_');
                     break;
                 case FT_FILE:
                     @unlink($this->getFilePath($f));
                     #--- delete conversions
                     #
                     foreach ($this->getFileConversions($f) as $vs_format => $va_file_conversion) {
                         @unlink($this->getFileConversionPath($f, $vs_format));
                     }
                     break;
             }
         }
         if ($o_db->numErrors() == 0) {
             //if ($vb_is_hierarchical = $this->isHierarchical()) {
             //}
             # clear object
             $this->logChange("D");
             $this->clear();
         } else {
             if ($vb_we_set_transaction) {
                 $this->removeTransaction(false);
             }
             return false;
         }
         if ($vb_we_set_transaction) {
             $this->removeTransaction(true);
         }
         return true;
     } else {
         $this->postError(400, _t("Mode was %1; must be write", $this->getMode(true)), "BaseModel->delete()");
         return false;
     }
 }
예제 #2
0
 /**
  * Delete record represented by this object. Uses the Datamodel object
  * to generate possible dependencies and relationships.
  *
  * @param bool $pb_delete_related delete stuff related to the record? pass non-zero value if you want to.
  * @param array $pa_options Options for delete process. Options are:
  *		hard = if true records which can support "soft" delete are really deleted rather than simply being marked as deleted
  * @param array $pa_fields instead of deleting the record represented by this object instance you can
  * pass an array of field => value assignments which is used in a SQL-DELETE-WHERE clause.
  * @param array $pa_table_list this is your possibility to pass an array of table name => true assignments
  * to specify which tables to omit when deleting related stuff
  */
 public function delete($pb_delete_related = false, $pa_options = null, $pa_fields = null, $pa_table_list = null)
 {
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     $vn_id = $this->getPrimaryKey();
     if ($this->hasField('deleted') && (!isset($pa_options['hard']) || !$pa_options['hard'])) {
         $this->setMode(ACCESS_WRITE);
         $this->set('deleted', 1);
         if ($vn_rc = $this->update(array('force' => true))) {
             if (!defined('__CA_DONT_DO_SEARCH_INDEXING__')) {
                 if (!BaseModel::$search_indexer) {
                     BaseModel::$search_indexer = new SearchIndexer($this->getDb());
                 }
                 BaseModel::$search_indexer->startRowUnIndexing($this->tableNum(), $vn_id);
                 BaseModel::$search_indexer->commitRowUnIndexing($this->tableNum(), $vn_id);
             }
         }
         $this->logChange("D");
         return $vn_rc;
     }
     $this->clearErrors();
     if (!$this->getPrimaryKey() && !is_array($pa_fields)) {
         # is there a record loaded?
         $this->postError(770, _t("No record loaded"), "BaseModel->delete()");
         return false;
     }
     if (!is_array($pa_table_list)) {
         $pa_table_list = array();
     }
     $pa_table_list[$this->tableName()] = true;
     if ($this->getMode() == ACCESS_WRITE) {
         $vb_we_set_transaction = false;
         if (!$this->inTransaction()) {
             $o_t = new Transaction($this->getDb());
             $this->setTransaction($o_t);
             $vb_we_set_transaction = true;
         }
         $o_db = $this->getDb();
         if (is_array($pa_fields)) {
             $vs_sql = "DELETE FROM " . $this->tableName() . " WHERE ";
             $vs_wheres = "";
             while (list($vs_field, $vm_val) = each($pa_fields)) {
                 $vn_datatype = $this->_getFieldTypeType($vs_field);
                 switch ($vn_datatype) {
                     # -----------------------------
                     case 0:
                         # number
                         if ($vm_val == "") {
                             $vm_val = 0;
                         }
                         break;
                         # -----------------------------
                     # -----------------------------
                     case 1:
                         # string
                         $vm_val = $this->quote($vm_val);
                         break;
                         # -----------------------------
                 }
                 if ($vs_wheres) {
                     $vs_wheres .= " AND ";
                 }
                 $vs_wheres .= "({$vs_field} = {$vm_val})";
             }
             $vs_sql .= $vs_wheres;
         } else {
             $vs_sql = "DELETE FROM " . $this->tableName() . " WHERE " . $this->primaryKey() . " = " . $this->getPrimaryKey(1);
         }
         if ($this->isHierarchical()) {
             // TODO: implement delete of children records
             $vs_parent_id_fld = $this->getProperty('HIERARCHY_PARENT_ID_FLD');
             $qr_res = $o_db->query("\n\t\t\t\t\tSELECT " . $this->primaryKey() . "\n\t\t\t\t\tFROM " . $this->tableName() . "\n\t\t\t\t\tWHERE\n\t\t\t\t\t\t{$vs_parent_id_fld} = ?\n\t\t\t\t", $this->getPrimaryKey());
             if ($qr_res->nextRow()) {
                 $this->postError(780, _t("Can't delete item because it has sub-records"), "BaseModel->delete()");
                 if ($vb_we_set_transaction) {
                     $this->removeTransaction(false);
                 }
                 return false;
             }
         }
         #
         # --- delete search index entries
         #
         // TODO: FIX THIS ISSUE!
         // NOTE: we delete the indexing here, before we actually do the
         // SQL delete because the search indexer relies upon the relevant
         // relationships to be intact (ie. exist) in order to properly remove the indexing for them.
         //
         // In particular, the incremental indexing used by the MySQL Fulltext plugin fails to properly
         // update if it can't traverse the relationships it is to remove.
         //
         // By removing the search indexing here we run the risk of corrupting the search index if the SQL
         // delete subsequently fails. Specifically, the indexing for rows that still exist in the database
         // will be removed. Wrapping everything in a MySQL transaction deals with it for MySQL Fulltext, but
         // other non-SQL engines (Lucene, SOLR, etc.) are still affected.
         //
         // At some point we need to come up with something clever to handle this. Most likely it means moving all of the actual
         // analysis to startRowUnindexing() and only executing commands in commitRowUnIndexing(). For now we blithely assume that
         // SQL deletes always succeed. If they don't we can always reindex. Only the indexing is affected, not the underlying data.
         if (!defined('__CA_DONT_DO_SEARCH_INDEXING__')) {
             if (!BaseModel::$search_indexer) {
                 BaseModel::$search_indexer = new SearchIndexer($this->getDb());
             }
             BaseModel::$search_indexer->startRowUnIndexing($this->tableNum(), $vn_id);
             BaseModel::$search_indexer->commitRowUnIndexing($this->tableNum(), $vn_id);
         }
         # --- Check ->many and many<->many relations
         $va_one_to_many_relations = $this->_DATAMODEL->getOneToManyRelations($this->tableName());
         #
         # Note: cascading delete code is very slow when used
         # on a record with a large number of related records as
         # each record in check individually for cascading deletes...
         # it is possible to make this *much* faster by crafting clever-er queries
         #
         if (is_array($va_one_to_many_relations)) {
             foreach ($va_one_to_many_relations as $vs_many_table => $va_info) {
                 foreach ($va_info as $va_relationship) {
                     if (isset($pa_table_list[$vs_many_table . '/' . $va_relationship["many_table_field"]]) && $pa_table_list[$vs_many_table . '/' . $va_relationship["many_table_field"]]) {
                         continue;
                     }
                     # do any records exist?
                     $t_related = $this->_DATAMODEL->getTableInstance($vs_many_table);
                     $o_trans = $this->getTransaction();
                     $t_related->setTransaction($o_trans);
                     $qr_record_check = $o_db->query("\n\t\t\t\t\t\t\tSELECT " . $t_related->primaryKey() . "\n\t\t\t\t\t\t\tFROM " . $vs_many_table . "\n\t\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\t\t(" . $va_relationship["many_table_field"] . " = " . $this->getPrimaryKey(1) . ")\n\t\t\t\t\t\t");
                     $pa_table_list[$vs_many_table . '/' . $va_relationship["many_table_field"]] = true;
                     //print "FOR ".$vs_many_table.'/'.$va_relationship["many_table_field"].":".$qr_record_check->numRows()."<br>\n";
                     if ($qr_record_check->numRows() > 0) {
                         if ($pb_delete_related) {
                             while ($qr_record_check->nextRow()) {
                                 if ($t_related->load($qr_record_check->get($t_related->primaryKey()))) {
                                     $t_related->setMode(ACCESS_WRITE);
                                     $t_related->delete($pb_delete_related, array_merge($pa_options, array('hard' => true)), null, $pa_table_list);
                                     if ($t_related->numErrors()) {
                                         $this->postError(790, _t("Can't delete item because items related to it have sub-records (%1)", $vs_many_table), "BaseModel->delete()");
                                         if ($vb_we_set_transaction) {
                                             $this->removeTransaction(false);
                                         }
                                         return false;
                                     }
                                 }
                             }
                         } else {
                             $this->postError(780, _t("Can't delete item because it is in use (%1)", $vs_many_table), "BaseModel->delete()");
                             if ($vb_we_set_transaction) {
                                 $this->removeTransaction(false);
                             }
                             return false;
                         }
                     }
                 }
             }
         }
         # --- do deletion
         if ($this->debug) {
             echo $vs_sql;
         }
         $o_db->query($vs_sql);
         if ($o_db->numErrors() > 0) {
             $this->errors = $o_db->errors();
             if ($vb_we_set_transaction) {
                 $this->removeTransaction(false);
             }
             return false;
         }
         # cancel and pending queued tasks against this record
         $tq = new TaskQueue();
         $tq->cancelPendingTasksForRow(join("/", array($this->tableName(), $vn_id)));
         $this->_FILES_CLEAR = array();
         # --- delete media and file field files
         foreach ($this->FIELDS as $f => $attr) {
             switch ($attr['FIELD_TYPE']) {
                 case FT_MEDIA:
                     $versions = $this->getMediaVersions($f);
                     foreach ($versions as $v) {
                         $this->_removeMedia($f, $v);
                     }
                     $this->_removeMedia($f, '_undo_');
                     break;
                 case FT_FILE:
                     @unlink($this->getFilePath($f));
                     #--- delete conversions
                     #
                     foreach ($this->getFileConversions($f) as $vs_format => $va_file_conversion) {
                         @unlink($this->getFileConversionPath($f, $vs_format));
                     }
                     break;
             }
         }
         if ($o_db->numErrors() == 0) {
             //if ($vb_is_hierarchical = $this->isHierarchical()) {
             //}
             # clear object
             $this->logChange("D");
             $this->clear();
         } else {
             if ($vb_we_set_transaction) {
                 $this->removeTransaction(false);
             }
             return false;
         }
         if ($vb_we_set_transaction) {
             $this->removeTransaction(true);
         }
         return true;
     } else {
         $this->postError(400, _t("Mode was %1; must be write", $this->getMode(true)), "BaseModel->delete()");
         return false;
     }
 }