2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* This is a special kind of DataObjectSet used to represent the items linked to in a 1-many or many-many
|
|
|
|
* join. It provides add and remove methods that will update the database.
|
2008-02-25 03:10:37 +01:00
|
|
|
* @package sapphire
|
|
|
|
* @subpackage model
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
class ComponentSet extends DataObjectSet {
|
|
|
|
/**
|
|
|
|
* Type of relationship (eg '1-1', '1-many').
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $type;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Object that owns this set.
|
|
|
|
* @var DataObject
|
|
|
|
*/
|
|
|
|
protected $ownerObj;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class of object that owns this set.
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $ownerClass;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Table that holds this relationship.
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $tableName;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class of child side of the relationship.
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $childClass;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Field to join on.
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $joinField;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the ComponentSet specific information.
|
|
|
|
* @param string $type Type of relationship (eg '1-1', '1-many').
|
|
|
|
* @param DataObject $ownerObj Object that owns this set.
|
|
|
|
* @param string $ownerClass Class of object that owns this set.
|
|
|
|
* @param string $tableName Table that holds this relationship.
|
|
|
|
* @param string $childClass Class of child side of the relationship.
|
|
|
|
* @param string $joinField Field to join on.
|
|
|
|
*/
|
|
|
|
function setComponentInfo($type, $ownerObj, $ownerClass, $tableName, $childClass, $joinField = null) {
|
|
|
|
$this->type = $type;
|
|
|
|
$this->ownerObj = $ownerObj;
|
|
|
|
$this->ownerClass = $ownerClass ? $ownerClass : $ownerObj->class;
|
|
|
|
$this->tableName = $tableName;
|
|
|
|
$this->childClass = $childClass;
|
|
|
|
$this->joinField = $joinField;
|
|
|
|
}
|
|
|
|
|
2009-02-11 01:10:37 +01:00
|
|
|
/**
|
|
|
|
* 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();
|
2009-02-11 01:18:00 +01:00
|
|
|
|
|
|
|
if(!isset($componentName)) {
|
|
|
|
user_error('ComponentSet::getExtraData() passed a NULL component name', E_USER_ERROR);
|
|
|
|
}
|
2009-02-11 01:14:42 +01:00
|
|
|
|
|
|
|
if(!is_numeric($childID)) {
|
|
|
|
user_error('ComponentSet::getExtraData() passed a non-numeric child ID', E_USER_ERROR);
|
|
|
|
}
|
2009-02-11 01:10:37 +01:00
|
|
|
|
|
|
|
// @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;
|
|
|
|
}
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* 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)) {
|
2009-02-10 07:21:08 +01:00
|
|
|
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);
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
} else {
|
|
|
|
if(!$this->childClass) {
|
|
|
|
user_error("ComponentSet::add() \$this->childClass not set", E_USER_ERROR);
|
|
|
|
}
|
|
|
|
|
2009-02-11 01:10:37 +01:00
|
|
|
$item = DataObject::get_by_id($this->childClass, $item);
|
2008-11-24 20:28:46 +01:00
|
|
|
if(!$item) return;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2008-03-10 22:48:13 +01:00
|
|
|
if (!$child) $child = $item;
|
2007-07-19 12:40:28 +02:00
|
|
|
$joinField = $this->joinField;
|
|
|
|
$child->$joinField = $this->ownerObj->ID;
|
|
|
|
$child->write();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
$parentField = $this->ownerClass . 'ID';
|
|
|
|
$childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
|
|
|
|
|
2008-11-24 00:28:16 +01:00
|
|
|
DB::query( "DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$item->ID}" );
|
2007-07-19 12:40:28 +02:00
|
|
|
|
2008-11-24 00:28:16 +01:00
|
|
|
$extraKeys = $extraValues = '';
|
2007-07-19 12:40:28 +02:00
|
|
|
if($extraFields) foreach($extraFields as $k => $v) {
|
2008-11-24 00:28:16 +01:00
|
|
|
$extraKeys .= ", \"$k\"";
|
2009-04-06 01:24:56 +02:00
|
|
|
$extraValues .= ", '" . DB::getConn()->addslashes($v) . "'";
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2008-11-24 00:28:16 +01:00
|
|
|
DB::query("INSERT INTO \"$this->tableName\" (\"$parentField\",\"$childField\" $extraKeys) VALUES ({$this->ownerObj->ID}, {$item->ID} $extraValues)");
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2007-09-15 01:38:26 +02:00
|
|
|
$has = array();
|
2007-07-19 12:40:28 +02:00
|
|
|
// Index current data
|
2008-08-11 05:03:52 +02:00
|
|
|
if($this->items) foreach($this->items as $item) {
|
|
|
|
$has[$item->ID] = true;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Keep track of items to delete
|
2008-08-11 05:03:52 +02:00
|
|
|
$itemsToDelete = $has;
|
2007-07-19 12:40:28 +02:00
|
|
|
|
2008-08-11 05:03:52 +02:00
|
|
|
// 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);
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2008-08-11 05:03:52 +02:00
|
|
|
|
|
|
|
// delete items not in the list
|
|
|
|
$removeList = array();
|
|
|
|
foreach($itemsToDelete as $id => $actuallyDelete) {
|
|
|
|
if($actuallyDelete) $removeList[] = $id;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2008-08-11 05:03:52 +02:00
|
|
|
$this->removeMany($removeList);
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove an item from this set.
|
2008-08-11 05:03:52 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @param DataObject|string|int $item Item to remove, either as a DataObject or as the ID.
|
|
|
|
*/
|
|
|
|
function remove($item) {
|
|
|
|
if(is_object($item)) {
|
2008-02-25 03:10:37 +01:00
|
|
|
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);
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
} 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');
|
2008-11-24 00:28:16 +01:00
|
|
|
DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$item->ID}");
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2008-08-11 05:03:52 +02:00
|
|
|
* @param array $itemList The items to remove, as a numerical array with IDs or as a DataObjectSet
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
function removeMany($itemList) {
|
2008-08-11 05:03:52 +02:00
|
|
|
if(!count($itemList)) return false;
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if($this->type == '1-to-many') {
|
2008-08-11 05:03:52 +02:00
|
|
|
foreach($itemList as $item) $this->remove($item);
|
2007-07-19 12:40:28 +02:00
|
|
|
} else {
|
2008-08-11 05:03:52 +02:00
|
|
|
$itemCSV = implode(", ", $itemList);
|
|
|
|
$parentField = $this->ownerClass . 'ID';
|
|
|
|
$childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
|
2008-11-24 00:28:16 +01:00
|
|
|
DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" IN ($itemCSV)");
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove all items that match the SQL filter.
|
2008-10-16 14:02:41 +02:00
|
|
|
* @deprecated 2.3 Not flexible enough, use custom code
|
2007-07-19 12:40:28 +02:00
|
|
|
* @param string $filter Filter to be inserted into the WHERE clause
|
|
|
|
*/
|
|
|
|
function removeByFilter($filter) {
|
|
|
|
$parentField = $this->ownerClass . 'ID';
|
2008-11-24 00:28:16 +01:00
|
|
|
DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND $filter");
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove all items in this set.
|
|
|
|
*/
|
|
|
|
function removeAll() {
|
|
|
|
if(!empty($this->tableName)) {
|
|
|
|
$parentField = $this->ownerClass . 'ID';
|
2008-11-24 00:28:16 +01:00
|
|
|
DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID}");
|
2007-07-19 12:40:28 +02:00
|
|
|
} 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.
|
2008-08-09 06:53:34 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
function debug() {
|
|
|
|
$size = count($this->items);
|
|
|
|
|
|
|
|
$output = <<<OUT
|
|
|
|
<h3>ComponentSet</h3>
|
|
|
|
<ul>
|
|
|
|
<li>Type: {$this->type}</li>
|
|
|
|
<li>Size: $size</li>
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
OUT;
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
?>
|