mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
889 lines
25 KiB
PHP
889 lines
25 KiB
PHP
<?php
|
|
/**
|
|
* This class represents a set of {@link ViewableData} subclasses (mostly {@link DataObject} or {@link ArrayData}).
|
|
* It is used by the ORM-layer of Silverstripe to return query-results from {@link SQLQuery}.
|
|
* @package sapphire
|
|
* @subpackage model
|
|
*/
|
|
class DataObjectSet extends ViewableData implements SS_List {
|
|
/**
|
|
* The DataObjects in this set.
|
|
* @var array
|
|
*/
|
|
protected $items = array();
|
|
|
|
protected $odd = 0;
|
|
|
|
/**
|
|
* True if the current DataObject is the first in this set.
|
|
* @var boolean
|
|
*/
|
|
protected $first = true;
|
|
|
|
/**
|
|
* True if the current DataObject is the last in this set.
|
|
* @var boolean
|
|
*/
|
|
protected $last = false;
|
|
|
|
/**
|
|
* The current DataObject in this set.
|
|
* @var DataObject
|
|
*/
|
|
protected $current = null;
|
|
|
|
/**
|
|
* Create a new DataObjectSet. If you pass one or more arguments, it will try to convert them into {@link ArrayData} objects.
|
|
* @todo Does NOT automatically convert objects with complex datatypes (e.g. converting arrays within an objects to its own DataObjectSet)
|
|
*
|
|
* @param ViewableData|array|mixed $items Parameters to use in this set, either as an associative array, object with simple properties, or as multiple parameters.
|
|
*/
|
|
public function __construct($items = null) {
|
|
if($items) {
|
|
// if the first parameter is not an array, or we have more than one parameter, collate all parameters to an array
|
|
// otherwise use the passed array
|
|
$itemsArr = (!is_array($items) || count(func_get_args()) > 1) ? func_get_args() : $items;
|
|
|
|
// We now have support for using the key of a data object set
|
|
foreach($itemsArr as $i => $item) {
|
|
if(is_subclass_of($item, 'ViewableData')) {
|
|
$this->items[$i] = $item;
|
|
} elseif(is_object($item) || ArrayLib::is_associative($item)) {
|
|
$this->items[$i] = new ArrayData($item);
|
|
} else {
|
|
user_error(
|
|
"DataObjectSet::__construct: Passed item #{$i} is not an object or associative array,
|
|
can't be properly iterated on in templates",
|
|
E_USER_WARNING
|
|
);
|
|
$this->items[$i] = $item;
|
|
}
|
|
}
|
|
}
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* Necessary for interface ArrayAccess. Returns whether an item with $key exists
|
|
* @param mixed $key
|
|
* @return bool
|
|
*/
|
|
public function offsetExists($key) {
|
|
return isset($this->items[$key]);
|
|
}
|
|
|
|
/**
|
|
* Necessary for interface ArrayAccess. Returns item stored in array with index $key
|
|
* @param mixed $key
|
|
* @return DataObject
|
|
*/
|
|
public function offsetGet($key) {
|
|
return $this->items[$key];
|
|
}
|
|
|
|
/**
|
|
* Necessary for interface ArrayAccess. Set an item with the key in $key
|
|
* @param mixed $key
|
|
* @param mixed $value
|
|
*/
|
|
public function offsetSet($key, $value) {
|
|
$this->items[$key] = $value;
|
|
}
|
|
|
|
/**
|
|
* Necessary for interface ArrayAccess. Unset an item with the key in $key
|
|
* @param mixed $key
|
|
*/
|
|
public function offsetUnset($key) {
|
|
unset($this->items[$key]);
|
|
}
|
|
|
|
/**
|
|
* Destory all of the DataObjects in this set.
|
|
*/
|
|
public function destroy() {
|
|
foreach($this as $item) {
|
|
$item->destroy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes all the items in this set.
|
|
*/
|
|
public function emptyItems() {
|
|
$this->items = array();
|
|
}
|
|
|
|
/**
|
|
* Convert this DataObjectSet to an array of DataObjects.
|
|
* @param string $index Index the array by this field.
|
|
* @return array
|
|
*/
|
|
public function toArray($index = null) {
|
|
$map = array();
|
|
foreach($this as $item) {
|
|
if($index) $map[$item->$index] = $item;
|
|
else $map[] = $item;
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Convert this DataObjectSet to an array of maps.
|
|
* @param string $index Index the array by this field.
|
|
* @return array
|
|
*/
|
|
public function toNestedArray($index = null){
|
|
if(!$index) {
|
|
$index = "ID";
|
|
}
|
|
|
|
$map = array();
|
|
|
|
foreach( $this as $item ) {
|
|
$map[$item->$index] = $item->getAllFields();
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of ID => Title for the items in this set.
|
|
*
|
|
* This is an alias of {@link DataObjectSet->map()}
|
|
*
|
|
* @deprecated 2.5 Please use map() instead
|
|
*
|
|
* @param string $index The field to use as a key for the array
|
|
* @param string $titleField The field (or method) to get values for the map
|
|
* @param string $emptyString Empty option text e.g "(Select one)"
|
|
* @param bool $sort Sort the map alphabetically based on the $titleField value
|
|
* @return array
|
|
*/
|
|
public function toDropDownMap($index = 'ID', $titleField = 'Title', $emptyString = null, $sort = false) {
|
|
return $this->map($index, $titleField, $emptyString, $sort);
|
|
}
|
|
|
|
public function add($item) {
|
|
$this->push($item);
|
|
}
|
|
|
|
/**
|
|
* Add an item to the DataObject Set.
|
|
* @param DataObject $item Item to add.
|
|
* @param string $key Key to index this DataObject by.
|
|
*/
|
|
public function push($item, $key = null) {
|
|
if($key != null) {
|
|
unset($this->items[$key]);
|
|
$this->items[$key] = $item;
|
|
} else {
|
|
$this->items[] = $item;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an item to the beginning of the DataObjectSet
|
|
* @param DataObject $item Item to add
|
|
* @param string $key Key to index this DataObject by.
|
|
*/
|
|
public function insertFirst($item, $key = null) {
|
|
if($key == null) {
|
|
array_unshift($this->items, $item);
|
|
} else {
|
|
$this->items = array_merge(array($key=>$item), $this->items);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert a DataObject at the beginning of this set.
|
|
* @param DataObject $item Item to insert.
|
|
*/
|
|
public function unshift($item) {
|
|
$this->insertFirst($item);
|
|
}
|
|
|
|
/**
|
|
* Remove a DataObject from the beginning of this set and return it.
|
|
* This is the equivalent of pop() but acts on the head of the set.
|
|
* Opposite of unshift().
|
|
*
|
|
* @return DataObject (or null if there are no items in the set)
|
|
*/
|
|
public function shift() {
|
|
return array_shift($this->items);
|
|
}
|
|
|
|
/**
|
|
* Remove a DataObject from the end of this set and return it.
|
|
* This is the equivalent of shift() but acts on the tail of the set.
|
|
* Opposite of push().
|
|
*
|
|
* @return DataObject (or null if there are no items in the set)
|
|
*/
|
|
public function pop() {
|
|
return array_pop($this->items);
|
|
}
|
|
|
|
/**
|
|
* Remove a DataObject from this set.
|
|
* @param DataObject $itemObject Item to remove.
|
|
*/
|
|
public function remove($itemObject) {
|
|
foreach($this->items as $key=>$item){
|
|
if($item === $itemObject){
|
|
unset($this->items[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replaces $itemOld with $itemNew
|
|
*
|
|
* @param DataObject $itemOld
|
|
* @param DataObject $itemNew
|
|
*/
|
|
public function replace($itemOld, $itemNew) {
|
|
foreach($this->items as $key => $item) {
|
|
if($item === $itemOld) {
|
|
$this->items[$key] = $itemNew;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merge another set onto the end of this set.
|
|
* To merge without causing duplicates, consider calling
|
|
* {@link removeDuplicates()} after this method on the new set.
|
|
*
|
|
* @param DataObjectSet $anotherSet Set to mege onto this set.
|
|
*/
|
|
public function merge($anotherSet){
|
|
if($anotherSet) {
|
|
foreach($anotherSet as $item){
|
|
$this->push($item);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a specific slice of an existing set.
|
|
*
|
|
* @param int $offset
|
|
* @param int $length
|
|
* @return DataObjectSet
|
|
*/
|
|
public function getRange($offset, $length) {
|
|
$set = array_slice($this->items, (int)$offset, (int)$length);
|
|
return new DataObjectSet($set);
|
|
}
|
|
|
|
/**
|
|
* Returns an Iterator for this DataObjectSet.
|
|
* This function allows you to use DataObjectSets in foreach loops
|
|
* @return DataObjectSet_Iterator
|
|
*/
|
|
public function getIterator() {
|
|
return new DataObjectSet_Iterator($this->items);
|
|
}
|
|
|
|
/**
|
|
* Returns false if the set is empty.
|
|
* @return boolean
|
|
*/
|
|
public function exists() {
|
|
return $this->count() > 0;
|
|
}
|
|
|
|
/**
|
|
* Return the first item in the set.
|
|
* @return DataObject
|
|
*/
|
|
public function First() {
|
|
if(count($this->items) < 1)
|
|
return null;
|
|
|
|
$keys = array_keys($this->items);
|
|
return $this->items[$keys[0]];
|
|
}
|
|
|
|
/**
|
|
* Return the last item in the set.
|
|
* @return DataObject
|
|
*/
|
|
public function Last() {
|
|
if(count($this->items) < 1)
|
|
return null;
|
|
|
|
$keys = array_keys($this->items);
|
|
return $this->items[$keys[sizeof($keys)-1]];
|
|
}
|
|
|
|
/**
|
|
* @deprecated 3.0 Use {@link DataObjectSet::Count()}.
|
|
*/
|
|
public function TotalItems() {
|
|
return $this->Count();
|
|
}
|
|
|
|
/**
|
|
* Returns the actual number of items in this dataset.
|
|
* @return int
|
|
*/
|
|
public function Count() {
|
|
return sizeof($this->items);
|
|
}
|
|
|
|
/**
|
|
* Returns this set as a XHTML unordered list.
|
|
* @return string
|
|
*/
|
|
public function UL() {
|
|
if($this->exists()) {
|
|
$result = "<ul id=\"Menu1\">\n";
|
|
foreach($this as $item) {
|
|
$result .= "<li onclick=\"location.href = this.getElementsByTagName('a')[0].href\"><a href=\"$item->Link\">$item->Title</a></li>\n";
|
|
}
|
|
$result .= "</ul>\n";
|
|
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns this set as a XHTML unordered list.
|
|
* @return string
|
|
*/
|
|
public function forTemplate() {
|
|
return $this->UL();
|
|
}
|
|
|
|
/**
|
|
* Returns an array of ID => Title for the items in this set.
|
|
*
|
|
* @param string $index The field to use as a key for the array
|
|
* @param string $titleField The field (or method) to get values for the map
|
|
* @param string $emptyString Empty option text e.g "(Select one)"
|
|
* @param bool $sort Sort the map alphabetically based on the $titleField value
|
|
* @return array
|
|
*/
|
|
public function map($index = 'ID', $titleField = 'Title', $emptyString = null, $sort = false) {
|
|
$map = array();
|
|
foreach($this as $item) {
|
|
$map[$item->$index] = ($item->hasMethod($titleField))
|
|
? $item->$titleField() : $item->$titleField;
|
|
}
|
|
if($emptyString) $map = array('' => $emptyString) + $map;
|
|
|
|
if($sort) asort($map);
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Find an item in this list where the field $key is equal to $value
|
|
* Eg: $doSet->find('ID', 4);
|
|
* @return ViewableData The first matching item.
|
|
*/
|
|
public function find($key, $value) {
|
|
foreach($this as $item) {
|
|
if($item->$key == $value) return $item;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a column of the given field
|
|
* @param string $value The field name
|
|
* @return array
|
|
*/
|
|
public function column($value = "ID") {
|
|
$list = array();
|
|
foreach($this as $item ){
|
|
$list[] = ($item->hasMethod($value)) ? $item->$value() : $item->$value;
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of DataObjectSets. The array is keyed by index.
|
|
*
|
|
* @param string $index The field name to index the array by.
|
|
* @return array
|
|
*/
|
|
public function groupBy($index){
|
|
foreach($this as $item ){
|
|
$key = ($item->hasMethod($index)) ? $item->$index() : $item->$index;
|
|
if(!isset($result[$key])) {
|
|
$result[$key] = new DataObjectSet();
|
|
}
|
|
$result[$key]->push($item);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Groups the items by a given field.
|
|
* Returns a DataObjectSet suitable for use in a nested template.
|
|
* @param string $index The field to group by
|
|
* @param string $childControl The name of the nested page control
|
|
* @return DataObjectSet
|
|
*/
|
|
public function GroupedBy($index, $childControl = "Children") {
|
|
$grouped = $this->groupBy($index);
|
|
$groupedAsSet = new DataObjectSet();
|
|
foreach($grouped as $group) {
|
|
$groupedAsSet->push($group->First()->customise(array(
|
|
$childControl => $group
|
|
)));
|
|
}
|
|
return $groupedAsSet;
|
|
}
|
|
|
|
/**
|
|
* Returns a nested unordered list out of a "chain" of DataObject-relations,
|
|
* using the automagic ComponentSet-relation-methods to find subsequent DataObjectSets.
|
|
* The formatting of the list can be different for each level, and is evaluated as an SS-template
|
|
* with access to the current DataObjects attributes and methods.
|
|
*
|
|
* Example: Groups (Level 0, the "calling" DataObjectSet, needs to be queried externally)
|
|
* and their Members (Level 1, determined by the Group->Members()-relation).
|
|
*
|
|
* @param array $nestingLevels
|
|
* Defines relation-methods on DataObjects as a string, plus custom
|
|
* SS-template-code for the list-output. Use "Root" for the current DataObjectSet (is will not evaluate into
|
|
* a function).
|
|
* Caution: Don't close the list-elements (determined programatically).
|
|
* You need to escape dollar-signs that need to be evaluated as SS-template-code.
|
|
* Use $EvenOdd to get appropriate classes for CSS-styling.
|
|
* Format:
|
|
* array(
|
|
* array(
|
|
* "dataclass" => "Root",
|
|
* "template" => "<li class=\"\$EvenOdd\"><a href=\"admin/crm/show/\$ID\">\$AccountName</a>"
|
|
* ),
|
|
* array(
|
|
* "dataclass" => "GrantObjects",
|
|
* "template" => "<li class=\"\$EvenOdd\"><a href=\"admin/crm/showgrant/\$ID\">#\$GrantNumber: \$TotalAmount.Nice, \$ApplicationDate.ShortMonth \$ApplicationDate.Year</a>"
|
|
* )
|
|
* );
|
|
* @param string $ulExtraAttributes Extra attributes
|
|
*
|
|
* @return string Unordered List (HTML)
|
|
*/
|
|
public function buildNestedUL($nestingLevels, $ulExtraAttributes = "") {
|
|
return $this->getChildrenAsUL($nestingLevels, 0, "", $ulExtraAttributes);
|
|
}
|
|
|
|
/**
|
|
* Gets called recursively on the child-objects of the chain.
|
|
*
|
|
* @param array $nestingLevels see {@buildNestedUL}
|
|
* @param int $level Current nesting level
|
|
* @param string $template Template for list item
|
|
* @param string $ulExtraAttributes Extra attributes
|
|
* @return string
|
|
*/
|
|
public function getChildrenAsUL($nestingLevels, $level = 0, $template = "<li id=\"record-\$ID\" class=\"\$EvenOdd\">\$Title", $ulExtraAttributes = null, &$itemCount = 0) {
|
|
$output = "";
|
|
$hasNextLevel = false;
|
|
$ulExtraAttributes = " $ulExtraAttributes";
|
|
$output = "<ul" . eval($ulExtraAttributes) . ">\n";
|
|
|
|
$currentNestingLevel = $nestingLevels[$level];
|
|
|
|
// either current or default template
|
|
$currentTemplate = (!empty($currentNestingLevel)) ? $currentNestingLevel['template'] : $template;
|
|
$myViewer = SSViewer::fromString($currentTemplate);
|
|
|
|
if(isset($nestingLevels[$level+1]['dataclass'])){
|
|
$childrenMethod = $nestingLevels[$level+1]['dataclass'];
|
|
}
|
|
// sql-parts
|
|
|
|
$filter = (isset($nestingLevels[$level+1]['filter'])) ? $nestingLevels[$level+1]['filter'] : null;
|
|
$sort = (isset($nestingLevels[$level+1]['sort'])) ? $nestingLevels[$level+1]['sort'] : null;
|
|
$join = (isset($nestingLevels[$level+1]['join'])) ? $nestingLevels[$level+1]['join'] : null;
|
|
$limit = (isset($nestingLevels[$level+1]['limit'])) ? $nestingLevels[$level+1]['limit'] : null;
|
|
$having = (isset($nestingLevels[$level+1]['having'])) ? $nestingLevels[$level+1]['having'] : null;
|
|
|
|
foreach($this as $parent) {
|
|
$evenOdd = ($itemCount % 2 == 0) ? "even" : "odd";
|
|
$parent->setField('EvenOdd', $evenOdd);
|
|
$template = $myViewer->process($parent);
|
|
|
|
// if no output is selected, fall back to the id to keep the item "clickable"
|
|
$output .= $template . "\n";
|
|
|
|
if(isset($childrenMethod)) {
|
|
// workaround for missing groupby/having-parameters in instance_get
|
|
// get the dataobjects for the next level
|
|
$children = $parent->$childrenMethod($filter, $sort, $join, $limit, $having);
|
|
if($children) {
|
|
$output .= $children->getChildrenAsUL($nestingLevels, $level+1, $currentTemplate, $ulExtraAttributes);
|
|
}
|
|
}
|
|
$output .= "</li>\n";
|
|
$itemCount++;
|
|
}
|
|
|
|
$output .= "</ul>\n";
|
|
|
|
return $output;
|
|
}
|
|
|
|
public function canSortBy($by) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Sorts the current DataObjectSet instance.
|
|
* @param string $fieldname The name of the field on the DataObject that you wish to sort the set by.
|
|
* @param string $direction Direction to sort by, either "ASC" or "DESC".
|
|
*/
|
|
public function sort($fieldname, $direction = "ASC") {
|
|
if($this->items) {
|
|
if (is_string($fieldname) && preg_match('/(.+?)(\s+?)(A|DE)SC$/', $fieldname, $matches)) {
|
|
$fieldname = $matches[1];
|
|
$direction = $matches[3].'SC';
|
|
}
|
|
column_sort($this->items, $fieldname, $direction, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove duplicates from this set based on the dataobjects field.
|
|
* Assumes all items contained in the set all have that field.
|
|
* Useful after merging to sets via {@link merge()}.
|
|
*
|
|
* @param string $field the field to check for duplicates
|
|
*/
|
|
public function removeDuplicates($field = 'ID') {
|
|
$exists = array();
|
|
foreach($this->items as $key => $item) {
|
|
if(isset($exists[$fullkey = ClassInfo::baseDataClass($item) . ":" . $item->$field])) {
|
|
unset($this->items[$key]);
|
|
}
|
|
$exists[$fullkey] = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns information about this set in HTML format for debugging.
|
|
* @return string
|
|
*/
|
|
public function debug() {
|
|
$val = "<h2>" . $this->class . "</h2><ul>";
|
|
foreach($this as $item) {
|
|
$val .= "<li style=\"list-style-type: disc; margin-left: 20px\">" . Debug::text($item) . "</li>";
|
|
}
|
|
$val .= "</ul>";
|
|
return $val;
|
|
}
|
|
|
|
/**
|
|
* Groups the set by $groupField and returns the parent of each group whose class
|
|
* is $groupClassName. If $collapse is true, the group will be collapsed up until an ancestor with the
|
|
* given class is found.
|
|
* @param string $groupField The field to group by.
|
|
* @param string $groupClassName Classname.
|
|
* @param string $sortParents SORT clause to insert into the parents SQL.
|
|
* @param string $parentField Parent field.
|
|
* @param boolean $collapse Collapse up until an ancestor with the given class is found.
|
|
* @param string $requiredParents Required parents
|
|
* @return DataObjectSet
|
|
*/
|
|
public function groupWithParents($groupField, $groupClassName, $sortParents = null, $parentField = 'ID', $collapse = false, $requiredParents = null) {
|
|
$groupTable = ClassInfo::baseDataClass($groupClassName);
|
|
|
|
// Each item in this DataObjectSet is grouped into a multidimensional array
|
|
// indexed by it's parent. The parent IDs are later used to find the parents
|
|
// that make up the returned set.
|
|
$groupedSet = array();
|
|
|
|
// Array to store the subgroups matching the requirements
|
|
$resultsArray = array();
|
|
|
|
// Put this item into the array indexed by $groupField.
|
|
// the keys are later used to retrieve the top-level records
|
|
foreach( $this as $item ) {
|
|
$groupedSet[$item->$groupField][] = $item;
|
|
}
|
|
|
|
$parentSet = null;
|
|
|
|
// retrieve parents for this set
|
|
|
|
// TODO How will we collapse the hierarchy to bridge the gap?
|
|
|
|
// if collapse is specified, then find the most direct ancestor of type
|
|
// $groupClassName
|
|
if($collapse) {
|
|
// The most direct ancestors with the type $groupClassName
|
|
$parentSet = array();
|
|
|
|
// get direct parents
|
|
$parents = DataObject::get($groupClassName, "\"$groupTable\".\"$parentField\" IN( " . implode( ",", array_keys( $groupedSet ) ) . ")", $sortParents );
|
|
|
|
// for each of these parents...
|
|
foreach($parents as $parent) {
|
|
// store the old parent ID. This is required to change the grouped items
|
|
// in the $groupSet array
|
|
$oldParentID = $parent->ID;
|
|
|
|
// get the parental stack
|
|
$parentObjects= $parent->parentStack();
|
|
$parentStack = array();
|
|
|
|
foreach( $parentObjects as $parentObj )
|
|
$parentStack[] = $parentObj->ID;
|
|
|
|
// is some particular IDs are required, then get the intersection
|
|
if($requiredParents && count($requiredParents)) {
|
|
$parentStack = array_intersect($requiredParents, $parentStack);
|
|
}
|
|
|
|
$newParent = null;
|
|
|
|
// If there are no parents, the group can be omitted
|
|
if(empty($parentStack)) {
|
|
$newParent = new DataObjectSet();
|
|
} else {
|
|
$newParent = DataObject::get_one( $groupClassName, "\"$groupTable\".\"$parentField\" IN( " . implode( ",", $parentStack ) . ")" );
|
|
}
|
|
|
|
// change each of the descendant's association from the old parent to
|
|
// the new parent. This effectively collapses the hierarchy
|
|
foreach( $groupedSet[$oldParentID] as $descendant ) {
|
|
$groupedSet[$newParent->ID][] = $descendant;
|
|
}
|
|
|
|
// Add the most direct ancestor of type $groupClassName
|
|
$parentSet[] = $newParent;
|
|
}
|
|
// otherwise get the parents of these items
|
|
} else {
|
|
|
|
$requiredIDs = array_keys( $groupedSet );
|
|
|
|
if( $requiredParents && cont($requiredParents)) {
|
|
$requiredIDs = array_intersect($requiredParents, $requiredIDs);
|
|
}
|
|
|
|
if(empty($requiredIDs)) {
|
|
$parentSet = new DataObjectSet();
|
|
} else {
|
|
$parentSet = DataObject::get( $groupClassName, "\"$groupTable\".\"$parentField\" IN( " . implode( ",", $requiredIDs ) . ")", $sortParents );
|
|
}
|
|
|
|
$parentSet = $parentSet->toArray();
|
|
}
|
|
|
|
foreach($parentSet as $parent) {
|
|
$resultsArray[] = $parent->customise(array(
|
|
"GroupItems" => new DataObjectSet($groupedSet[$parent->$parentField])
|
|
));
|
|
}
|
|
|
|
return new DataObjectSet($resultsArray);
|
|
}
|
|
|
|
/**
|
|
* Add a field to this set without writing it to the database
|
|
* @param DataObject $field Field to add
|
|
*/
|
|
function addWithoutWrite($field) {
|
|
$this->items[] = $field;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the DataObjectSet contains all of the IDs givem
|
|
* @param $idList An array of object IDs
|
|
*/
|
|
function containsIDs($idList) {
|
|
foreach($idList as $item) $wants[$item] = true;
|
|
foreach($this as $item) if($item) unset($wants[$item->ID]);
|
|
return !$wants;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the DataObjectSet contains all of and *only* the IDs given.
|
|
* Note that it won't like duplicates very much.
|
|
* @param $idList An array of object IDs
|
|
*/
|
|
function onlyContainsIDs($idList) {
|
|
return $this->containsIDs($idList) && sizeof($idList) == $this->count();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Sort a 2D array by particular column.
|
|
* @param array $data The array to sort.
|
|
* @param mixed $column The name of the column you wish to sort by, or an array of column=>directions to sort by.
|
|
* @param string $direction Direction to sort by, either "ASC" or "DESC".
|
|
* @param boolean $preserveIndexes Preserve indexes
|
|
*/
|
|
function column_sort(&$data, $column, $direction = "ASC", $preserveIndexes = true) {
|
|
global $column_sort_field;
|
|
|
|
// if we were only given a string for column, move it into an array
|
|
if (is_string($column)) $column = array($column => $direction);
|
|
|
|
// convert directions to integers
|
|
foreach ($column as $k => $v) {
|
|
if ($v == 'ASC') {
|
|
$column[$k] = 1;
|
|
}
|
|
elseif ($v == 'DESC') {
|
|
$column[$k] = -1;
|
|
}
|
|
elseif (!is_numeric($v)) {
|
|
$column[$k] = 0;
|
|
}
|
|
}
|
|
|
|
$column_sort_field = $column;
|
|
|
|
if($preserveIndexes) {
|
|
uasort($data, "column_sort_callback_basic");
|
|
} else {
|
|
usort($data, "column_sort_callback_basic");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback used by column_sort
|
|
*/
|
|
function column_sort_callback_basic($a, $b) {
|
|
global $column_sort_field;
|
|
$result = 0;
|
|
// loop through each sort field
|
|
foreach ($column_sort_field as $field => $multiplier) {
|
|
// if A < B then no further examination is necessary
|
|
if ($a->$field < $b->$field) {
|
|
$result = -1 * $multiplier;
|
|
break;
|
|
}
|
|
// if A > B then no further examination is necessary
|
|
elseif ($a->$field > $b->$field) {
|
|
$result = $multiplier;
|
|
break;
|
|
}
|
|
// A == B means we need to compare the two using the next field
|
|
// if this was the last field, then function returns that objects
|
|
// are equivalent
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* An Iterator for a DataObjectSet
|
|
*
|
|
* @package sapphire
|
|
* @subpackage model
|
|
*/
|
|
class DataObjectSet_Iterator implements Iterator {
|
|
function __construct($items) {
|
|
$this->items = $items;
|
|
|
|
$this->current = $this->prepareItem(current($this->items));
|
|
}
|
|
|
|
/**
|
|
* Prepare an item taken from the internal array for
|
|
* output by this iterator. Ensures that it is an object.
|
|
* @param DataObject $item Item to prepare
|
|
* @return DataObject
|
|
*/
|
|
protected function prepareItem($item) {
|
|
if(is_object($item)) {
|
|
$item->iteratorProperties(key($this->items), sizeof($this->items));
|
|
}
|
|
// This gives some reliablity but it patches over the root cause of the bug...
|
|
// else if(key($this->items) !== null) $item = new ViewableData();
|
|
return $item;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the current object of the iterator.
|
|
* @return DataObject
|
|
*/
|
|
public function current() {
|
|
return $this->current;
|
|
}
|
|
|
|
/**
|
|
* Return the key of the current object of the iterator.
|
|
* @return mixed
|
|
*/
|
|
public function key() {
|
|
return key($this->items);
|
|
}
|
|
|
|
/**
|
|
* Return the next item in this set.
|
|
* @return DataObject
|
|
*/
|
|
public function next() {
|
|
$this->current = $this->prepareItem(next($this->items));
|
|
return $this->current;
|
|
}
|
|
|
|
/**
|
|
* Rewind the iterator to the beginning of the set.
|
|
* @return DataObject The first item in the set.
|
|
*/
|
|
public function rewind() {
|
|
$this->current = $this->prepareItem(reset($this->items));
|
|
return $this->current;
|
|
}
|
|
|
|
/**
|
|
* Check the iterator is pointing to a valid item in the set.
|
|
* @return boolean
|
|
*/
|
|
public function valid() {
|
|
return $this->current !== false;
|
|
}
|
|
|
|
/**
|
|
* Return the next item in this set without progressing the iterator.
|
|
* @return DataObject
|
|
*/
|
|
public function peekNext() {
|
|
return $this->getOffset(1);
|
|
}
|
|
|
|
/**
|
|
* Return the prvious item in this set, without affecting the iterator.
|
|
* @return DataObject
|
|
*/
|
|
public function peekPrev() {
|
|
return $this->getOffset(-1);
|
|
}
|
|
|
|
/**
|
|
* Return the object in this set offset by $offset from the iterator pointer.
|
|
* @param int $offset The offset.
|
|
* @return DataObject|boolean DataObject of offset item, or boolean FALSE if not found
|
|
*/
|
|
public function getOffset($offset) {
|
|
$keys = array_keys($this->items);
|
|
foreach($keys as $i => $key) {
|
|
if($key == key($this->items)) break;
|
|
}
|
|
|
|
if(isset($keys[$i + $offset])) {
|
|
$requiredKey = $keys[$i + $offset];
|
|
return $this->items[$requiredKey];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
?>
|