diff options
-rw-r--r-- | core/config/config.php | 1 | ||||
-rw-r--r-- | core/controllers/welcome.php | 27 | ||||
-rw-r--r-- | core/helpers/album.php | 54 | ||||
-rw-r--r-- | core/helpers/core_installer.php | 21 | ||||
-rw-r--r-- | core/helpers/photo.php | 65 | ||||
-rw-r--r-- | core/libraries/ORM_MPTT.php | 142 | ||||
-rw-r--r-- | core/models/item.php | 45 | ||||
-rw-r--r-- | core/tests/Album_Test.php | 47 | ||||
-rw-r--r-- | core/tests/Core_Installer_Test.php | 7 | ||||
-rw-r--r-- | core/tests/File_Structure_Test.php | 1 | ||||
-rw-r--r-- | core/tests/Item_Test.php | 29 | ||||
-rw-r--r-- | core/tests/Photo_Test.php | 57 | ||||
-rw-r--r-- | core/tests/test.jpg | bin | 0 -> 6232 bytes | |||
-rw-r--r-- | core/views/welcome.html.php | 39 | ||||
-rw-r--r-- | modules/mptt/libraries/MPTT.php | 1187 | ||||
-rw-r--r-- | themes/default/images/carousel.png | bin | 0 -> 15631 bytes | |||
-rw-r--r-- | themes/default/views/footer.html.php | 8 | ||||
-rw-r--r-- | themes/default/views/photo.html.php | 64 | ||||
-rw-r--r-- | themes/default/views/sidebar.html.php | 159 |
19 files changed, 605 insertions, 1348 deletions
diff --git a/core/config/config.php b/core/config/config.php index 5ab35a0e..de872455 100644 --- a/core/config/config.php +++ b/core/config/config.php @@ -120,7 +120,6 @@ $config['modules'] = array ( MODPATH . 'gallery_unit_test', MODPATH . 'unit_test', - MODPATH . 'mptt', MODPATH . 'forge', THEMEPATH . 'default', diff --git a/core/controllers/welcome.php b/core/controllers/welcome.php index f1201cff..a5719cf0 100644 --- a/core/controllers/welcome.php +++ b/core/controllers/welcome.php @@ -42,6 +42,33 @@ class Welcome_Controller extends Template_Controller { url::redirect("welcome"); } + function add($count) { + srand(time()); + $parents = ORM::factory("item")->where("type", "album")->find_all()->as_array(); + for ($i = 0; $i < $count; $i++) { + $parent = $parents[array_rand($parents)]; + switch(rand(0, 1)) { + case 0: + $album = album::create($parent->id, "rnd_" . rand(), "Rnd $i", "rnd $i"); + $parents[] = $album; + break; + + case 1: + photo::create($parent->id, DOCROOT . "themes/default/images/thumbnail.jpg", + "thumbnail.jpg", "rnd_" . rand(), "sample thumbnail"); + break; + } + + print "$i "; + if (!($i % 100)) { + set_time_limit(30); + } + } + print "<br/>"; + print html::anchor("welcome", "return"); + $this->auto_render = false; + } + private function _get_config_errors() { $errors = array(); if (!file_exists(VARPATH)) { diff --git a/core/helpers/album.php b/core/helpers/album.php new file mode 100644 index 00000000..415e654c --- /dev/null +++ b/core/helpers/album.php @@ -0,0 +1,54 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * This is the API for handling albums. + * + * Note: by design, this class does not do any permission checking. + */ +class Album_Core { + /** + * Create a new album. + * @param integer $parent_id id of parent album + * @param string $name the name of this new album (it will become the directory name on disk) + * @param integer $title the title of the new album + * @param string $description (optional) the longer description of this album + * @return Item_Model + */ + static function create($parent_id, $name, $title, $description=null) { + $album = ORM::factory("item"); + $album->type = "album"; + $album->title = $title; + $album->description = $description; + $album->name = $name; + + while (ORM::Factory("item") + ->where("parent_id", $parent_id) + ->where("name", $album->name) + ->find()->id) { + $album->name = "{$name}-" . rand(); + } + + $album = $album->add_to_parent($parent_id); + mkdir($album->path()); + mkdir($album->thumbnail_path()); + return $album; + } +} diff --git a/core/helpers/core_installer.php b/core/helpers/core_installer.php index 5720976b..d100e0d1 100644 --- a/core/helpers/core_installer.php +++ b/core/helpers/core_installer.php @@ -41,14 +41,14 @@ class core_installer { $db->query("CREATE TABLE `items` ( `id` int(9) NOT NULL auto_increment, - `type` char(32) default NULL, + `type` char(32) NOT NULL, `title` char(255) default NULL, `description` char(255) default NULL, - `path` char(255) default NULL, - `left` int(9) default NULL, - `right` int(9) default NULL, - `parent_id` int(9) default NULL, - `scope` int(9) default NULL, + `name` char(255) default NULL, + `left` int(9) NOT NULL, + `right` int(9) NOT NULL, + `parent_id` int(9) NOT NULL, + `level` int(9) NOT NULL, PRIMARY KEY (`id`), KEY `parent_id` (`parent_id`), KEY `type` (`type`)) @@ -64,9 +64,14 @@ class core_installer { $core->save(); $root = ORM::factory("item"); + $root->type = 'album'; $root->title = "Gallery"; $root->description = "Welcome to your Gallery3"; - $root->make_root(); + $root->left = 1; + $root->right = 2; + $root->parent_id = 0; + $root->level = 1; + $root->save(); } } @@ -74,5 +79,7 @@ class core_installer { $db = Database::instance(); $db->query("DROP TABLE IF EXISTS `items`;"); $db->query("DROP TABLE IF EXISTS `modules`;"); + system("/bin/rm -rf " . VARPATH . "albums"); + system("/bin/rm -rf " . VARPATH . "thumbnails"); } } diff --git a/core/helpers/photo.php b/core/helpers/photo.php new file mode 100644 index 00000000..e796ef03 --- /dev/null +++ b/core/helpers/photo.php @@ -0,0 +1,65 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * This is the API for handling photos. + * + * Note: by design, this class does not do any permission checking. + */ +class Photo_Core { + /** + * Create a new photo. + * @param integer $parent_id id of parent album + * @param string $filename path to the photo file on disk + * @param string $name the filename to use for this photo in the album + * @param integer $title the title of the new photo + * @param string $description (optional) the longer description of this photo + * @return Item_Model + */ + static function create($parent_id, $filename, $name, $title, $description=null) { + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->title = $title; + $photo->description = $description; + $photo->name = $name; + + $pi = pathinfo(basename($filename)); + if (empty($pi["extension"])) { + throw new Exception("@todo UNKNOWN_FILE_TYPE"); + } + + while (ORM::Factory("item") + ->where("parent_id", $parent_id) + ->where("name", $photo->name) + ->find()->id) { + $photo->name = rand() . "." . $pi["extension"]; + } + + $photo->add_to_parent($parent_id); + + copy($filename, $photo->path()); + + /** @todo: parameterize these values */ + $image = Image::factory($filename); + $image->resize(200, 140, Image::WIDTH)->save($photo->thumbnail_path()); + $image->resize(800, 600, Image::WIDTH)->save($photo->resize_path()); + return $photo; + } +} diff --git a/core/libraries/ORM_MPTT.php b/core/libraries/ORM_MPTT.php new file mode 100644 index 00000000..6d3478ad --- /dev/null +++ b/core/libraries/ORM_MPTT.php @@ -0,0 +1,142 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * Implement Modified Preorder Tree Traversal on top of ORM. + * + * MPTT is an efficient way to store and retrieve hierarchical data in a single database table. + * For a good description, read http://www.sitepoint.com/article/hierarchical-data-database/3/ + * + * This code was heavily influenced by code from: + * - http://code.google.com/p/kohana-mptt/ + * - http://code.google.com/p/kohana-mptt/wiki/Documentation + * - http://code.google.com/p/s7ncms/source/browse/trunk/modules/s7ncms/libraries/ORM_MPTT.php + * + * Unfortunately that code was not ready for production and I did not want to absorb their code + * and licensing issues so I've reimplemented just the features that we need. + */ +class ORM_MPTT_Core extends ORM { + private $model_name = null; + private $parent = null; + private $parents = null; + private $children = null; + + function __construct($id=null) { + parent::__construct($id); + $this->model_name = inflector::singular($this->table_name); + } + + /** + * Add this node as a child of the parent provided. + * + * @param integer $parent_id the id of the parent node + * @return ORM + */ + function add_to_parent($parent_id) { + $this->_lock(); + + try { + $parent = ORM::factory($this->model_name, $parent_id); + $parent->_grow(); + $this->left = $parent->right - 2; + $this->right = $parent->right - 1; + $this->parent_id = $parent->id; + $this->level = $parent->level + 1; + $this->save(); + } catch (Exception $e) { + $this->_unlock(); + throw $e; + } + + $this->_unlock(); + return $this; + } + + /** + * Return the parent of this node + * + * @return ORM + */ + function parent() { + if (!isset($this->parent)) { + $this->parent = + ORM::factory($this->model_name)->where("id", $this->parent_id)->find(); + } + return $this->parent; + } + + /** + * Return all the parents of this node, in order from root to leaf. + * + * @return array ORM + */ + function parents() { + if (!isset($this->parents)) { + $this->parents = $this->where("`left` <= {$this->left}") + ->where("`right` >= {$this->right}") + ->orderby("left", "ASC") + ->find_all(); + } + return $this->parents; + } + + /** + * Return all of the children of this node, unordered. + * + * @return array ORM + */ + function children() { + if (!isset($this->children)) { + $this->children = + $this->where("parent_id", $this->id)->find_all(); + } + return $this->children; + } + + /** + * Grow this node's space enough to make room for 1 or more new nodes. + * + * @param integer $count the number of new nodes to add + */ + private function _grow($count=1) { + $size = $count * 2; + $this->db->query( + "UPDATE `{$this->table_name}` SET `left` = `left` + $size WHERE `left` >= {$this->right}"); + $this->db->query( + "UPDATE `{$this->table_name}` SET `right` = `right` + $size WHERE `right` >= {$this->right}"); + $this->right += 2; + } + + /** + * Lock the tree to prevent concurrent modification. + */ + private function _lock() { + $result = $this->db->query("SELECT GET_LOCK('{$this->table_name}', 1) AS L")->current(); + if (empty($result->L)) { + throw new Exception("@todo UNABLE_TO_LOCK_EXCEPTION"); + } + } + + /** + * Unlock the tree. + */ + private function _unlock() { + $this->db->query("SELECT RELEASE_LOCK('{$this->table_name}')"); + } +} diff --git a/core/models/item.php b/core/models/item.php index 8b08b699..3d5f08a2 100644 --- a/core/models/item.php +++ b/core/models/item.php @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -class Item_Model extends MPTT { - protected $left_column = "left"; - protected $right_column = "right"; +class Item_Model extends ORM_MPTT { + protected $children = 'items'; public function is_album() { return $this->type == 'album'; @@ -29,13 +28,39 @@ class Item_Model extends MPTT { return $this->type == 'photo'; } - // MPTT::get_children returns null if there are no children; change that to an empty array for - // consistency. - public function get_children() { - $children = parent::get_children(); - if (empty($children)) { - $children = array(); + private function _get_path() { + $paths = array(); + foreach ($this->parents() as $parent) { + if ($parent->id > 1) { + $paths[] = $parent->name; + } + } + $path = implode($paths, "/"); + if (!$this->saved) { + $path .= $this->name; + } + return $path; + } + + public function path() { + return VARPATH . "albums/{$this->_get_path()}"; + } + + public function thumbnail_path() { + if ($this->is_album()) { + return VARPATH . "thumbnails/{$this->_get_path()}"; + } else { + $pi = pathinfo(VARPATH . "thumbnails/{$this->_get_path()}"); + return "{$pi['dirname']}/{$pi['filename']}_thumb.{$pi['extension']}"; + } + } + + public function resize_path() { + if ($this->is_album()) { + return VARPATH . "thumbnails/{$this->_get_path()}"; + } else { + $pi = pathinfo(VARPATH . "thumbnails/{$this->_get_path()}"); + return "{$pi['dirname']}/{$pi['filename']}_resize.{$pi['extension']}"; } - return $children; } } diff --git a/core/tests/Album_Test.php b/core/tests/Album_Test.php new file mode 100644 index 00000000..52ee4833 --- /dev/null +++ b/core/tests/Album_Test.php @@ -0,0 +1,47 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Album_Test extends Unit_Test_Case { + public function create_album_test() { + $rand = rand(); + $album = album::create(1, $rand, $rand, $rand); + + $this->assert_equal(VARPATH . "albums/$rand", $album->path()); + $this->assert_equal(VARPATH . "thumbnails/$rand", $album->thumbnail_path()); + $this->assert_equal(VARPATH . "thumbnails/$rand", $album->resize_path()); + + $this->assert_true(is_dir($album->path()), "missing path: {$album->path()}"); + $this->assert_true(is_dir($album->resize_path()), "missing path: {$album->resize_path()}"); + + $this->assert_equal(1, $album->parent_id); // MPTT tests will cover other hierarchy checks + $this->assert_equal($rand, $album->name); + $this->assert_equal($rand, $album->title); + $this->assert_equal($rand, $album->description); + + $this->assert_equal($album->parent()->right - 2, $album->left); + $this->assert_equal($album->parent()->right - 1, $album->right); + } + + public function create_conflicting_album_test() { + $rand = rand(); + $album1 = album::create(1, $rand, $rand, $rand); + $album2 = album::create(1, $rand, $rand, $rand); + $this->assert_true($album1->name != $album2->name); + } +} diff --git a/core/tests/Core_Installer_Test.php b/core/tests/Core_Installer_Test.php index 7758f472..4903805b 100644 --- a/core/tests/Core_Installer_Test.php +++ b/core/tests/Core_Installer_Test.php @@ -40,11 +40,14 @@ class Core_Installer_Test extends Unit_Test_Case { } public function install_creates_root_item_test() { + $max_right = + Database::instance()->query("SELECT MAX(`right`) AS `right` FROM items")->current()->right; + $root = ORM::factory('item')->find(1); $this->assert_equal("Gallery", $root->title); $this->assert_equal(1, $root->left); - $this->assert_equal(2, $root->right); + $this->assert_equal($max_right, $root->right); $this->assert_equal(null, $root->parent_id); - $this->assert_equal(1, $root->scope); + $this->assert_equal(1, $root->level); } } diff --git a/core/tests/File_Structure_Test.php b/core/tests/File_Structure_Test.php index 00663bc6..d3f4ae23 100644 --- a/core/tests/File_Structure_Test.php +++ b/core/tests/File_Structure_Test.php @@ -110,6 +110,7 @@ class GalleryCodeFilterIterator extends FilterIterator { strstr($path_name, MODPATH . 'forge') !== false || strstr($path_name, MODPATH . 'unit_test') !== false || strstr($path_name, MODPATH . 'mptt') !== false || + strstr($path_name, MODPATH . 'kodoc') !== false || strstr($path_name, DOCROOT . 'var') !== false || strstr($path_name, DOCROOT . 'test') !== false); } diff --git a/core/tests/Item_Test.php b/core/tests/Item_Test.php deleted file mode 100644 index d077932f..00000000 --- a/core/tests/Item_Test.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php defined("SYSPATH") or die("No direct script access."); -/** - * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2008 Bharat Mediratta - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. - */ -class Item_Test extends Unit_Test_Case { - public function create_item_test() { - ORM::factory('item'); - } - - public function create_root_item_test() { - $root = ORM::factory('item')->find(1); - $this->assert_equal("Gallery", $root->title); - } -} diff --git a/core/tests/Photo_Test.php b/core/tests/Photo_Test.php new file mode 100644 index 00000000..8b0a48cc --- /dev/null +++ b/core/tests/Photo_Test.php @@ -0,0 +1,57 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Photo_Test extends Unit_Test_Case { + public function create_photo_test() { + $rand = rand(); + $photo = photo::create(1, DOCROOT . "core/tests/test.jpg", "$rand.jpg", $rand, $rand); + + $this->assert_equal(VARPATH . "albums/$rand.jpg", $photo->path()); + $this->assert_equal(VARPATH . "thumbnails/{$rand}_thumb.jpg", $photo->thumbnail_path()); + $this->assert_equal(VARPATH . "thumbnails/{$rand}_resize.jpg", $photo->resize_path()); + + $this->assert_true(is_file($photo->path()), "missing: {$photo->path()}"); + $this->assert_true(is_file($photo->resize_path()), "missing: {$photo->resize_path()}"); + $this->assert_true(is_file($photo->thumbnail_path()), "missing: {$photo->thumbnail_path()}"); + + $this->assert_equal(1, $photo->parent_id); // MPTT tests will cover other hierarchy checks + $this->assert_equal("$rand.jpg", $photo->name); + $this->assert_equal($rand, $photo->title); + $this->assert_equal($rand, $photo->description); + + $this->assert_equal($photo->parent()->right - 2, $photo->left); + $this->assert_equal($photo->parent()->right - 1, $photo->right); + } + + public function create_conflicting_photo_test() { + $rand = rand(); + $photo1 = photo::create(1, DOCROOT . "core/tests/test.jpg", "$rand.jpg", $rand, $rand); + $photo2 = photo::create(1, DOCROOT . "core/tests/test.jpg", "$rand.jpg", $rand, $rand); + $this->assert_true($photo1->name != $photo2->name); + } + + public function create_photo_with_no_extension_test() { + try { + photo::create(1, "unknown_file", "name", "title", "description"); + $this->assert_false("should fail with an exception"); + } catch (Exception $e) { + // pass + } + } +} diff --git a/core/tests/test.jpg b/core/tests/test.jpg Binary files differnew file mode 100644 index 00000000..1f3525e5 --- /dev/null +++ b/core/tests/test.jpg diff --git a/core/views/welcome.html.php b/core/views/welcome.html.php index b7a290a0..3bc85dc4 100644 --- a/core/views/welcome.html.php +++ b/core/views/welcome.html.php @@ -26,7 +26,7 @@ } p { - margin: 0 0 1em 0; + margin: 0 0 0 0; padding-left: 1em; } @@ -59,6 +59,18 @@ ul { margin-top: -.25em; } + + ul.choices { + padding-top: 0; + padding-left: 1em; + margin: 0px; + } + + ul.choices li { + display: inline; + list-stype-type: none; + padding: 0px; + } </style> </head> <body> @@ -72,24 +84,25 @@ This is a <b><a href="http://www.google.com/images?q=scaffold">scaffold</a></b>: a <i>temporary structure built to support the developers as - they create the real product</i>. - </p> - - <p> - As we flesh out Gallery 3, we'll make it possible for you to - peer inside and see the application taking shape. - Eventually, this page will go away and you'll start in the - application itself. In the meantime, here are some useful - links to get you started. + they create the real product</i>. As we flesh out Gallery 3, + we'll make it possible for you to peer inside and see the + application taking shape. Eventually, this page will go + away and you'll start in the application itself. In the + meantime, here are some useful links to get you started. </p> <h2>System Configuration</h2> <?= $syscheck ?> <h2>Activities</h2> - <p> - <?= html::anchor("album/1", "Browse Gallery") ?> - </p> + <p> <?= html::anchor("album/1", "Browse Gallery") ?> </p> + <ul class="choices"> + <li> add: [</li> + <? foreach (array(1, 10, 50, 100, 500, 1000) as $count): ?> + <li> <?= html::anchor("welcome/add/$count", "$count") ?> </li> + <? endforeach ?> + <li>] photos and albums </li> + </ul> <h2>Documentation</h2> <ul> diff --git a/modules/mptt/libraries/MPTT.php b/modules/mptt/libraries/MPTT.php deleted file mode 100644 index 84182a90..00000000 --- a/modules/mptt/libraries/MPTT.php +++ /dev/null @@ -1,1187 +0,0 @@ -<?php - -/** -* File: libraries/MPTT.php -* -* An implementation of Joe Celko's Nested Sets as a Kohana model with ORM support. -* -* -* Many thanks to -* * Thunder who supplied a similar class for Code Igniter and gave permission to me -* to release it under whatever license I deem necessary: -* http://www.codeigniter.com/wiki/Nested_Sets/ -* * mpatek, his class was the initial start -* http://bakery.cakephp.org/articles/view/modified-preorder-tree-traversal-component -* * Propel, for inspiring some methods and the parent_id and scope stuff -* -* MPTT class -* author - dlib -* license - BSD -*/ - -class MPTT extends ORM { - - public $children; - - public $parent; - - protected $left_column = 'lft'; - - protected $right_column = 'rgt'; - - protected $parent_column = 'parent_id'; - - protected $scope_column = 'scope'; - - protected $_scope = 1; - - /** - * Patch through to ORM construct - * - * @return void - */ - public function __construct($id = FALSE) - { - parent::__construct($id); - - if (!empty($id)) - { - //Set the right scope on new objects - $scope_column=$this->scope_column; - $this->set_scope($this->$scope_column); - $this->where=''; - } - - - } - - ////////////////////////////////////////// - // Lock functions - ////////////////////////////////////////// - - /** - * Locks tree table - * This is a straight write lock - the database blocks until the previous lock is released - */ - protected function lock_tree($aliases = array()) - { - $sql = "LOCK TABLE " . $this->table . " WRITE"; - return self::$db->query($sql); - } - - /** - * Unlocks tree table - * Releases previous lock - */ - protected function unlock_tree() - { - $sql = "UNLOCK TABLES"; - return self::$db->query($sql); - } - - /** - * Bit of an odd function since the node is supplied - * supply a node object and it will return a whole bunch of descendants - * @return tree object - * @param $root_node mixed - - */ - public function get_tree($root_node) - { - - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - - if(is_object($root_node)) - { - $root=$root_node; - } - else - { - $root=$this->get_node($root_node); - } - - $children=$this->new_node(); - $children->where('`'.$lft_col.'`>'.$root->$lft_col.' AND `'.$rgt_col.'`<'.$root->$rgt_col); - $children->where($this->scope_column,$this->get_scope()); - $children->orderby($lft_col,'ASC'); - $children=$children->find_all(); - - if(count($children)>0) - { - - $parent=$root; - $parent->children=array(); - - foreach($children as $child_data) - { - - $child=$child_data; - $child->children=array(); - $child->parent=$parent; - $child->scope=$this->get_scope(); - - while(!$this->_is_descendant_of($child, $parent)) - { - $parent = $parent->parent; - } - - $parent->children[]=$child; - $parent=$child; - - } - } - - return $root; - } - /* - * ***************** - * Retrieval methods - * ***************** - */ - /** - * Current object will obtain its children - * @see http://trac.symfony-project.com/wiki/sfPropelActAsNestedSetBehaviorPlugin - * @see http://www.phpriot.com/articles/nested-trees-2/5 - * @return - */ - public function get_children($return=false) - { - if(!$this->has_descendants()) - return false; - - $parent_id=$this->id; - - $this->children=array(); - $children=$this->new_node(); - $children->where($this->parent_column,$parent_id); - $children->where($this->scope_column,$this->get_scope()); - $children->orderby($this->left_column); - - foreach($children->find_all() as $child) - { - $child->parent=$this; - - $this->children[]=$child; - } - if($return== true) - { - return $this->children; - } - return $this; - } - - /** - * The current object will obtain all descendants - * @return ORM object - * @param $node_id Object - */ - public function get_descendants() - { - if($this->has_descendants()) - return $this->get_tree($this); - } - /** - * Return an array of all leaves in the entire tree - * - * @return array of ORM objects - */ - public function get_leaves() - { - - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - - $leaves=$this->new_node(); - $leaves->where($this->scope_column,$this->get_scope()); - $leaves->where($rgt_col.' = '.$lft_col . ' + 1'); - - return $leaves->find_all(); - } - /** - * Get the path leading up to the current node - * @return array with ORM objects - * - */ - public function get_path() - { - - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - - $path=$this->new_node(); - $path->where($this->scope_column,$this->get_scope()); - $path->where($lft_col . ' <= '.$this->$lft_col . ' AND ' . $rgt_col . ' >=' .$this->$rgt_col . ' ORDER BY '.$lft_col); - - - return $path->find_all(); - - } - - /** - * Returns the root node - * @return array $resultNode The node returned - */ - function get_root() - { - return $this->get_node_where('`'.$this->left_column . '` = 1 '); - } - /** - * Returns a node by id - * @return object - * @param $node_id integer [optional] - */ - function get_node($node_id) - { - $scope_column=$this->scope_column; - $class = get_class($this); - $node=new $class(); - - $node=$node->find($node_id,true); - - return $node; - } - /** - * Returns a new empty node - * @return object - * @param $node_id integer [optional] - */ - function new_node(){ - - $class = get_class($this); - $node=new $class(); - return $node; - } - /** - * Returns one node with where condition - * @return object - * @param $node_id integer [optional] - */ - function get_node_where($where) - { - $scope_column=$this->scope_column; - $class = get_class($this); - $node = new $class(); - $node->where($where); - $node->where($this->scope_column,$this->get_scope()); - - $node=$node->find(false,false); - return $node; - } - /** - * Returns the first child node of the given parentNode - * - * @return array $resultNode The first child of the parent node supplied - */ - function get_first_child() - { - $lft_col = $this->left_column; - - return $this->get_node_where($this->left_column . " = " . ($this->$lft_col+1)); - } - - /** - * Returns the last child node of the given parentNode - * - * @return array $resultNode the last child of the parent node supplied - */ - function get_last_child() - { - $rgt_col = $this->right_column; - - return $this->get_node_where($this->right_column . " = " . ($this->$rgt_col-1)); - } - - /** - * Returns the node that is the immediately prior sibling of the given node - * - * @return array $resultNode The node returned - */ - function get_prev_sibling() - { - $lft_col = $this->left_column; - - return $this->get_node_where($this->right_column . " = " . ($this->$lft_col-1)); - } - - /** - * Returns the node that is the next sibling of the given node - * - * @return array $resultNode The node returned - */ - function get_next_sibling() - { - $rgt_col = $this->right_column; - - return $this->get_node_where($this->left_column . " = " . ($this->$rgt_col+1)); - } - /** - * Returns the node that represents the parent of the given node - * - * @return array $resultNode the node returned - */ - function get_parent() - { - - $leftcol = $this->left_column; - $rightcol = $this->right_column; - - $whereArg = " $leftcol < " . $this->$leftcol . - " AND $rightcol > " . $this->$rightcol . - " ORDER BY $rightcol ASC"; - return $this->get_node_where($whereArg); - } -/* - * ***************** - * Modifying methods - * ***************** - */ - /** - * Adds the first entry to the table (only call once in an empty table) else corruption will follow - * @return $node an array of left and right values - */ - function make_root() - { - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - $scp_col=$this->scope_column; - - if(is_numeric($this->$lft_col) ) - { - Log::add('error', 'Cannot make existing node root' ); - //existing nodes cannot become root - return false; - } - - $this->$lft_col=1; - $this->$rgt_col=2; - $this->$scp_col=$this->get_scope(); - - $this->save_node(); - - return $this; - } - /** - * Not yet implemented - * - */ - function insert_as_parent_of($child_node) - { - - } - /** - * inserts a the object node as the first child of the supplied parent node - * @param array $parentNode The node array of the parent to use - * - * @return - */ - function insert_as_first_child_of($parent_node) - { - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - $scp_col=$this->scope_column; - $parent_column=$this->parent_column; - - //Set parent id (id of the parent, is childs parent id) - $this->$parent_column=$parent_node->id; - - $this->$lft_col=$parent_node->$lft_col+1; - $this->$rgt_col=$parent_node->$lft_col+2; - //Get scope from current object (obsolete) - $this->$scp_col=$this->get_scope(); - - $this->lock_tree(); - $this->modify_node($this->$lft_col,2); - $this->save_node(); - $this->unlock_tree(); - - return $this; - } - /** - * Same as insertNewChild except the new node is added as the last child - * @param array $parentNode The node array of the parent to use - * - * @return - */ - function insert_as_last_child_of($parent_node) - { - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - $scp_col=$this->scope_column; - $parent_column=$this->parent_column; - - //Set parent id (id of the parent, is childs parent id) - $this->$parent_column=$parent_node->id; - - $this->$lft_col=$parent_node->$rgt_col; - $this->$rgt_col=$parent_node->$rgt_col+1; - $this->$scp_col=$this->get_scope(); - - $this->lock_tree(); - $this->modify_node($this->$lft_col,2); - $this->save_node(); - $this->unlock_tree(); - - return $this; - } - /** - * Adds a new node to the left of the supplied focusNode - * @param array $focusNode The node to use as the position marker - * - * @return - */ - function insert_as_prev_sibling_of($focus_node) - { - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - $parent_column=$this->parent_column; - $scp_col=$this->scope_column; - - //Set parent id (siblings have the same parent) - $this->$parent_column=$focus_node->$parent_column; - - $this->$lft_col=$focus_node->$lft_col; - $this->$rgt_col=$focus_node->$lft_col+1; - $this->$scp_col=$this->get_scope(); - - $this->lock_tree(); - $this->modify_node($this->$lft_col,2); - $this->save_node(); - $this->unlock_tree(); - - return $this; - } - /** - * Adds a new node to the right of the supplied focusNode - * @param array $focusNode The node to use as the position marker - * - * @return - */ - function insert_as_next_sibling_of($focus_node) - { - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - $parent_column=$this->parent_column; - $scp_col=$this->scope_column; - - //Set parent id (siblings have the same parent) - $this->$parent_column=$focus_node->$parent_column; - - $this->$lft_col=$focus_node->$rgt_col+1; - $this->$rgt_col=$focus_node->$rgt_col+2; - $this->$scp_col=$this->get_scope(); - - $this->lock_tree(); - $this->modify_node($this->$lft_col,2); - $this->save_node(); - $this->unlock_tree(); - - return $this; - } - /** - * Why not, kill the entire tree - * - * @return - */ - function delete_tree() - { - $where=array($this->scope_column=>$this->get_scope()); - - self::$db->delete($this->table, $where); - return; - } - /** - * - * overrides delete of ORM - */ - public function delete() - { - return $this->delete_node(); - } - /** - * Deletes the given node ((itself) from the tree table - * @param children boolean set to false to not delete children - * and move them up the tree - * @return boolean - */ - function delete_node($children=true) - { - - $table = $this->table; - $leftcol = $this->left_column; - $rightcol = $this->right_column; - $leftanchor = $this->$leftcol; - $leftval = $this->$leftcol; - $rightval = $this->$rightcol; - $parent_column = $this->parent_column; - if($children==true) - { - - $where=$leftcol . '>='.$leftval .' - AND '. $rightcol .' <= ' . $rightval . ' - AND '. $this->scope_column .' = '.$this->get_scope() ; - - $this->lock_tree(); - //Delete node and children - self::$db->delete($this->table, $where); - //Modify other nodes to restore tree integrity - $this->modify_node($rightval+1, $leftval - $rightval - 1); - $this->unlock_tree(); - } - else - { - if($this->is_root()){ - Log::add('error', 'Cannot delete root node without deleting its children' ); - return false; - } - $this->lock_tree(); - //Get children before the parent is gone - //Set parent ids right again - $parent_node=$this->get_node($this->$parent_column); - - $children=$this->get_children(true); - - //Delete the node - $where=array('id'=>$this->id); - self::$db->delete($this->table, $where); - - //First update - $sql = 'UPDATE `' . $this->table . '` - SET '. $this->left_column .'='.$this->left_column .'-1, '. - $this->right_column.' = '.$this->right_column . '-1 - WHERE '. $this->left_column .' >= '.$this->$leftcol . ' - AND '.$this->right_column .' <= '.$this->$rightcol .' - AND '.$this->scope_column.' = '.$this->get_scope().';'; - self::$db->query($sql); - - //Second update - $sql = 'UPDATE `' . $this->table . '` - SET '. $this->left_column .'='.$this->left_column .' -2 - WHERE '. $this->right_column .' > '.$this->$rightcol . '-1 - AND '.$this->left_column .' > '.$this->$rightcol .'-1 - AND '.$this->scope_column.' = '.$this->get_scope().';'; - self::$db->query($sql); - - //Third update - $sql = 'UPDATE `' . $this->table . '` - SET '. $this->right_column .'='.$this->right_column .'-2 - WHERE '. $this->right_column .' > '.$this->$rightcol . '-1 - AND '.$this->scope_column.' = '.$this->get_scope().';'; - self::$db->query($sql); - - //Set the parent ids - if(is_array($children)) - { - foreach($children as $child) - { - $child->$parent_column=$parent_node->id; - $child->save_node(); - } - } - $this->unlock_tree(); - - } - - - return true; - } - - /** - * Delete descendants but not node itself - * - * @return - */ - function delete_descendants() - { - $this->lock_tree(); - $this->get_children(); - foreach($this->children as $child) - { - $child->delete_node(); - - } - $this->unlock_tree(); - $this->children=array(); - return true; - } - /** - * Deletes children but not descendants, - * descendants move up the tree - * @return - */ - function delete_children() - { - $this->get_children(); - foreach($this->children as $child) - { - $child->delete_node(false); - - } - $this->unlock_tree(); - $this->children=array(); - return true; - } - - // ------------------------------------------------------------------------- - // MODIFY/REORGANISE TREE - // - // Methods to move nodes around the tree. Method names should be - // relatively self-explanatory! Hopefully ;) - // - // ------------------------------------------------------------------------- - - //inter-scope moves might be something for later - /** - * Moves the given node to make it the next sibling of "target" - * - * @param array $target The node to use as the position marker - * @return array $newpos The new left and right values of the node moved - */ - function move_to_next_sibling_of( $target) - { - $rgt_col = $this->right_column; - $parent_column=$this->parent_column; - - //Set parent id (siblings have the same parent) - $parent_id=$target->$parent_column; - - //only move when scopes are equal - if($target->get_scope()==$this->get_scope()) - { - $this->update_parent_id($parent_id); - - return $this->move_sub_tree( $target->$rgt_col+1); - } - return false; - } - - /** - * Moves the given node to make it the prior sibling of "target" - * - * @param array $target The node to use as the position marker - * @return array $newpos The new left and right values of the node moved - */ - function move_to_prev_sibling_of( $target) - { - $lft_col = $this->left_column; - $parent_column=$this->parent_column; - - //Set parent id (siblings have the same parent) - $parent_id=$target->$parent_column; - - //only move when scopes are equal - if($target->get_scope()==$this->get_scope()) - { - $this->update_parent_id($parent_id); - - return $this->move_sub_tree( $target->$lft_col); - } - return false; - - } - - /** - * Moves the given node to make it the first child of "target" - * - * @param array $target The node to use as the position marker - * @return array $newpos The new left and right values of the node moved - */ - function move_to_first_child_of( $target) - { - $lft_col = $this->left_column; - $parent_column=$this->parent_column; - - //Set parent id (id of the parent, is childs parent id) - $parent_id=$target->id; - - //only move when scopes are equal - if($target->get_scope()==$this->get_scope()) - { - $this->update_parent_id($parent_id); - - return $this->move_sub_tree( $target->$lft_col+1); - } - return false; - } - - /** - * Moves the given node to make it the last child of "target" - * - * @param array $target The node to use as the position marker - * @return array $newpos The new left and right values of the node moved - */ - function move_to_last_child_of($target) - { - $rgt_col = $this->right_column; - $parent_column=$this->parent_column; - - //Set parent id (id of the parent, is childs parent id) - $parent_id=$target->id; - - //only move when scopes are equal - if($target->get_scope()==$this->get_scope()) - { - $this->update_parent_id($parent_id); - - return $this->move_sub_tree($target->$rgt_col); - } - return false; - } - - /* - * ***************** - * Check methods - * ***************** - */ - /** - * Returns true or false - * (in reality, it checks to see if the given left and - * right values _appear_ to be valid not necessarily that they _are_ valid) - * - * @return boolean - */ - function is_valid_node() - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - - return ($this->$leftcol < $this->$rightcol); - } - - /** - * Tests whether the given node has an ancestor - * (effectively the opposite of isRoot yes|no) - * - * @return boolean - */ - function has_parent() - { - return $this->is_valid_node($this->get_parent()); - } - - /** - * Tests whether the given node has a prior sibling or not - * - * @return boolean - */ - function has_prev_sibling() - { - return $this->is_valid_node($this->get_prev_sibling()); - } - - /** - * Test to see if node has siblings after itself - * - * @return boolean - */ - function has_next_sibling() - { - return $this->is_valid_node($this->get_next_sibling()); - } - - /** - * Test to see if node has children - * - * @return boolean - */ - function has_descendants() - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - return (($this->$rightcol - $this->$leftcol) > 1); - } - - /** - * Test to see if the given node is also the root node - * - * @return boolean - */ - function is_root() - { - $leftcol = $this->left_column; - return ($this->$leftcol == 1); - } - - /** - * Test to see if the given node is a leaf node (ie has no children) - * - * @return boolean - */ - function is_leaf() - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - return (($this->$rightcol - $this->$leftcol) == 1); - } - - /** - * Test to see if the node is a descendant of the given node - * @param array $control_node the node to use as the parent or ancestor - * @return boolean - */ - function is_descendant_of($control_node) - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - - return ( ($this->$leftcol > $control_node->$leftcol) - and ($this->$rightcol < $control_node->$rightcol) - ); - } - - /** - * Test to see if the node is a descendant of the given node - * @param array $control_node the node to use as the parent or ancestor - * @return boolean - */ - function is_child_of($control_node) - { - $child_id=$this->id; - $parent_id=$control_node->id; - - self::$db->select('count(*) as is_child'); - self::$db->from($this->table); - self::$db->where('id',$child_id); - self::$db->where($this->parent_column,$parent_id); - self::$db->where($this->scope_column, $this->get_scope()); - - $result=self::$db->get(); - - if ($row = $result->current()) - { - return $row->is_child > 0; - } - - return false; - - } - - /** - * Test to determine whether the node is infact also the $control_node (is A === B) - * @param array $control_node The node prototype to use for the comparison - * @return boolean - */ - function is_equal_to($control_node) - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - - return (($this->$leftcol==$control_node->$leftcol) and ($this->$rightcol==$control_node->$rightcol)); - } - - /** - * Combination method of is_descendant and is_equal - * - * @param array $controlNode The node prototype to use for the comparison - * @return boolean - */ - function is_descendant_or_equal_to($controlNode) - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - - return (($this->$leftcol>=$control_node->$leftcol) and ($this->$rightcol<=$control_node->$rightcol)); - } - /* - * ***************** - * Informational methods - * ***************** - */ - /** - * Returns the tree level for the given node (assuming root node is at level 0) - * - * @return integer The level of the supplied node - */ - function get_level() - { - $leftval = (int) $this->$lft_col; - $rightval = (int) $this->$rgt_col; - - self::$db->select('COUNT(*) AS level'); - self::$db->from($this->table); - self::$db->where($this->left_column.' < '.$leftval); - self::$db->where($this->right_column.' < '.$rightval); - self::$db->where($this->scope_column, $this->get_scope()); - - $query=self::$db->get(); - if($query->count() > 0) { - $result = $query->current(); - return (int) $result->level; - } else { - return 0; - } - } - /** - * Output number of descendants this node has - * @return integer - * - */ - function get_number_of_descendants(){ - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - // Generate WHERE - - return (int) ( $this->$rgt_col - $this->$lft_col -1)/2; - - } - /** - * Output number of children of this node - * @return integer - */ - function get_number_of_children() - { - - self::$db->select('count(*) as num_children'); - self::$db->from($this->table); - self::$db->where($this->parent_column,$this->id); - self::$db->where($this->scope_column, $this->get_scope()); - - $result=self::$db->get(); - - if($row = $result->current()) - return (int) $row->num_children; - - return -1; - } - /** - * Get current scope of the object - * @return integer - */ - function get_scope() - { - return $this->_scope; - } - /** - * Set scope of current object, retrieved objects calls this in constructor - */ - function set_scope($value) - { - $this->_scope=$value; - return $this; - } - - - - /* ***************************************** - * Print methods, more or less debug methods - * ***************************************** - */ - /** - * Debug tree - */ - function debug_tree($tree, $disp_col, $ind = '') - { - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - $parent_column=$this->parent_column; - - echo $ind .'#'.$tree->id.' '.$tree->$lft_col.'- '.$tree->$disp_col .' p:'.$tree->$parent_column.' -'.$tree->$rgt_col.'<br>'; - if(is_array($tree->children)) - { - foreach($tree->children as $child) - { - $this->debug_tree($child,$disp_col,'....'.$ind); - } - } - - - } - /** - * Will rebuild tree according to the parent_id column - * Warning, the order of the tree might not exactly be maintained - * Might be slow for big trees - * Call this method only with the root_id and its left value. - * @return - * @param $parent_id Object - * @param $left Object - */ - function rebuild_tree($parent_id, $left) { - $this->lock_tree(); - // the right value of this node is the left value + 1 - $right = $left+1; - - // get all children of this node - self::$db->select('id'); - self::$db->where($this->parent_column, $parent_id); - self::$db->where($this->scope_column, $this->get_scope()); - self::$db->from($this->table); - $result=self::$db->get(); - - foreach ($result as $row) { - // recursive execution of this function for each - // child of this node - // $right is the current right value, which is - // incremented by the rebuild_tree function - $right = $this->rebuild_tree($row->id, $right); - } - - // we've got the left value, and now that we've processed - // the children of this node we also know the right value - - self::$db->set($this->left_column, $left); - self::$db->set($this->right_column, $right); - self::$db->where('id',$parent_id); - self::$db->where($this->scope_column, $this->get_scope()); - self::$db->update($this->table); - // return the right value of this node + 1 - return $right+1; - - $this->unlock_tree(); - } - - /* - * Protected functions - * - */ - /** - * check whether child is child of parent (internal) - * - * @return - * @param $child Object - * @param $parent Object - */ - protected function _is_descendant_of($child, $parent) - { - $lft_col = $this->left_column; - $rgt_col = $this->right_column; - - return ($child->$lft_col > $parent->$lft_col && $child->$rgt_col < $parent->$rgt_col); - } - - /** - * The method that performs moving/renumbering operations - * - * @param array $targetValue Position integer to use as the target - * @return array $newpos The new left and right values of the node moved - * @access private - */ - protected function move_sub_tree($targetValue) - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - - $sizeOfTree = $this->$rightcol - $this->$leftcol + 1; - $this->modify_node($targetValue, $sizeOfTree); - - - if($this->$leftcol >= $targetValue) - { - $this->$leftcol += $sizeOfTree; - $this->$rightcol += $sizeOfTree; - } - - $newpos = $this->modify_node_range($this->$leftcol, $this->$rightcol, $targetValue - $this->$leftcol); - - $this->modify_node($this->$rightcol+1, - $sizeOfTree); - - if($this->$leftcol <= $targetValue) - { - $newpos[$this->left_column] -= $sizeOfTree; - $newpos[$this->right_column] -= $sizeOfTree; - } - - return $newpos; - } - /** - * _modifyNodeRange - * - * @param $lowerbound integer value of lowerbound of range to move - * @param $upperbound integer value of upperbound of range to move - * @param $changeVal unsigned integer of change amount - * @access private - */ - - protected function modify_node_range($lowerbound, $upperbound, $changeVal) - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - $table = $this->table; - $scope_col = $this->scope_column; - - $sql = "UPDATE $table - SET $leftcol = $leftcol + $changeVal - WHERE $leftcol >= $lowerbound - AND $leftcol <= $upperbound - AND ".$this->scope_column.' = '.$this->$scope_col.';'; - - self::$db->query($sql); - - $sql = "UPDATE $table - SET $rightcol = $rightcol + $changeVal - WHERE $rightcol >= $lowerbound - AND $rightcol <= $upperbound - AND ".$this->scope_column.' = '.$this->$scope_col.';'; - - self::$db->query($sql); - - $retArray = array( - $this->left_column => $lowerbound+$changeVal, - $this->right_column => $upperbound+$changeVal - ); - return $retArray; - } // END: Method _modifyNodeRange - - /** - * Update the parent id of this record, the ORM class handles - * it when the parent id didn't change - * @return - * @param $node Object - * @param $parent_id Object - */ - protected function update_parent_id($parent_id) - { - $parent_column=$this->parent_column; - $this->$parent_column=$parent_id; - return $this->save_node(); - } - /** - * _modifyNode - * - * Adds $changeVal to all left and right values that are greater than or - * equal to $node_int - * - * @param $node_int The value to start the shift from - * @param $changeVal unsigned integer value for change - * @access private - */ - protected function modify_node($node_int, $changeVal) - { - $leftcol = $this->left_column; - $rightcol = $this->right_column; - $table = $this->table; - $scope_col = $this->scope_column; - - $sql = "UPDATE $table " . - "SET $leftcol = $leftcol + $changeVal ". - "WHERE $leftcol >= $node_int - AND ".$this->scope_column.' = '.$this->$scope_col.';'; - - self::$db->query($sql); - - $sql = "UPDATE $table " . - "SET $rightcol = $rightcol + $changeVal ". - "WHERE $rightcol >= $node_int - AND ".$this->scope_column.' = '.$this->$scope_col.';'; - - self::$db->query($sql); - - return true; - } // END: _modifyNode - /** - * save_node - * - * Inserts a new node into the tree, or saves the current one - * - * @return boolean True/False dependent upon the success of the operation - * @access private - */ - protected function save_node( ) - { - if ($this->save()) { - // Return true on success - return TRUE; - } - - return false; - } -} diff --git a/themes/default/images/carousel.png b/themes/default/images/carousel.png Binary files differnew file mode 100644 index 00000000..e516b1d3 --- /dev/null +++ b/themes/default/images/carousel.png diff --git a/themes/default/views/footer.html.php b/themes/default/views/footer.html.php index ab40a417..9a080a57 100644 --- a/themes/default/views/footer.html.php +++ b/themes/default/views/footer.html.php @@ -1,7 +1,9 @@ <? defined("SYSPATH") or die("No direct script access."); ?> - <div id="gFooter"> - Powered by <a href="#">GalleryX</a> | <a href="#">About this Gallery</a> | ... - </div> + <div id="ft"> + <div id="gFooter"> + Powered by <a href="#">Gallery3</a> | <a href="#">About this Gallery</a> | ... + </div><!-- END #gFooter --> + </div><!-- END YUI #ft --> </div> </body> </html> diff --git a/themes/default/views/photo.html.php b/themes/default/views/photo.html.php new file mode 100644 index 00000000..e4d47d61 --- /dev/null +++ b/themes/default/views/photo.html.php @@ -0,0 +1,64 @@ +<? defined("SYSPATH") or die("No direct script access."); ?> +<?= $theme->display('header.html') ?> +<div id="bd"> + <div id="yui-main"> + <div id="gContent" class="yui-b"> + + <div id="gItem"> + <a href="" class="buttonlink">Full size (1024x768)</a> + <a href="" class="buttonlink">Slideshow</a> + + <img id="photo-id-1" alt="photo" src="images/thumbnail.jpg" /> + <h1>Photo title</h1> + <p>Photo description: Lorem ipsum dolor sit amet.</p> + </div> + + <div id="gComments"> + <h2>Comments</h2> + + <ul id="gCommentThread"> + <li id="gComment-1" class="gComment odd"> + <p><a href="#" class="gAuthor">Andy</a> said 2 hours ago <span class="understate">(October 23, 2008 11:30am)</span></p> + <div> + Lorem ipsum dolor sit amet. + </div> + </li> + <li id="gComment-2" class="gComment even"> + <p> + <a href="#" class="gAuthor">Other user</a> said 30 minutes ago <span class="understate">(October 23, 2008 1:00pm)</span> + </p> + <div> + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard + dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. + It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It + was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with + desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. + </div> + </li> + </ul> + + <form id="gCommentAdd" class="gExpandedForm"> + <fieldset> + <legend>Add comment</legend> + <div class="row"> + <label for="gCommentAuthor">Your Name</label> + <input type="text" id="gCommentAuthor" class="text" /> + </div> + <div> + <label for="gCommentEmail">Your Email (not displayed)</label> + <input type="text" id="gCommentEmail" class="text" /> + </div> + <div class="row"> + <label for="gCommentText">Comment</label> + <textarea id="gCommentText"></textarea> + </div> + <input type="submit" class="button" value="Add" /> + </fieldset> + </form> + </div><!-- END #gComments --> + + </div><!-- END #gContent --> + </div><!-- END yui-main --> + <?= $theme->display('sidebar.html') ?> +</div><!-- END YUI #bd --> +<?= $theme->display('footer.html'); ?> diff --git a/themes/default/views/sidebar.html.php b/themes/default/views/sidebar.html.php index f8acc25b..9f4509e3 100644 --- a/themes/default/views/sidebar.html.php +++ b/themes/default/views/sidebar.html.php @@ -1,106 +1,24 @@ <? defined("SYSPATH") or die("No direct script access."); ?> <div id="gSidebar" class="yui-b"> - <div id="gAlbumTree"> - <h2>Album Navigation</h2> - - <div id="gTreeContainer"><!-- there might be a better way to make this accessible to the JS --> - <ul> - <li>Gallery - <ul> - <li>Friends & Family - <ul> - <li>Christmas 2006</li> - <li>Family Reunion</li> - <li>Christmas 2007</li> - </ul> - </li> - <li>Vactions - <ul> - <li>Cuba</li> - <li>Europe</li> - </ul> - </li> - </ul> - </li> - </ul> + <div id="gCarousel" class="gBlock"> + <div class="gBlockHeader"> + <h2>Album: <a href="browse.html">Christmas 2007</a></h2> + <a href="#" class="minimize">[-]</a> </div> - </div> - - <script type="text/javascript"> - //global variable to allow console inspection of tree: - var tree; - - //anonymous function wraps the remainder of the logic: - (function() { - - //function to initialize the tree: - function treeInit() { - buildRandomTextNodeTree(); - } - - //Function creates the tree and - //builds between 3 and 7 children of the root node: - function buildRandomTextNodeTree() { - //instantiate the tree: - tree = new YAHOO.widget.TreeView("gTreeContainer"); - - // Expand and collapse happen prior to the actual expand/collapse, - // and can be used to cancel the operation - tree.subscribe("expand", function(node) { - YAHOO.log(node.index + " was expanded", "info", "example"); - // return false; // return false to cancel the expand - }); - - tree.subscribe("collapse", function(node) { - YAHOO.log(node.index + " was collapsed", "info", "example"); - }); - - // Trees with TextNodes will fire an event for when the label is clicked: - tree.subscribe("labelClick", function(node) { - YAHOO.log(node.index + " label was clicked", "info", "example"); - }); - - //The tree is not created in the DOM until this method is called: - tree.draw(); - } - - //Add an onDOMReady handler to build the tree when the document is ready - YAHOO.util.Event.onDOMReady(treeInit); - })(); - </script> + <img src="<?= $theme->url("images/carousel.png") ?>" width="214" class="gBlockContent" /> + </div> - <table class="gMetadata"> - <caption><h2>Album Info</h2></caption> - <tbody> - <tr> - <th>Name:</th> - <td><strong>Christmas 2007</strong></td> - </tr> - <tr> - <th>Taken:</th> - <td><span class="date" title="January 21, 2008 8:30pm">January 21, 2008</td> - </tr> - <tr> - <th>Location:</th> - <td><a href="#" title="see the location on a map">Mountain View</a></td> - </tr> - <tr> - <th>Owner:</th> - <td><a href="#">username</a></td> - </tr> - <tr> - <th>Uploaded:</th> - <td><span class="date" title="October 23, 2008 11:37am">October 23, 2008</td> - </tr> - </tbody> - </table> + <div class="gTagCloud gBlock"> + <div class="gBlockHeader"> + <h2>Tags</h2> + <a href="#">?</a> + <a href="#" class="minimize">[-]</a> + </div> - <div class="gTagCloud"> - <h2>Tag cloud</h2> - <ul> + <ul class="gBlockContent"> <li><a href="#" class="m size0">animation</a></li> <li><a href="#" class="m size0">art</a></li> <li><a href="#" class="m size1">blind</a></li> @@ -137,5 +55,54 @@ <li><a href="#" class="m size1">usability</a></li> <li><a href="#" class="m size0">writing</a></li> </ul> + + <form id="gAddTag" class="gBlockContent"> + <input type="text" class="text" value="add new tags ..." id="newtags" /> + <input type="submit" class="submit" value="add" /> + <label for="newtags" class="understate">(separated by commas)</label> + </form> + </div> + + <div class="gBlock"> + <div class="gBlockHeader"> + <h2>Item Info</h2> + <a href="#" class="minimize">[-]</a> + </div> + <table class="gMetadata gBlockContent"> + <tbody> + <tr> + <th>Title:</th> + <td>Christmas 2007</td> + </tr> + <tr> + <th>Taken:</th> + <td>January 21, 2008</td> + </tr> + <tr> + <th>Uploaded:</th> + <td>January 27, 2008</td> + </tr> + <tr> + <th>Owner:</th> + <td><a href="#">username</a></td> + </tr> + <tr> + <td colspan="2" class="toggle"> + <a href="#">more \/</a> + </td> + </tr> + </tbody> + </table> </div> -</div> + + <div class="gBlock"> + <div class="gBlockHeader"> + <h2>Location</h2> + <a href="#" class="minimize">[-]</a> + </div> + <iframe class="gBlockContent" width="214" height="214" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://maps.google.com/maps?f=q&hl=en&geocode=&q=mountain+view&sll=37.0625,-95.677068&sspn=50.823846,89.648437&ie=UTF8&z=12&g=mountain+view&ll=37.433704,-122.056046&output=embed&s=AARTsJoyjpSOFMFEv5XZbREeW_hGGS28pQ"></iframe> + <br /> + <small class="gBlockContent"><a href="http://maps.google.com/maps?f=q&hl=en&geocode=&q=mountain+view&sll=37.0625,-95.677068&sspn=50.823846,89.648437&ie=UTF8&z=12&g=mountain+view&ll=37.433704,-122.056046&source=embed" style="color:#0000FF;text-align:left">View Larger Map</a></small> + </div> + +</div><!-- END #gSideBar --> |