diff options
Diffstat (limited to 'engine/classes/ElggPriorityList.php')
| -rw-r--r-- | engine/classes/ElggPriorityList.php | 358 | 
1 files changed, 358 insertions, 0 deletions
| diff --git a/engine/classes/ElggPriorityList.php b/engine/classes/ElggPriorityList.php new file mode 100644 index 000000000..8a3b836a8 --- /dev/null +++ b/engine/classes/ElggPriorityList.php @@ -0,0 +1,358 @@ +<?php +/** + * Iterate over elements in a specific priority. + * + * $pl = new ElggPriorityList(); + * $pl->add('Element 0'); + * $pl->add('Element 10', 10); + * $pl->add('Element -10', -10); + * + * foreach ($pl as $priority => $element) { + *	var_dump("$priority => $element"); + * } + * + * Yields: + * -10 => Element -10 + * 0 => Element 0 + * 10 => Element 10 + * + * Collisions on priority are handled by inserting the element at or as close to the + * requested priority as possible: + * + * $pl = new ElggPriorityList(); + * $pl->add('Element 5', 5); + * $pl->add('Colliding element 5', 5); + * $pl->add('Another colliding element 5', 5); + * + * foreach ($pl as $priority => $element) { + *	var_dump("$priority => $element"); + * } + * + * Yields: + *	5 => 'Element 5', + *	6 => 'Colliding element 5', + *	7 => 'Another colliding element 5' + * + * You can do priority lookups by element: + * + * $pl = new ElggPriorityList(); + * $pl->add('Element 0'); + * $pl->add('Element -5', -5); + * $pl->add('Element 10', 10); + * $pl->add('Element -10', -10); + * + * $priority = $pl->getPriority('Element -5'); + *  + * Or element lookups by priority. + * $element = $pl->getElement(-5); + * + * To remove elements, pass the element. + * $pl->remove('Element -10'); + * + * To check if an element exists: + * $pl->contains('Element -5'); + * + * To move an element: + * $pl->move('Element -5', -3); + * + * ElggPriorityList only tracks priority. No checking is done in ElggPriorityList for duplicates or + * updating. If you need to track this use objects and an external map: + * + * function elgg_register_something($id, $display_name, $location, $priority = 500) { + *	// $id => $element. + *	static $map = array(); + *	static $list; + * + *	if (!$list) { + *		$list = new ElggPriorityList(); + *	} + * + *	// update if already registered. + *	if (isset($map[$id])) { + *		$element = $map[$id]; + *		// move it first because we have to pass the original element. + *		if (!$list->move($element, $priority)) { + *			return false; + *		} + *		$element->display_name = $display_name; + *		$element->location = $location; + *	} else { + *		$element = new stdClass(); + *		$element->display_name = $display_name; + *		$element->location = $location; + *		if (!$list->add($element, $priority)) { + *			return false; + *		} + *		$map[$id] = $element; + *	} + * + *	return true; + * } + * + * @package Elgg.Core + * @subpackage Helpers + */ +class ElggPriorityList +	implements Iterator, Countable { + +	/** +	 * The list of elements +	 * +	 * @var array +	 */ +	private $elements = array(); + +	/** +	 * Create a new priority list. +	 * +	 * @param array $elements An optional array of priorities => element +	 */ +	public function __construct(array $elements = array()) { +		if ($elements) { +			foreach ($elements as $priority => $element) { +				$this->add($element, $priority); +			} +		} +	} + +	/** +	 * Adds an element to the list. +	 * +	 * @warning This returns the priority at which the element was added, which can be 0. Use +	 *          !== false to check for success. +	 * +	 * @param mixed $element  The element to add to the list. +	 * @param mixed $priority Priority to add the element. In priority collisions, the original element +	 *                        maintains its priority and the new element is to the next available +	 *                        slot, taking into consideration all previously registered elements. +	 *                        Negative elements are accepted. +	 * @return int            The priority of the added element. +	 */ +	public function add($element, $priority = null, $exact = false) { +		if ($priority !== null && !is_numeric($priority)) { +			return false; +		} else { +			$priority = $this->getNextPriority($priority); +		} + +		$this->elements[$priority] = $element; +		$this->sorted = false; +		return $priority; +	} + +	/** +	 * Removes an element from the list. +	 * +	 * @warning The element must have the same attributes / values. If using $strict, it must have +	 *          the same types. array(10) will fail in strict against array('10') (str vs int). +	 * +	 * @param type $element +	 * @return bool +	 */ +	public function remove($element, $strict = false) { +		$index = array_search($element, $this->elements, $strict); +		if ($index !== false) { +			unset($this->elements[$index]); +			return true; +		} else { +			return false; +		} +	} + +	/** +	 * Move an existing element to a new priority. +	 * +	 * @param mixed  $current_priority +	 * @param int    $new_priority +	 * +	 * @return int The new priority. +	 */ +	public function move($element, $new_priority, $strict = false) { +		$new_priority = (int) $new_priority; +		 +		$current_priority = $this->getPriority($element, $strict); +		if ($current_priority === false) { +			return false; +		} + +		if ($current_priority == $new_priority) { +			return true; +		} + +		// move the actual element so strict operations still work +		$element = $this->getElement($current_priority); +		unset($this->elements[$current_priority]); +		return $this->add($element, $new_priority); +	} + +	/** +	 * Returns the elements +	 * +	 * @return array +	 */ +	public function getElements() { +		$this->sortIfUnsorted(); +		return $this->elements; +	} + +	/** +	 * Sort the elements optionally by a callback function. +	 * +	 * If no user function is provided the elements are sorted by priority registered. +	 * +	 * The callback function should accept the array of elements as the first argument and should +	 * return a sorted array. +	 * +	 * This function can be called multiple times. +	 * +	 * @param type $callback +	 * @return bool +	 */ +	public function sort($callback = null) { +		if (!$callback) { +			ksort($this->elements, SORT_NUMERIC); +		} else { +			$sorted = call_user_func($callback, $this->elements); + +			if (!$sorted) { +				return false; +			} + +			$this->elements = $sorted; +		} +		 +		$this->sorted = true; +		return true; +	} + +	/** +	 * Sort the elements if they haven't been sorted yet. +	 * +	 * @return bool +	 */ +	private function sortIfUnsorted() { +		if (!$this->sorted) { +			return $this->sort(); +		} +	} + +	/** +	 * Returns the next priority available. +	 * +	 * @param int $near Make the priority as close to $near as possible. +	 * @return int +	 */ +	public function getNextPriority($near = 0) { +		$near = (int) $near; +		 +		while (array_key_exists($near, $this->elements)) { +			$near++; +		} + +		return $near; +	} + +	/** +	 * Returns the priority of an element if it exists in the list. +	 *  +	 * @warning This can return 0 if the element's priority is 0. +	 * +	 * @param mixed $element The element to check for. +	 * @param bool  $strict  Use strict checking? +	 * @return mixed False if the element doesn't exists, the priority if it does. +	 */ +	public function getPriority($element, $strict = false) { +		return array_search($element, $this->elements, $strict); +	} + +	/** +	 * Returns the element at $priority. +	 * +	 * @param int $priority +	 * @return mixed The element or false on fail. +	 */ +	public function getElement($priority) { +		return (isset($this->elements[$priority])) ? $this->elements[$priority] : false; +	} + +	/** +	 * Returns if the list contains $element. +	 * +	 * @param mixed $element The element to check. +	 * @param bool  $strict  Use strict checking? +	 * @return bool +	 */ +	public function contains($element, $strict = false) { +		return $this->getPriority($element, $strict) !== false; +	} + +	 +	/********************** +	 * Interface methods * +	 **********************/ + +	/** +	 * Iterator +	 */ + +	/** +	 * PHP Iterator Interface +	 * +	 * @see Iterator::rewind() +	 * @return void +	 */ +	public function rewind() { +		$this->sortIfUnsorted(); +		return reset($this->elements); +	} + +	/** +	 * PHP Iterator Interface +	 * +	 * @see Iterator::current() +	 * @return mixed +	 */ +	public function current() { +		$this->sortIfUnsorted(); +		return current($this->elements); +	} + +	/** +	 * PHP Iterator Interface +	 * +	 * @see Iterator::key() +	 * @return int +	 */ +	public function key() { +		$this->sortIfUnsorted(); +		return key($this->elements); +	} + +	/** +	 * PHP Iterator Interface +	 * +	 * @see Iterator::next() +	 * @return mixed +	 */ +	public function next() { +		$this->sortIfUnsorted(); +		return next($this->elements); +	} + +	/** +	 * PHP Iterator Interface +	 * +	 * @see Iterator::valid() +	 * @return bool +	 */ +	public function valid() { +		$this->sortIfUnsorted(); +		$key = key($this->elements); +		return ($key !== NULL && $key !== FALSE); +	} + +	// Countable +	public function count() { +		return count($this->elements); +	} +}
\ No newline at end of file | 
