fieldtype of extra fields on the join table. * * @example new ManyManyList('Group','Group_Members', 'GroupID', 'MemberID'); */ function __construct($dataClass, $joinTable, $localKey, $foreignKey, $extraFields = array()) { parent::__construct($dataClass); $this->joinTable = $joinTable; $this->localKey = $localKey; $this->foreignKey = $foreignKey; $this->extraFields = $extraFields; $baseClass = ClassInfo::baseDataClass($dataClass); // Join to the many-many join table $this->dataQuery->innerJoin($joinTable, "\"$joinTable\".\"$this->localKey\" = \"$baseClass\".\"ID\""); // Query the extra fields from the join table if($extraFields) $this->dataQuery->selectFromTable($joinTable, array_keys($extraFields)); } /** * Return a filter expression for the foreign ID. */ protected function foreignIDFilter() { // Apply relation filter if(is_array($this->foreignID)) { return "\"$this->joinTable\".\"$this->foreignKey\" IN ('" . implode("', '", array_map('Convert::raw2sql', $this->foreignID)) . "')"; } else if($this->foreignID !== null){ return "\"$this->joinTable\".\"$this->foreignKey\" = '" . Convert::raw2sql($this->foreignID) . "'"; } } /** * Add an item to this many_many relationship * Does so by adding an entry to the joinTable. * @param $extraFields A map of additional columns to insert into the joinTable */ function add($item, $extraFields = null) { if(is_numeric($item)) $itemID = $item; else if($item instanceof $this->dataClass) $itemID = $item->ID; else throw new InvalidArgumentException("ManyManyList::add() expecting a $this->dataClass object, or ID value", E_USER_ERROR); // Validate foreignID if(!$this->foreignID) { throw new Exception("ManyManyList::add() can't be called until a foreign ID is set", E_USER_WARNING); } // Delete old entries, to prevent duplication $this->removeById($itemID); // Insert new entry/entries foreach((array)$this->foreignID as $foreignID) { $manipulation = array(); $manipulation[$this->joinTable]['command'] = 'insert'; if($extraFields) foreach($extraFields as $k => $v) { $manipulation[$this->joinTable]['fields'][$k] = "'" . Convert::raw2sql($v) . "'"; } $manipulation[$this->joinTable]['fields'][$this->localKey] = $itemID; $manipulation[$this->joinTable]['fields'][$this->foreignKey] = $foreignID; DB::manipulate($manipulation); } } /** * Remove the given item from this list. * Note that for a ManyManyList, the item is never actually deleted, only the join table is affected * @param $itemID The ID of the item to remove. */ function remove($item) { if(!($item instanceof $this->dataClass)) throw new InvalidArgumentException("ManyManyList::remove() expecting a $this->dataClass object"); return $this->removeByID($item->ID); } /** * Remove the given item from this list. * Note that for a ManyManyList, the item is never actually deleted, only the join table is affected * @param $itemID The item it */ function removeByID($itemID) { if(!is_numeric($itemID)) throw new InvalidArgumentException("ManyManyList::removeById() expecting an ID"); $query = new SQLQuery("*", array("\"$this->joinTable\"")); $query->setDelete(true); if($filter = $this->foreignIDFilter()) { $query->setWhere($filter); } else { user_error("Can't call ManyManyList::remove() until a foreign ID is set", E_USER_WARNING); } $query->addWhere("\"$this->localKey\" = {$itemID}"); $query->execute(); } /** * Remove all items from this many-many join. To remove a subset of items, filter it first. */ function removeAll() { $query = $this->dataQuery()->query(); $query->setDelete(true); $query->setSelect(array('*')); $query->setFrom("\"$this->joinTable\""); $query->execute(); } /** * Find the extra field data for a single row of the relationship * join table, given the known child ID. * * @todo Add tests for this / refactor it / something * * @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(!isset($componentName)) { user_error('ComponentSet::getExtraData() passed a NULL component name', E_USER_ERROR); } if(!is_numeric($childID)) { user_error('ComponentSet::getExtraData() passed a non-numeric child ID', E_USER_ERROR); } // @todo Optimize into a single query instead of one per extra field if($this->extraFields) { foreach($this->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; } }