type = $type; $this->ownerObj = $ownerObj; $this->ownerClass = $ownerClass ? $ownerClass : $ownerObj->class; $this->tableName = $tableName; $this->childClass = $childClass; $this->joinField = $joinField; } /** * Find the extra field data for a single row of the relationship * join table, given the known child ID. * * @param string $componentName The name of the component * @param int $childID The ID of the child for the relationship * @return array Map of fieldName => fieldValue */ function getExtraData($componentName, $childID) { $ownerObj = $this->ownerObj; $parentField = $this->ownerClass . 'ID'; $childField = ($this->childClass == $this->ownerClass) ? 'ChildID' : ($this->childClass . 'ID'); $result = array(); if(!$componentName) return false; // @todo Optimize into a single query instead of one per extra field $extraFields = $ownerObj->many_many_extraFields($componentName); if($extraFields) { foreach($extraFields as $fieldName => $dbFieldSpec) { $query = DB::query("SELECT \"$fieldName\" FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$childID}"); $value = $query->value(); $result[$fieldName] = $value; } } return $result; } /** * Get an array of all the IDs in this component set, where the keys are the same as the * values. * @return array */ function getIdList() { $list = array(); foreach($this->items as $item) { $list[$item->ID] = $item->ID; } return $list; } /** * Add an item to this set. * @param DataObject|int|string $item Item to add, either as a DataObject or as the ID. * @param array $extraFields A map of extra fields to add. */ function add($item, $extraFields = null) { if(!isset($item)) { user_error("ComponentSet::add() Not passed an object or ID", E_USER_ERROR); } if(is_object($item)) { if(!is_a($item, $this->childClass)) { user_error("ComponentSet::add() Tried to add an '{$item->class}' object, but a '{$this->childClass}' object expected", E_USER_ERROR); } } else { if(!$this->childClass) { user_error("ComponentSet::add() \$this->childClass not set", E_USER_ERROR); } $item = DataObject::get_by_id($this->childClass, $item); if(!$item) return; } // If we've already got a database object, then update the database if($this->ownerObj->ID && is_numeric($this->ownerObj->ID)) { $this->loadChildIntoDatabase($item, $extraFields); } // In either case, add something to $this->items $this->items[] = $item; } /** * Method to save many-many join data into the database for the given $item. * Used by add() and write(). * @param DataObject|string|int The item to save, as either a DataObject or the ID. * @param array $extraFields Map of extra fields. */ protected function loadChildIntoDatabase($item, $extraFields = null) { if($this->type == '1-to-many') { $child = DataObject::get_by_id($this->childClass,$item->ID); if (!$child) $child = $item; $joinField = $this->joinField; $child->$joinField = $this->ownerObj->ID; $child->write(); } else { $parentField = $this->ownerClass . 'ID'; $childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID'); DB::query( "DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$item->ID}" ); $extraKeys = $extraValues = ''; if($extraFields) foreach($extraFields as $k => $v) { $extraKeys .= ", \"$k\""; $extraValues .= ", '" . addslashes($v) . "'"; } DB::query("INSERT INTO \"$this->tableName\" (\"$parentField\",\"$childField\" $extraKeys) VALUES ({$this->ownerObj->ID}, {$item->ID} $extraValues)"); } } /** * Add a number of items to the component set. * @param array $items Items to add, as either DataObjects or IDs. */ function addMany($items) { foreach($items as $item) { $this->add($item); } } /** * Sets the ComponentSet to be the given ID list. * Records will be added and deleted as appropriate. * @param array $idList List of IDs. */ function setByIDList($idList) { $has = array(); // Index current data if($this->items) foreach($this->items as $item) { $has[$item->ID] = true; } // Keep track of items to delete $itemsToDelete = $has; // add items in the list // $id is the database ID of the record if($idList) foreach($idList as $id) { $itemsToDelete[$id] = false; if($id && !isset($has[$id])) $this->add($id); } // delete items not in the list $removeList = array(); foreach($itemsToDelete as $id => $actuallyDelete) { if($actuallyDelete) $removeList[] = $id; } $this->removeMany($removeList); } /** * Remove an item from this set. * * @param DataObject|string|int $item Item to remove, either as a DataObject or as the ID. */ function remove($item) { if(is_object($item)) { if(!is_a($item, $this->childClass)) { user_error("ComponentSet::remove() Tried to remove an '{$item->class}' object, but a '{$this->childClass}' object expected", E_USER_ERROR); } } else { $item = DataObject::get_by_id($this->childClass, $item); } // Manipulate the database, if it's in there if($this->ownerObj->ID && is_numeric($this->ownerObj->ID)) { if($this->type == '1-to-many') { $child = DataObject::get_by_id($this->childClass,$item->ID); $joinField = $this->joinField; if($child->$joinField == $this->ownerObj->ID) { $child->$joinField = null; $child->write(); } } else { $parentField = $this->ownerClass . 'ID'; $childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID'); DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$item->ID}"); } } // Manipulate the in-memory array of items if($this->items) foreach($this->items as $i => $candidateItem) { if($candidateItem->ID == $item->ID) { unset($this->items[$i]); break; } } } /** * Remove many items from this set. * @param array $itemList The items to remove, as a numerical array with IDs or as a DataObjectSet */ function removeMany($itemList) { if(!count($itemList)) return false; if($this->type == '1-to-many') { foreach($itemList as $item) $this->remove($item); } else { $itemCSV = implode(", ", $itemList); $parentField = $this->ownerClass . 'ID'; $childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID'); DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" IN ($itemCSV)"); } } /** * Remove all items that match the SQL filter. * @deprecated 2.3 Not flexible enough, use custom code * @param string $filter Filter to be inserted into the WHERE clause */ function removeByFilter($filter) { $parentField = $this->ownerClass . 'ID'; DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND $filter"); } /** * Remove all items in this set. */ function removeAll() { if(!empty($this->tableName)) { $parentField = $this->ownerClass . 'ID'; DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID}"); } else { foreach($this->items as $item) { $this->remove($item); } } } /** * Write this set to the database. * Called by DataObject::write(). * @param boolean $firstWrite This should be set to true if it the first time the set is being written. */ function write($firstWrite = false) { if($firstWrite) { foreach($this->items as $item) { $this->loadChildIntoDatabase($item); } } } /** * Returns information about this set in HTML format for debugging. * * @return string */ function debug() { $size = count($this->items); $output = <<ComponentSet OUT; return $output; } } ?>