ソースを参照

Add SortableBehavior

euromark 12 年 前
コミット
a560a666ce

+ 159 - 0
Model/Behavior/SortableBehavior.php

@@ -0,0 +1,159 @@
+<?php
+App::uses('ModelBehavior', 'Model');
+
+/**
+ * Allow Sort up/down of records.
+ *
+ * Expects a sort field to be present. This field will be sorted DESC.
+ * The higher the sort value, the higher the record in the list.
+ * You can also reverse the direction.
+ *
+ * Natural (default) order:
+ * The sort value of new records is 0.
+ *
+ * Reversed order:
+ * The sort value of a new record will be calculated (currently highest + 1).
+ *
+ * @cakephp 2.x
+ * @author Mark Scherer
+ * @license MIT
+ */
+class SortableBehavior extends ModelBehavior {
+
+	protected $_defaults = array(
+		'field' => 'sort',
+		'reverse' => false // TO make 0 the highest value
+	);
+
+	/**
+	 * SortableBehavior::setup()
+	 *
+	 * @param Model $Model
+	 * @param mixed $settings
+	 * @return void
+	 */
+	public function setup(Model $Model, $settings = array()) {
+		if (!isset($this->settings[$Model->alias])) {
+			$this->settings[$Model->alias] = $this->_defaults;
+		}
+		$this->settings[$Model->alias] = array_merge(
+		$this->settings[$Model->alias], (array)$settings);
+	}
+
+	/**
+	 * SortableBehavior::beforeSave()
+	 *
+	 * @param Model $Model
+	 * @param mixed $options
+	 * @return void
+	 */
+	public function beforeSave(Model $Model, $options = array()) {
+		if ($Model->id === false && isset($Model->data[$Model->alias]) &&
+			!isset($Model->data[$Model->alias][$this->settings[$Model->alias]['field']])) {
+			$sort = $this->_determineNextSortValue($Model);
+			$Model->data[$Model->alias][$this->settings[$Model->alias]['field']] = $sort;
+		}
+
+		return true;
+	}
+
+	/**
+	 * SortableBehavior::_determineNextSortValue()
+	 *
+	 * @param Model $Model
+	 * @return int Sort value.
+	 */
+	protected function _determineNextSortValue(Model $Model) {
+		if (empty($this->settings[$Model->alias]['reverse'])) {
+			return 0;
+		}
+		$sort = $Model->find('first', array(
+			'fields' => array(
+				$this->settings[$Model->alias]['field']
+			),
+			'order' => array(
+				$this->settings[$Model->alias]['field'] => 'DESC'
+			)
+		));
+		if (!empty($sort)) {
+			$sort = $sort[$Model->alias][$this->settings[$Model->alias]['field']];
+			$sort++;
+		} else {
+			$sort = 1;
+		}
+		return $sort;
+	}
+
+	/**
+	 * @return boolean Success
+	 */
+	public function moveUp(Model $Model, $id, $placements = 1) {
+		return $this->_moveUpDown($Model, false, $id, $placements);
+	}
+
+	/**
+	 * @return boolean Success
+	 */
+	public function moveDown(Model $Model, $id, $placements = 1) {
+		return $this->_moveUpDown($Model, true, $id, $placements);
+	}
+
+	/**
+	 * @return boolean Success
+	 */
+	protected function _moveUpDown(Model $Model, $naturalOrder, $id, $placements = 1) {
+		// FIXME: Sort over more than one placement.
+		if ($naturalOrder && empty($this->settings[$Model->alias]['reverse'])) {
+			$order = '<=';
+			$findOrder = 'DESC';
+		} else {
+			$order = '>=';
+			$findOrder = 'ASC';
+		}
+		$sort = $Model->find('list', array(
+			'fields' => array(
+				$this->settings[$Model->alias]['field']
+			),
+			'conditions' => array(
+				'id' => $id
+			)
+		));
+		if (empty($sort)) {
+			return false;
+		}
+		list($sort) = array_values($sort);
+		$data = $Model->find('list', array(
+			'fields' => array(
+				'id',
+				$this->settings[$Model->alias]['field']
+			),
+			'conditions' => array(
+				$this->settings[$Model->alias]['field'] . ' ' . $order => $sort
+			),
+			'order' => array(
+				$this->settings[$Model->alias]['field'] => $findOrder
+			),
+			'limit' => $placements + 1
+		));
+		$value = end($data);
+		$key = key($data);
+		if ($key == $id) {
+			return;
+		}
+		$lastId = $Model->id;
+		if ($sort == $value) {
+			if ($naturalOrder && empty($this->settings[$Model->alias]['reverse'])) {
+				$value++;
+			} else {
+				$value--;
+			}
+		}
+		$Model->id = $key;
+		$Model->saveField($this->settings[$Model->alias]['field'], $sort);
+		$Model->id = $id;
+		$Model->saveField($this->settings[$Model->alias]['field'], $value);
+		$Model->id = $lastId;
+		return true;
+	}
+
+}

+ 170 - 0
Test/Case/Model/Behavior/SortableBehaviorTest.php

@@ -0,0 +1,170 @@
+<?php
+
+App::uses('SortableBehavior', 'Tools.Model/Behavior');
+App::uses('MyCakeTestCase', 'Tools.TestSuite');
+
+class SortableBehaviorTest extends MyCakeTestCase {
+
+	public $fixtures = array('plugin.tools.role');
+
+	public $SortableBehavior;
+
+	public $Model;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->SortableBehavior = new SortableBehavior();
+		$this->Model = ClassRegistry::init('Role');
+		$this->Model->Behaviors->load('Tools.Sortable');
+
+		// Reset order
+		$list = $this->_getList();
+		$count = count($list);
+		foreach ($list as $id => $order) {
+			$this->Model->id = $id;
+			$this->Model->saveField('sort', $count);
+			$count--;
+		}
+	}
+
+	public function testObject() {
+		$this->assertTrue(is_object($this->SortableBehavior));
+		$this->assertInstanceOf('SortableBehavior', $this->SortableBehavior);
+	}
+
+	/**
+	 * SortableBehaviorTest::testBasicUp()
+	 *
+	 * @return void
+	 */
+	public function testBasicUp() {
+		$list = $this->_getList();
+		debug($list);
+
+		$positionBefore = $this->_getPosition(4);
+		$this->assertSame(3, $positionBefore);
+
+		$this->Model->moveUp(4);
+
+		$positionAfter = $this->_getPosition(4);
+		$this->assertSame(2, $positionAfter);
+
+		$this->Model->moveUp(4);
+
+		$positionAfter = $this->_getPosition(4);
+		$this->assertSame(1, $positionAfter);
+
+		$this->Model->moveUp(4);
+
+		$positionAfter = $this->_getPosition(4);
+		$this->assertSame(1, $positionAfter);
+	}
+
+	/**
+	 * SortableBehaviorTest::testUp()
+	 *
+	 * @return void
+	 */
+	public function testUp() {
+		$list = $this->_getList();
+		debug($list);
+
+		$positionBefore = $this->_getPosition(4);
+		$this->assertSame(3, $positionBefore);
+
+		$this->Model->moveUp(4, 2);
+
+		$positionAfter = $this->_getPosition(4);
+		$this->assertSame(1, $positionAfter);
+	}
+
+	/**
+	 * SortableBehaviorTest::testBasicDown()
+	 *
+	 * @return void
+	 */
+	public function testBasicDown() {
+		$positionBefore = $this->_getPosition(2);
+		$this->assertSame(2, $positionBefore);
+
+		$this->Model->moveDown(2);
+
+		$positionAfter = $this->_getPosition(2);
+		$this->assertSame(3, $positionAfter);
+
+		$this->Model->moveDown(2);
+
+		$positionAfter = $this->_getPosition(2);
+		$this->assertSame(4, $positionAfter);
+
+		$this->Model->moveDown(2);
+
+		$positionAfter = $this->_getPosition(2);
+		$this->assertSame(4, $positionAfter);
+	}
+
+	/**
+	 * SortableBehaviorTest::testDown()
+	 *
+	 * @return void
+	 */
+	public function testDown() {
+		$positionBefore = $this->_getPosition(2);
+		$this->assertSame(2, $positionBefore);
+
+		$this->Model->moveDown(2, 3);
+
+		$positionAfter = $this->_getPosition(2);
+		$this->assertSame(4, $positionAfter);
+	}
+
+	/**
+	 * SortableBehaviorTest::testAddNew()
+	 *
+	 * @return void
+	 */
+	public function testAddNew() {
+		$this->Model->create();
+		$data = array(
+			'name' => 'new'
+		);
+		$this->Model->save($data);
+		$id = $this->Model->id;
+		$list = $this->_getList();
+		$position = $this->_getPosition($id);
+		$this->assertSame(count($list), $position);
+	}
+
+	/**
+	 * Get 1-based position in the list.
+	 *
+	 * @param mixed $id
+	 * @param mixed $list
+	 * @return int Position or null if not found
+	 */
+	protected function _getPosition($id, $list = array()) {
+		if (!$list) {
+			$list = $this->_getList();
+		}
+		$count = 0;
+		$position = null;
+		foreach ($list as $k => $v) {
+			$count++;
+			if ($id == $k) {
+				$position = $count;
+				break;
+			}
+		}
+		return $position;
+	}
+
+	protected function _getList() {
+		$options = array(
+			'order' => array('sort' => 'DESC'),
+			'fields' => array('id', 'sort')
+		);
+		return $this->Model->find('list', $options);
+	}
+
+}