c63e599e701b6a94131d32d6aef634a40151424a
Author: AD7six
Date: 2008-11-20 12:43:16 +0100
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..00d12ab
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,5 @@
+<IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteRule ^$ webroot/ [L]
+ RewriteRule (.*) webroot/$1 [L]
+ </IfModule>
\ No newline at end of file
diff --git a/app_controller.php b/app_controller.php
new file mode 100644
index 0000000..1da7543
--- /dev/null
+++ b/app_controller.php
@@ -0,0 +1,307 @@
+<?php
+/* SVN FILE: $Id: app_controller.php 697 2008-11-10 20:50:32Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * This file is application-wide controller file. You can put all
+ * application-wide controller-related methods here.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 697 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-10 21:50:32 +0100 (Mon, 10 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * AppController class
+ *
+ * @uses Controller
+ * @package cookbook
+ * @subpackage cookbook
+ */
+class AppController extends Controller {
+/**
+ * components variable
+ *
+ * @var array
+ * @access public
+ */
+ var $components = array('Auth', 'Users.Bakery', 'RequestHandler');
+/**
+ * helpers variable
+ *
+ * @var array
+ * @access public
+ */
+ var $helpers = array('Tree', 'Menu', 'Html', 'Form', 'Time', 'Javascript', 'Cache');
+/**
+ * currentNode variable
+ *
+ * @var bool
+ * @access public
+ */
+ var $currentNode = false;
+/**
+ * currentPath variable
+ *
+ * @var bool
+ * @access public
+ */
+ var $currentPath = array();
+/**
+ * beforeFilter function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeFilter() {
+ // Store where they came from
+ $realReferer = $this->referer(null, true);
+ $sessionReferer = $this->Session->read('referer');
+ if ($this->name == 'App') {
+ } elseif (empty ($this->data) && !isset($this->params['requested'])) {
+ if ($realReferer) {
+ if ((!$sessionReferer) || ($realReferer != '/' . $this->params['url']['url'])) {
+ $this->Session->write('referer', $realReferer);
+ }
+ } elseif (!$sessionReferer) {
+ $this->Session->write('referer', $this->referer(array('action' => 'index')));
+ }
+ } elseif (!$sessionReferer) {
+ $this->Session->write('referer', $this->referer(array('action' => 'index')));
+ }
+
+ $this->params['lang'] = isset($this->params['lang'])?$this->params['lang']:
+ (isset($this->params['named']['lang'])?$this->params['named']['lang']:'en');
+ Configure::write('Config.language', $this->params['lang']);
+ if (($this->name != 'App') && !in_array($this->params['lang'], Configure::read('Languages.all'))) {
+ $this->Session->setFlash(__('Whoops, not a valid language.', true));
+ return $this->redirect($this->Session->read('referer'), 301, true);
+ }
+
+ if (!isset($this->params['requested']) && strpos($this->params['url']['url'], 'en') === 0) {
+ return $this->redirect($this->params['pass'], 301, true);
+ }
+
+ if (!in_array($this->params['lang'], array(null, 'en'))) {
+ if (isset($this->Node)) {
+ $this->Node->setLanguage($this->params['lang']);
+ } elseif (isset($this->{$this->modelClass}->Node)) {
+ $this->{$this->modelClass}->Node->setLanguage($this->params['lang']);
+ }
+ }
+ if (!$this->Auth->user()) {
+ $this->Auth->authError = __('Please login to continue', true);
+ }
+ $this->Auth->loginRedirect = $this->Session->read('referer');
+ $this->Auth->autoRedirect = false;
+ $this->Auth->allow('display');
+ $this->{$this->modelClass}->currentUserId = $this->Auth->user('id');
+ }
+/**
+ * beforeRender function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeRender() {
+ if (!isset ($this->viewVars['data'])) {
+ $this->set('data', $this->data);
+ }
+ $this->set('modelClass', $this->modelClass);
+ $this->set('isAdmin', isset($this->params['admin']));
+ if ($this->name == 'App' && Configure::read()) {
+ $this->layout = 'error';
+ }
+ }
+/**
+ * redirect function
+ *
+ * @param mixed $url
+ * @param mixed $code
+ * @param bool $exit
+ * @access public
+ * @return void
+ */
+ function redirect($url, $code = null, $exit = true) {
+ if (!isset($this->params['lang'])) {
+ $this->params['lang'] = 'en';
+ }
+ if (!isset($url['lang']) && !in_array($this->params['lang'], array(null, 'en'))) {
+ $url['lang'] = $this->params['lang'];
+ }
+ return parent::redirect($url, $code, $exit);
+ }
+/**
+ * admin_add function
+ *
+ * @access public
+ * @return void
+ */
+ function admin_add () {
+ if (!empty ($this->data)) {
+ $this->data['Revision']['user_id'] = $this->Auth->user('id');
+ if ($this->{$this->modelClass}->save($this->data)) {
+ $this->Session->setFlash($this->{$this->modelClass}->name . ' added');
+ $this->redirect($this->Session->read('referer'), null, true);
+ } else {
+ $this->Session->setFlash('Please correct the errors below.');
+ }
+ }
+ // Populate belongTo select list vars
+ foreach (array('belongsTo', 'hasAndBelongsToMany') as $type) {
+ foreach (array_keys($this->{$this->modelClass}->$type) as $model) {
+ if (is_array($this->{$this->modelClass}->$model->actsAs) && array_key_exists('Tree', $this->{$this->modelClass}->$model->actsAs)) {
+ $items = $this->{$this->modelClass}->$model->generateTreeList();
+ } else {
+ if (is_array($this->{$this->modelClass}->$model->displayField)) {
+ $order = implode($this->{$this->modelClass}->$model->displayField , ', ');
+ } else {
+ $order = $this->{$this->modelClass}->$model->alias . '.' . $this->{$this->modelClass}->$model->displayField;
+ }
+ $items = $this->{$this->modelClass}->$model->find('list', compact('order'));
+ }
+ $this->set(Inflector::underscore(Inflector::pluralize($model)), $items);
+ }
+ }
+ $this->render('admin_edit');
+ }
+/**
+ * admin_delete function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_delete($id) {
+ if ($this->{$this->modelClass}->del($id)) {
+ $this->Session->setFlash($this->modelClass . ' with id ' . $id . ' deleted');
+ } else {
+ $this->Session->setFlash('Can\'t delete ' . $this->modelClass . ' with id ' . $id);
+ }
+ $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_edit function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_edit($id) {
+ if (!$this->{$this->modelClass}->hasAny(array($this->{$this->modelClass}->primaryKey => $id))) {
+ $this->redirect(array('action' => 'index'), null, true);
+ }
+ if (!empty ($this->data)) {
+ //$this->data['Revision']['user_id'] = $this->Auth->user('id');
+ if ($this->{$this->modelClass}->save($this->data)) {
+ $this->Session->setFlash($this->{$this->modelClass}->alias . ' updated');
+ $this->redirect($this->Session->read('referer'), null, true);
+ } else {
+ $this->Session->setFlash('Please correct the errors below.');
+ }
+ } else {
+ $this->data = $this->{$this->modelClass}->read(null, $id);
+ }
+ // Populate belongTo select list vars
+ foreach (array('belongsTo', 'hasAndBelongsToMany') as $type) {
+ foreach (array_keys($this->{$this->modelClass}->$type) as $model) {
+ if (is_array($this->{$this->modelClass}->$model->actsAs) && array_key_exists('Tree', $this->{$this->modelClass}->$model->actsAs)) {
+ $items = $this->{$this->modelClass}->$model->generateTreeList();
+ } else {
+ if (is_array($this->{$this->modelClass}->$model->displayField)) {
+ $order = implode($this->{$this->modelClass}->$model->displayField , ', ');
+ } else {
+ $order = $this->{$this->modelClass}->$model->alias . '.' . $this->{$this->modelClass}->$model->displayField;
+ }
+ $items = $this->{$this->modelClass}->$model->find('list', compact('order'));
+ }
+ $this->set(Inflector::underscore(Inflector::pluralize($model)), $items);
+ }
+ }
+ }
+/**
+ * admin_view function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_view ($id) {
+ if (!$this->{$this->modelClass}->hasAny(array($this->{$this->modelClass}->primaryKey => $id))) {
+ $this->redirect(array('action' => 'index'), null, true);
+ }
+ $this->data = $this->{$this->modelClass}->read(null, $id);
+ if(!$this->data) {
+ $this->Session->setFlash('Invalid ' . $this->modelClass);
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+ }
+/**
+ * admin_index function
+ *
+ * @access public
+ * @return void
+ */
+ function admin_index() {
+ if (!isset($this->__conditions)) {
+ App::import('Component', 'Filter');
+ $this->Filter =& new FilterComponent();
+ $this->Component->_loadComponents($this->Filter);
+ $this->Filter->startup($this);
+ $conditions = $this->Filter->parse();
+ } else {
+ $conditions = $this->__conditions;
+ }
+ $Node = ClassRegistry::init('Node');
+ $collections = $Node->find('all', array('conditions' => array('Node.parent_id' => 1), 'fields' => 'Node.*, Revision.title'));
+ $books = $Node->find('all', array('conditions' => array('Node.depth' => 2), 'fields' => 'Node.*, Revision.title'));
+ $this->set(compact('collections', 'books'));
+ $this->data = $this->paginate($conditions);
+ }
+/**
+ * admin_search method
+ *
+ * @param mixed $term
+ * @access public
+ * @return void
+ */
+ function admin_search($term = null) {
+ if ($this->data) {
+ $term = trim($this->data[$this->modelClass]['query']);
+ $this->redirect(array(urlencode($term)));
+ }
+ if (!$term) {
+ $this->redirect(array('action' => 'index'));
+ }
+ $this->__conditions = $this->{$this->modelClass}->searchConditions($term);
+ $this->Session->setFlash(sprintf(__('All %s matching the term "%s"', true), Inflector::humanize($this->name), htmlspecialchars($term)));
+ $this->admin_index();
+ $this->render('admin_index');
+ }
+
+ function appError($message = 'x') {
+ if (Configure::read()) {
+ debug (func_get_args()); die;
+ }
+ $this->Session->setFlash('Whoops! nothing to see there');
+ $this->redirect('/');
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/app_helper.php b/app_helper.php
new file mode 100755
index 0000000..e76234f
--- /dev/null
+++ b/app_helper.php
@@ -0,0 +1,78 @@
+<?php
+/* SVN FILE: $Id: app_helper.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * This file is application-wide helper file. You can put all
+ * application-wide helper-related methods here.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2005-2007, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2007, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cake
+ * @subpackage cake.cake
+ * @since CakePHP(tm) v 0.2.9
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * This is a placeholder class.
+ * Create the same file in app/app_helper.php
+ *
+ * Add your application-wide methods in the class below, your helpers
+ * will inherit them.
+ *
+ * @package cake
+ * @subpackage cake.cake
+ */
+class AppHelper extends Helper {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'App';
+/**
+ * url function
+ *
+ * @param mixed $url
+ * @param bool $full
+ * @access public
+ * @return void
+ */
+ function url($url = null, $full = false) {
+ if (is_array($url)) {
+ if (isset($url['lang'])) {
+ if ($url['lang'] == 'en') {
+ unset ($url['lang']);
+ }
+ } elseif (!empty($this->params['lang']) && !in_array($this->params['lang'], array(null, 'en'))) {
+ $url['lang'] = $this->params['lang'];
+ }
+
+ }
+ $return = Router::url($url, $full);
+ $id = Configure::read('Site.homeNode');
+ if (strpos($return, 'view/' . $id . '/')) {
+ $return = $this->webroot;
+ if (isset($url['lang'])) {
+ $return .= $url['lang'] . '/';
+ }
+ }
+ return $return;
+ }
+}
+?>
\ No newline at end of file
diff --git a/app_model.php b/app_model.php
new file mode 100644
index 0000000..630abae
--- /dev/null
+++ b/app_model.php
@@ -0,0 +1,140 @@
+<?php
+/* SVN FILE: $Id: app_model.php 689 2008-11-05 10:30:07Z AD7six $ */
+
+/**
+ * Application model for Cake.
+ *
+ * This file is application-wide model file. You can put all
+ * application-wide model-related methods here.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * Application model for Cake.
+ *
+ * Add your application-wide methods in the class below, your models
+ * will inherit them.
+ *
+ * @package cake
+ * @subpackage cake.app
+ */
+class AppModel extends Model {
+/**
+ * Convenience method to enable debugging from any point in a models use - and enable logging
+ * from that point forward (will remove any queries logged before the call)
+ *
+ * @param int $val
+ * @param bool $force
+ * @access public
+ * @return void
+ */
+ function debug($val = 2, $force = false) {
+ if ($force || Configure::read()) {
+ Configure::write('debug',$val);
+ $db =& ConnectionManager::getDataSource($this->useDbConfig);
+ if ($val > 1) {
+ $db->fullDebug = true;
+ $db->_queriesCnt = 0;
+ $db->_queriesTime = null;
+ $db->_queriesLog = array();
+ $db->_queriesLogMax = 200;
+ } else {
+ $db->fullDebug = false;
+ }
+ }
+ }
+/**
+ * searchConditions method
+ *
+ * Get generic search conditions - searching in all fields of the model, and checking associated models if
+ * appropriate. Override to make more specific
+ *
+ * @param string $term
+ * @param bool $extended
+ * @return void
+ * @access public
+ */
+ function searchConditions($term = '', $extended = false) {
+ if (strpos($term, '%') === false) {
+ $term = '%' . $term . '%';
+ }
+ $models = array($this);
+ if ($this->recursive != -1) {
+ foreach (array('belongsTo', 'hasOne') as $association) {
+ foreach ($this->$association as $alias => $modelArray) {
+ if (is_string($modelArray)) {
+ $alias = $modelArray;
+ }
+ if ($this->$alias->useDbConfig != $this->useDbConfig) {
+ continue;
+ }
+ $models[] = $this->$alias;
+ }
+ }
+ }
+ $conditions = array();
+ foreach ($models as $model) {
+ foreach ($model->schema() as $key => $details) {
+ if (in_array($details['type'], array('string', 'text'))) {
+ $conditions['OR'][$model->alias . '.' . $key . ' LIKE'] = $term;
+ }
+ }
+ }
+ return $conditions;
+ }
+/**
+ * noHtml method
+ *
+ * @param mixed $vals
+ * @return void
+ * @access public
+ */
+ function noHtml($vals) {
+ foreach ($vals as $val) {
+ $noHtml = strip_tags($val);
+ if ($noHtml != $val) {
+ return false;
+ }
+ }
+ return true;
+ }
+/**
+ * schema method
+ *
+ * Prevent fullDebug for describe queries, so they aren't in the log
+ *
+ * @param bool $field
+ * @return void
+ * @access public
+ */
+ function schema($field = false) {
+ $db =& ConnectionManager::getDataSource($this->useDbConfig);
+ $fullDebug = $db->fullDebug;
+ $db->fullDebug = false;
+ $return = parent::schema($field);
+ $db->fullDebug = $fullDebug;
+ return $return;
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/config/acl.ini.php b/config/acl.ini.php
new file mode 100644
index 0000000..e31659e
--- /dev/null
+++ b/config/acl.ini.php
@@ -0,0 +1,76 @@
+;<?php die() ?>
+; SVN FILE: $Id: acl.ini.php 689 2008-11-05 10:30:07Z AD7six $
+;/**
+; * Short description for file.
+; *
+; *
+; * PHP versions 4 and 5
+; *
+; * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+; * Copyright (c) 2006, Cake Software Foundation, Inc.
+; * 1785 E. Sahara Avenue, Suite 490-204
+; * Las Vegas, Nevada 89104
+; *
+; * Licensed under The MIT License
+; * Redistributions of files must retain the above copyright notice.
+; *
+; * @filesource
+; * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+; * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+; * @package cake
+; * @subpackage cake.app.config
+; * @since CakePHP v 0.10.0.1076
+; * @version $Revision: 689 $
+; * @modifiedby $LastChangedBy: AD7six $
+; * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+; * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+; */
+
+; acl.ini.php - Cake ACL Configuration
+; ---------------------------------------------------------------------
+; Use this file to specify user permissions.
+; aco = access control object (something in your application)
+; aro = access request object (something requesting access)
+;
+; User records are added as follows:
+;
+; [uid]
+; groups = group1, group2, group3
+; allow = aco1, aco2, aco3
+; deny = aco4, aco5, aco6
+;
+; Group records are added in a similar manner:
+;
+; [gid]
+; allow = aco1, aco2, aco3
+; deny = aco4, aco5, aco6
+;
+; The allow, deny, and groups sections are all optional.
+; NOTE: groups names *cannot* ever be the same as usernames!
+;
+; ACL permissions are checked in the following order:
+; 1. Check for user denies (and DENY if specified)
+; 2. Check for user allows (and ALLOW if specified)
+; 3. Gather user's groups
+; 4. Check group denies (and DENY if specified)
+; 5. Check group allows (and ALLOW if specified)
+; 6. If no aro, aco, or group information is found, DENY
+;
+; ---------------------------------------------------------------------
+
+;-------------------------------------
+;Users
+;-------------------------------------
+
+[username-goes-here]
+groups = group1, group2
+deny = aco1, aco2
+allow = aco3, aco4
+
+;-------------------------------------
+;Groups
+;-------------------------------------
+
+[groupname-goes-here]
+deny = aco5, aco6
+allow = aco7, aco8
\ No newline at end of file
diff --git a/config/bootstrap.php b/config/bootstrap.php
new file mode 100644
index 0000000..3cc8d9f
--- /dev/null
+++ b/config/bootstrap.php
@@ -0,0 +1,71 @@
+<?php
+/* SVN FILE: $Id: bootstrap.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 0.10.8.2117
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ *
+ * This file is loaded automatically by the app/webroot/index.php file after the core bootstrap.php is loaded
+ * This is an application wide file to load any function that is not used within a class define.
+ * You can also use this to include or require any files in your application.
+ *
+ */
+/**
+ * The settings below can be used to set additional paths to models, views and controllers.
+ * This is related to Ticket #470 (https://trac.cakephp.org/ticket/470)
+ *
+ * $modelPaths = array('full path to models', 'second full path to models', 'etc...');
+ * $viewPaths = array('this path to views', 'second full path to views', 'etc...');
+ * $controllerPaths = array('this path to controllers', 'second full path to controllers', 'etc...');
+ *
+ */
+//EOF
+// Which node to use for the home page
+Configure::write('Site.homeNode', 3);
+
+Configure::write('Site.name', 'CakePHP Cookbook');
+
+Configure::write('Site.email', 'team@cakefoundation.org');
+
+Configure::write('Site.database', 'bakery');
+
+$langs = array('ar', 'en', 'fa', 'fr', 'de', 'es', 'pt', 'nl', 'id', 'it', 'ja', 'bg', 'hu', 'pl', 'cz', 'cn', 'ko', 'ro');
+sort($langs);
+Configure::write('Languages.all', $langs);
+define('ADMIN', '800');
+define('EDITOR', '700');
+define('MODERATOR', '600');
+define('COMMENTER', '300');
+define('READ', '200');
+define('NONE', '100');
+define('INVALID', '0');
+if (Configure::read()) {
+ define('CACHE_DURATION', '+1 minute');
+} else {
+ define('CACHE_DURATION', '+99 days');
+ ob_start('ob_gzhandler');
+}
+?>
\ No newline at end of file
diff --git a/config/database.php.default b/config/database.php.default
new file mode 100644
index 0000000..f520a8c
--- /dev/null
+++ b/config/database.php.default
@@ -0,0 +1,98 @@
+<?php
+/* SVN FILE: $Id: database.php.default 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * This is core configuration file.
+ *
+ * Use it to configure core behaviour ofCake.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2005-2007, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2007, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP(tm) v 0.2.9
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * In this file you set up your database connection details.
+ *
+ * @package cake
+ * @subpackage cake.config
+ */
+/**
+ * Database configuration class.
+ * You can specify multiple configurations for production, development and testing.
+ *
+ * driver => The name of a supported driver; valid options are as follows:
+ * mysql - MySQL 4 & 5,
+ * mysqli - MySQL 4 & 5 Improved Interface (PHP5 only),
+ * sqlite - SQLite (PHP5 only),
+ * postgres - PostgreSQL 7 and higher,
+ * mssql - Microsoft SQL Server 2000 and higher,
+ * db2 - IBM DB2, Cloudscape, and Apache Derby (http://php.net/ibm-db2)
+ * oracle - Oracle 8 and higher
+ * adodb-[drivername] - ADOdb interface wrapper (see below),
+ * pear-[drivername] - PEAR::DB wrapper
+ *
+ * You can add custom database drivers (or override existing drivers) by adding the
+ * appropriate file to app/models/datasources/dbo. Drivers should be named 'dbo_x.php',
+ * where 'x' is the name of the database.
+ *
+ * persistent => true / false
+ * Determines whether or not the database should use a persistent connection
+ *
+ * connect =>
+ * ADOdb set the connect to one of these
+ * (http://phplens.com/adodb/supported.databases.html) and
+ * append it '|p' for persistent connection. (mssql|p for example, or just mssql for not persistent)
+ * For all other databases, this setting is deprecated.
+ *
+ * host =>
+ * the host you connect to the database
+ * To add a port number use 'port' => #
+ *
+ * prefix =>
+ * Uses the given prefix for all the tables in this database. This setting can be overridden
+ * on a per-table basis with the Model::$tablePrefix property.
+ *
+ */
+class DATABASE_CONFIG {
+ var $default = array(
+ 'driver' => 'mysql',
+ 'persistent' => false,
+ 'host' => 'localhost',
+ 'port' => '',
+ 'login' => 'user',
+ 'password' => 'password',
+ 'database' => 'database_name',
+ 'schema' => '',
+ 'prefix' => '',
+ 'encoding' => ''
+ );
+ var $test = array(
+ 'driver' => 'mysql',
+ 'persistent' => false,
+ 'host' => 'localhost',
+ 'port' => '',
+ 'login' => 'user',
+ 'password' => 'password',
+ 'database' => 'test_database_name',
+ 'schema' => '',
+ 'prefix' => '',
+ 'encoding' => ''
+ );
+}
+?>
\ No newline at end of file
diff --git a/config/inflections.php b/config/inflections.php
new file mode 100644
index 0000000..a42bebc
--- /dev/null
+++ b/config/inflections.php
@@ -0,0 +1,72 @@
+<?php
+/* SVN FILE: $Id: inflections.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Custom Inflected Words.
+ *
+ * This file is used to hold words that are not matched in the normail Inflector::pluralize() and
+ * Inflector::singularize()
+ *
+ * PHP versions 4 and %
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 1.0.0.2312
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * This is a key => value array of regex used to match words.
+ * If key matches then the value is returned.
+ *
+ * $pluralRules = array('/(s)tatus$/i' => '\1\2tatuses', '/^(ox)$/i' => '\1\2en', '/([m|l])ouse$/i' => '\1ice');
+ */
+ $pluralRules = array();
+/**
+ * This is a key only array of plural words that should not be inflected.
+ * Notice the last comma
+ *
+ * $uninflectedPlural = array('.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox');
+ */
+ $uninflectedPlural = array();
+/**
+ * This is a key => value array of plural irregular words.
+ * If key matches then the value is returned.
+ *
+ * $irregularPlural = array('atlas' => 'atlases', 'beef' => 'beefs', 'brother' => 'brothers')
+ */
+ $irregularPlural = array();
+/**
+ * This is a key => value array of regex used to match words.
+ * If key matches then the value is returned.
+ *
+ * $singularRules = array('/(s)tatuses$/i' => '\1\2tatus', '/(matr)ices$/i' =>'\1ix','/(vert|ind)ices$/i')
+ */
+ $singularRules = array();
+/**
+ * This is a key only array of singular words that should not be inflected.
+ * You should not have to change this value below if you do change it use same format
+ * as the $uninflectedPlural above.
+ */
+ $uninflectedSingular = $uninflectedPlural;
+/**
+ * This is a key => value array of singular irregular words.
+ * Most of the time this will be a reverse of the above $irregularPlural array
+ * You should not have to change this value below if you do change it use same format
+ *
+ * $irregularSingular = array('atlases' => 'atlas', 'beefs' => 'beef', 'brothers' => 'brother')
+ */
+ $irregularSingular = array_flip($irregularPlural);
+?>
\ No newline at end of file
diff --git a/config/lucene.php b/config/lucene.php
new file mode 100644
index 0000000..689ec15
--- /dev/null
+++ b/config/lucene.php
@@ -0,0 +1,30 @@
+<?php
+
+class Lucene_Config {
+ var $default = array(
+ 'index_file' => 'search_index',
+ 'Revision' => array(
+ 'find_options' => array('conditions' => array('Revision.status' => 'current'), 'order' => 'Revision.id ASC'),
+ 'fields' => array(
+ 'id' => array('alias' => 'cake_id','type' => 'Keyword'),
+ 'slug' => array('type' => 'UnIndexed'),
+ 'title' => array('type' => 'Text'),
+ 'content' => array('type' => 'Text', 'prepare' => 'strip_tags'),
+ 'lang' => array('type' => 'Keyword'),
+ 'book' => array('type' => 'Keyword'),
+ 'collection' => array('type' => 'Keyword'),
+ 'node_id' => array('type' => 'Keyword'),
+ ),
+ ),
+ 'Comment' => array(
+ 'find_options' => array('conditions' => array('Comment.published' => 1), 'order' => 'Comment.id ASC'),
+ 'fields' => array(
+ 'id' => array('alias' => 'cake_id','type' => 'UnIndexed'),
+ 'title' => array('type' => 'Text'),
+ 'lang' => array('type' => 'Keyword'),
+ 'body' => array('type' => 'Text', 'alias' => 'content'),
+ ),
+ )
+ );
+}
+?>
\ No newline at end of file
diff --git a/config/routes.php b/config/routes.php
new file mode 100644
index 0000000..fdb5daa
--- /dev/null
+++ b/config/routes.php
@@ -0,0 +1,99 @@
+<?php
+/* SVN FILE: $Id: routes.php 703 2008-11-19 12:13:40Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * In this file, you set up routes to your controllers and their actions.
+ * Routes are very important mechanism that allows you to freely connect
+ * different urls to chosen controllers and their actions (functions).
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 703 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-19 13:13:40 +0100 (Wed, 19 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Here, we are connecting '/' (base path) to controller called 'Pages',
+ * its action called 'display', and we pass a param to select the view file
+ * to use (in this case, /app/views/pages/home.thtml)...
+ */
+if (!empty($fromUrl) && strpos($fromUrl, 'admin') === 0) {
+ Router::connectNamed(true);
+} else {
+ Router::connectNamed(array('node', 'user', 'language', 'status'), array('default' => true));
+}
+Router::parseExtensions('rss', 'xml');
+
+Router::connect('/', array('controller' => 'nodes', 'action' => 'index'), array('lang' => 'en'));
+Router::connect('/:lang/', array('controller' => 'nodes', 'action' => 'index'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/:lang/stats/*', array('controller' => 'nodes', 'action' => 'stats'), array('lang' => '[a-z]{2}'));
+Router::connect('/stats', array('controller' => 'nodes', 'action' => 'stats'), array('lang' => 'en'));
+
+Router::connect('/:lang/todo/*', array('controller' => 'nodes', 'action' => 'todo'), array('lang' => '[a-z]{2}'));
+Router::connect('/todo', array('controller' => 'nodes', 'action' => 'todo'), array('lang' => 'en'));
+
+Router::connect('/:lang/view/*', array('controller' => 'nodes', 'action' => 'view'), array('lang' => '[a-z]{2}'));
+Router::connect('/view/*', array('controller' => 'nodes', 'action' => 'view'), array('lang' => 'en'));
+
+Router::connect('/search/*', array('controller' => 'revisions', 'action' => 'search'), array('lang' => 'en'));
+Router::connect('/:lang/search/*', array('controller' => 'revisions', 'action' => 'search'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/results/*', array('controller' => 'revisions', 'action' => 'results'), array('lang' => 'en'));
+Router::connect('/:lang/results/*', array('controller' => 'revisions', 'action' => 'results'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/compare/*', array('controller' => 'nodes', 'action' => 'compare'), array('lang' => 'en'));
+Router::connect('/:lang/compare/*', array('controller' => 'nodes', 'action' => 'compare'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/changes/*', array('controller' => 'changes', 'action' => 'index'), array('lang' => 'en'));
+Router::connect('/:lang/changes/*', array('controller' => 'changes', 'action' => 'index'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/history/*', array('controller' => 'nodes', 'action' => 'history'), array('lang' => 'en'));
+Router::connect('/:lang/history/*', array('controller' => 'nodes', 'action' => 'history'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/toc/*', array('controller' => 'nodes', 'action' => 'toc'), array('lang' => 'en'));
+Router::connect('/:lang/toc/*', array('controller' => 'nodes', 'action' => 'toc'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/complete/*', array('controller' => 'nodes', 'action' => 'single_page'), array('lang' => 'en'));
+Router::connect('/:lang/complete/*', array('controller' => 'nodes', 'action' => 'single_page'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/edit/*', array('controller' => 'nodes', 'action' => 'edit'), array('lang' => 'en'));
+Router::connect('/:lang/edit/*', array('controller' => 'nodes', 'action' => 'edit'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/add/*', array('controller' => 'nodes', 'action' => 'add'), array('lang' => 'en'));
+Router::connect('/:lang/add/*', array('controller' => 'nodes', 'action' => 'add'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/comments/:id/*', array('controller' => 'comments', 'action' => 'index'), array('id' => '[0-9]+', 'lang' => 'en'));
+Router::connect('/:lang/comments/:id/*', array('controller' => 'comments', 'action' => 'index'), array('id' => '[0-9]+', 'lang' => '[a-z]{2}'));
+
+Router::connect('/comment/*', array('controller' => 'comments', 'action' => 'add'), array('lang' => 'en'));
+Router::connect('/:lang/comment/*', array('controller' => 'comments', 'action' => 'add'), array('lang' => '[a-z]{2}'));
+
+Router::connect('/admin', array('prefix' => 'admin', 'controller' => 'revisions', 'action' => 'pending', 'admin' => true), array('admin' => true));
+Router::connect('/:lang/admin', array('prefix' => 'admin', 'controller' => 'revisions', 'action' => 'pending', 'admin' => true), array('admin' => true, 'lang' => '[a-z]{2}'));
+
+Router::connect('/img/*', array('controller' => 'attachments', 'action' => 'view'), array('lang' => 'en'));
+// Legacy
+Router::connect('/chapter/*', array('controller' => 'redirect', 'action' => 'process', 'chapter'));
+Router::connect('/appendix/*', array('controller' => 'redirect', 'action' => 'process', 'appendix'));
+
+if (isset($fromUrl) && strpos('admin', $fromUrl) !== false) {
+ Router::connectNamed(array('query', 'collection', 'lang'), array('greedy' => false, 'default' => true));
+}
+?>
\ No newline at end of file
diff --git a/config/sql/mode_m.dot b/config/sql/mode_m.dot
new file mode 100644
index 0000000..265cd50
--- /dev/null
+++ b/config/sql/mode_m.dot
@@ -0,0 +1,21 @@
+digraph G {
+overlap=false;
+splines=true;
+edge [fontname="Helvetica",fontsize=8];
+ranksep=0.1;
+nodesep=0.1;
+node [shape=record,fontname="Helvetica",fontsize=9];
+ "Change" [shape=Mrecord,label="{<0> Change|{<f0> id\lrevision_id\luser_id\lauthor_id\lstatus_from\lstatus_to\lcomment\lcreated\l|<f1> integer[11]\lstring[36]\linteger[11]\linteger[11]\lstring[10]\lstring[10]\lstring[255]\ldatetime\l}}"];
+ "Node" [shape=Mrecord,label="{<0> Node|{<f0> id\lshow_in_toc\llft\lrght\lparent_id\lstatus\lcomment_level\ledit_level\ldepth\lsequence\lcreated\lmodified\l|<f1> string[36]\lboolean[1]\linteger[10]\linteger[10]\lstring[36]\linteger[2]\linteger[4], default: \"200\"\linteger[4], default: \"200\"\linteger[2]\lstring[20]\ldatetime\ldatetime\l}}"];
+ "Comment" [shape=Mrecord,label="{<0> Comment|{<f0> id\lnode_id\luser_id\lrevision_id\llang\ltitle\lauthor\lemail\lurl\lbody\lpublished\lcreated\lmodified\l|<f1> integer[10]\linteger[10]\linteger[10]\linteger[10]\lstring[2]\lstring[150]\lstring[255]\lstring[255]\lstring[255]\ltext\lboolean[1], default: \"1\"\ldatetime\ldatetime\l}}"];
+ "Revision" [shape=Mrecord,label="{<0> Revision|{<f0> id\lnode_id\lunder_node_id\lafter_node_id\lstatus\luser_id\llang\lslug\ltitle\lcontent\ltype\lreason\lflags\lcreated\lmodified\l|<f1> string[36]\lstring[36]\lstring[36]\lstring[36]\lstring[30], default: \"pending\"\linteger[10]\lstring[3]\lstring[30]\lstring[200]\ltext\lstring[50]\lstring[300]\lstring[100]\ldatetime\ldatetime\l}}"];
+ "Change" -> "User" [label="Change->User"]
+ "Change" -> "User" [label="Change->Author"]
+ "Change" -> "Revision" [label="Change->Revision"]
+ "Change" -> "Node" [label="Change->Node"]
+ "Revision" -> "Node" [label="Revision->Node"]
+ "Comment" -> "Node" [label="Comment->Node"]
+ "Comment" -> "Revision" [label="Comment->Revision"]
+ "Comment" -> "User" [label="Comment->User"]
+ "Revision" -> "User" [label="Revision->User"]
+}
diff --git a/config/sql/mode_t.dot b/config/sql/mode_t.dot
new file mode 100644
index 0000000..745c0bd
--- /dev/null
+++ b/config/sql/mode_t.dot
@@ -0,0 +1,17 @@
+digraph G {
+overlap=false;
+splines=true;
+edge [fontname="Helvetica",fontsize=8];
+ranksep=0.1;
+nodesep=0.1;
+node [shape=record,fontname="Helvetica",fontsize=9];
+ "Attachment" [shape=Mrecord,label="{<0> Attachment|{<f0> id\luser_id\lclass\lforeign_id\lfilename\lext\ldir\lmimetype\lfilesize\lheight\lwidth\ldescription\lchecksum\lcreated\lmodified\l|<f1> integer[11]\linteger[11]\lstring[30]\lstring[36]\lstring[255]\lstring[6], default: \"gif\"\lstring[255]\lstring[30]\linteger[11]\linteger[4]\linteger[4]\lstring[100]\lstring[32]\ldatetime\ldatetime\l}}"];
+ "Change" [shape=Mrecord,label="{<0> Change|{<f0> id\lrevision_id\luser_id\lauthor_id\lstatus_from\lstatus_to\lcomment\lcreated\l|<f1> integer[11]\lstring[36]\linteger[11]\linteger[11]\lstring[10]\lstring[10]\lstring[255]\ldatetime\l}}"];
+ "Comment" [shape=Mrecord,label="{<0> Comment|{<f0> id\lnode_id\luser_id\lrevision_id\llang\ltitle\lauthor\lemail\lurl\lbody\lpublished\lcreated\lmodified\l|<f1> integer[10]\linteger[10]\linteger[10]\linteger[10]\lstring[2]\lstring[150]\lstring[255]\lstring[255]\lstring[255]\ltext\lboolean[1], default: \"1\"\ldatetime\ldatetime\l}}"];
+ "Node" [shape=Mrecord,label="{<0> Node|{<f0> id\lshow_in_toc\llft\lrght\lparent_id\lstatus\lcomment_level\ledit_level\ldepth\lsequence\lcreated\lmodified\l|<f1> string[36]\lboolean[1]\linteger[10]\linteger[10]\lstring[36]\linteger[2]\linteger[4], default: \"200\"\linteger[4], default: \"200\"\linteger[2]\lstring[20]\ldatetime\ldatetime\l}}"];
+ "Revision" [shape=Mrecord,label="{<0> Revision|{<f0> id\lnode_id\lunder_node_id\lafter_node_id\lstatus\luser_id\llang\lslug\ltitle\lcontent\ltype\lreason\lflags\lcreated\lmodified\l|<f1> string[36]\lstring[36]\lstring[36]\lstring[36]\lstring[30], default: \"pending\"\linteger[10]\lstring[3]\lstring[30]\lstring[200]\ltext\lstring[50]\lstring[300]\lstring[100]\ldatetime\ldatetime\l}}"];
+ "Change" -> "Revision" [label="revision_id"]
+ "Comment" -> "Node" [label="node_id"]
+ "Comment" -> "Revision" [label="revision_id"]
+ "Revision" -> "Node" [label="node_id"]
+}
diff --git a/config/sql/sample_data.sql b/config/sql/sample_data.sql
new file mode 100644
index 0000000..0362f7d
--- /dev/null
+++ b/config/sql/sample_data.sql
@@ -0,0 +1,14 @@
+INSERT INTO `nodes` (`id`, `lft`, `rght`, `parent_id`, `status`, `comment_level`, `edit_level`, `depth`, `sequence`, `created`, `modified`) VALUES
+(1, 1, 6, NULL, 1, 200, 600, 0, '', '2008-01-16 17:08:41', '2008-07-09 20:09:59'),
+(2, 2, 5, 1, 1, 200, 600, 1, '', '2008-01-16 17:08:41', '2008-07-09 20:09:59'),
+(3, 3, 4, 2, 1, 200, 200, 2, '', '2008-01-16 17:08:41', '2008-07-09 20:09:59');
+
+--
+-- Dumping data for table `revisions`
+--
+
+INSERT INTO `revisions` (`id`, `node_id`, `under_node_id`, `after_node_id`, `status`, `user_id`, `lang`, `slug`, `title`, `content`, `type`, `reason`, `flags`, `created`, `modified`) VALUES
+(1, 2, 0, 0, 'current', 1, 'en', '1-2-collection', '1.2 Collection', '<p>All things related to CakePHP version 1.2</p>', NULL, NULL, '', '2008-02-19 01:30:18', '2008-02-19 01:30:19'),
+(2, 1, 0, 0, 'current', 1, 'en', 'collection-index', 'Collection index', '<p>All you ever wanted to know about CakePHP.</p>', NULL, '', '', '2008-07-01 07:56:33', '2008-07-01 11:17:48'),
+(3, 3, 0, 0, 'current', 1, 'en', 'the-manual', 'The Manual', '<p><strong><a href="http://book.cakephp.org/view/305/the-manual">Click here for the CakePHP 1.1.x version of the manual</a></strong></p>\r\n\r\n<p>Welcome to the Cookbook, the CakePHP documentation. The Cookbook is a wiki-like system allowing contributions from the public. With an open system, we hope to maintain a high level of quality, validity, and accuracy for the CakePHP documentation. The Cookbook also makes it easy for anybody to contribute.</p>\r\n\r\n<p>A <em><strong>huge</strong></em> thank you to <a href="http://www.ad7six.com/" title="Mi Blog">AD7six</a>, who championed the Cookbook by putting in endless hours developing, testing and improving this application.</p>\r\n<h4>How it Works:</h4>\r\n<ol>\r\n <li>You visit the site and notice an error, something that is incomplete, something that hasn''t been covered at all, or something that just isn''t worded to your liking.</li>\r\n <li>Log in to Cookbook using your <a href="http://bakery.cakephp.org" title="The Bakery: Everything CakePHP">Bakery</a> account.</li>\r\n <li>Submit additions/edits for review using valid, semantic HTML.</li>\r\n <li>Check back in the next day or so to see your changes approved.</li>\r\n <li>Please review <a href="http://manual.cakephp.org/view/482/contributing-to-the-cookbook">the guidelines for submitting to the Cookbook</a> to ensure consistency.</li>\r\n</ol>\r\n<br/>\r\n<h4>Translations</h4>\r\n<p>Email John David Anderson (docs at cakephp dot org) or on IRC (#cakephp on freenode as _psychic_) to discuss any translation efforts you would like to participate in.</p>\r\n<p>Translator tips:</p>\r\n<ul>\r\n <li>Do not use <a href="http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references" title="What Are HTML Entities?">html entities</a> for accented characters, the book uses UTF-8.</li>\r\n <li>Use <a href="http://en.wikipedia.org/wiki/Register_%28linguistics%29" title="What is Informal Form?">Informal Form</a>.</li>\r\n <li>Translate both the content and the title at the same time.</li>\r\n <li>Browse and edit in the language the content is being translated to - otherwise it get''s logged as an English Edit with only a slim chance that a reviewer knows what language you are writing in.</li>\r\n</ul>\r\n<br />\r\n<p>We''re committed to making the documentation for CakePHP better than it has ever been. We hope you''ll join us by using the Cookbook and giving back to a project that we''ve all benefited so much from.</p>', NULL, 'I changed the title', '', '2008-07-08 10:43:59', '2008-07-09 11:06:38');
+
diff --git a/config/sql/schema.php b/config/sql/schema.php
new file mode 100644
index 0000000..f11a0b9
--- /dev/null
+++ b/config/sql/schema.php
@@ -0,0 +1,93 @@
+<?php
+/* SVN FILE: $Id: schema.php 673 2008-10-06 14:05:17Z AD7six $ */
+/* Cookbook schema generated on: 2008-07-28 20:07:15 : 1217271015*/
+class CookbookSchema extends CakeSchema {
+ var $name = 'Cookbook';
+
+ function before($event = array()) {
+ return true;
+ }
+
+ function after($event = array()) {
+ }
+
+ var $attachments = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL),
+ 'class' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 30, 'key' => 'index'),
+ 'foreign_id' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 36),
+ 'filename' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'ext' => array('type'=>'string', 'null' => false, 'default' => 'gif', 'length' => 6),
+ 'dir' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'mimetype' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 30),
+ 'filesize' => array('type'=>'integer', 'null' => true, 'default' => NULL),
+ 'height' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 4),
+ 'width' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 4),
+ 'description' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 100),
+ 'checksum' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 32),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'idxfk_foreign' => array('column' => array('class', 'foreign_id'), 'unique' => 0))
+ );
+ var $changes = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
+ 'revision_id' => array('type'=>'integer', 'null' => false, 'default' => NULL),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL),
+ 'author_id' => array('type'=>'integer', 'null' => false, 'default' => NULL),
+ 'status_from' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'status_to' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'comment' => array('type'=>'string', 'null' => false, 'default' => NULL),
+ 'created' => array('type'=>'datetime', 'null' => false, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+ var $comments = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'node_id' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 10),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'revision_id' => array('type'=>'integer', 'null' => true, 'default' => '0', 'length' => 10),
+ 'lang' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 2),
+ 'title' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 150),
+ 'author' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'email' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'url' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'body' => array('type'=>'text', 'null' => true, 'default' => NULL),
+ 'published' => array('type'=>'boolean', 'null' => false, 'default' => '1'),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+ var $nodes = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'lft' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'rght' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'status' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 2),
+ 'comment_level' => array('type'=>'integer', 'null' => false, 'default' => '200', 'length' => 4),
+ 'edit_level' => array('type'=>'integer', 'null' => false, 'default' => '200', 'length' => 4),
+ 'show_in_toc' => array('type'=>'boolean', 'null' => false, 'default' => '1'),
+ 'depth' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 2),
+ 'sequence' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 20),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'LFT_RGHT' => array('column' => array('lft', 'rght'), 'unique' => 0), 'RGHT_LFT' => array('column' => array('lft', 'rght', 'rght', 'lft'), 'unique' => 0))
+ );
+ var $revisions = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'node_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'under_node_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'after_node_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'status' => array('type'=>'string', 'null' => false, 'default' => 'pending', 'length' => 30),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'lang' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 3),
+ 'slug' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 50),
+ 'title' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 200),
+ 'content' => array('type'=>'text', 'null' => true, 'default' => NULL),
+ 'type' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 50),
+ 'reason' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 300),
+ 'flags' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 100),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'node_id' => array('column' => array('node_id', 'lang', 'status'), 'unique' => 0))
+ );
+}
+?>
\ No newline at end of file
diff --git a/config/sql/schema.sql b/config/sql/schema.sql
new file mode 100644
index 0000000..b5169a6
--- /dev/null
+++ b/config/sql/schema.sql
@@ -0,0 +1,91 @@
+#Cookbook sql generated on: 2008-07-28 20:07:20 : 1217271020
+
+DROP TABLE IF EXISTS `attachments`;
+DROP TABLE IF EXISTS `changes`;
+DROP TABLE IF EXISTS `comments`;
+DROP TABLE IF EXISTS `nodes`;
+DROP TABLE IF EXISTS `revisions`;
+
+
+CREATE TABLE `attachments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL,
+ `class` varchar(30) NOT NULL,
+ `foreign_id` varchar(36) NOT NULL,
+ `filename` varchar(255) DEFAULT NULL,
+ `ext` varchar(6) DEFAULT 'gif' NOT NULL,
+ `dir` varchar(255) DEFAULT NULL,
+ `mimetype` varchar(30) DEFAULT NULL,
+ `filesize` int(11) DEFAULT NULL,
+ `height` int(4) DEFAULT NULL,
+ `width` int(4) DEFAULT NULL,
+ `description` varchar(100) NOT NULL,
+ `checksum` varchar(32) DEFAULT NULL,
+ `created` datetime DEFAULT NULL,
+ `modified` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY idxfk_foreign (`class`, `foreign_id`));
+
+CREATE TABLE `changes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `revision_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `status_from` varchar(10) NOT NULL,
+ `status_to` varchar(10) NOT NULL,
+ `comment` varchar(255) NOT NULL,
+ `created` datetime NOT NULL,
+ PRIMARY KEY (`id`));
+
+CREATE TABLE `comments` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `node_id` int(10) DEFAULT 0 NOT NULL,
+ `user_id` int(10) NOT NULL,
+ `revision_id` int(10) DEFAULT 0,
+ `lang` varchar(2) NOT NULL,
+ `title` varchar(150) DEFAULT NULL,
+ `author` varchar(255) DEFAULT NULL,
+ `email` varchar(255) DEFAULT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `body` text DEFAULT NULL,
+ `published` tinyint(1) DEFAULT 1 NOT NULL,
+ `created` datetime DEFAULT NULL,
+ `modified` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`));
+
+CREATE TABLE `nodes` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `lft` int(10) NOT NULL,
+ `rght` int(10) NOT NULL,
+ `parent_id` int(10) DEFAULT NULL,
+ `status` int(2) DEFAULT 0 NOT NULL,
+ `comment_level` int(4) DEFAULT 200 NOT NULL,
+ `edit_level` int(4) DEFAULT 200 NOT NULL,
+ `show_in_toc` tinyint(1) DEFAULT 1 NOT NULL,
+ `depth` int(2) DEFAULT 0 NOT NULL,
+ `sequence` varchar(20) NOT NULL,
+ `created` datetime DEFAULT NULL,
+ `modified` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY LFT_RGHT (`lft`, `rght`),
+ KEY RGHT_LFT (`lft`, `rght`, `rght`, `lft`));
+
+CREATE TABLE `revisions` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `node_id` int(10) NOT NULL,
+ `under_node_id` int(10) DEFAULT NULL,
+ `after_node_id` int(10) DEFAULT NULL,
+ `status` varchar(30) DEFAULT 'pending' NOT NULL,
+ `user_id` int(10) NOT NULL,
+ `lang` varchar(3) DEFAULT NULL,
+ `slug` varchar(50) DEFAULT NULL,
+ `title` varchar(200) DEFAULT NULL,
+ `content` text DEFAULT NULL,
+ `type` varchar(50) DEFAULT NULL,
+ `reason` varchar(300) DEFAULT NULL,
+ `flags` varchar(100) DEFAULT NULL,
+ `created` datetime DEFAULT NULL,
+ `modified` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY node_id (`node_id`, `lang`, `status`));
+
diff --git a/config/sql/schema_uuid.php b/config/sql/schema_uuid.php
new file mode 100644
index 0000000..2fa1fc4
--- /dev/null
+++ b/config/sql/schema_uuid.php
@@ -0,0 +1,74 @@
+<?php
+/* SVN FILE: $Id: schema_uuid.php 566 2008-07-14 09:23:58Z AD7six $ */
+/* Cookbook schema generated on: 2008-07-14 11:07:35 : 1216026935*/
+class CookbookUuidSchema extends CakeSchema {
+ var $name = 'CookbookUuid';
+
+ function before($event = array()) {
+ return true;
+ }
+
+ function after($event = array()) {
+ }
+
+ var $changes = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
+ 'revision_id' => array('type'=>'integer', 'null' => false, 'default' => NULL),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL),
+ 'author_id' => array('type'=>'integer', 'null' => false, 'default' => NULL),
+ 'status_from' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'status_to' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'comment' => array('type'=>'string', 'null' => false, 'default' => NULL),
+ 'created' => array('type'=>'datetime', 'null' => false, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+ var $comments = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'node_id' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 36),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'revision_id' => array('type'=>'integer', 'null' => true, 'default' => '0', 'length' => 10),
+ 'lang' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 2),
+ 'title' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 150),
+ 'author' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'email' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'url' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'body' => array('type'=>'text', 'null' => true, 'default' => NULL),
+ 'published' => array('type'=>'boolean', 'null' => false, 'default' => '1'),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+ var $nodes = array(
+ 'id' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 36, 'key' => 'primary'),
+ 'lft' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'rght' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'parent_id' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 36),
+ 'status' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 2),
+ 'comment_level' => array('type'=>'integer', 'null' => false, 'default' => '200', 'length' => 4),
+ 'edit_level' => array('type'=>'integer', 'null' => false, 'default' => '200', 'length' => 4),
+ 'depth' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 2),
+ 'sequence' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 20),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'LFT_RGHT' => array('column' => array('lft', 'rght'), 'unique' => 0), 'RGHT_LFT' => array('column' => array('lft', 'rght', 'rght', 'lft'), 'unique' => 0))
+ );
+ var $revisions = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
+ 'node_id' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 36, 'key' => 'index'),
+ 'under_node_id' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 36),
+ 'after_node_id' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 36),
+ 'status' => array('type'=>'string', 'null' => false, 'default' => 'pending', 'length' => 30),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'lang' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 3),
+ 'slug' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 30),
+ 'title' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 200),
+ 'content' => array('type'=>'text', 'null' => true, 'default' => NULL),
+ 'type' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 50),
+ 'reason' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 300),
+ 'flags' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 100),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'node_id' => array('column' => array('node_id', 'lang', 'status'), 'unique' => 0))
+ );
+}
+?>
diff --git a/config/sql/schematic_m_dot.png b/config/sql/schematic_m_dot.png
new file mode 100644
index 0000000..2db573b
Binary files /dev/null and b/config/sql/schematic_m_dot.png differ
diff --git a/config/sql/schematic_m_dotGmodeheir.png b/config/sql/schematic_m_dotGmodeheir.png
new file mode 100644
index 0000000..2db573b
Binary files /dev/null and b/config/sql/schematic_m_dotGmodeheir.png differ
diff --git a/config/sql/schematic_m_neato.png b/config/sql/schematic_m_neato.png
new file mode 100644
index 0000000..aff422a
Binary files /dev/null and b/config/sql/schematic_m_neato.png differ
diff --git a/config/sql/schematic_m_neatoGmodelsubset.png b/config/sql/schematic_m_neatoGmodelsubset.png
new file mode 100644
index 0000000..8e1e1f8
Binary files /dev/null and b/config/sql/schematic_m_neatoGmodelsubset.png differ
diff --git a/config/sql/schematic_t_dot.png b/config/sql/schematic_t_dot.png
new file mode 100644
index 0000000..b4b69ac
Binary files /dev/null and b/config/sql/schematic_t_dot.png differ
diff --git a/config/sql/schematic_t_dotGmodeheir.png b/config/sql/schematic_t_dotGmodeheir.png
new file mode 100644
index 0000000..b4b69ac
Binary files /dev/null and b/config/sql/schematic_t_dotGmodeheir.png differ
diff --git a/config/sql/schematic_t_neato.png b/config/sql/schematic_t_neato.png
new file mode 100644
index 0000000..23316e0
Binary files /dev/null and b/config/sql/schematic_t_neato.png differ
diff --git a/config/sql/schematic_t_neatoGmodelsubset.png b/config/sql/schematic_t_neatoGmodelsubset.png
new file mode 100644
index 0000000..f8ad8f2
Binary files /dev/null and b/config/sql/schematic_t_neatoGmodelsubset.png differ
diff --git a/controllers/attachments_controller.php b/controllers/attachments_controller.php
new file mode 100755
index 0000000..4bc0824
--- /dev/null
+++ b/controllers/attachments_controller.php
@@ -0,0 +1,343 @@
+<?php
+/* SVN FILE: $Id: attachments_controller.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for attachments_controller.php
+ *
+ * Long description for attachments_controller.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package base
+ * @subpackage base.controllers
+ * @since v 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * AttachmentsController class
+ *
+ * @uses AppController
+ * @package base
+ * @subpackage base.controllers
+ */
+class AttachmentsController extends AppController {
+/**
+ * name property
+ *
+ * @var string 'Attachments'
+ * @access public
+ */
+ var $name = 'Attachments';
+/**
+ * paginate property
+ *
+ * @var array
+ * @access public
+ */
+ var $paginate = array('limit' => 10, 'order' => 'Attachment.id DESC');
+/**
+ * publicAccess property
+ *
+ * If set to true, you don't need to login to see uploaded, mediaView served, content.
+ * Otherwise, you do.
+ *
+ * @var bool true
+ * @access public
+ */
+ var $publicAccess = true;
+/**
+ * beforeFilter method
+ *
+ * @return void
+ * @access public
+ */
+ function beforeFilter() {
+ if (isset($this->params['admin'])) {
+ $this->helpers[] = 'Number';
+ }
+ if ($this->publicAccess && isset($this->Auth)) {
+ $this->Auth->allow('view');
+ }
+ parent::beforeFilter();
+ }
+/**
+ * admin_add method
+ *
+ * GET request for /admin/add/ModelName/ModelId will create an attachment for ModelName.ModelId
+ *
+ * @param string $class
+ * @param mixed $foreignKey
+ * @access public
+ * @return void
+ */
+ function admin_add ($class = null, $foreignKey = null) {
+ if (!$class|| !$foreignKey) {
+ $this->Session->setFlash(__('Attachment controller - no class or foreignKey error.', true));
+ $this->_back();
+ }
+ $this->Attachment->bindModel(array(
+ 'belongsTo' => array(
+ $class => array (
+ 'class' => $class,
+ 'conditions' => array('Attachment.class' => $class)
+ )
+ )
+ ));
+
+ $pClass = Inflector::pluralize($class);
+ if ($this->data) {
+ $this->data[$this->modelClass]['foreign_id'] = $foreignKey;
+ $this->data[$this->modelClass]['class'] = $class;
+ $this->data[$this->modelClass]['user_id'] = $this->Auth->user('id');
+ $editTest['class'] = $class;
+ $editTest['foreign_id'] = $foreignKey;
+ $editTest['filename'] = $this->data[$this->modelClass]['filename']['name'];
+ if ($id = $this->{$this->modelClass}->field('id', $editTest)) {
+ $this->data[$this->modelClass]['id'] = $id;
+ }
+ if ($this->Attachment->save($this->data)) {
+ if ($id) {
+ $this->Session->setFlash(sprintf(__('Existing Attachment for %s, id %s updated', true), $class, $this->data[$this->modelClass]['foreign_id']));
+ } else {
+ $this->Session->setFlash(sprintf(__('New Attachment for %s, id %s added', true), $class, $this->data[$this->modelClass]['foreign_id']));
+ }
+ $this->redirect(array(
+ 'controller' => Inflector::underscore(Inflector::Pluralize($class)),
+ 'action' => 'edit',
+ $this->data[$this->modelClass]['foreign_id']),
+ null, true);
+ }
+ }
+ $this->_setSelects($class);
+ $this->render('admin_add');
+ }
+/**
+ * admin_edit method
+ *
+ * Edit descriptions etc for an attacment. Not used to upload a new version of a file
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_edit($id) {
+ if (!empty($this->data)) {
+ $this->data[$this->modelClass]['user_id'] = $this->Auth->user('id');
+ if ($this->Attachment->save($this->data)) {
+ $this->Session->setFlash(sprintf(__('%s with id %s updated', true), $this->modelClass, $id));
+ $this->_back();
+ } else {
+ $this->Session->setFlash(__('errors in form', true));
+ }
+ } else {
+ $this->data = $this->Attachment->read(null, $id);
+ }
+ $this->_setSelects($this->data[$this->modelClass]['class']);
+ $this->render('admin_edit');
+ }
+/**
+ * admin_export method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_export() {
+ $recursive = -1;
+ $this->data = $this->Attachment->find('all', compact('recursive'));
+ $filename = 'attachments_backup_' . date('Ymd-Hi') . '.xml';
+ $this->RequestHandler->renderAs($this, 'xml');
+ if (!isset($this->params['requested'])) {
+ Configure::write('debug', 0);
+ $this->RequestHandler->respondAs('xml', array('attachment' => $filename));
+ }
+ //$file = new File(TMP . $filename);
+ //$file->write($out);
+ }
+/**
+ * admin_import method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_import() {
+ if ($this->data) {
+ if ($this->data['Attachment']['take_backup']) {
+ $this->requestAction('/admin/attachments/export', array('return'));
+ }
+ if (!$this->data['Attachment']['file']['error']) {
+ $xml = file_get_contents($this->data['Attachment']['file']['tmp_name']);
+ $file = new File(TMP . 'attachments_imported_' . date('Ymd-Hi') . '.xml');
+ $file->write($xml);
+ } elseif ($this->data['Attachment']['backup']) {
+ $xml = file_get_contents(TMP . $this->data['Attachment']['backup']);
+ } else {
+ $this->Session->setFlash('No Xml file to import');
+ $this->redirect(array());
+ }
+ $uploads = 0;
+ uses('Xml');
+ $xml = new Xml($xml);
+ $xml = Set::reverse($xml);
+ $meta = Set::extract($xml, '/Contents/Meta');
+ $attachments = Set::extract($xml, '/Contents/Attachment');
+ if ($attachments) {
+ foreach ($attachments as $row) {
+ extract ($row['Attachment']);
+ $conditions = compact('class', 'foreign_id', 'filename', 'dir');
+ $existing = $this->Attachment->field('id', $conditions);
+ $file = new File(APP . 'uploads' . DS . $dir . DS . $filename, true);
+ $file->write(base64_decode($source));
+ $this->Attachment->create();
+ unset ($row['Attachment']['id']);
+ if ($existing) {
+ $this->Attachment->id = $existing;
+ }
+ if (!isset($row['Attachment']['user_id']) || !$row['Attachment']['user_id']) {
+ $row['Attachment']['user_id'] = $this->Auth->user('id');
+ }
+ if ($this->Attachment->save($row)) {
+ $this->Attachment->reprocess();
+ $uploads++;
+ }
+ }
+ }
+ $message = array();
+ if ($uploads) {
+ $message[] = $uploads . ' images imported';
+ }
+ if ($message) {
+ $message = implode ($message, '. ') . '.';
+ } else {
+ $message = 'File imported but no changes detected';
+ }
+ $this->Session->setFlash($message);
+ $this->_back();
+ }
+ $tmp = new Folder(TMP);
+ $backups = $tmp->find('attachments_.*\.xml');
+ if ($backups) {
+ $backups = array_combine($backups, $backups);
+ $backups = array_reverse($backups);
+ } else {
+ $backups = array();
+ }
+ $this->set('backups', $backups);
+ }
+/**
+ * admin_view method
+ *
+ * @param mixed $id
+ * @param mixed $slug
+ * @param string $size
+ * @access public
+ * @return void
+ */
+ function admin_view($id, $slug = null, $size = 'large') {
+ $this->view($id, $slug, $size);
+ }
+/**
+ * view method
+ *
+ * Serve up files directly from the uploads folder.
+ *
+ * @param mixed $id
+ * @param mixed $name
+ * @param mixed $size
+ * @return void
+ * @access public
+ */
+ function view($id, $name = null, $size = 'large') {
+ Configure::write('debug', 2);
+ $this->Attachment->recursive = -1;
+ $correctSlug = false;
+ if (is_numeric($id)) {
+ $correctSlug = true;
+ $row = $this->Attachment->read(null, $id);
+ } else {
+ $params = func_get_args();
+ $file = array_pop($params);
+ $folder = implode($params, '/');
+ $extension = array_pop(explode('.', $file));
+ if (strpos($extension, '_') !== false) {
+ list($extension_, $size) = explode('_', $extension);
+ $file = str_replace($extension, $extension_, $file);
+ $extension = $extension_;
+ }
+ $conditions['dir'] = $folder;
+ $conditions['filename'] = $file;
+ $conditions['ext'] = $extension;
+ $row = $this->Attachment->find('first', compact('conditions'));
+ }
+ if (!$row) {
+ debug('No file found for ' . implode(func_get_args(), '/'));
+ die;
+ }
+ extract ($row['Attachment']);
+ if ($correctSlug) {
+ if ($description) {
+ $description = $this->Attachment->slug($description);
+ if (!$description) {
+ $description = $filename;
+ if (substr($description, -3) != $ext) {
+ $description .= '.' . $ext;
+ }
+ } else {
+ $description = str_replace('-' . $ext, '.' . $ext, $description);
+ if (strpos('.' . $ext, $description) === false) {
+ $description .= '.' . $ext;
+ }
+ }
+ }
+ if ($name != $description) {
+ $this->redirect(array($id, $description, $size), 301);
+ }
+ }
+ $data = compact('modified');
+ if (!file_exists(APP . 'uploads' . DS . $dir . DS . $filename . '_' . $size)) {
+ $filename = 'missing.png';
+ $ext = 'png';
+ $data['path'] = 'uploads' . DS;
+ } else {
+ $data['path'] = 'uploads' . DS . $dir . DS;
+ }
+ $data['id'] = $filename;
+ $data['extension'] = $ext;
+ $data['download'] = isset($this->params['named']['download'])?$this->params['named']['download']:false;
+ $data['name'] = $description;
+ $data['size'] = $size;
+ if ($this->publicAccess) {
+ $data['cache'] = '+ 99days';
+ }
+ $this->set($data);
+ $this->view = 'Media';
+ $this->render();
+ Configure::write('debug', 0);
+ }
+/**
+ * setSelects method
+ *
+ * @param mixed $class
+ * @return void
+ * @access protected
+ */
+ function _setSelects($class = null) {
+ if ($class) {
+ $this->set('foreignClass',$class);
+ $this->set('foreigns',$this->Attachment->$class->find('list'));
+ }
+ if (in_array('_setSelects', get_class_methods('AppController'))) {
+ parent::_setSelects();
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/controllers/changes_controller.php b/controllers/changes_controller.php
new file mode 100644
index 0000000..69796b0
--- /dev/null
+++ b/controllers/changes_controller.php
@@ -0,0 +1,154 @@
+<?php
+/* SVN FILE: $Id: changes_controller.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for changes_controller.php
+ *
+ * Long description for changes_controller.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ * @since 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * ChangesController class
+ *
+ * @uses AppController
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ */
+class ChangesController extends AppController {
+/**
+ * paginate property
+ *
+ * @var array
+ * @access public
+ */
+ var $paginate = array('order' => 'Change.created DESC', 'limit' => 50);
+/**
+ * beforeFilter method
+ *
+ * @return void
+ * @access public
+ */
+ function beforeFilter() {
+ parent::beforeFilter();
+ $this->Auth->allow('index');
+ }
+/**
+ * admin_init method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_init($clean = false) {
+ if ($clean) {
+ $this->Change->deleteAll('1=1');
+ }
+ $recursive = -1;
+ $fields = array('id', 'status', 'user_id', 'created', 'modified', 'reason');
+ foreach ($this->Change->Revision->find('all', compact('recursive', 'fields')) as $revision) {
+ $this->Change->create();
+ $change = array(
+ 'revision_id' => $revision['Revision']['id'],
+ 'user_id' => $revision['Revision']['user_id'],
+ 'author_id' => $revision['Revision']['user_id'],
+ 'status_from' => 'new',
+ 'status_to' => 'pending',
+ 'created' => $revision['Revision']['created'],
+ 'comment' => $revision['Revision']['reason']?$revision['Revision']['reason']:'Edit submitted'
+ );
+ $this->Change->save($change);
+ if ($revision['Revision']['status'] != 'pending') {
+ switch ($revision['Revision']['status']) {
+ case 'previous':
+ case 'current':
+ $status = 'published';
+ $comment = 'publishing this change';
+ break;
+ case 'reject':
+ $status = 'rejected';
+ $comment = 'Change not accepted';
+ break;
+ default:
+ $status = 'unknown';
+ $comment = 'no comment';
+ }
+ $change = array(
+ 'revision_id' => $revision['Revision']['id'],
+ 'user_id' => 4, // attach all imports to John
+ 'author_id' => $revision['Revision']['user_id'],
+ 'status_from' => 'pending',
+ 'status_to' => $status,
+ 'created' => $revision['Revision']['modified'],
+ 'comment' => $comment
+ );
+ $this->Change->create();
+ $this->Change->save($change);
+ }
+ }
+ $this->redirect('/admin');
+ }
+/**
+ * index method
+ *
+ * @return void
+ * @access public
+ */
+ function index($nodeId = null) {
+ $conditions = array(
+ 'Revision.title !=' => ''
+ );
+ $language = $this->params['lang'];
+ if (isset($this->params['named']['language'])) {
+ $language = $this->params['named']['language'];
+ }
+ if ($language != '*') {
+ $conditions['Revision.lang'] = $language;
+ }
+ if ($nodeId) {
+ $this->Change->Node->recursive = 0;
+ $node = $this->Change->Node->read(array('Node.sequence', 'Revision.title'), $nodeId);
+ $title = '';
+ if ($node['Node']['sequence']) {
+ $title .= $node['Node']['sequence'] . ' - ';
+ }
+ $title .= $node['Revision']['title'];
+ $this->pageTitle = sprintf(__('Recent Changes for %s', true), $title);
+ $conditions['Revision.node_id'] = $nodeId;
+ } else {
+ if ($language == '*') {
+ $this->pageTitle = __('Recent Changes for all languages', true);
+ } else {
+ $this->pageTitle = __('Recent Changes', true);
+ }
+
+ }
+ if (isset($this->params['named']['user'])) {
+ $userId = $this->Change->User->field('id', array('username' => $this->params['named']['user']));
+ if ($userId) {
+ $conditions['Change.user_id'] = $userId;
+ $this->pageTitle .= ' ' . sprintf(__('by %s', true), $this->params['named']['user']);
+ }
+ }
+ if (isset($this->params['named']['status'])) {
+ $conditions['Change.status_to'] = $this->params['named']['status'];
+ $this->pageTitle .= ' ' . sprintf(__('restricted to status: %s', true), $this->params['named']['status']);
+ }
+ $this->data = $this->paginate($conditions);
+ }
+}
+?>
\ No newline at end of file
diff --git a/controllers/comments_controller.php b/controllers/comments_controller.php
new file mode 100644
index 0000000..936f2ad
--- /dev/null
+++ b/controllers/comments_controller.php
@@ -0,0 +1,210 @@
+<?php
+/* SVN FILE: $Id: comments_controller.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for comments_controller.php
+ *
+ * Long description for comments_controller.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ * @since 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * CommentsController class
+ *
+ * @uses AppController
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ */
+class CommentsController extends AppController {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Comments';
+/**
+ * paginate property
+ *
+ * @var array
+ * @access public
+ */
+ var $paginate = array('order' => 'Comment.created DESC', 'recursive' => 1);
+/**
+ * beforeFilter function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeFilter() {
+ parent::beforeFilter();
+ $this->Auth->allowedActions = array('index', 'view', 'recent');
+ }
+/**
+ * admin_index function
+ *
+ * @param mixed $nodeId
+ * @access public
+ * @return void
+ */
+ function admin_index($nodeId=null) {
+ $this->Comment->recursive = 2;
+ $this->paginate['limit']= 10;
+ $this->paginate['order'] = 'Comment.id desc';
+ if ($nodeId) {
+ $this->params['named']['node_id'] = $nodeId;
+ }
+ parent::admin_index();
+ }
+/**
+ * add function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function add($id=null) {
+ $Node = $this->Comment->Node->read(null , $id);
+ if (!$Node) {
+ $this->Session->setFlash(__('Invalid Node.', true));
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+ if (!empty ($this->data)) {
+ $this->data['Comment']['user_id'] = $this->Auth->user('id');
+ $this->data['Comment']['node_id'] = $Node['Node']['id'];
+ $this->data['Comment']['revision_id'] = $Node['Revision']['id'];
+ $this->Comment->create();
+ if($this->Comment->save($this->data)) {
+ $this->Session->setFlash(__('Your comment has been added', true));
+ return $this->redirect($this->Session->read('referer'), null, true);
+ } else {
+ $this->Session->setFlash(__('Please correct errors below.', true));
+ }
+ }
+ $this->set(compact('id','parentId', 'Node'));
+ }
+/**
+ * index function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function index($id=null) {
+ if (!$id && isset($this->params['id'])) {
+ $id = $this->params['id'];
+ }
+ $this->params['id'] = $id;
+ $this->Comment->Node->recursive = 0;
+ $Node = $this->Comment->Node->read(null, $id);
+ if (!$Node) {
+ $this->Session->setFlash(__('Invalid Node.', true));
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+ if (($Node['Node']['comment_level'] > READ) && ($this->Auth->user('Level') < $Node['Node']['comment_level'])) {
+ $this->Session->setFlash(__('No permissions to see comments for that section', true));
+ $this->redirect($this->Session->read('referer'));
+ }
+ $title = '';
+ if ($Node['Node']['sequence']) {
+ $title .= $Node['Node']['sequence'] . ' - ';
+ }
+ $title .= $Node['Revision']['title'];
+ $this->pageTitle = sprintf(__('Comments for %s', true), $title);
+ $conditions['Comment.node_id'] = $Node['Node']['id'];
+ $conditions['Comment.lang'] = $this->params['lang'];
+ $conditions['Comment.published'] = 1;
+ if ($this->params['url']['ext'] == 'html') {
+ $order = 'Comment.created ASC';
+ } else {
+ $order = 'Comment.created DESC';
+ }
+ $recursive = 0;
+ $this->data = $this->Comment->find('all', compact('conditions', 'order', 'recursive'));
+ $userIds = array_unique(Set::extract($this->data, '{n}.Comment.user_id'));
+ if ($userIds) {
+ $commenters = $this->Comment->User->find('all', array('fields' => array('id', 'IF(display_name=1,realname,username) AS name'),
+ 'conditions' => array('id' => $userIds), 'recursive' => -1));
+ if ($commenters) {
+ $commenters = Set::combine($commenters, '{n}.User.id', '{n}.0.name');
+ }
+ } else {
+ $commenters = array();
+ }
+ $this->set(compact('Node', 'commenters'));
+
+ }
+/**
+ * view function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function view($id=null) {
+ if (!$id) {
+ $this->Session->setFlash(__('Invalid Comment.', true));
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+ $this->data= $this->Comment->read(null,$id);
+ }
+/**
+ * recent method
+ *
+ * @return void
+ * @access public
+ */
+ function recent() {
+ $conditions = array();
+ $language = $this->params['lang'];
+ if (isset($this->params['named']['language'])) {
+ $language = $this->params['named']['language'];
+ }
+ if ($language != '*') {
+ $conditions['Comment.lang'] = $language;
+ $this->pageTitle = __('Recent Comments', true);
+ } else {
+ $this->pageTitle = __('Recent Comments for all languages', true);
+ }
+ if (isset($this->params['named']['node'])) {
+ $conditions['Comment.node_id'] = $this->params['named']['node'];
+ }
+ if (isset($this->params['named']['user'])) {
+ $userId = $this->Comment->User->field('id', array('username' => $this->params['named']['user']));
+ if ($userId) {
+ $conditions['Comment.user_id'] = $userId;
+ $this->pageTitle .= ' ' . sprintf(__('by %s', true), $this->params['named']['user']);
+ }
+ }
+ $this->data = $this->paginate($conditions);
+ $userIds = array_unique(Set::extract($this->data, '{n}.Comment.user_id'));
+ if ($userIds) {
+ $commenters = $this->Comment->User->find('all', array('fields' => array('id', 'IF(display_name=1,realname,username) AS name'),
+ 'conditions' => array('id' => $userIds), 'recursive' => -1));
+ if ($commenters) {
+ $commenters = Set::combine($commenters, '{n}.User.id', '{n}.0.name');
+ }
+ } else {
+ $commenters = array();
+ }
+ $this->set(compact('commenters'));
+
+ $this->render('index');
+ }
+}
+?>
\ No newline at end of file
diff --git a/controllers/components/filter.php b/controllers/components/filter.php
new file mode 100644
index 0000000..e06e186
--- /dev/null
+++ b/controllers/components/filter.php
@@ -0,0 +1,175 @@
+<?php
+/* SVN FILE: $Id: filter.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for filter.php
+ *
+ * Long description for filter.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package mi-base
+ * @subpackage mi-base.app.controllers
+ * @since v 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * FilterComponent class
+ *
+ * @uses Object
+ * @package mi-base
+ * @subpackage mi-base.app.controllers
+ */
+class FilterComponent extends Object {
+/**
+ * name property
+ *
+ * @var string 'Filter'
+ * @access public
+ */
+ var $name = 'Filter';
+/**
+ * components property
+ *
+ * @var array
+ * @access public
+ */
+ var $components = array('Session');
+/**
+ * ignore property
+ *
+ * Named parameters to exclude when determining the filter to apply
+ *
+ * @var array
+ * @access private
+ */
+ var $__ignore = array('limit', 'show', 'sort', 'page', 'direction', 'step');
+/**
+ * startup method
+ *
+ * @param controller
+ * @access public
+ * @return void
+ */
+ function startup (&$controller) {
+ $this->controller =& $controller;
+ }
+/**
+ * Determine the conditions to apply based on either the POSTed filter conditions or the session
+ * stored filter conditions, and/or any additional named parameter filters
+ *
+ * @TODO Rewrite to Use, or update and ticket, postConditions
+ * @param string $mode
+ * @param array $ignore additional named params to ignore
+ * @param array $filter initial filter conditions
+ * @access public
+ * @return $conditions array of conditions to apply
+ */
+ function parse($mode = 'both', $ignore = array(), $filter = array()) {
+ if (is_array($mode)) {
+ extract (array_merge(array('mode' => 'both'), $mode));
+ }
+ $mode = low($mode);
+ if ($mode == 'post' || $mode == 'both') {
+ $operators = array(
+ 'equal' => '= ',
+ 'greaterThan' => '> ',
+ 'greaterThanOrEqual' => '>= ',
+ 'lessThan' => '< ',
+ 'lessThanOrEqual' => '<= ',
+ 'notEqual' => '!= ',
+ 'like' => 'LIKE ',
+ 'notLike' => 'NOT LIKE ',
+ 'null' => 'NULL',
+ 'notNull' => 'NOT NULL',
+ 'between' => 'BETWEEN ',
+ 'in' => 'in');
+ $this->controller->set('filterOptions', $operators);
+ $filter = array();
+ if ($this->controller->data) {
+ $operator = false;
+ foreach ($this->controller->data as $alias => $fields) {
+ if (isset($this->controller->$alias)) {
+ $inst = $this->controller->$alias;
+ } elseif(isset($inst->{$this->controller->modelClass}->$alias)) {
+ $inst = $inst->{$this->controller->modelClass}->$alias;
+ } else {
+ $inst = ClassRegistry::init($alias);
+ }
+ $i = 0;
+ foreach ($fields as $field => $value) {
+ $value = $fields[$field];
+ $i++;
+ if ($i % 2) {
+ $field = str_replace('_type', '', $field);
+ if (!$value) {
+ if (!$this->controller->data[$alias][$field]) {
+ continue;
+ } else {
+ $value = 'equal';
+ }
+ }
+ $operator = $operators[$value];
+ if ($value == 'null') {
+ $filter[$alias . '.' . $field] = null;
+ $fields[$field] = null;
+ } elseif ($value == 'notNull') {
+ $filter[$alias . '.' . $field . ' !='] = null;
+ $fields[$field] = null;
+ } elseif (in_array($value, array('like', 'notLike')) && strpos('%', $this->controller->data[$alias][$field]) === false) {
+ $fields[$field] = $fields[$field] . '%';
+ }
+ } elseif (!in_array($value, array(null, '', 'NOT NULL'))) {
+ if (!$operator) {
+ $this->controller->data[$alias][$field . '_type'] = 'equal';
+ $operator = '= ';
+ }
+ if ($operator == 'in') {
+ $filter[$alias . '.' . $field] = explode(',', $value);
+ foreach ($filter[$alias . '.' . $field] as $key => $val) {
+ $filter[$alias . '.' . $field][$key] = trim($val);
+ }
+ } elseif (is_array($value)) {
+ $value = $inst->deconstruct($field, $value);
+ if ($value) {
+ $filter[$alias . '.' . $field . ' ' . $operator] = $value;
+ }
+ } else {
+ $filter[$alias . '.' . $field . ' ' . $operator] = $value;
+ }
+ }
+ }
+ }
+ $this->Session->write($this->controller->modelClass . '.filter', $filter);
+ $this->Session->write($this->controller->modelClass . '.filterForm', $this->data);
+ } elseif ($this->Session->check($this->controller->modelClass . '.filter')) {
+ $filter = $this->Session->read($this->controller->modelClass . '.filter');
+ }
+ }
+ if ($mode == 'named' || $mode == 'both') {
+ $ignore = array_merge($this->__ignore, $ignore);
+ $filter = am($filter, $this->controller->params['named']);
+ foreach ($ignore as $ignore) {
+ unset ($filter[$ignore]);
+ }
+ }
+ foreach ($filter as $key => $condition) {
+ if (!strpos($key, '.')) {
+ unset($filter[$key]);
+ $filter[$this->controller->modelClass . '.' . $key] = $condition;
+ }
+ }
+ return $filter;
+ }
+}
+?>
\ No newline at end of file
diff --git a/controllers/nodes_controller.php b/controllers/nodes_controller.php
new file mode 100644
index 0000000..15d3c82
--- /dev/null
+++ b/controllers/nodes_controller.php
@@ -0,0 +1,1105 @@
+<?php
+/* SVN FILE: $Id: nodes_controller.php 704 2008-11-19 12:15:11Z AD7six $ */
+/**
+ * Short description for nodes_controller.php
+ *
+ * Long description for nodes_controller.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ * @since 1.0
+ * @version $Revision: 704 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-19 13:15:11 +0100 (Wed, 19 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * NodesController class
+ *
+ * @uses AppController
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ */
+class NodesController extends AppController {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Nodes';
+/**
+ * cacheAction variable
+ *
+ * Cache times are set in the actions, as if set here, given the numerous routes, a large number of permutations
+ * need to be defined
+ *
+ * @var string
+ * @access public
+ */
+ var $cacheAction = false;
+/**
+ * currentNode variable
+ *
+ * @var mixed
+ * @access public
+ */
+ var $currentNode;
+/**
+ * currentPath variable
+ *
+ * @var mixed
+ * @access public
+ */
+ var $currentPath;
+/**
+ * paginate variable
+ *
+ * @var array
+ * @access public
+ */
+ var $paginate = array ('limit' => 2);
+/**
+ * beforeFilter function
+ *
+ * First, check if the url matches routes and if not redirect
+ * Second, check the slug matches and if not redirect
+ *
+ * @access public
+ * @return void
+ */
+ function beforeFilter() {
+ parent::beforeFilter();
+ if (Configure::read() && !$this->Node->find('count')) {
+ $this->Node->initialize();
+ }
+ $this->Node->Revision->currentUserId = $this->Node->currentUserId;
+ if (!isset($this->params['requested']) && $this->action != 'todo') {
+ $urlIsCorrect = true;
+ if ((!isset($this->params['admin']) &&
+ (($this->params['lang'] == 'en' && $this->params['url']['url'] != '/')
+ || ($this->params['lang'] != 'en' && $this->params['url']['url'] != '/' . $this->params['lang'])))
+ ) {
+ if ($this->action == 'view' && isset($this->params['pass'][0]) && $this->params['pass'][0] == Configure::read('Site.homeNode')) {
+ if ($this->params['lang'] == 'en') {
+ $urlIsCorrect = false;
+ $url = '/';
+ } elseif ($this->params['url']['url'] != $this->params['lang']) {
+ $urlIsCorrect = false;
+ $url = '/' . $this->params['lang'];
+ }
+ }
+ if ($this->params['lang'] != 'en') {
+ $this->params['pass']['lang'] = $this->params['lang'];
+ }
+ if ($this->params['url']['ext'] == 'html') {
+ $normalized = Router::normalize($this->params['pass']);
+ } else {
+ $normalized = Router::normalize(am($this->params['pass'], array('ext' => $this->params['url']['ext'])));
+ }
+ if ($normalized != '/' . $this->params['url']['url']) {
+ $urlIsCorrect = false;
+ $url = '/' . $normalized;
+ }
+ }
+ if (!isset($this->params['admin']) && isset($this->params['pass'][0])) {
+ $urlSlug = isset($this->params['pass'][1])?$this->params['pass'][1]:'';
+ $conditions['Node.id'] = $this->params['pass'][0];
+ $fields = array ('Node.id', 'Node.id', 'Revision.slug');
+ $recursive = 0;
+ $result = $this->Node->find('first', compact('conditions', 'fields', 'recursive'));
+ if ($result) {
+ $this->Node->id = $this->currentNode = $result['Node']['id'];
+ } else {
+ $this->redirect($this->Session->read('referer'), null, true);
+ }
+ $base = '/';
+ $here = '/' . $this->params['url']['url'];
+ if ($this->params['lang'] != 'en') {
+ $base .= $this->params['lang'] . '/';
+ if (strlen($here) < 4 ) {
+ $here .= '/';
+ }
+ }
+ if (!($this->data)&&($base != $here)) {
+ if ($urlSlug<>$result['Revision']['slug']) {
+ $urlIsCorrect = false;
+ $url = array($result['Node']['id'], $result['Revision']['slug']);
+ }
+ }
+ } elseif (isset($this->params['pass'][0])) {
+ $this->Node->id = $this->currentNode = $this->params['pass'][0];
+ }
+ if (!$urlIsCorrect) {
+ if ($this->params['url']['ext'] != 'html') {
+ $url['ext'] = $this->params['url']['ext'];
+ }
+ $this->redirect($url, 301);
+ }
+ $fields = array ('Node.id', 'Node.depth', 'Node.id', 'Node.lft', 'Node.rght', 'Node.comment_level', 'Node.edit_level', 'Revision.id', 'Revision.slug', 'Revision.title', 'Revision.content');
+ if (!isset($this->currentNode)) {
+ $topNode = $this->Node->find(array('Node.depth' => '0'), array('Node.id'), null, 0);
+ $this->currentNode = $topNode['Node']['id'];
+ }
+ $this->currentPath = $this->Node->getPath($this->currentNode, $fields, 0);
+ $this->set('currentPath', $this->currentPath);
+ }
+ $this->Auth->allowedActions = array('index', 'view', 'single_page', 'toc', 'collections', 'app_name',
+ 'compare', 'history', 'stats', 'todo');
+ }
+/**
+ * beforeRender function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeRender() {
+ $crumbPath = isset($this->currentPath) ? $this->currentPath : array();
+ if (!isset($this->params['admin'])) {
+ array_shift ($crumbPath);
+ array_shift ($crumbPath);
+ }
+ $this->set('viewAllLevel', $this->Node->viewAllLevel);
+ $this->set('crumbPath', $crumbPath);
+ if (!isset($this->params['admin'])) {
+ $titles = Set::extract($this->currentPath, '{n}.Revision.title');
+ if ($titles) {
+ array_shift($titles);
+ $this->pageTitle = implode(' :: ', array_reverse($titles));
+ }
+ }
+ parent::beforeRender();
+ }
+/**
+ * admin_delete function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_delete($id = null) {
+ $parent = $this->Node->field('parent_id');
+ if ($this->Node->delete()) {
+ $this->Node->reset($parent);
+ }
+ return $this->redirect(array('action' => 'toc', $parent), null, true);
+ }
+/**
+ * admin_edit method
+ *
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function admin_edit($id) {
+ if (!$this->Node->hasAny(array('id' => $id))) {
+ $this->redirect(array('action' => 'index'), null, true);
+ }
+ if (!empty ($this->data)) {
+ if ($this->Node->save($this->data)) {
+ if ($this->data['Node']['show_subsections_inline']) {
+ $lft = $this->Node->field('lft');
+ $rght = $this->Node->field('rght');
+ $this->Node->updateAll(array('sequence' => null, 'show_in_toc' => 0), array('lft >' => $lft, 'rght <' => $rght));
+ }
+ $this->Node->reset($this->Node->field('parent_id'));
+ $this->Session->setFlash('Node updated');
+ $this->redirect($this->Session->read('referer'), null, true);
+ } else {
+ $this->Session->setFlash('Please correct the errors below.');
+ }
+ } else {
+ $this->data = $this->Node->read(null, $id);
+ }
+ }
+/**
+ * admin_export method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_export($id = null) {
+ $this->data = $this->Node->exportData($id);
+ $filename = 'contents_backup_' . date('Ymd-Hi') . '.xml';
+ $this->RequestHandler->renderAs($this, 'xml');
+ if (!isset($this->params['requested'])) {
+ $this->RequestHandler->respondAs('xml', array('attachment' => $filename));
+ }
+ $this->render('view_all');
+ $file = new File(TMP . $filename);
+ $file->write(ob_get_clean());
+ }
+/**
+ * admin_import method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_import() {
+ if ($this->data) {
+ if ($this->data['Node']['take_backup']) {
+ $this->requestAction('/admin/nodes/export', array('return'));
+ }
+ if (!$this->data['Node']['file']['error']) {
+ $xml = file_get_contents($this->data['Node']['file']['tmp_name']);
+ if (!file_exists(TMP . $this->data['Node']['file']['name'])) {
+ $file = new File(TMP . $this->data['Node']['file']['name']);
+ $file->write($xml);
+ }
+ } elseif ($this->data['Node']['backup']) {
+ $xml = file_get_contents(TMP . $this->data['Node']['backup']);
+ } else {
+ $this->Session->setFlash('No Xml file to import');
+ $this->redirect(array());
+ }
+ list($return, $messages, $actions) = $this->Node->import($xml, $this->data['Node'], $this->Auth->user('id'));
+ extract($actions);
+ if ($messages) {
+ $message = implode ($messages, '. ') . '.';
+ } else {
+ $message = 'No messages - possible problem in node import function';
+ }
+ $this->Session->setFlash($message);
+ if ($mods && !$this->data['Node']['auto_approve']) {
+ $this->redirect(array('controller' => 'revisions', 'action' => 'pending'));
+ } else {
+ $this->redirect(array('action' => 'index'));
+ }
+ $this->autoRender = false;
+ } else {
+ $schema = $this->Node->schema('id');
+ if ($schema['length'] != 36 && $this->Node->find('count')) {
+ $this->Session->setFlash('The import is designed only to work with UUID installs. Adding new content is disabled - only edits and moves possible.');
+ }
+ }
+ $tmp = new Folder(TMP);
+ $backups = $tmp->find('.*\.xml');
+ if ($backups) {
+ $backups = array_combine($backups, $backups);
+ $backups = array_reverse($backups);
+ } else {
+ $backups = array();
+ }
+ $this->set('backups', $backups);
+ }
+/**
+ * admin_index function
+ *
+ * @access public
+ * @return void
+ */
+ function admin_index() {
+ $this->paginate['limit'] = 10;
+ $this->paginate['order'] = 'Node.lft';
+ $this->paginate['recursive'] = 0;
+ $this->Node->recursive = 0;
+ if (isset($this->params['named']['restrict_to'])) {
+ $restrictTo = $this->params['named']['restrict_to'];
+ unset($this->params['named']['restrict_to']);
+ $limits = $this->Node->find('first', array('conditions' => array('Node.id' => $restrictTo), 'fields' => array('lft', 'rght', 'Revision.title')));
+ $this->params['named']['lft >='] = $limits['Node']['lft'];
+ $this->params['named']['rght <='] = $limits['Node']['rght'];
+ $this->Session->setFlash('Only "' . $limits['Revision']['title'] . '" and below');
+ }
+ $conditions = array('Revision.status' => 'pending', 'Revision.lang' => $this->params['lang']);
+ $recursive = -1;
+ $fields = array('DISTINCT node_id');
+ $pendingUpdates = $this->Node->Revision->find('all', compact('conditions', 'recursive', 'fields'));
+ $pendingUpdates = Set::extract($pendingUpdates, '{n}.Revision.node_id');
+ parent::admin_index();
+ $userIds = Set::extract($this->data, '{n}.Revision.user_id');
+ $users = $this->Node->Revision->User->find('list', array('conditions' => array('User.id' => $userIds)));
+ $this->set(compact('pendingUpdates', 'users'));
+ }
+/**
+ * admin_merge method
+ *
+ * @TODO Finish this logic
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function admin_merge($id) {
+ if ($this->data) {
+ if (!array_key_exists('confirmation', $this->data['Node'])) {
+ $this->data['Node']['confirmation'] = false;
+ } elseif ($this->data['Node']['confirmation']) {
+ if ($this->Node->merge($this->data['Node']['id'], $this->data['Node']['merge_id'])) {
+ $this->Session->setFlash('content merged successfully');
+ $this->redirect(array('admin' => false, 'action' => 'view', $this->data['Node']['merge_id']));
+ }
+ }
+ $preview = array('title' => '', 'content' => '');
+ if ($this->data['Node']['merge_id']) {
+ $preview['title'] = $this->Node->Revision->field('title', array('Revision.node_id' => $this->data['Node']['merge_id'], 'Revision.status' => 'current', 'lang' => $this->params['lang']));
+ $preview['content'] = $this->Node->Revision->field('content', array('Revision.node_id' => $this->data['Node']['id'], 'Revision.status' => 'current', 'lang' => $this->params['lang']));
+ $this->set(compact('preview'));
+ }
+ $this->Node->id = $this->data['Node']['id'];
+ } else {
+ $this->data = $this->Node->read();
+ }
+ $conditions = array();
+ $depth = 0; //$this->Node->field('depth', array('id' => $this->data['Node']['id']));
+ if ($depth > 2) {
+ $book = $this->Node->book($this->data['Node']['id'], array('lft', 'rght'));
+ $conditions['Node.lft <'] = $book['lft'];
+ $conditions['Node.rght >'] = $book['rght'];
+ } elseif ($depth == 2) {
+ $collection = $this->Node->collection($this->data['Node']['id'], array('lft', 'rght'));
+ $conditions['Node.lft <'] = $collection['lft'];
+ $conditions['Node.rght >'] = $collection['rght'];
+ }
+ $this->set('nodes', $this->Node->generateTreeList($conditions));
+ }
+/**
+ * admin_move method
+ *
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function admin_move($id) {
+ if ($this->data) {
+ $this->Node->id = $this->data['Node']['id'];
+ if ($this->Node->saveField('parent_id', $this->data['Node']['parent_id'])) {
+ $this->Node->reset();
+ $this->Session->setFlash('Parent changed');
+ }
+ $this->redirect($this->Session->read('referer'));
+ } else {
+ $this->data = $this->Node->read();
+ }
+ $conditions = array();
+ $depth = 0; //$this->Node->field('depth', array('id' => $this->data['Node']['id']));
+ if ($depth > 2) {
+ $book = $this->Node->book($this->data['Node']['id'], array('lft', 'rght'));
+ $conditions['Node.lft <'] = $book['lft'];
+ $conditions['Node.rght >'] = $book['rght'];
+ } elseif ($depth == 2) {
+ $collection = $this->Node->collection($this->data['Node']['id'], array('lft', 'rght'));
+ $conditions['Node.lft <'] = $collection['lft'];
+ $conditions['Node.rght >'] = $collection['rght'];
+ }
+ $this->set('nodes', $this->Node->generateTreeList($conditions));
+ }
+/**
+ * admin_move_up function
+ *
+ * @param mixed $nodeId
+ * @param int $step
+ * @access public
+ * @return void
+ */
+ function admin_move_up($nodeId, $step = 1) {
+ if (!$this->Node->moveUp($nodeId, $step)) {
+ $this->Session->setFlash('Could not move previous.');
+ }
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_move_down function
+ *
+ * @param mixed $nodeId
+ * @param int $step
+ * @access public
+ * @return void
+ */
+ function admin_move_down($nodeId, $step = 1) {
+ if (!$this->Node->moveDown($nodeId, $step)) {
+ $this->Session->setFlash('Could not move after.');
+ }
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_promote function
+ *
+ * @param mixed $nodeId
+ * @access public
+ * @return void
+ */
+ function admin_promote($nodeId) {
+ $this->Node->id = $nodeId;
+ $node = $this->Node->read(null, $nodeId);
+ $parent = $this->Node->getParentNode();
+ if ($parent) {
+ $this->Node->saveField('parent_id', $parent['Node']['parent_id']);
+ $this->Node->reset($this->Node->field('parent_id'));
+ } else {
+ $this->Session->setFlash($node['Revision']['title'] . ' has no parent, cannot promote.');
+ }
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_recover_tree function
+ *
+ * @param string $mode
+ * @access public
+ * @return void
+ */
+ function admin_recover_tree($mode = 'parent') {
+ set_time_limit(1000);
+ if ($mode == 'parent') {
+ if ($this->Node->recover()) {
+ $this->Session->setFlash('Based upon the parent id fields, the lft and rght fields have been repopulated.');
+ } else {
+ $this->Session->setFlash('Recovery was not successful!');
+ }
+ } else {
+ if ($this->Node->recoverTree('MPTT')) {
+ $this->Session->setFlash('Based upon the left and right fields, the parent Id has been repopulated.');
+ } else {
+ $this->Session->setFlash('Recovery was not successful!');
+ }
+ }
+ $this->Node->reset();
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_remove function
+ *
+ * @param mixed $nodeId
+ * @param bool $delete
+ * @access public
+ * @return void
+ */
+ function admin_remove($nodeId, $delete = false) {
+ $parent = $this->Node->field('parent_id');
+ $this->Node->removeFromTree($nodeId, $delete);
+ $this->Node->reset($parent);
+ return $this->redirect(array('action' => 'toc', $parent), null, true);
+ }
+/**
+ * admin_reset function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_reset($id = null) {
+ $this->Node->unbindModel(array('hasOne' => array('Revision')), false);
+ $this->Node->reset($id);
+ $this->Session->setFlash('Depths and Sequences regenerated');
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_reset_depths function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_reset_depths($id = null) {
+ $this->Node->unbindModel(array('hasOne' => array('Revision')), false);
+ $this->Node->resetDepths($id);
+ $this->Session->setFlash('Depth values updated based upon position in tree');
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_reset_sequences function
+ *
+ * @param mixed $parentId
+ * @access public
+ * @return void
+ */
+ function admin_reset_sequences($parentId = null) {
+ $this->Node->unbindModel(array('hasOne' => array('Revision')), false);
+ $prefix = null;
+ if ($parentId) {
+ $prefix = $this->Node->field('Node.id');
+ }
+ $this->Node->resetSequences($parentId, $prefix);
+ $this->Session->setFlash('Sequence values updated based upon position in tree');
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_set_parent function
+ *
+ * @param mixed $nodeId
+ * @param mixed $parentId
+ * @access public
+ * @return void
+ */
+ function admin_set_parent($nodeId, $parentId = null) {
+ if ($parentId) {
+ $parent = $this->Node->field('parent_id', array('Node.id' => $parentId));
+ } else {
+ $parent = null;
+ }
+ $this->Node->saveField('parent_id', $parentId);
+ $this->Node->reset($parent);
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_toc function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_toc($id = null) {
+ $this->helpers[] = 'Tree';
+ $fields = array('Node.*', 'Revision.title', 'Revision.slug');
+ if ($id) {
+ $conditions['OR']['Node.parent_id'] = array($id, $this->Node->field('parent_id'));
+ $conditions['OR'][] = array(
+ 'Node.lft <=' => $this->Node->field('lft', array('Node.id' => $id)),
+ 'Node.rght >=' => $this->Node->field('rght', array('Node.id' => $id))
+ );
+ $this->data = $this->Node->find('all', compact('conditions', 'fields', 'recursive'));
+ } else {
+ $this->data = array($this->Node->find('first', compact('conditions', 'fields', 'recursive')));
+ }
+ }
+/**
+ * admin_verify_tree function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_verify_tree($detailed = true) {
+ $return = $this->Node->verify($detailed);
+ if ($return === true) {
+ $this->Session->setFlash('Tree is valid');
+ return $this->redirect($this->Session->read('referer'), null, true);
+ } else {
+ $flash = array('Errors Found:');
+ foreach ($return as $error) {
+ $flash[] = ' ' . implode($error, ' ');
+ }
+ $flash = implode($flash, '<br />');
+ $this->Session->setFlash($flash);
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+ }
+/**
+ * admin_view function
+ *
+ * @param mixed $nodeId
+ * @access public
+ * @return void
+ */
+ function admin_view($nodeId) {
+ $this->Node->viewAllLevel = 99;
+ $this->view($nodeId);
+ }
+/**
+ * collections method
+ *
+ * Only called by requestAction
+ *
+ * @access public
+ * @return void
+ */
+ function collections($lang = 'en') {
+ $this->autoRender = false;
+ $this->Node->setLanguage($lang);
+ $recursive = 0;
+ $first = $this->Node->field('id', array('Node.parent_id' => null));
+ $conditions = array('Node.parent_id' => $first);
+ $fields = array('Node.id', 'Revision.title', 'Revision.slug');
+ $data = $this->Node->find('all', compact('recursive', 'conditions', 'fields'));
+ cache('views/collections_' . $lang, serialize($data), CACHE_DURATION);
+ return $data;
+ }
+/**
+ * app_name method
+ *
+ * @param string $lang
+ * @return void
+ * @access public
+ */
+ function app_name($lang = 'en') {
+ $this->autoRender = false;
+ $this->Node->setLanguage($lang);
+ $recursive = 0;
+ $conditions = array('Node.parent_id' => null);
+ $fields = array('Revision.title', 'Revision.content');
+ $data = $this->Node->find('first', compact('recursive', 'conditions', 'fields'));
+ $return['name'] = $data['Revision']['title'];
+ $return['tag_line'] = strip_tags($data['Revision']['content']);
+ cache('views/app_name_' . $lang, serialize($return), CACHE_DURATION);
+ return $return;
+ }
+/**
+ * index method
+ *
+ * @return void
+ * @access public
+ */
+ function index() {
+ $id = Configure::read('Site.homeNode');
+ $this->Node->id = $this->currentNode = $id;
+ if (!$this->Node->exists()) {
+ $this->Node->id = $this->currentNode = $id = $this->Node->field('id', array('Node.depth' => 2));
+ }
+ if ($this->params['url']['ext'] == 'xml') {
+ $url = array('action' => 'single_page', $id, $this->Node->Revision->field('slug', array('node_id' => $id)), 'ext' => 'xml');
+ $this->redirect($url);
+ }
+ $fields = array ('Node.id', 'Node.depth', 'Node.id', 'Node.lft', 'Node.rght', 'Node.comment_level', 'Node.edit_level', 'Revision.id', 'Revision.slug', 'Revision.title', 'Revision.content');
+ $this->currentPath = $this->Node->getPath($this->currentNode, $fields, 0);
+ $this->set('currentPath', $this->currentPath);
+ $this->_view();
+ }
+/**
+ * toc function
+ *
+ * Generate the data required for either the toc page, or the menu navigation links
+ * For the menu data (generated via a requestAction call) The logic is find all nodes that
+ * Are direct children of what's being displayed
+ * Are siblings of what's being displayed
+ * Are in the path of what's being displayed
+ * Are top level items
+ *
+ * @param mixed $nodeId
+ * @access public
+ * @return void
+ */
+ function toc($nodeId = null) {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ if (isset($this->params['requested'])) {
+ $this->currentPath = $this->params['currentPath'];
+ $this->currentNode = $this->params['currentNode'];
+ $this->set(array('currentPath' => $this->currentPath, 'currentNode' => $this->currentNode));
+ }
+ $this->Node->recursive = 0;
+ $fields = array('Node.*', 'Revision.id', 'Revision.status', 'Revision.lang', 'Revision.title', 'Revision.slug');
+ $path = $this->currentPath;
+ if (count($path) > 2) {
+ $direct = false;
+ array_shift($path);
+ array_shift($path);
+ $ids = Set::extract($path, '/Node/id');
+ } else {
+ $direct = true;
+ }
+ if (isset($this->params['requested'])) {
+ if ($direct) {
+ $conditions['Node.parent_id'] = $this->currentNode['id'];
+ return $this->Node->find('all', compact('conditions', 'fields', 'recursive', 'order'));
+ }
+ $conditions['Node.show_in_toc'] = 1;
+ $conditions['Node.parent_id'] = $ids;
+ $recursive = 0;
+ $order = 'Node.lft ASC';
+ $this->data = $this->Node->find('all', compact('conditions', 'fields', 'recursive', 'order'));
+ return $this->data;
+ } else {
+ $book = array_shift($path);
+ $conditions['Node.show_in_toc'] = 1;
+ $conditions['Node.lft >='] = $book['Node']['lft'];
+ $conditions['Node.rght <='] = $book['Node']['rght'];
+ //$conditions['Node.depth <'] = 7;
+ $this->data = $this->Node->find('all', compact('conditions', 'fields', 'recursive', 'order'));
+ }
+ $this->set('data', $this->data);
+ $this->data = false;
+ }
+/**
+ * todo method
+ *
+ * List all sections that are not translated
+ *
+ * @return void
+ * @access public
+ */
+ function todo() {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ $this->paginate['limit'] = 20;
+ $conditions = array('Revision.status' => 'pending', 'Revision.lang' => $this->params['lang']);
+ $recursive = -1;
+ $fields = array('DISTINCT node_id');
+ $pendingUpdates = $this->Node->Revision->find('all', compact('conditions', 'recursive', 'fields'));
+ $pendingUpdates = Set::extract($pendingUpdates, '{n}.Revision.node_id');
+ $this->set(compact('pendingUpdates'));
+ if ($this->params['lang'] == 'en') {
+ $this->data = $this->paginate(array('Revision.content LIKE' => '<h'));
+ return $this->render('english_todo');
+ } else {
+ $this->data = $this->paginate(array('OR' => array('Revision.id' => null, 'Revision.flags LIKE' => 'englishChanged')));
+ }
+ }
+/**
+ * view function
+ *
+ * @param mixed $nodeId
+ * @param mixed $slug
+ * @access public
+ * @return void
+ */
+ function view($nodeId = false, $slug = '') {
+ if ($this->params['url']['ext'] == 'xml') {
+ $this->redirect(am(array('action' => 'single_page'), $this->passedArgs));
+ }
+ if (!$nodeId || $nodeId == Configure::read('Site.homeNode')) {
+ $this->redirect(array('index'));
+ }
+ $this->Node->id = $this->currentNode;
+ $depth = $this->Node->field('Node.depth');
+ if ($depth < $this->Node->viewAllLevel) {
+ $this->_view();
+ } else {
+ $this->_view(true);
+ }
+ }
+/**
+ * single_page function
+ *
+ * @param mixed $nodeId
+ * @access public
+ * @return void
+ */
+ function single_page($nodeId = null) {
+ if ($this->params['url']['ext'] == 'xml') {
+ $this->data = $this->Node->exportData($this->currentNode);
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ $this->render('view_all');
+ return;
+ }
+ if ($this->currentNode) {
+ $this->data = $this->Node->id = $this->currentNode;
+ $depth = $this->Node->field('depth');
+ if ($depth < 2 || $depth >= $this->Node->viewAllLevel) {
+ $this->redirect(array('action' => 'view', $this->currentNode), null, true);
+ }
+ $this->Node->viewAllLevel = $depth;
+ } else {
+ $this->redirect('/', null, true);
+ }
+ $this->_view(true);
+ }
+/**
+ * stats method
+ *
+ * @return void
+ * @access public
+ */
+ function stats() {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ $languages = Configure::read('Languages.all');
+ $db =& ConnectionManager::getDataSource($this->Node->useDbConfig);
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ $nodes = $this->Node->find('count', array('recursive' => -1));
+ $counts = $this->Node->Revision->find('all', array(
+ 'conditions' => array('status' => 'current', 'lang' => $languages),
+ 'fields' => array('lang', 'COUNT(Revision.id) AS count'),
+ 'group' => 'lang',
+ 'order' => 'count DESC',
+ 'recursive' => -1));
+ $counts = Set::combine($counts, '/Revision/lang', '/0/count');
+ foreach ($languages as $lang) {
+ if (!isset($counts[$lang])) {
+ $counts[$lang] = 0;
+ }
+ if ($lang == 'en') {
+ $userLimit = 15;
+ } else {
+ $userLimit = 6;
+ }
+ $data[$lang]['top_contributors'] = $this->Node->Revision->find('all', array(
+ 'conditions' => array('status' => 'current', 'lang' => $lang, 'user_id >' => 0),
+ 'fields' => array('user_id', 'COUNT(Revision.id) AS count'),
+ 'group' => 'user_id',
+ 'order' => 'count DESC',
+ 'limit' => $userLimit,
+ 'recursive' => -1));
+ $data[$lang]['last_update'] = $this->Node->Revision->field(
+ $db->calculate($this->Node, 'max', array('created')),
+ array('status' => 'current', 'lang' => $lang));
+ }
+ $users = Set::extract($data, '{[a-z]+}.top_contributors.{n}.Revision.user_id');
+ $userIds = array();
+ foreach ($users as $lang => $ids) {
+ if ($ids) {
+ $userIds = am($userIds, $ids);
+ }
+ }
+ $userIds = array_unique($userIds);
+ $users = $this->Node->Revision->User->find('all', array(
+ 'conditions' => array('User.id' => $userIds),
+ 'fields' => array('User.id', 'User.username', 'User.realname', 'Profile.url'),
+ 'recursive' => 0));
+ $users = Set::combine($users, '/User/id', '/');
+ $this->set(compact('counts', 'data', 'nodes', 'users'));
+ }
+/**
+ * add function
+ *
+ * @param mixed $parentId
+ * @access public
+ * @return void
+ */
+ function add($parentId = null) {
+ if (!isset($this->params['admin']) && !$parentId && $this->Node->hasAny(array('Node.depth' => '> 0'))) {
+ $this->Session->setFlash(__('Invalid Collection', true));
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+ $this->Node->create();
+ if (isset($this->data['Revision']['under_node_id'])) {
+ if (isset ($this->data['Revision']['content2'])) {
+ $this->data['Revision']['content'] = $this->data['Revision']['content2'];
+ unset ($this->data['Revision']['content2']);
+ }
+
+ $this->data['Revision']['status'] = 'pending';
+ $this->data['Revision']['user_id'] = $this->Auth->user('id');
+ $this->data['Revision']['node_id'] = 0;
+ $parentId = $this->data['Revision']['under_node_id'];
+ $this->Node->Revision->itsAnAdd = true;
+ if ($this->Node->Revision->save($this->data, true)) {
+ if ($this->Session->read('Auth.User.Level') == ADMIN) {
+ $this->Node->Revision->publish($this->Node->Revision->id, $this->data['Revision']['reason'], true);
+ if (!$this->data['Node']['show_in_toc']) {
+ $this->Node->save($this->data);
+ $this->Node->reset($parentId);
+ $this->redirect(array('action' => 'view', $parentId));
+ }
+ $this->redirect(array('action' => 'view', $this->Node->id));
+ }
+ $this->Session->setFlash(__('Thanks for your contribution! Your suggestion has been submitted for review.', true));
+ return $this->redirect($this->Session->read('referer'));
+ } else {
+ $this->data['Revision'] = $this->Node->Revision->data['Revision'];
+ }
+ } else {
+ $this->data['Node']['show_in_toc'] = true;
+ $this->data['Revision']['preview'] = true;
+ }
+ $parent = $this->currentPath[count($this->currentPath) -1]['Node'];
+ if (!isset($this->data['Revision']['under_node_id']) && $parentId) {
+ $this->data['Revision']['under_node_id'] = $parentId;
+ }
+ if ($parent['rght'] == $parent['lft'] + 1) {
+ $afters = false;
+ } elseif (!isset($this->data['Revision']['after_node_id']) && !$parent) {
+ $afters[-1] = '-choose parent first-';
+ } elseif (!isset($this->data['Revision']['after_node_id']) && $parent) {
+ $afters[-1] = '-choose parent first-';
+ $conditions = array('Node.parent_id' => $parent['id']);
+ $order = 'Node.lft asc';
+ $afters = array($parent['id'] => 'first');
+ $more = $this->Node->generateTreeList($conditions, null, array('{0} {1}', '{n}.Node.id', '{n}.Revision.title'), null, 0);
+ foreach ($more as $id => $display) {
+ $afters[$id] = $display;
+ $this->data['Revision']['after_node_id'] = $id;
+ }
+ } elseif (isset($this->data['Revision']['after_node_id'])) {
+ if ($this->data['Revision']['after_node_id']<0) {
+ $this->data['Revision']['after_node_id'] = 0;
+ }
+ $conditions = array('Node.parent_id' => $this->data['Revision']['under_node_id']);
+ $order = 'Node.lft asc';
+ $afters = array($parent['id'] => 'first');
+ $more = $this->Node->generateTreeList($conditions, null, array('{0} {1}', '{n}.Node.id', '{n}.Revision.title'), null, 0);
+ foreach ($more as $id => $display) {
+ $afters[$id] = $display;
+ }
+ }
+ $conditions = array();
+ if (!isset($this->params['admin'])) {
+ $conditions['Node.lft >='] = $parent['lft'];
+ $conditions['Node.rght <='] = $parent['rght'];
+ }
+ $parents = $this->Node->generateTreeList($conditions, null, array('{0} {1}', '{n}.Node.id', '{n}.Revision.title'), null, 0);
+
+ $this->helpers[] = 'Highlight';
+ $this->set(compact('parents', 'afters'));
+ }
+/**
+ * compare method
+ *
+ * @param mixed $id
+ * @param mixed $slug
+ * @param string $lang
+ * @return void
+ * @access public
+ */
+ function compare($id, $slug, $lang = 'en') {
+ if ($this->Node->language == $lang) {
+ $this->Session->setFlash(__('This function is for comparing different (public) language content', true));
+ $this->redirect(array('action' => 'view', $id, $slug));
+ }
+ if (!isset($this->params['admin'])) {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ }
+ $recursive = 1;
+ $this->Node->id = $this->currentNode;
+ $fields = array('Node.id', 'Node.sequence', 'Revision.id', 'Revision.title', 'Revision.content', 'Revision.lang');
+ $this->data['original'] = $this->Node->findById($this->currentNode, $fields, null, $recursive);
+ $this->Node->setLanguage($lang);
+ $this->data['compare'] = $this->Node->findById($this->currentNode, $fields, null, $recursive);
+ $this->helpers[] = 'Highlight';
+ $this->helpers[] = 'Diff';
+ $this->render('compare');
+ }
+/**
+ * edit function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function edit($id) {
+ if (!empty ($this->data)) {
+ if (isset ($this->data['Revision']['content2'])) {
+ $this->data['Revision']['content'] = $this->data['Revision']['content2'];
+ unset ($this->data['Revision']['content2']);
+ }
+ $this->data['Revision']['node_id'] = $id;
+ $this->data['Revision']['status'] = 'pending';
+ $this->data['Revision']['user_id'] = $this->Auth->user('id');
+ if ($this->Node->Revision->save($this->data)) {
+ if ($this->Session->read('Auth.User.Level') == ADMIN) {
+ $this->Node->Revision->publish($this->Node->Revision->id, $this->data['Revision']['reason'], true);
+ if ($this->Node->field('show_in_toc') != $this->data['Node']['show_in_toc']) {
+ $this->Node->save($this->data);
+ $parent = $this->Node->field('parent_id');
+ $this->Node->reset($parent);
+ if (!$this->data['Node']['show_in_toc']) {
+ $this->redirect(array('action' => 'view', $parent));
+ }
+ }
+ $this->redirect(array('action' => 'view', $id));
+ }
+ $this->Session->setFlash(__('Thanks for your contribution! Your suggestion has been submitted for review.', true));
+ return $this->redirect($this->Session->read('referer'));
+ } else {
+ $this->data['Revision'] = $this->Node->Revision->data['Revision'];
+ }
+ } else {
+ $this->data = $this->Node->read(null);
+ $this->data['Revision']['reason'] = '';
+ $this->data['Revision']['preview'] = true;
+ $this->data['Node']['show_in_toc'] = true;
+ }
+ $Attachment = ClassRegistry::init('Attachment');
+ $recursive = -1;
+ $conditions = array('Attachment.class' => 'Node', 'Attachment.foreign_id' => $id);
+ $this->set('attachments', $Attachment->find('all', compact('conditions', 'recursive')));
+ $this->helpers[] = 'Highlight';
+ $this->render('edit');
+ }
+/**
+ * history method
+ *
+ * @param mixed $id
+ * @param mixed $slug
+ * @param bool $englishToo
+ * @return void
+ * @access public
+ */
+ function history($id, $slug, $englishToo = false) {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ $language = array($this->params['lang']);
+ if ($englishToo && $this->params['lang'] != 'en') {
+ $language[] = 'en';
+ }
+ $conditions['Revision.node_id'] = $id;
+ $conditions['Revision.lang'] = $language;
+ $conditions['Revision.status'] = array('current', 'previous', 'pending');
+ $this->paginate['recursive'] = 0;
+ $this->paginate['limit'] = 100;
+ $this->paginate['order'] = 'Revision.created desc';
+ $this->paginate['fields'] = array(
+ 'Revision.id',
+ 'Revision.user_id',
+ 'Revision.lang',
+ 'Revision.status',
+ 'Revision.reason',
+ 'Revision.created',
+ );
+ $this->data = $this->paginate('Revision', $conditions);
+ $userIds = array_unique(Set::extract($this->data, '{n}.Revision.user_id'));
+ if ($userIds) {
+ $users = $this->Node->Revision->User->find('all', array(
+ 'fields' => array('id', 'IF(display_name=1,realname,username) AS name'),
+ 'conditions' => array('id' => $userIds), 'recursive' => -1));
+ if ($users) {
+ $users = Set::combine($users, '{n}.User.id', '{n}.0.name');
+ }
+ } else {
+ $users = array();
+ }
+ $this->set(compact('users'));
+ }
+/**
+ * view function
+ *
+ * If view all, find the requested node and all nodes under.
+ * If not, find only children which are set to not show in the TOC, and for any results
+ * find all of their children
+ * A time limit is set such that 10 nodes per second can be processed without timing out
+ * this means 300 nodes typically before the custom time limit extension kicks in.
+ *
+ * @param bool $viewAll
+ * @access protected
+ * @return void
+ */
+ function _view($viewAll = false) {
+ if (!isset($this->params['admin'])) {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ }
+ $recursive = 1;
+ $this->Node->id = $this->currentNode;
+ $fields = array('Node.id', 'Node.parent_id', 'Node.depth', 'Node.sequence', 'Node.lft', 'Node.rght', 'Node.edit_level', 'Node.comment_level', 'Revision.id', 'Revision.slug', 'Revision.title', 'Revision.content', 'Revision.flags', 'Revision.modified');
+ $this->data = $this->Node->findById($this->currentNode, $fields, null, $recursive);
+ $conditions = array(
+ 'Node.lft >' => $this->data['Node']['lft'],
+ 'Node.rght <' => $this->data['Node']['rght']
+ );
+ $count = ($this->data['Node']['rght'] - $this->data['Node']['lft']) / 2;
+ set_time_limit(max(30, $count / 10));
+ $order = 'Node.lft';
+ $children = array();
+ if ($viewAll) {
+ $children = $this->Node->find('all',compact('conditions', 'fields', 'order', 'recursive'));
+ } else {
+ $conditions = array(
+ 'Node.show_in_toc' => false,
+ 'Node.parent_id' => $this->data['Node']['id']
+ );
+ $children = $this->Node->find('all',compact('conditions', 'fields', 'order', 'recursive'));
+ $conditions = array();
+ if ($children) {
+ foreach ($children as $child) {
+ $conditions['OR'][] = array(
+ 'Node.lft >' => $child['Node']['lft'],
+ 'Node.rght <' => $child['Node']['rght']
+ );
+ }
+ $grandChildren = $this->Node->find('all',compact('conditions', 'fields', 'order', 'recursive'));
+ $children = am($children, $grandChildren);
+ $children = Set::sort($children, '/Node/lft', 'asc');
+ }
+ }
+ if ($children) {
+ $data = am(array ('Node' => $this->data), $children);
+ } else {
+ $data = array ('Node' => $this->data);
+ }
+ $neighbours = $this->Node->findNeighbors(null, $viewAll ? false : true);
+ $conditions = array('Revision.status' => 'pending', 'Revision.lang' => $this->params['lang']);
+ $recursive = -1;
+ $fields = array('DISTINCT node_id');
+ $pendingUpdates = $this->Node->Revision->find('all', compact('conditions', 'recursive', 'fields'));
+ $pendingUpdates = Set::extract($pendingUpdates, '{n}.Revision.node_id');
+ $this->set(compact('data', 'neighbours', 'children', 'pendingUpdates'));
+ $this->set('loginFields', $this->Auth->fields);
+ $this->helpers[] = 'Highlight';
+ $this->helpers[] = 'Text';
+ $this->render('view_all');
+ }
+}
+?>
\ No newline at end of file
diff --git a/controllers/redirect_controller.php b/controllers/redirect_controller.php
new file mode 100755
index 0000000..66c0d28
--- /dev/null
+++ b/controllers/redirect_controller.php
@@ -0,0 +1,154 @@
+<?php
+/* SVN FILE: $Id: redirect_controller.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ * @since v 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * RedirectController class used to process requests for pages that come from manual.cakephp.org and send
+ * them to the right page on book.cakephp.org
+ *
+ * @uses AppController
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ */
+class RedirectController extends AppController {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Redirect';
+/**
+ * uses variable
+ *
+ * @var array
+ * @access public
+ */
+ var $uses = array('Node');
+/**
+ * beforeFilter function. Allow access to the process function, don't call the parent and set the language
+ * to english (so that the subsequent redirect has it available)
+ *
+ * @access public
+ * @return void
+ */
+ function beforeFilter() {
+ $this->Auth->allow('process');
+ $this->params['lang'] = 'en';
+ }
+/**
+ * process function. Based on the passed params, send the user to the equivalent page.
+ * example, user enteres /chapter/intro, the later redirect should send them to /view/307/slug-for-307
+ *
+ * @param string $section
+ * @param string $name
+ * @access public
+ */
+ function process ($section = null, $name = null) {
+ $node = null;
+ if ($section == 'chapter') {
+ switch ($name) {
+ case 'intro':
+ $node = 307;
+ break;
+ case 'basic_concepts':
+ $node = 309;
+ break;
+ case 'installing':
+ $node = 308;
+ break;
+ case 'configuration':
+ $node = 310;
+ break;
+ case 'scaffolding':
+ $node = 311;
+ break;
+ case 'models':
+ $node = 312;
+ break;
+ case 'controllers':
+ $node = 313;
+ break;
+ case 'views':
+ $node = 314;
+ break;
+ case 'components':
+ $node = 315;
+ break;
+ case 'helpers':
+ $node = 316;
+ break;
+ case 'constants':
+ $node = 317;
+ break;
+ case 'validation':
+ $node = 318;
+ break;
+ case 'plugins':
+ $node = 319;
+ break;
+ case 'acl':
+ $node = 320;
+ break;
+ case 'sanitize':
+ $node = 321;
+ break;
+ case 'session':
+ $node = 322;
+ break;
+ case 'request_handler':
+ $node = 323;
+ break;
+ case 'security':
+ $node = 324;
+ break;
+ case 'view_cache':
+ $node = 325;
+ break;
+ }
+ } elseif ($section == 'appendix') {
+ switch ($name) {
+ case 'blog_tutorial':
+ $node = 326;
+ break;
+ case 'simple_user_auth':
+ $node = 327;
+ break;
+ case 'conventions':
+ $node = 328;
+ break;
+ }
+ }
+ if ($node) {
+ $slug = $this->Node->Revision->field('slug', array('node_id' => $node));
+ $this->redirect(array('controller' => 'nodes', 'action' => 'view', $node, $slug), 301);
+ }
+ // Optional "didn't find what you were looking for" message
+ //$this->Session->setFlash('Ooops couldn\'t find the corresponding section - however you\'ll find what you are looking for somewhere in the Cookbook');
+ $this->redirect('/');
+ }
+}
+?>
\ No newline at end of file
diff --git a/controllers/revisions_controller.php b/controllers/revisions_controller.php
new file mode 100755
index 0000000..5f2f69b
--- /dev/null
+++ b/controllers/revisions_controller.php
@@ -0,0 +1,655 @@
+<?php
+/* SVN FILE: $Id: revisions_controller.php 711 2008-11-19 18:20:57Z AD7six $ */
+/**
+ * Short description for revisions_controller.php
+ *
+ * Long description for revisions_controller.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ * @since 1.0
+ * @version $Revision: 711 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-19 19:20:57 +0100 (Wed, 19 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * RevisionsController class
+ *
+ * @uses AppController
+ * @package cookbook
+ * @subpackage cookbook.controllers
+ */
+class RevisionsController extends AppController {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Revisions';
+/**
+ * paginate variable
+ *
+ * @var array
+ * @access public
+ */
+ var $paginate = array('order' => 'Revision.created DESC');
+/**
+ * beforeFilter function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeFilter() {
+ if ($this->action == 'view') {
+ $urlSlug = isset($this->params['pass'][1])?$this->params['pass'][1]:'';
+ $conditions['Revision.id'] = $this->params['pass'][0];
+ $fields = array ('id', 'lang', 'slug', 'node_id');
+ $recursive = 0;
+ $result = $this->Revision->find('first', compact('conditions', 'fields', 'recursive'));
+ $this->currentNode = $result['Revision']['node_id'];
+ if (!($this->data)&&($urlSlug<>$result['Revision']['slug'])) {
+ $this->redirect(array($result['Revision']['id'], $result['Revision']['slug']), null, true);
+ }
+ } elseif (isset($this->params['pass'][0])) {
+ $this->Revision->id = $this->currentNode = $this->params['pass'][0];
+ }
+
+ if (!$this->currentNode) {
+ $conditions = array('Node.depth' => '0', 'Node.sequence' => 0);
+ $fields = array('id');
+ $recursive = 0;
+ $topNode = $this->Revision->find('first', compact('conditions', 'fields', 'recursive'));
+ $this->currentNode = $topNode['Node']['id'];
+ }
+ if ($this->currentNode) {
+ $fields = array('Node.id', 'Node.depth', 'Node.sequence', 'Node.lft', 'Node.rght', 'Node.edit_level', 'Node.comment_level', 'Revision.id', 'Revision.slug', 'Revision.title');
+ $this->currentPath = $this->Revision->Node->getPath($this->currentNode, $fields, 0);
+ }
+ $this->set('currentPath', $this->currentPath);
+ $this->Auth->allowedActions = array('search', 'results', 'view', 'compare');
+ parent::beforeFilter();
+ }
+/**
+ * beforeRender function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeRender() {
+ $crumbPath = isset($this->currentPath)?$this->currentPath:array();
+ $this->set('crumbPath', $crumbPath);
+ parent::beforeRender();
+ }
+/**
+ * search function
+ *
+ * @access public
+ * @return void
+ */
+ function search(){
+ $this->set('query' , '');
+ if(!empty($this->data)){
+ $params['query'] = $this->data['Search']['query'];
+ $params['collection'] = $this->data['Search']['collection'];
+ $params['lang'] = $this->data['Search']['lang'];
+ $params['action'] = 'results';
+ $this->redirect($params);
+ }
+ }
+
+/**
+ * results function
+ *
+ * updated to self correct if the url the index has does not match the current content's url and cache
+ * results pages
+ * Caching is enabled if the passed term contains a single sluggable character. This partially, but not completely,
+ * prevents utf8 search results overwritting themselves
+ *
+ * @access public
+ * @return void
+ */
+ function results(){
+ if(empty($this->passedArgs['query'])){
+ $this->redirect($this->referer());
+ }
+ $this->helpers[] ='Searchable.Search';
+ $this->helpers[] ='Paginator';
+ $limit = !empty($this->passedArgs['limit']) ? $this->passedArgs['limit'] : 20;
+ $page = !empty($this->passedArgs['page']) ? $this->passedArgs['page'] : 1;
+ $lang = !empty($this->passedArgs['lang']) ? $this->passedArgs['lang'] : $this->params['lang'];
+ $collection = !empty($this->passedArgs['collection']) ? $this->passedArgs['collection'] : 2;
+
+ // we should make a query object and use Zend_Search_Lucene api to construct it
+ $query = $this->passedArgs['query'];
+ $langQuery = ' AND lang:'. $lang;
+ $collectionQuery = ' AND collection:'. $collection;
+ $results = $this->Revision->search($query.$langQuery.$collectionQuery, $limit, $page);
+ $searchSlugs = Set::combine($results, '/Result/cake_id', '/Result/slug');
+ $conditions['id'] = array_keys($searchSlugs);
+ $fields = array('slug');
+ $recursive = -1;
+ $slugs = $this->Revision->find('list', compact('conditions', 'fields', 'recursive'));
+ $diff = array_diff($searchSlugs, $slugs);
+ if ($diff) {
+ foreach ($diff as $id => $_) {
+ $this->Revision->add_to_index($id);
+ }
+ $results = $this->Revision->search($query.$langQuery.$collectionQuery, $limit, $page);
+ }
+ //we need to pop the lang val from the terms
+ $terms = $this->Revision->terms();
+ $terms = array_diff($terms, array($lang, $collection));
+ if ($results) {
+ //Paginator cheating ;) maybe put it in an element in the view?
+ $count = $this->Revision->hits_count();
+ $pageCount = ceil($count/$limit);
+ $this->params['paging']['Revision']['page'] = $page;
+ $this->params['paging']['Revision']['count'] = $count;
+ $this->params['paging']['Revision']['current'] = $limit;
+ $this->params['paging']['Revision']['nextPage'] = $page + 1 < $pageCount ? $page + 1 : '';
+ $this->params['paging']['Revision']['prevPage'] = $page - 1 < 1 ? '' : $page - 1;
+ $this->params['paging']['Revision']['pageCount'] = $pageCount;
+ $this->params['paging']['Revision']['options']['page'] = $page;
+ $this->params['paging']['Revision']['options']['limit'] = $limit;
+ $this->params['paging']['Revision']['defaults']['page'] = $page;
+ $this->params['paging']['Revision']['defaults']['limit'] = $limit;
+ $this->set(compact('results', 'count', 'page', 'limit', 'terms', 'query'));
+ } else {
+ // fallback
+ $conditions = array('OR' => array(
+ 'Revision.title LIKE' => '%' . $this->passedArgs['query'] . '%',
+ 'Revision.content LIKE' => '%' . $this->passedArgs['query'] . '%'
+ ));
+ $nodes = $this->paginate('Node', $conditions);
+ if (Configure::read() && $nodes) {
+ $this->Session->setFlash('Search Index needs rebuilding - the index returned no results, ' .
+ 'but a simple LIKE %' . $this->passedArgs['query'] . '% search did.');
+ }
+ foreach ($nodes as $row) {
+ $this->Revision->id = $row['Revision']['id'];
+ $this->Revision->read();
+ $this->Revision->add_to_index($row['Revision']['id']);
+ $result = $row['Revision'];
+ $result['content'] = strip_tags($result['content']);
+ $result['cake_model'] = 'Revision';
+ $result['cake_id'] = $row['Revision']['id'];
+ $results[]['Result'] = $result;
+ }
+ $this->set(compact('results', 'terms'));
+ }
+ if (Inflector::slug($this->passedArgs['query'])) {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ }
+ }
+/**
+ * admin_build_index method
+ *
+ * Build or refresh the search index. This is an intensive method!
+ * If $clear evaluates to true, the existing index folder will be deleted. This ensures that all file permissions
+ * are as expected.
+ * A time limit of 2s per current revision is set - which should be sufficient even on a loaded/low power
+ * machine. Small sections take ~0.1 to index, large sections can take upto 30s
+ *
+ * @param bool $clear
+ * @return void
+ * @access public
+ */
+ function admin_build_index($clear = false){
+ $count = $this->Revision->find('count', array('status' => 'current'));
+ set_time_limit (max(30, $count) * 2);
+ if ($clear) {
+ if (file_exists(TMP . 'search_index')) {
+ $folder = new Folder(TMP . 'search_index');
+ if (!$folder->delete()) {
+ $this->Session->setFlash('The search index folder "' . TMP . 'search_index/" could not be deleted!<br />Manually delete this folder, ensure that the webuser can write to "' . TMP . '", and try again');
+ $this->redirect(array('action' => 'index'));
+ }
+ $this->Session->setFlash('The search index "' . TMP . 'search_index" was deleted');
+ }
+ }
+ $start = getMicrotime();
+ $this->Revision->build_index();
+ $this->Session->setFlash('Search index rebuilt');
+ if (Configure::read()) {
+ $time = round(getMicrotime() - $start, 1);
+ $this->log('Clearing cache files ' . $time . 's', 'searchable');
+ }
+ clearCache();
+ if (Configure::read()) {
+ $time = round(getMicrotime() - $start, 1);
+ $this->log('Cache cleared ' . $time . 's', 'searchable');
+ }
+ $this->redirect(array('action' => 'index'));
+ }
+/**
+ * admin_add_to_tree function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_add_to_tree ($id) {
+ if ($this->Revision->Node->addToTree($id)) {
+ $this->Session->setFlash('Revision '.$id.' added to the tree, move if necessary. NOT PUBLIC YET (need to approve).');
+ } else {
+ $this->Session->setFlash('Error adding revision '.$id.' to the tree.');
+ }
+ return $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_approve function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_approve($id) {
+ $data = $this->Revision->read(array('lang', 'node_id'), $id);
+ $isSignificant = false;
+ if (
+ $this->params['lang'] == 'en' &&
+ $data['Revision']['node_id'] &&
+ $this->Revision->find('list',
+ array('conditions' => array(
+ 'Revision.node_id' => $data['Revision']['node_id'],
+ 'Revision.status' => array('current', 'pending'),
+ 'Revision.lang !=' => 'en'
+ ))
+ )
+ ) {
+ $isSignificant = true;
+ }
+ $this->set('isSignificant', $isSignificant);
+ if ($this->data) {
+ if (isset($this->data['Revision']['is_significant'])) {
+ $isSignificant = $this->data['Revision']['is_significant'];
+ }
+ if ($this->Revision->publish($id, $this->data['Revision']['reason'], $isSignificant)) {
+ $this->Session->setFlash('Revision '.$id.' is now public.');
+ // Reuse the ignore redirect logic
+ $this->admin_ignore($id);
+ } else {
+ $this->Session->setFlash('Could not approve revision '.$id.'');
+ }
+ } else {
+ $this->data['Revision']['reason'] = $this->Revision->field('reason');
+ }
+ $this->render('admin_change_status');
+ }
+/**
+ * admin_reject function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_reject($id = null) {
+ if ($this->data) {
+ if ($this->Revision->reject($id, $this->data['Revision']['reason'])) {
+ $this->Session->setFlash('Revision ' . $id . ' marked as rejected');
+ // Reuse the ignore redirect logic
+ $this->admin_ignore($id);
+ } else {
+ $this->Session->setFlash('Could not reject revision '.$id.'');
+ }
+ } else {
+ $this->data = $this->Revision->read('reason', $id);
+ }
+ $this->render('admin_change_status');
+ }
+/**
+ * admin_reset method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_reset() {
+ $this->Revision->reset();
+ $this->redirect($this->Session->read('referer'), null, false);
+ }
+/**
+ * admin_edit function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_edit($id) {
+ if (!$this->Revision->hasAny(array($this->Revision->primaryKey => $id))) {
+ $this->redirect(array('action' => 'index'), null, true);
+ }
+ if (!empty ($this->data)) {
+ if (isset ($this->data['Revision']['content2'])) {
+ $this->data['Revision']['content'] = $this->data['Revision']['content2'];
+ unset ($this->data['Revision']['content2']);
+ }
+ if ($this->Revision->save($this->data)) {
+ $this->Session->setFlash($this->Revision->alias . ' updated');
+ $this->redirect($this->Session->read('referer'), null, true);
+ } else {
+ $this->Session->setFlash('Please correct the errors below.');
+ }
+ } else {
+ $this->data = $this->Revision->read(null, $id);
+ }
+ $users = $this->Revision->User->find('list');
+ $nodes = $this->Revision->Node->generateTreeList();
+ $under_nodes = $after_nodes = array();
+ if (!$this->data['Node']['id']) {
+ $under_nodes = $nodes;
+ $after_nodes = $this->Revision->Node->generateTreeList(array('Node.parent_id' => $this->data['Revision']['under_node_id']));
+ $nodes = array(null => 'Not added yet');
+ }
+ $this->set(compact('users', 'nodes', 'under_nodes', 'after_nodes'));
+ }
+/**
+ * admin_hide function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_hide($id) {
+ if (!$this->Revision->hasAny(array('Revision.id' => $id, 'Revision.status' => 'current'))) {
+ $this->Session->setFlash('Could not hide revision '.$id.', not the currently visible version');
+ } elseif ($this->Revision->hide($id)) {
+ $this->Session->setFlash('Revision '.$id.' is now hidden.');
+ } else {
+ $this->Session->setFlash('Could not revert revision '.$id.', unknown problem');
+ }
+ $this->redirect($this->Session->read('referer'), null, true);
+ }
+/**
+ * admin_history function
+ *
+ * @param mixed $nodeId
+ * @access public
+ * @return void
+ */
+ function admin_history($nodeId) {
+ $this->paginate['limit'] = 20;
+ $this->paginate['order'] = 'Revision.created desc';
+ $this->params['named']['node_id'] = $nodeId;
+ $this->admin_index();
+ $this->render('admin_index');
+ }
+/**
+ * admin_ignore method
+ *
+ * Called when reviewing content, just redirects to the next pending revision
+ *
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function admin_ignore($id = null) {
+ if ($this->action == 'admin_ignore') {
+ $this->Session->setFlash('Revision ' . $id . ' left in the pending queue');
+ }
+ $this->Revision->order = array('Revision.id ASC');
+ $id = $this->Revision->field('id', array('id >' . $id, 'status' => 'pending', 'lang' =>
+ $this->Revision->field('lang')));
+ if ($id) {
+ return $this->redirect(array('action' => 'view', $id), null, true);
+ } else {
+ return $this->redirect(array('action' => 'pending'), null, true);
+ }
+ }
+/**
+ * admin_index method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_index() {
+ if (isset($this->params['named']['restrict_to'])) {
+ $restrictTo = $this->params['named']['restrict_to'];
+ unset($this->params['named']['restrict_to']);
+ $limits = $this->Revision->Node->find('first', array('conditions' => array('Node.id' => $restrictTo), 'fields' => array('lft', 'rght', 'Revision.title')));
+ $this->params['named']['Node.lft >='] = $limits['Node']['lft'];
+ $this->params['named']['Node.rght <='] = $limits['Node']['rght'];
+ $this->Session->setFlash('Only "' . $limits['Revision']['title'] . '" and below');
+ }
+ return parent::admin_index();
+ }
+/**
+ * admin_invalid method
+ *
+ * @return void
+ * @access public
+ */
+ function admin_invalid($fix = false) {
+ $query = 'SELECT nodes.id, revisions.lang, count(revisions.id) FROM nodes left join revisions on revisions.node_id = nodes.id WHERE revisions.status = "current" GROUP BY nodes.id, revisions.lang HAVING count(revisions.id) > 1';
+ $results = $this->Revision->query($query);
+ if ($results) {
+ if ($fix) {
+ debug ($results);
+ die;
+ }
+ $this->Session->setFlash('Duplicate "current" revisions found');
+ $nodes = Set::extract('/nodes/id', $results);
+ $this->params['named']['node_id'] = $nodes;
+ $this->params['named']['status'] = 'current';
+ $this->params['named']['lang'] = 'en';
+ $this->admin_index();
+ $this->render('admin_index');
+ return;
+ }
+ $results = $this->Revision->Node->find('list', array('conditions' => array('Revision.id' => null)));
+ if ($results) {
+ if ($fix) {
+ set_time_limit (max(30, count($results) * 2));
+ $problem = array();
+ foreach ($results as $node_id => $_) {
+ $id = $this->Revision->find('first', array(
+ 'fields' => array('Revision.id'),
+ 'recursive' => -1,
+ 'conditions' => array(
+ 'Revision.node_id' => $node_id,
+ 'Revision.status' => 'previous'
+ ),
+ 'order' => 'Revision.id DESC'
+ ));
+ if (!$id) {
+ $id = $this->Revision->find('first', array(
+ 'fields' => array('Revision.id'),
+ 'recursive' => -1,
+ 'conditions' => array(
+ 'Revision.node_id' => $node_id,
+ 'Revision.status' => 'pending'
+ ),
+ 'order' => 'Revision.id DESC'
+ ));
+ }
+ if (!$id) {
+ $id = $this->Revision->find('first', array(
+ 'fields' => array('Revision.id'),
+ 'recursive' => -1,
+ 'conditions' => array(
+ 'Revision.node_id' => $node_id,
+ ),
+ 'order' => 'Revision.id DESC'
+ ));
+ }
+ if ($id) {
+ $this->Revision->id = $id['Revision']['id'];
+ $this->Revision->publish();
+ } else {
+ if (!$this->Revision->find('count', array('conditions' => array('Revision.node_id' => $node_id)))) {
+ $this->Revision->Node->del($node_id);
+ } else {
+ $problem[] = $node_id;
+ }
+ }
+ }
+ if (!$problem) {
+ $this->redirect(array('fix'));
+ }
+ $this->Session->setFlash('Nodes with no current revision found');
+ $this->params['named']['node_id'] = array_keys($results);
+ $this->params['named']['lang'] = 'en';
+ $this->admin_index();
+ $this->render('admin_index');
+ return;
+ }
+ }
+ if ($fix) {
+ $this->Session->setFlash('Any problems encountered have been automatically fixed.');
+ $this->redirect(array('controller' => 'nodes', 'action' => 'index'));
+ } else {
+ $this->Session->setFlash('No invalid revisions found');
+ }
+ $this->redirect($this->Session->read('referer'));
+ }
+/**
+ * admin_pending function
+ *
+ * @access public
+ * @return void
+ */
+ function admin_pending () {
+ $this->paginate['limit'] = 10;
+ $this->paginate['order'] = 'Revision.id ASC';
+ $this->paginate['conditions'] = array('Revision.status' => 'pending');
+ $this->Revision->recursive = 1;
+ $this->Revision->bindModel(array('belongsTo' => array(
+ 'AfterNode' => array(
+ 'className' => 'Node',
+ 'foreignKey' => 'after_node_id',
+ ),
+ 'UnderNode' => array(
+ 'className' => 'Node',
+ 'foreignKey' => 'under_node_id',
+ )
+ )),false );
+ $this->admin_index();
+ }
+/**
+ * admin_reset_slugs function
+ *
+ * @param mixed $id
+ * @param bool $updateIndex
+ * @access public
+ * @return void
+ */
+ function admin_reset_slugs($id = null, $updateIndex = false) {
+ $updates = $this->Revision->resetSlugs($id, $updateIndex);
+ if (!$updates) {
+ $this->Session->setFlash('Function ran successfully however no slugs updated!');
+ } else {
+ $conditions = array('Revision.id' => $updates);
+ $fields = array('CONCAT(Revision.lang, ": ", Node.sequence, " - ", Revision.title) as c_title');
+ $order = array('Node.lft', 'Revision.lang');
+ $recursive = 0;
+ $titles = $this->Revision->find('all', compact('conditions', 'fields', 'order', 'recursive'));
+ $titles = Set::extract($titles, '/0/c_title');
+ $message = 'Function ran successfully updating the slugs for the following: <br />' . implode($titles, '<br />') . '<br />The search index was ' . ($updateIndex?'':'NOT') . ' updated.';
+ $this->Session->setFlash($message);
+ }
+ $this->redirect($this->Session->read('referer'), null, false);
+ }
+/**
+ * admin_view function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function admin_view($id) {
+ $this->Revision->recursive = 0;
+ $this->Revision->bindModel(array('belongsTo' => array(
+ 'AfterNode' => array(
+ 'className' => 'Node',
+ 'foreignKey' => 'after_node_id',
+ ),
+ 'UnderNode' => array(
+ 'className' => 'Node',
+ 'foreignKey' => 'under_node_id',
+ )
+ )),false );
+ $this->Node->Revision->recursive = 0;
+ $this->data = $this->Revision->read(null, $id);
+ $user = $this->Revision->User->read(null, $this->data['Revision']['user_id']);
+ $user = $user['User'];
+ if ($this->data['Revision']['status'] != 'current') {
+ $this->Revision->Node->setLanguage($this->data['Revision']['lang']);
+ $this->set('current', $this->Revision->Node->find('first', array('conditions' => array('Node.id' => $this->data['Revision']['node_id']))));
+ }
+ $this->Revision->id = $this->currentNode;
+ $conditions = array('Node.parent_id' => $this->currentNode, 'Revision.status' => 'current');
+ $fields = array ('Node.depth', 'Node.sequence', 'Node.parent_id', 'Node.lft', 'Node.rght', 'id', 'slug', 'title', 'content');
+ $recursive = 0;
+ $children = $this->Revision->find('all', compact('conditions', 'fields', 'recursive'));
+ $viewAllLevel = $this->Revision->viewAllLevel;
+ $comments = $this->Revision->Node->Comment->findAllByNode_id($this->data['Node']['id']);
+
+ $this->helpers[] = 'Highlight';
+ $this->helpers[] = 'Diff';
+ $this->set(compact('neighbours', 'children', 'viewAllLevel', 'comments', 'user'));
+ }
+/**
+ * compare method
+ *
+ * @param mixed $id1
+ * @param mixed $id2
+ * @return void
+ * @access public
+ */
+ function compare($id1 = null, $id2 = null) {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ $this->Revision->recursive = 0;
+ $this->data['this'] = $this->Revision->find('first', array('conditions' => array('Revision.id' => $id1)));
+ if (!in_array($this->data['this']['Revision']['status'], array('current', 'previous'))) {
+ $this->Session->setFlash(__('Only current and previous revisions can be viewed', true));
+ $this->redirect($this->Session->read('referer'));
+ }
+ $this->data['other'] = $this->Revision->find('first', array('conditions' => array('Revision.id' => $id2)));
+ if (!in_array($this->data['other']['Revision']['status'], array('current', 'previous'))) {
+ $this->Session->setFlash(__('Only current and previous revisions can be viewed', true));
+ $this->redirect($this->Session->read('referer'));
+ }
+ if ($this->data['this']['Revision']['node_id'] != $this->data['other']['Revision']['node_id']) {
+ $this->Session->setFlash(__('Only possible to compare revisions of the same node', true));
+ $this->redirect($this->Session->read('referer'));
+ }
+ $this->helpers[] = 'Highlight';
+ $this->helpers[] = 'Diff';
+ $this->render('view');
+ }
+/**
+ * view method
+ *
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function view($id) {
+ $this->cacheAction = array('duration' => CACHE_DURATION, 'callbacks' => false);
+ $this->Revision->recursive = 0;
+ $this->data['this'] = $this->Revision->find('first', array('conditions' => array('Revision.id' => $id)));
+ if (!in_array($this->data['this']['Revision']['status'], array('current', 'previous'))) {
+ $this->Session->setFlash(__('Only current and previous revisions can be viewed', true));
+ $this->redirect($this->Session->read('referer'));
+ }
+ if ($this->data['this']['Revision']['status'] != 'current') {
+ $this->Revision->Node->setLanguage($this->data['this']['Revision']['lang']);
+ $this->data['current'] = $this->Revision->Node->find('first', array(
+ 'conditions' => array('Node.id' => $this->data['this']['Revision']['node_id'])));
+ }
+ $this->helpers[] = 'Highlight';
+ $this->helpers[] = 'Diff';
+ }
+}
+?>
\ No newline at end of file
diff --git a/docs/install_instructions.txt b/docs/install_instructions.txt
new file mode 100644
index 0000000..1ff7fde
--- /dev/null
+++ b/docs/install_instructions.txt
@@ -0,0 +1,17 @@
+=== Install Instructions ===
+
+This app is designed to run with the branch version of 1.2
+
+The user system is linked to the bakery, it's therefore a good idea to have
+a working http://cakeforge.org/projects/bakery/ install first.
+
+== DB setup ==
+create the book database and tables using either the schema.php or schema.sql file
+Add the sample data from the config/sql folder
+if you haven't installed the bakery code, you at least need to create the user
+table from the bakery schema
+
+== Config setup ==
+Create a core.php file, or copy from a stock 1.2 install
+Create a database.php file naming the database with a default and bakery
+connection
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..1759394
--- /dev/null
+++ b/index.php
@@ -0,0 +1,26 @@
+<?php
+/* SVN FILE: $Id: index.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app
+ * @since CakePHP v 0.10.0.1076
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+require 'webroot' . DIRECTORY_SEPARATOR . 'index.php';
+?>
\ No newline at end of file
diff --git a/locale/default.pot b/locale/default.pot
new file mode 100644
index 0000000..1a8974b
--- /dev/null
+++ b/locale/default.pot
@@ -0,0 +1,481 @@
+# LANGUAGE translation of CakePHP Application
+# Copyright YEAR NAME <EMAIL@ADDRESS>
+# Generated from files:
+# Revision: 697 /cookbook/app_controller.php
+# Revision: 689 /cookbook/controllers/attachments_controller.php
+# Revision: 689 /cookbook/controllers/changes_controller.php
+# Revision: 702 /cookbook/views/layouts/default.ctp
+# Revision: 689 /cookbook/controllers/comments_controller.php
+# Revision: 706 /cookbook/views/nodes/view_all.ctp
+# Revision: 600 /cookbook/views/comments/index.ctp
+# Revision: 704 /cookbook/controllers/nodes_controller.php
+# Revision: 698 /cookbook/controllers/revisions_controller.php
+# Revision: 701 /cookbook/models/node.php
+# Revision: 659 /cookbook/plugins/users/views/users/login.ctp
+# Revision: 705 /cookbook/views/elements/secondary_nav.ctp
+# Revision: 600 /cookbook/views/changes/index.ctp
+# Revision: 638 /cookbook/views/changes/rss/index.ctp
+# Revision: 600 /cookbook/views/comments/add.ctp
+# Revision: 666 /cookbook/views/elements/attachments.ctp
+# Revision: 673 /cookbook/views/elements/comment.ctp
+# Revision: 661 /cookbook/views/elements/comment_form.ctp
+# Revision: 600 /cookbook/views/elements/login_hint.ctp
+# Revision: 705 /cookbook/views/elements/node_options.ctp
+# Revision: 673 /cookbook/views/elements/paging.ctp
+# Revision: 600 /cookbook/views/elements/search.ctp
+# Revision: 672 /cookbook/views/elements/side_menu.ctp
+# Revision: 600 /cookbook/views/elements/toc.ctp
+# Revision: 673 /cookbook/views/nodes/toc.ctp
+# Revision: 600 /cookbook/views/layouts/error.ctp
+# Revision: 672 /cookbook/views/nodes/add.ctp
+# Revision: 673 /cookbook/views/nodes/edit.ctp
+# Revision: 600 /cookbook/views/nodes/admin_merge.ctp
+# Revision: 600 /cookbook/views/nodes/history.ctp
+# Revision: 706 /cookbook/views/nodes/stats.ctp
+# Revision: 707 /cookbook/views/nodes/todo.ctp
+# Revision: 673 /cookbook/views/revisions/results.ctp
+# Revision: 689 /cookbook/webroot/test.php
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"POT-Creation-Date: 2008-11-19 13:34+0100\n"
+"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
+"Last-Translator: NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: /app_controller.php:93
+msgid "Whoops, not a valid language."
+msgstr ""
+
+#: /app_controller.php:109
+msgid "Please login to continue"
+msgstr ""
+
+#: /app_controller.php:293
+msgid "All %s matching the term \"%s\""
+msgstr ""
+
+#: /controllers/attachments_controller.php:85
+msgid "Attachment controller - no class or foreignKey error."
+msgstr ""
+
+#: /controllers/attachments_controller.php:110
+msgid "Existing Attachment for %s, id %s updated"
+msgstr ""
+
+#: /controllers/attachments_controller.php:112
+msgid "New Attachment for %s, id %s added"
+msgstr ""
+
+#: /controllers/attachments_controller.php:137
+msgid "%s with id %s updated"
+msgstr ""
+
+#: /controllers/attachments_controller.php:140
+msgid "errors in form"
+msgstr ""
+
+#: /controllers/changes_controller.php:130
+msgid "Recent Changes for %s"
+msgstr ""
+
+#: /controllers/changes_controller.php:134
+#: /views/layouts/default.ctp:33
+msgid "Recent Changes for all languages"
+msgstr ""
+
+#: /controllers/changes_controller.php:136
+#: /views/layouts/default.ctp:28
+msgid "Recent Changes"
+msgstr ""
+
+#: /controllers/changes_controller.php:144
+#: /controllers/comments_controller.php:191
+msgid "by %s"
+msgstr ""
+
+#: /controllers/changes_controller.php:149
+msgid "restricted to status: %s"
+msgstr ""
+
+#: /controllers/comments_controller.php:84;116
+msgid "Invalid Node."
+msgstr ""
+
+#: /controllers/comments_controller.php:93
+msgid "Your comment has been added"
+msgstr ""
+
+#: /controllers/comments_controller.php:96
+msgid "Please correct errors below."
+msgstr ""
+
+#: /controllers/comments_controller.php:120
+msgid "No permissions to see comments for that section"
+msgstr ""
+
+#: /controllers/comments_controller.php:128
+#: /views/nodes/view_all.ctp:110;120
+msgid "Comments for %s"
+msgstr ""
+
+#: /controllers/comments_controller.php:161
+msgid "Invalid Comment."
+msgstr ""
+
+#: /controllers/comments_controller.php:180
+#: /views/comments/index.ctp:6
+msgid "Recent Comments"
+msgstr ""
+
+#: /controllers/comments_controller.php:182
+#: /views/layouts/default.ctp:43
+msgid "Recent Comments for all languages"
+msgstr ""
+
+#: /controllers/nodes_controller.php:846
+msgid "Invalid Collection"
+msgstr ""
+
+#: /controllers/nodes_controller.php:871;976
+msgid "Thanks for your contribution! Your suggestion has been submitted for review."
+msgstr ""
+
+#: /controllers/nodes_controller.php:931
+msgid "This function is for comparing different (public) language content"
+msgstr ""
+
+#: /controllers/revisions_controller.php:615;620;643
+msgid "Only current and previous revisions can be viewed"
+msgstr ""
+
+#: /controllers/revisions_controller.php:624
+msgid "Only possible to compare revisions of the same node"
+msgstr ""
+
+#: /models/node.php:170
+msgid "Default Title"
+msgstr ""
+
+#: /models/node.php:173
+msgid "default_slug"
+msgstr ""
+
+#: /models/node.php:176
+msgid "Default Content"
+msgstr ""
+
+#: /plugins/users/views/users/login.ctp:8;20
+#: /views/elements/secondary_nav.ctp:23
+msgid "Login"
+msgstr ""
+
+#: /plugins/users/views/users/login.ctp:13
+msgid "Forgot your password?"
+msgstr ""
+
+#: /views/changes/index.ctp:18
+msgid "change submitted by %s, %s"
+msgstr ""
+
+#: /views/changes/index.ctp:24
+#: /views/changes/rss/index.ctp:14
+msgid "accepted"
+msgstr ""
+
+#: /views/changes/index.ctp:27
+#: /views/changes/rss/index.ctp:17
+msgid "not accepted"
+msgstr ""
+
+#: /views/changes/index.ctp:30
+#: /views/changes/rss/index.ctp:20
+msgid "pending"
+msgstr ""
+
+#: /views/changes/index.ctp:35
+msgid "changed from %s to %s by %s, %s"
+msgstr ""
+
+#: /views/changes/index.ctp:41
+msgid "submitted by %s"
+msgstr ""
+
+#: /views/comments/add.ctp:6
+msgid "Comment: %s"
+msgstr ""
+
+#: /views/comments/index.ctp:4
+msgid "Comments: %s"
+msgstr ""
+
+#: /views/comments/index.ctp:11
+msgid "No Comments yet!"
+msgstr ""
+
+#: /views/comments/index.ctp:20
+msgid "This page as a feed"
+msgstr ""
+
+#: /views/elements/attachments.ctp:2
+msgid "Images/Files associated with this content"
+msgstr ""
+
+#: /views/elements/comment.ctp:9
+msgid "unknown"
+msgstr ""
+
+#: /views/elements/comment_form.ctp:4
+msgid "Login to add a comment"
+msgstr ""
+
+#: /views/elements/comment_form.ctp:17
+msgid "Comment on %s"
+msgstr ""
+
+#: /views/elements/login_hint.ctp:3
+msgid "Login with your Bakery account"
+msgstr ""
+
+#: /views/elements/login_hint.ctp:4
+msgid "Not got one? Hop on over to %s and sign up"
+msgstr ""
+
+#: /views/elements/node_options.ctp:8
+msgid "Edit"
+msgstr ""
+
+#: /views/elements/node_options.ctp:11
+msgid "View just this section"
+msgstr ""
+
+#: /views/elements/node_options.ctp:14
+msgid "Comments (%s)"
+msgstr ""
+
+#: /views/elements/node_options.ctp:19
+msgid "there is a pending change for this section"
+msgstr ""
+
+#: /views/elements/node_options.ctp:22
+msgid "History"
+msgstr ""
+
+#: /views/elements/node_options.ctp:32
+msgid "This text may be out of sync with the English version"
+msgstr ""
+
+#: /views/elements/node_options.ctp:39
+msgid "Compare to original content"
+msgstr ""
+
+#: /views/elements/paging.ctp:4
+msgid "Page %s"
+msgstr ""
+
+#: /views/elements/paging.ctp:5
+msgid "<< previous"
+msgstr ""
+
+#: /views/elements/paging.ctp:8
+msgid "Next >>"
+msgstr ""
+
+#: /views/elements/search.ctp:22
+#: /views/elements/search_form.ctp:12
+msgid "Search"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:10
+msgid "Top Contributors"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:13
+msgid "todo"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:17
+msgid "Logged in as %s"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:18
+msgid "Logout"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:26
+msgid "About CakePHP"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:27
+msgid "Donate"
+msgstr ""
+
+#: /views/elements/side_menu.ctp:14
+#: /views/nodes/view_all.ctp:95
+msgid "All in one page"
+msgstr ""
+
+#: /views/elements/side_menu.ctp:23
+#: /views/nodes/view_all.ctp:102
+msgid "Suggest a new section here"
+msgstr ""
+
+#: /views/elements/side_menu.ctp:30
+msgid "Options"
+msgstr ""
+
+#: /views/elements/toc.ctp:13
+#: /views/nodes/toc.ctp:5
+msgid "Table of Contents"
+msgstr ""
+
+#: /views/elements/toc.ctp:16
+msgid "see fully expanded table of contents (only)"
+msgstr ""
+
+#: /views/elements/toc.ctp:19
+msgid "Books in "
+msgstr ""
+
+#: /views/elements/toc.ctp:21
+msgid "Available collections"
+msgstr ""
+
+#: /views/layouts/default.ctp:38
+msgid "Recent comments"
+msgstr ""
+
+#: /views/layouts/default.ctp:51;55
+msgid "My Submissions"
+msgstr ""
+
+#: /views/layouts/default.ctp:88
+msgid "Welcome to %s"
+msgstr ""
+
+#: /views/layouts/error.ctp:6
+msgid "CakePHP: the rapid development php framework:"
+msgstr ""
+
+#: /views/layouts/error.ctp:34
+msgid "CakePHP: the rapid development php framework"
+msgstr ""
+
+#: /views/nodes/add.ctp:17
+#: /views/nodes/edit.ctp:26
+msgid "Please review the guidelines for submitting to the Cookbook to ensure consistency."
+msgstr ""
+
+#: /views/nodes/add.ctp:22
+msgid "Add a new section"
+msgstr ""
+
+#: /views/nodes/add.ctp:28
+#: /views/nodes/edit.ctp:32
+msgid "Show me a preview before submitting"
+msgstr ""
+
+#: /views/nodes/add.ctp:30
+msgid "under"
+msgstr ""
+
+#: /views/nodes/add.ctp:33
+msgid "after"
+msgstr ""
+
+#: /views/nodes/add.ctp:39
+#: /views/nodes/edit.ctp:35
+msgid "Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically"
+msgstr ""
+
+#: /views/nodes/add.ctp:44
+msgid "Optionally explain in brief why you are proposing this addition (In English Please) :)"
+msgstr ""
+
+#: /views/nodes/admin_merge.ctp:18
+msgid "You are sure? Please check the preview"
+msgstr ""
+
+#: /views/nodes/compare.ctp:11
+#: /views/revisions/view.ctp:12
+msgid "Differences"
+msgstr ""
+
+#: /views/nodes/edit.ctp:40
+msgid "What is the reason for the edit? (In English Please) :)"
+msgstr ""
+
+#: /views/nodes/history.ctp:6
+msgid "See English edits too"
+msgstr ""
+
+#: /views/nodes/stats.ctp:5
+msgid "Here's a shout out to those who have dedicated time, sweat and tears to write, translate and edit the cookbook contents."
+msgstr ""
+
+#: /views/nodes/stats.ctp:7;37
+msgid "Top %s Contributors"
+msgstr ""
+
+#: /views/nodes/stats.ctp:9;38
+msgid "Last update: %s"
+msgstr ""
+
+#: /views/nodes/stats.ctp:27;63
+msgid "%s (%s current)"
+msgstr ""
+
+#: /views/nodes/stats.ctp:39
+msgid "%s%% translated"
+msgstr ""
+
+#: /views/nodes/stats.ctp:41
+msgid "The cookbook needs you! No submissions for this language!"
+msgstr ""
+
+#: /views/nodes/stats.ctp:44
+msgid "The cookbook needs you! This language will soon be removed if not updated."
+msgstr ""
+
+#: /views/nodes/stats.ctp:46
+msgid "The cookbook needs you! No updates for one month."
+msgstr ""
+
+#: /views/nodes/todo.ctp:7
+msgid "These is no <a href=\"%s\">po file</a> for %s"
+msgstr ""
+
+#: /views/nodes/todo.ctp:9
+msgid "These sections either do not have a translation, or the English text has changed since it was translated"
+msgstr ""
+
+#: /views/nodes/view_all.ctp:40;67
+msgid "See comments for this section"
+msgstr ""
+
+#: /views/nodes/view_all.ctp:114;126
+msgid "Change history for %s"
+msgstr ""
+
+#: /views/revisions/results.ctp:8
+msgid "Search Results"
+msgstr ""
+
+#: /views/revisions/results.ctp:32
+msgid "No results"
+msgstr ""
+
+#: /views/revisions/search.ctp:1
+msgid "Search book.cakephp.org"
+msgstr ""
+
+#: /webroot/test.php:75
+msgid "Debug setting does not allow access to this url."
+msgstr ""
+
diff --git a/locale/eng/LC_MESSAGES/default.po b/locale/eng/LC_MESSAGES/default.po
new file mode 100644
index 0000000..4dbbb8b
--- /dev/null
+++ b/locale/eng/LC_MESSAGES/default.po
@@ -0,0 +1,441 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Cookbook 1.0\n"
+"POT-Creation-Date: 2008-11-19 13:34+0100\n"
+"PO-Revision-Date: 2008-11-19 13:52+0100\n"
+"Last-Translator: Andy Dawson <andydawson76@yahoo.co.uk>\n"
+"Language-Team: Documentation CakePHP <docs@cakephp.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: /app_controller.php:93
+msgid "Whoops, not a valid language."
+msgstr ""
+
+#: /app_controller.php:109
+msgid "Please login to continue"
+msgstr ""
+
+#: /app_controller.php:293
+msgid "All %s matching the term \"%s\""
+msgstr ""
+
+#: /controllers/attachments_controller.php:85
+msgid "Attachment controller - no class or foreignKey error."
+msgstr ""
+
+#: /controllers/attachments_controller.php:110
+msgid "Existing Attachment for %s, id %s updated"
+msgstr ""
+
+#: /controllers/attachments_controller.php:112
+msgid "New Attachment for %s, id %s added"
+msgstr ""
+
+#: /controllers/attachments_controller.php:137
+msgid "%s with id %s updated"
+msgstr "%s with id %s updated"
+
+#: /controllers/attachments_controller.php:140
+msgid "errors in form"
+msgstr ""
+
+#: /controllers/changes_controller.php:130
+msgid "Recent Changes for %s"
+msgstr ""
+
+#: /controllers/changes_controller.php:134
+#: /views/layouts/default.ctp:33
+msgid "Recent Changes for all languages"
+msgstr ""
+
+#: /controllers/changes_controller.php:136
+#: /views/layouts/default.ctp:28
+msgid "Recent Changes"
+msgstr ""
+
+#: /controllers/changes_controller.php:144
+#: /controllers/comments_controller.php:191
+msgid "by %s"
+msgstr ""
+
+#: /controllers/changes_controller.php:149
+msgid "restricted to status: %s"
+msgstr ""
+
+#: /controllers/comments_controller.php:84;116
+msgid "Invalid Node."
+msgstr ""
+
+#: /controllers/comments_controller.php:93
+msgid "Your comment has been added"
+msgstr ""
+
+#: /controllers/comments_controller.php:96
+msgid "Please correct errors below."
+msgstr ""
+
+#: /controllers/comments_controller.php:120
+msgid "No permissions to see comments for that section"
+msgstr ""
+
+#: /controllers/comments_controller.php:128
+#: /views/nodes/view_all.ctp:110;120
+msgid "Comments for %s"
+msgstr ""
+
+#: /controllers/comments_controller.php:161
+msgid "Invalid Comment."
+msgstr ""
+
+#: /controllers/comments_controller.php:180
+#: /views/comments/index.ctp:6
+msgid "Recent Comments"
+msgstr ""
+
+#: /controllers/comments_controller.php:182
+#: /views/layouts/default.ctp:43
+msgid "Recent Comments for all languages"
+msgstr ""
+
+#: /controllers/nodes_controller.php:846
+msgid "Invalid Collection"
+msgstr ""
+
+#: /controllers/nodes_controller.php:871;976
+msgid "Thanks for your contribution! Your suggestion has been submitted for review."
+msgstr ""
+
+#: /controllers/nodes_controller.php:931
+msgid "This function is for comparing different (public) language content"
+msgstr ""
+
+#: /controllers/revisions_controller.php:615;620;643
+msgid "Only current and previous revisions can be viewed"
+msgstr ""
+
+#: /controllers/revisions_controller.php:624
+msgid "Only possible to compare revisions of the same node"
+msgstr ""
+
+#: /models/node.php:170
+msgid "Default Title"
+msgstr "This section has yet to be written"
+
+#: /models/node.php:173
+msgid "default_slug"
+msgstr "missing"
+
+#: /models/node.php:176
+msgid "Default Content"
+msgstr "<p>This section has yet to be written, if you have an idea of what to put here please use the links and let us know your suggestion!</p>"
+
+#: /plugins/users/views/users/login.ctp:8;20
+#: /views/elements/secondary_nav.ctp:23
+msgid "Login"
+msgstr ""
+
+#: /plugins/users/views/users/login.ctp:13
+msgid "Forgot your password?"
+msgstr ""
+
+#: /views/changes/index.ctp:18
+msgid "change submitted by %s, %s"
+msgstr ""
+
+#: /views/changes/index.ctp:24
+#: /views/changes/rss/index.ctp:14
+msgid "accepted"
+msgstr ""
+
+#: /views/changes/index.ctp:27
+#: /views/changes/rss/index.ctp:17
+msgid "not accepted"
+msgstr ""
+
+#: /views/changes/index.ctp:30
+#: /views/changes/rss/index.ctp:20
+msgid "pending"
+msgstr ""
+
+#: /views/changes/index.ctp:35
+msgid "changed from %s to %s by %s, %s"
+msgstr ""
+
+#: /views/changes/index.ctp:41
+msgid "submitted by %s"
+msgstr ""
+
+#: /views/comments/add.ctp:6
+msgid "Comment: %s"
+msgstr ""
+
+#: /views/comments/index.ctp:4
+msgid "Comments: %s"
+msgstr ""
+
+#: /views/comments/index.ctp:11
+msgid "No Comments yet!"
+msgstr ""
+
+#: /views/comments/index.ctp:20
+msgid "This page as a feed"
+msgstr ""
+
+#: /views/elements/attachments.ctp:2
+msgid "Images/Files associated with this content"
+msgstr ""
+
+#: /views/elements/comment.ctp:9
+msgid "unknown"
+msgstr ""
+
+#: /views/elements/comment_form.ctp:4
+msgid "Login to add a comment"
+msgstr ""
+
+#: /views/elements/comment_form.ctp:17
+msgid "Comment on %s"
+msgstr ""
+
+#: /views/elements/login_hint.ctp:3
+msgid "Login with your Bakery account"
+msgstr ""
+
+#: /views/elements/login_hint.ctp:4
+msgid "Not got one? Hop on over to %s and sign up"
+msgstr ""
+
+#: /views/elements/node_options.ctp:8
+msgid "Edit"
+msgstr ""
+
+#: /views/elements/node_options.ctp:11
+msgid "View just this section"
+msgstr ""
+
+#: /views/elements/node_options.ctp:14
+msgid "Comments (%s)"
+msgstr ""
+
+#: /views/elements/node_options.ctp:19
+msgid "there is a pending change for this section"
+msgstr ""
+
+#: /views/elements/node_options.ctp:22
+msgid "History"
+msgstr ""
+
+#: /views/elements/node_options.ctp:32
+msgid "This text may be out of sync with the English version"
+msgstr ""
+
+#: /views/elements/node_options.ctp:39
+msgid "Compare to original content"
+msgstr ""
+
+#: /views/elements/paging.ctp:4
+msgid "Page %s"
+msgstr ""
+
+#: /views/elements/paging.ctp:5
+msgid "<< previous"
+msgstr ""
+
+#: /views/elements/paging.ctp:8
+msgid "Next >>"
+msgstr ""
+
+#: /views/elements/search.ctp:22
+#: /views/elements/search_form.ctp:12
+msgid "Search"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:10
+msgid "Top Contributors"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:13
+msgid "todo"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:17
+msgid "Logged in as %s"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:18
+msgid "Logout"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:26
+msgid "About CakePHP"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:27
+msgid "Donate"
+msgstr ""
+
+#: /views/elements/side_menu.ctp:14
+#: /views/nodes/view_all.ctp:95
+msgid "All in one page"
+msgstr ""
+
+#: /views/elements/side_menu.ctp:23
+#: /views/nodes/view_all.ctp:102
+msgid "Suggest a new section here"
+msgstr ""
+
+#: /views/elements/side_menu.ctp:30
+msgid "Options"
+msgstr ""
+
+#: /views/elements/toc.ctp:13
+#: /views/nodes/toc.ctp:5
+msgid "Table of Contents"
+msgstr "Table of Contents"
+
+#: /views/elements/toc.ctp:16
+msgid "see fully expanded table of contents (only)"
+msgstr ""
+
+#: /views/elements/toc.ctp:19
+msgid "Books in "
+msgstr ""
+
+#: /views/elements/toc.ctp:21
+msgid "Available collections"
+msgstr ""
+
+#: /views/layouts/default.ctp:38
+msgid "Recent comments"
+msgstr ""
+
+#: /views/layouts/default.ctp:51;55
+msgid "My Submissions"
+msgstr ""
+
+#: /views/layouts/default.ctp:88
+msgid "Welcome to %s"
+msgstr ""
+
+#: /views/layouts/error.ctp:6
+msgid "CakePHP: the rapid development php framework:"
+msgstr ""
+
+#: /views/layouts/error.ctp:34
+msgid "CakePHP: the rapid development php framework"
+msgstr ""
+
+#: /views/nodes/add.ctp:17
+#: /views/nodes/edit.ctp:26
+msgid "Please review the guidelines for submitting to the Cookbook to ensure consistency."
+msgstr ""
+
+#: /views/nodes/add.ctp:22
+msgid "Add a new section"
+msgstr ""
+
+#: /views/nodes/add.ctp:28
+#: /views/nodes/edit.ctp:32
+msgid "Show me a preview before submitting"
+msgstr ""
+
+#: /views/nodes/add.ctp:30
+msgid "under"
+msgstr ""
+
+#: /views/nodes/add.ctp:33
+msgid "after"
+msgstr ""
+
+#: /views/nodes/add.ctp:39
+#: /views/nodes/edit.ctp:35
+msgid "Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically"
+msgstr ""
+
+#: /views/nodes/add.ctp:44
+msgid "Optionally explain in brief why you are proposing this addition (In English Please) :)"
+msgstr ""
+
+#: /views/nodes/admin_merge.ctp:18
+msgid "You are sure? Please check the preview"
+msgstr ""
+
+#: /views/nodes/compare.ctp:11
+#: /views/revisions/view.ctp:12
+msgid "Differences"
+msgstr ""
+
+#: /views/nodes/edit.ctp:40
+msgid "What is the reason for the edit? (In English Please) :)"
+msgstr ""
+
+#: /views/nodes/history.ctp:6
+msgid "See English edits too"
+msgstr ""
+
+#: /views/nodes/stats.ctp:5
+msgid "Here's a shout out to those who have dedicated time, sweat and tears to write, translate and edit the cookbook contents."
+msgstr ""
+
+#: /views/nodes/stats.ctp:7;37
+msgid "Top %s Contributors"
+msgstr ""
+
+#: /views/nodes/stats.ctp:9;38
+msgid "Last update: %s"
+msgstr ""
+
+#: /views/nodes/stats.ctp:27;63
+msgid "%s (%s current)"
+msgstr ""
+
+#: /views/nodes/stats.ctp:39
+msgid "%s%% translated"
+msgstr ""
+
+#: /views/nodes/stats.ctp:41
+msgid "The cookbook needs you! No submissions for this language!"
+msgstr ""
+
+#: /views/nodes/stats.ctp:44
+msgid "The cookbook needs you! This language will soon be removed if not updated."
+msgstr ""
+
+#: /views/nodes/stats.ctp:46
+msgid "The cookbook needs you! No updates for one month."
+msgstr ""
+
+#: /views/nodes/todo.ctp:7
+msgid "These is no <a href=\"%s\">po file</a> for %s"
+msgstr ""
+
+#: /views/nodes/todo.ctp:9
+msgid "These sections either do not have a translation, or the English text has changed since it was translated"
+msgstr ""
+
+#: /views/nodes/view_all.ctp:40;67
+msgid "See comments for this section"
+msgstr ""
+
+#: /views/nodes/view_all.ctp:114;126
+msgid "Change history for %s"
+msgstr ""
+
+#: /views/revisions/results.ctp:8
+msgid "Search Results"
+msgstr ""
+
+#: /views/revisions/results.ctp:32
+msgid "No results"
+msgstr ""
+
+#: /views/revisions/search.ctp:1
+msgid "Search book.cakephp.org"
+msgstr "Search book.cakephp.org"
+
+#: /webroot/test.php:75
+msgid "Debug setting does not allow access to this url."
+msgstr "Debug setting does not allow access to this url."
+
diff --git a/locale/eng/LC_MESSAGES/empty b/locale/eng/LC_MESSAGES/empty
new file mode 100644
index 0000000..e69de29
diff --git a/locale/fre/LC_MESSAGES/default.po b/locale/fre/LC_MESSAGES/default.po
new file mode 100644
index 0000000..7399a33
--- /dev/null
+++ b/locale/fre/LC_MESSAGES/default.po
@@ -0,0 +1,472 @@
+# LANGUAGE translation of Cookbook Application
+# Copyright 2008 Andy Dawson <andydawson76@yahoo.co.uk>
+# Generated from files:
+# Revision: 657 app_controller.php
+# Revision: 609 controllers/attachments_controller.php
+# Revision: 600 controllers/changes_controller.php
+# Revision: 660 views/layouts/default.ctp
+# Revision: 600 controllers/comments_controller.php
+# Revision: 600 views/comments/index.ctp
+# Revision: 659 plugins/users/views/users/login.ctp
+# Revision: 661 views/elements/secondary_nav.ctp
+# Revision: 600 views/changes/index.ctp
+# Revision: 638 views/changes/rss/index.ctp
+# Revision: 600 views/comments/add.ctp
+# Revision: 600 views/elements/attachments.ctp
+# Revision: 661 views/elements/comment_form.ctp
+# Revision: 600 views/elements/login_hint.ctp
+# Revision: 54 views/elements/node_options.ctp
+# Revision: 600 views/elements/search.ctp
+# Revision: 54 views/elements/side_menu.ctp
+# Revision: 600 views/elements/toc.ctp
+# Revision: 600 views/layouts/error.ctp
+# Revision: 54 views/nodes/add.ctp
+# Revision: 54 views/nodes/edit.ctp
+# Revision: 600 views/nodes/admin_merge.ctp
+# Revision: 600 views/nodes/history.ctp
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: 2008-11-19 13:34+0100\n"
+"PO-Revision-Date: 2008-11-19 13:36+0100\n"
+"Last-Translator: Andy Dawson <andydawson76@yahoo.co.uk>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: /app_controller.php:93
+msgid "Whoops, not a valid language."
+msgstr "Oups, ce n'est pas une langue valide."
+
+#: /app_controller.php:109
+msgid "Please login to continue"
+msgstr "Merci de vous identifier pour continuer"
+
+#: /app_controller.php:293
+msgid "All %s matching the term \"%s\""
+msgstr "Tous %s valident le terme \"%s\""
+
+#: /controllers/attachments_controller.php:85
+msgid "Attachment controller - no class or foreignKey error."
+msgstr "Liaison du contrôleur - pas de classe ou erreur de clé étrangère"
+
+#: /controllers/attachments_controller.php:110
+msgid "Existing Attachment for %s, id %s updated"
+msgstr "Nouvelle liaison pour %s, l'id %s est ajouté"
+
+#: /controllers/attachments_controller.php:112
+msgid "New Attachment for %s, id %s added"
+msgstr "Nouvelle liaison pour %s, l'id %s est ajouté"
+
+#: /controllers/attachments_controller.php:137
+msgid "%s with id %s updated"
+msgstr "%s avec l'identifiant %s est mis à jour"
+
+#: /controllers/attachments_controller.php:140
+msgid "errors in form"
+msgstr "erreurs dans le formulaire"
+
+#: /controllers/changes_controller.php:130
+msgid "Recent Changes for %s"
+msgstr "Modifications récentes pour %s"
+
+#: /controllers/changes_controller.php:134
+#: /views/layouts/default.ctp:33
+msgid "Recent Changes for all languages"
+msgstr "Modifications récentes pour toutes les langues"
+
+#: /controllers/changes_controller.php:136
+#: /views/layouts/default.ctp:28
+msgid "Recent Changes"
+msgstr "Modifications récentes"
+
+#: /controllers/changes_controller.php:144
+#: /controllers/comments_controller.php:191
+msgid "by %s"
+msgstr "par %s"
+
+#: /controllers/changes_controller.php:149
+msgid "restricted to status: %s"
+msgstr "réservé au statut : %s"
+
+#: /controllers/comments_controller.php:84;116
+msgid "Invalid Node."
+msgstr "Noeud invalide"
+
+#: /controllers/comments_controller.php:93
+msgid "Your comment has been added"
+msgstr "Votre commentaire a été ajouté"
+
+#: /controllers/comments_controller.php:96
+msgid "Please correct errors below."
+msgstr "Merci de corriger les erreurs ci-dessous."
+
+#: /controllers/comments_controller.php:120
+msgid "No permissions to see comments for that section"
+msgstr "Pas de permission pour voir les commentaires de cette section"
+
+#: /controllers/comments_controller.php:128
+#: /views/nodes/view_all.ctp:110;120
+msgid "Comments for %s"
+msgstr "Commentaires pour %s"
+
+#: /controllers/comments_controller.php:161
+msgid "Invalid Comment."
+msgstr "Commentaire invalide."
+
+#: /controllers/comments_controller.php:180
+#: /views/comments/index.ctp:6
+msgid "Recent Comments"
+msgstr "Commentaires récents"
+
+#: /controllers/comments_controller.php:182
+#: /views/layouts/default.ctp:43
+msgid "Recent Comments for all languages"
+msgstr "Commentaires récents pour toutes les langues"
+
+#: /controllers/nodes_controller.php:846
+msgid "Invalid Collection"
+msgstr "Collection invalide"
+
+#: /controllers/nodes_controller.php:871;976
+msgid "Thanks for your contribution! Your suggestion has been submitted for review."
+msgstr "Merci pour votre contribution ! Votre suggestion a été transmise pour révision."
+
+#: /controllers/nodes_controller.php:931
+msgid "This function is for comparing different (public) language content"
+msgstr "Cette fonction permet la comparaison de contenus (publics) dans différentes langues"
+
+#: /controllers/revisions_controller.php:615;620;643
+msgid "Only current and previous revisions can be viewed"
+msgstr "Seules les révisions courantes et précédentes peuvent être visualisées"
+
+#: /controllers/revisions_controller.php:624
+msgid "Only possible to compare revisions of the same node"
+msgstr "Il est seulement possible de comparer les révisions d'un même noeud"
+
+#: /models/node.php:170
+msgid "Default Title"
+msgstr "Titre par défaut"
+
+#: /models/node.php:173
+msgid "default_slug"
+msgstr "slug_par_defaut"
+
+#: /models/node.php:176
+msgid "Default Content"
+msgstr "Contenu par défaut"
+
+#: /plugins/users/views/users/login.ctp:8;20
+#: /views/elements/secondary_nav.ctp:23
+msgid "Login"
+msgstr "Login"
+
+#: /plugins/users/views/users/login.ctp:13
+msgid "Forgot your password?"
+msgstr "Mot de passe oublié ?"
+
+#: /views/changes/index.ctp:18
+msgid "change submitted by %s, %s"
+msgstr "Modification soumise par %s, %s"
+
+#: /views/changes/index.ctp:24
+#: /views/changes/rss/index.ctp:14
+msgid "accepted"
+msgstr "accepté"
+
+#: /views/changes/index.ctp:27
+#: /views/changes/rss/index.ctp:17
+msgid "not accepted"
+msgstr "non accepté"
+
+#: /views/changes/index.ctp:30
+#: /views/changes/rss/index.ctp:20
+msgid "pending"
+msgstr "en attente"
+
+#: /views/changes/index.ctp:35
+msgid "changed from %s to %s by %s, %s"
+msgstr "changé de %s à %s par %s, %s"
+
+#: /views/changes/index.ctp:41
+msgid "submitted by %s"
+msgstr "soumis par %s"
+
+#: /views/comments/add.ctp:6
+msgid "Comment: %s"
+msgstr "Commentaire : %s"
+
+#: /views/comments/index.ctp:4
+msgid "Comments: %s"
+msgstr "Commentaires : %s"
+
+#: /views/comments/index.ctp:11
+msgid "No Comments yet!"
+msgstr "Pas encore de commentaires !"
+
+#: /views/comments/index.ctp:20
+msgid "This page as a feed"
+msgstr "Cette page possède un flux"
+
+#: /views/elements/attachments.ctp:2
+msgid "Images/Files associated with this content"
+msgstr "Images / Fichiers associés à ce contenu"
+
+#: /views/elements/comment.ctp:9
+msgid "unknown"
+msgstr "inconnu"
+
+#: /views/elements/comment_form.ctp:4
+msgid "Login to add a comment"
+msgstr "Identifiez-vous pour ajouter un commentaire"
+
+#: /views/elements/comment_form.ctp:17
+msgid "Comment on %s"
+msgstr "Commentaire sur %s"
+
+#: /views/elements/login_hint.ctp:3
+msgid "Login with your Bakery account"
+msgstr "Identifiez-vous avec votre compte Bakery"
+
+#: /views/elements/login_hint.ctp:4
+msgid "Not got one? Hop on over to %s and sign up"
+msgstr "Vous n'en avez pas ? Faites un saut à %s et inscrivez-vous"
+
+#: /views/elements/node_options.ctp:8
+msgid "Edit"
+msgstr "Editer"
+
+#: /views/elements/node_options.ctp:11
+msgid "View just this section"
+msgstr "Voir juste cette section"
+
+#: /views/elements/node_options.ctp:14
+msgid "Comments (%s)"
+msgstr "Commentaires (%s)"
+
+#: /views/elements/node_options.ctp:19
+msgid "there is a pending change for this section"
+msgstr "il y a des modifications en cours pour cette section"
+
+#: /views/elements/node_options.ctp:22
+msgid "History"
+msgstr "historique"
+
+#: /views/elements/node_options.ctp:32
+msgid "This text may be out of sync with the English version"
+msgstr "Ce texte semble désynchronisé de la version anglaise"
+
+#: /views/elements/node_options.ctp:39
+msgid "Compare to original content"
+msgstr "Comparer au contenu original"
+
+#: /views/elements/paging.ctp:4
+msgid "Page %s"
+msgstr "Page %s"
+
+#: /views/elements/paging.ctp:5
+msgid "<< previous"
+msgstr "<< précédent"
+
+#: /views/elements/paging.ctp:8
+msgid "Next >>"
+msgstr "Suivant >>"
+
+#: /views/elements/search.ctp:22
+#: /views/elements/search_form.ctp:12
+msgid "Search"
+msgstr "Rechercher"
+
+#: /views/elements/secondary_nav.ctp:10
+msgid "Top Contributors"
+msgstr "Top des contributeurs"
+
+#: /views/elements/secondary_nav.ctp:13
+msgid "todo"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:17
+msgid "Logged in as %s"
+msgstr "Connecté en tant que %s"
+
+#: /views/elements/secondary_nav.ctp:18
+msgid "Logout"
+msgstr "Déconnexion"
+
+#: /views/elements/secondary_nav.ctp:26
+msgid "About CakePHP"
+msgstr "A propos de CakePHP"
+
+#: /views/elements/secondary_nav.ctp:27
+msgid "Donate"
+msgstr "Donation"
+
+#: /views/elements/side_menu.ctp:14
+#: /views/nodes/view_all.ctp:95
+msgid "All in one page"
+msgstr "Tout sur une page"
+
+#: /views/elements/side_menu.ctp:23
+#: /views/nodes/view_all.ctp:102
+msgid "Suggest a new section here"
+msgstr "Suggérer une nouvelle section ici"
+
+#: /views/elements/side_menu.ctp:30
+msgid "Options"
+msgstr "Options"
+
+#: /views/elements/toc.ctp:13
+#: /views/nodes/toc.ctp:5
+msgid "Table of Contents"
+msgstr "Table des matières"
+
+#: /views/elements/toc.ctp:16
+msgid "see fully expanded table of contents (only)"
+msgstr "voir toute la table des matières dépliée"
+
+#: /views/elements/toc.ctp:19
+msgid "Books in "
+msgstr "Livres en"
+
+#: /views/elements/toc.ctp:21
+msgid "Available collections"
+msgstr "Collections disponibles"
+
+#: /views/layouts/default.ctp:38
+msgid "Recent comments"
+msgstr "Commentaires récents"
+
+#: /views/layouts/default.ctp:51;55
+msgid "My Submissions"
+msgstr "Mes soumissions"
+
+#: /views/layouts/default.ctp:88
+msgid "Welcome to %s"
+msgstr "Bienvenu dans %s"
+
+#: /views/layouts/error.ctp:6
+msgid "CakePHP: the rapid development php framework:"
+msgstr "CakePHP : le framework de développement rapide PHP :"
+
+#: /views/layouts/error.ctp:34
+msgid "CakePHP: the rapid development php framework"
+msgstr "CakePHP : le framework de développement rapide PHP"
+
+#: /views/nodes/add.ctp:17
+#: /views/nodes/edit.ctp:26
+msgid "Please review the guidelines for submitting to the Cookbook to ensure consistency."
+msgstr "Merci de revoir les directives de contribution au CookBook pour vous assurer de la cohérence"
+
+#: /views/nodes/add.ctp:22
+msgid "Add a new section"
+msgstr "Ajouter une nouvelle section"
+
+#: /views/nodes/add.ctp:28
+#: /views/nodes/edit.ctp:32
+msgid "Show me a preview before submitting"
+msgstr "Montrez-moi une prévisualisation avant de soumettre"
+
+#: /views/nodes/add.ctp:30
+msgid "under"
+msgstr "sous"
+
+#: /views/nodes/add.ctp:33
+msgid "after"
+msgstr "après"
+
+#: /views/nodes/add.ctp:39
+#: /views/nodes/edit.ctp:35
+msgid "Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically"
+msgstr "Contenus. Le code dans les balise pre sera échappé. Les soumissions sans formatage HTML seront formatés automatiquement"
+
+#: /views/nodes/add.ctp:44
+msgid "Optionally explain in brief why you are proposing this addition (In English Please) :)"
+msgstr "Expliquez brièvement pourquoi vous proposez cet ajout (en anglais s'il vous plait) :)"
+
+#: /views/nodes/admin_merge.ctp:18
+msgid "You are sure? Please check the preview"
+msgstr "Etes-vous sûr ? Merci de vérifier la prévisualisation"
+
+#: /views/nodes/compare.ctp:11
+#: /views/revisions/view.ctp:12
+msgid "Differences"
+msgstr "Différences"
+
+#: /views/nodes/edit.ctp:40
+msgid "What is the reason for the edit? (In English Please) :)"
+msgstr "Quelle est la raison de cette édition ? (en anglais s'il vous plait) :)"
+
+#: /views/nodes/history.ctp:6
+msgid "See English edits too"
+msgstr "Voyez également les éditions en anglais"
+
+#: /views/nodes/stats.ctp:5
+msgid "Here's a shout out to those who have dedicated time, sweat and tears to write, translate and edit the cookbook contents."
+msgstr ""
+
+#: /views/nodes/stats.ctp:7;37
+msgid "Top %s Contributors"
+msgstr "Top %s Contributors"
+
+#: /views/nodes/stats.ctp:9;38
+msgid "Last update: %s"
+msgstr ""
+
+#: /views/nodes/stats.ctp:27;63
+msgid "%s (%s current)"
+msgstr ""
+
+#: /views/nodes/stats.ctp:39
+msgid "%s%% translated"
+msgstr ""
+
+#: /views/nodes/stats.ctp:41
+msgid "The cookbook needs you! No submissions for this language!"
+msgstr ""
+
+#: /views/nodes/stats.ctp:44
+msgid "The cookbook needs you! This language will soon be removed if not updated."
+msgstr ""
+
+#: /views/nodes/stats.ctp:46
+msgid "The cookbook needs you! No updates for one month."
+msgstr ""
+
+#: /views/nodes/todo.ctp:7
+msgid "These is no <a href=\"%s\">po file</a> for %s"
+msgstr ""
+
+#: /views/nodes/todo.ctp:9
+msgid "These sections either do not have a translation, or the English text has changed since it was translated"
+msgstr ""
+
+#: /views/nodes/view_all.ctp:40;67
+msgid "See comments for this section"
+msgstr "Voir les commentaires pour cette section"
+
+#: /views/nodes/view_all.ctp:114;126
+msgid "Change history for %s"
+msgstr "Historique des modifications pour"
+
+#: /views/revisions/results.ctp:8
+msgid "Search Results"
+msgstr "Résultats de recherche"
+
+#: /views/revisions/results.ctp:32
+msgid "No results"
+msgstr "Aucun résultat"
+
+#: /views/revisions/search.ctp:1
+msgid "Search book.cakephp.org"
+msgstr "Chercher dans book.cakephp.org"
+
+#: /webroot/test.php:75
+msgid "Debug setting does not allow access to this url."
+msgstr ""
+
+#, fuzzy
+#~ msgid "Top %s contributors"
+#~ msgstr "Top des contributeurs"
+
diff --git a/locale/per/LC_MESSAGES/default.po b/locale/per/LC_MESSAGES/default.po
new file mode 100644
index 0000000..86fa017
--- /dev/null
+++ b/locale/per/LC_MESSAGES/default.po
@@ -0,0 +1,444 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Cookbook 1.0\n"
+"POT-Creation-Date: 2008-11-19 13:34+0100\n"
+"PO-Revision-Date: 2008-11-19 13:52+0100\n"
+"Last-Translator: Andy Dawson <andydawson76@yahoo.co.uk>\n"
+"Language-Team: Documentation CakePHP <docs@cakephp.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Persian\n"
+
+#: /app_controller.php:93
+msgid "Whoops, not a valid language."
+msgstr "اوپس! زبان معتبر نیست"
+
+#: /app_controller.php:109
+msgid "Please login to continue"
+msgstr "لطفاً برای ادامه به سیستم وارد شوید"
+
+#: /app_controller.php:293
+msgid "All %s matching the term \"%s\""
+msgstr "تمام %s هایی که شامل عبارت \"%s\" ستند"
+
+#: /controllers/attachments_controller.php:85
+msgid "Attachment controller - no class or foreignKey error."
+msgstr "کنترلکنندهی پیوستها - خطای نبود کلاس یا کلیدخارجی"
+
+#: /controllers/attachments_controller.php:110
+#, fuzzy
+msgid "Existing Attachment for %s, id %s updated"
+msgstr "پیوست جدید برای %s, شناسه %s اضافه شد"
+
+#: /controllers/attachments_controller.php:112
+msgid "New Attachment for %s, id %s added"
+msgstr "پیوست جدید برای %s, شناسه %s اضافه شد"
+
+#: /controllers/attachments_controller.php:137
+msgid "%s with id %s updated"
+msgstr "%s با شناسه %s به روز شد"
+
+#: /controllers/attachments_controller.php:140
+msgid "errors in form"
+msgstr "خطا در فرم"
+
+#: /controllers/changes_controller.php:130
+msgid "Recent Changes for %s"
+msgstr "تغییرات اخیر برای %s"
+
+#: /controllers/changes_controller.php:134
+#: /views/layouts/default.ctp:33
+msgid "Recent Changes for all languages"
+msgstr "تغییرات اخیر برای تمام زبانها"
+
+#: /controllers/changes_controller.php:136
+#: /views/layouts/default.ctp:28
+msgid "Recent Changes"
+msgstr "تغییرات اخیر"
+
+#: /controllers/changes_controller.php:144
+#: /controllers/comments_controller.php:191
+msgid "by %s"
+msgstr "بهوسیلهی %s"
+
+#: /controllers/changes_controller.php:149
+msgid "restricted to status: %s"
+msgstr "محدود به وضعیت: %s"
+
+#: /controllers/comments_controller.php:84;116
+msgid "Invalid Node."
+msgstr "گره نامعتبر."
+
+#: /controllers/comments_controller.php:93
+msgid "Your comment has been added"
+msgstr "نظر شما مطرح شد."
+
+#: /controllers/comments_controller.php:96
+msgid "Please correct errors below."
+msgstr "لطفاً خطاهای زیر را تصحیح کنید."
+
+#: /controllers/comments_controller.php:120
+msgid "No permissions to see comments for that section"
+msgstr "دسترسیای برای نظرات آن بخش وجود ندارد"
+
+#: /controllers/comments_controller.php:128
+#: /views/nodes/view_all.ctp:110;120
+msgid "Comments for %s"
+msgstr "نظرات %s"
+
+#: /controllers/comments_controller.php:161
+msgid "Invalid Comment."
+msgstr "نظر نامعتبر"
+
+#: /controllers/comments_controller.php:180
+#: /views/comments/index.ctp:6
+msgid "Recent Comments"
+msgstr "نظرات اخیر"
+
+#: /controllers/comments_controller.php:182
+#: /views/layouts/default.ctp:43
+msgid "Recent Comments for all languages"
+msgstr "نظرات اخیر در تمام زبانها"
+
+#: /controllers/nodes_controller.php:846
+msgid "Invalid Collection"
+msgstr "مجموعه نامعتبر"
+
+#: /controllers/nodes_controller.php:871;976
+msgid "Thanks for your contribution! Your suggestion has been submitted for review."
+msgstr "از همکاریتان ممنونیم! پیشنهاد شما برای بررسی ثبت گردید."
+
+#: /controllers/nodes_controller.php:931
+msgid "This function is for comparing different (public) language content"
+msgstr "این تابع برای ماقیسهی زبانهای مختلف (عمومی) است."
+
+#: /controllers/revisions_controller.php:615;620;643
+msgid "Only current and previous revisions can be viewed"
+msgstr "فقط نسخههای فعلی و قبلی میتوانند نشان داده شوند. "
+
+#: /controllers/revisions_controller.php:624
+msgid "Only possible to compare revisions of the same node"
+msgstr "فقط مقایسهی نسخههایی ممکن است که از یک گره باشند."
+
+#: /models/node.php:170
+msgid "Default Title"
+msgstr "عنوان پیشفرض"
+
+#: /models/node.php:173
+msgid "default_slug"
+msgstr "slug پیشفرض"
+
+#: /models/node.php:176
+msgid "Default Content"
+msgstr "محتوای پیشفرض"
+
+#: /plugins/users/views/users/login.ctp:8;20
+#: /views/elements/secondary_nav.ctp:23
+msgid "Login"
+msgstr ""
+
+#: /plugins/users/views/users/login.ctp:13
+msgid "Forgot your password?"
+msgstr ""
+
+#: /views/changes/index.ctp:18
+msgid "change submitted by %s, %s"
+msgstr "تغییرات توسط %s، %s ثبت گردید"
+
+#: /views/changes/index.ctp:24
+#: /views/changes/rss/index.ctp:14
+msgid "accepted"
+msgstr "قبول شده"
+
+#: /views/changes/index.ctp:27
+#: /views/changes/rss/index.ctp:17
+msgid "not accepted"
+msgstr "رد شده"
+
+#: /views/changes/index.ctp:30
+#: /views/changes/rss/index.ctp:20
+msgid "pending"
+msgstr "معلق"
+
+#: /views/changes/index.ctp:35
+msgid "changed from %s to %s by %s, %s"
+msgstr "تغییرات از %s تا %s توسط %s، %s"
+
+#: /views/changes/index.ctp:41
+msgid "submitted by %s"
+msgstr "ثبت کننده: %s"
+
+#: /views/comments/add.ctp:6
+msgid "Comment: %s"
+msgstr "نظر: %s"
+
+#: /views/comments/index.ctp:4
+msgid "Comments: %s"
+msgstr "نظر: %s"
+
+#: /views/comments/index.ctp:11
+msgid "No Comments yet!"
+msgstr "هیچ نظری داده نشده!"
+
+#: /views/comments/index.ctp:20
+msgid "This page as a feed"
+msgstr "این صفحه به عنوان یک منبع"
+
+#: /views/elements/attachments.ctp:2
+msgid "Images/Files associated with this content"
+msgstr "تصاویر/فایلهای مرتبط با این محتوا"
+
+#: /views/elements/comment.ctp:9
+msgid "unknown"
+msgstr "نامشخص"
+
+#: /views/elements/comment_form.ctp:4
+#, fuzzy
+msgid "Login to add a comment"
+msgstr "با حساب نانوایی (Bakery) خود وارد شوید"
+
+#: /views/elements/comment_form.ctp:17
+msgid "Comment on %s"
+msgstr ""
+
+#: /views/elements/login_hint.ctp:3
+msgid "Login with your Bakery account"
+msgstr "با حساب نانوایی (Bakery) خود وارد شوید"
+
+#: /views/elements/login_hint.ctp:4
+msgid "Not got one? Hop on over to %s and sign up"
+msgstr "هنوز حساب باز نکردید؟ یک سر به %s بزنید و ثبت نام کنید"
+
+#: /views/elements/node_options.ctp:8
+msgid "Edit"
+msgstr "ویرایش"
+
+#: /views/elements/node_options.ctp:11
+msgid "View just this section"
+msgstr "فقط این بخش را نمایش بده"
+
+#: /views/elements/node_options.ctp:14
+msgid "Comments (%s)"
+msgstr "نظرات (%s)"
+
+#: /views/elements/node_options.ctp:19
+msgid "there is a pending change for this section"
+msgstr "یک تغییر برای این مطلب موجود است"
+
+#: /views/elements/node_options.ctp:22
+msgid "History"
+msgstr ""
+
+#: /views/elements/node_options.ctp:32
+msgid "This text may be out of sync with the English version"
+msgstr "این متن احتمالاً با نسخهی انگلیسی همخوان نیست."
+
+#: /views/elements/node_options.ctp:39
+msgid "Compare to original content"
+msgstr "مقایسه با محتوای اصلی"
+
+#: /views/elements/paging.ctp:4
+msgid "Page %s"
+msgstr ""
+
+#: /views/elements/paging.ctp:5
+msgid "<< previous"
+msgstr " قبلی<<"
+
+#: /views/elements/paging.ctp:8
+msgid "Next >>"
+msgstr " بعدی >>"
+
+#: /views/elements/search.ctp:22
+#: /views/elements/search_form.ctp:12
+msgid "Search"
+msgstr "جستجو"
+
+#: /views/elements/secondary_nav.ctp:10
+msgid "Top Contributors"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:13
+msgid "todo"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:17
+msgid "Logged in as %s"
+msgstr "ورود بهعنوان %s"
+
+#: /views/elements/secondary_nav.ctp:18
+msgid "Logout"
+msgstr "خروج"
+
+#: /views/elements/secondary_nav.ctp:26
+msgid "About CakePHP"
+msgstr "دربارهی کیک پیاچپی (CakePHP)"
+
+#: /views/elements/secondary_nav.ctp:27
+msgid "Donate"
+msgstr "کمک مالی"
+
+#: /views/elements/side_menu.ctp:14
+#: /views/nodes/view_all.ctp:95
+msgid "All in one page"
+msgstr "همه با هم"
+
+#: /views/elements/side_menu.ctp:23
+#: /views/nodes/view_all.ctp:102
+msgid "Suggest a new section here"
+msgstr "پیشنهاد بخش جدیدی در اینجا بدهید"
+
+#: /views/elements/side_menu.ctp:30
+msgid "Options"
+msgstr ""
+
+#: /views/elements/toc.ctp:13
+#: /views/nodes/toc.ctp:5
+msgid "Table of Contents"
+msgstr "فهرست محتوا"
+
+#: /views/elements/toc.ctp:16
+msgid "see fully expanded table of contents (only)"
+msgstr "نمایش تمام فهرست محتوا "
+
+#: /views/elements/toc.ctp:19
+msgid "Books in "
+msgstr "کتابهای موجود در "
+
+#: /views/elements/toc.ctp:21
+msgid "Available collections"
+msgstr "مجموعههای موجود"
+
+#: /views/layouts/default.ctp:38
+msgid "Recent comments"
+msgstr "نظرات اخیر"
+
+#: /views/layouts/default.ctp:51;55
+msgid "My Submissions"
+msgstr "ارسالهای من"
+
+#: /views/layouts/default.ctp:88
+msgid "Welcome to %s"
+msgstr "به %s خوش آمدید"
+
+#: /views/layouts/error.ctp:6
+msgid "CakePHP: the rapid development php framework:"
+msgstr "کیکپیاچپی: توسعهی فرز سکوی پیاچپی"
+
+#: /views/layouts/error.ctp:34
+msgid "CakePHP: the rapid development php framework"
+msgstr "کیکپیاچپی: توسعهی فرز سکوی پیاچپی"
+
+#: /views/nodes/add.ctp:17
+#: /views/nodes/edit.ctp:26
+msgid "Please review the guidelines for submitting to the Cookbook to ensure consistency."
+msgstr "لطفاً راهنماهای لازم برای ارسال به کتاب آشپزی را مطالعه کنید."
+
+#: /views/nodes/add.ctp:22
+msgid "Add a new section"
+msgstr "ایجاد بخش جدید"
+
+#: /views/nodes/add.ctp:28
+#: /views/nodes/edit.ctp:32
+msgid "Show me a preview before submitting"
+msgstr "قبل از ارسال یک پیشنمایش نشان من بده"
+
+#: /views/nodes/add.ctp:30
+msgid "under"
+msgstr "تحت"
+
+#: /views/nodes/add.ctp:33
+msgid "after"
+msgstr "بعد از"
+
+#: /views/nodes/add.ctp:39
+#: /views/nodes/edit.ctp:35
+msgid "Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically"
+msgstr "محتویات. کدها فقط در تگ pre قبول میشوند. ارسالهایی که ساختار فرمت html ندارند اتوماتیک فرمت میشوند."
+
+#: /views/nodes/add.ctp:44
+msgid "Optionally explain in brief why you are proposing this addition (In English Please) :)"
+msgstr "لطفاً به انگلیسی و بهطور خلاصه بگویید که چرا دارید این مطلب را اضافه میکنید :)"
+
+#: /views/nodes/admin_merge.ctp:18
+msgid "You are sure? Please check the preview"
+msgstr "مطمئنید؟ پیش نمایش را لطفاً ببینید."
+
+#: /views/nodes/compare.ctp:11
+#: /views/revisions/view.ctp:12
+msgid "Differences"
+msgstr "تفاوتها"
+
+#: /views/nodes/edit.ctp:40
+msgid "What is the reason for the edit? (In English Please) :)"
+msgstr "منطق این ویرایش چیست؟ (لطفاً انگلیسی بنویسید) :)"
+
+#: /views/nodes/history.ctp:6
+msgid "See English edits too"
+msgstr "ویرایشهای انگلیسی را هم ببینید."
+
+#: /views/nodes/stats.ctp:5
+msgid "Here's a shout out to those who have dedicated time, sweat and tears to write, translate and edit the cookbook contents."
+msgstr ""
+
+#: /views/nodes/stats.ctp:7;37
+msgid "Top %s Contributors"
+msgstr ""
+
+#: /views/nodes/stats.ctp:9;38
+msgid "Last update: %s"
+msgstr ""
+
+#: /views/nodes/stats.ctp:27;63
+msgid "%s (%s current)"
+msgstr ""
+
+#: /views/nodes/stats.ctp:39
+msgid "%s%% translated"
+msgstr ""
+
+#: /views/nodes/stats.ctp:41
+msgid "The cookbook needs you! No submissions for this language!"
+msgstr ""
+
+#: /views/nodes/stats.ctp:44
+msgid "The cookbook needs you! This language will soon be removed if not updated."
+msgstr ""
+
+#: /views/nodes/stats.ctp:46
+msgid "The cookbook needs you! No updates for one month."
+msgstr ""
+
+#: /views/nodes/todo.ctp:7
+msgid "These is no <a href=\"%s\">po file</a> for %s"
+msgstr ""
+
+#: /views/nodes/todo.ctp:9
+msgid "These sections either do not have a translation, or the English text has changed since it was translated"
+msgstr ""
+
+#: /views/nodes/view_all.ctp:40;67
+msgid "See comments for this section"
+msgstr "نظرات این بخش را ببینید."
+
+#: /views/nodes/view_all.ctp:114;126
+msgid "Change history for %s"
+msgstr "تاریخچه ویرایش برای %s"
+
+#: /views/revisions/results.ctp:8
+msgid "Search Results"
+msgstr "نتایج جستجو"
+
+#: /views/revisions/results.ctp:32
+msgid "No results"
+msgstr ""
+
+#: /views/revisions/search.ctp:1
+msgid "Search book.cakephp.org"
+msgstr "book.cakephp.org را بگردید."
+
+#: /webroot/test.php:75
+msgid "Debug setting does not allow access to this url."
+msgstr "Debug setting does not allow access to this url."
+
diff --git a/locale/por/LC_MESSAGES/default.po b/locale/por/LC_MESSAGES/default.po
new file mode 100644
index 0000000..4a4f99e
--- /dev/null
+++ b/locale/por/LC_MESSAGES/default.po
@@ -0,0 +1,466 @@
+# Copyright 2008 Andy Dawson <andydawson76@yahoo.co.uk>
+# Generated from files:
+# Revision: 600 app_controller.php
+# Revision: 609 controllers/attachments_controller.php
+# Revision: 600 controllers/changes_controller.php
+# Revision: 639 views/layouts/default.ctp
+# Revision: 600 controllers/comments_controller.php
+# Revision: 600 views/comments/index.ctp
+# Revision: 600 views/changes/index.ctp
+# Revision: 638 views/changes/rss/index.ctp
+# Revision: 600 views/comments/add.ctp
+# Revision: 600 views/elements/attachments.ctp
+# Revision: 600 views/elements/login_hint.ctp
+# Revision: 54 views/elements/node_options.ctp
+# Revision: 600 views/elements/search.ctp
+# Revision: 650 views/elements/secondary_nav.ctp
+# Revision: 54 views/elements/side_menu.ctp
+# Revision: 600 views/elements/toc.ctp
+# Revision: 600 views/layouts/error.ctp
+# Revision: 54 views/nodes/add.ctp
+# Revision: 54 views/nodes/edit.ctp
+# Revision: 600 views/nodes/admin_merge.ctp
+# Revision: 600 views/nodes/history.ctp
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: 2008-11-19 13:34+0100\n"
+"PO-Revision-Date: 2008-11-19 13:36+0100\n"
+"Last-Translator: Andy Dawson <andydawson76@yahoo.co.uk>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: /app_controller.php:93
+msgid "Whoops, not a valid language."
+msgstr "Ops, não é uma linguagem válida."
+
+#: /app_controller.php:109
+msgid "Please login to continue"
+msgstr "Faça o login para continuar"
+
+#: /app_controller.php:293
+msgid "All %s matching the term \"%s\""
+msgstr "Todos %s combinam com o termo \"%s\""
+
+#: /controllers/attachments_controller.php:85
+msgid "Attachment controller - no class or foreignKey error."
+msgstr "Attachment controller - sem class ou erro na chave estrangeira (foreignKey)."
+
+#: /controllers/attachments_controller.php:110
+#, fuzzy
+msgid "Existing Attachment for %s, id %s updated"
+msgstr "Novo Anexo para %s, id %s adicionando"
+
+#: /controllers/attachments_controller.php:112
+msgid "New Attachment for %s, id %s added"
+msgstr "Novo Anexo para %s, id %s adicionando"
+
+#: /controllers/attachments_controller.php:137
+msgid "%s with id %s updated"
+msgstr "%s com id %s atualizado"
+
+#: /controllers/attachments_controller.php:140
+msgid "errors in form"
+msgstr "erros no formulário"
+
+#: /controllers/changes_controller.php:130
+msgid "Recent Changes for %s"
+msgstr "Alterações Recentes em %s"
+
+#: /controllers/changes_controller.php:134
+#: /views/layouts/default.ctp:33
+msgid "Recent Changes for all languages"
+msgstr "Alterações Recentes em todas as linguagens"
+
+#: /controllers/changes_controller.php:136
+#: /views/layouts/default.ctp:28
+msgid "Recent Changes"
+msgstr "Alterações Recentes"
+
+#: /controllers/changes_controller.php:144
+#: /controllers/comments_controller.php:191
+msgid "by %s"
+msgstr "por %s"
+
+#: /controllers/changes_controller.php:149
+msgid "restricted to status: %s"
+msgstr "status restrito para: %s"
+
+#: /controllers/comments_controller.php:84;116
+msgid "Invalid Node."
+msgstr "Nó inválido."
+
+#: /controllers/comments_controller.php:93
+msgid "Your comment has been added"
+msgstr "Seu comentário foi adicionando"
+
+#: /controllers/comments_controller.php:96
+msgid "Please correct errors below."
+msgstr "Por favor, corrija os erros abaixo."
+
+#: /controllers/comments_controller.php:120
+msgid "No permissions to see comments for that section"
+msgstr "Sem permissões para ver os comentários desta seção"
+
+#: /controllers/comments_controller.php:128
+#: /views/nodes/view_all.ctp:110;120
+msgid "Comments for %s"
+msgstr "Comentários de %s"
+
+#: /controllers/comments_controller.php:161
+msgid "Invalid Comment."
+msgstr "Comentário Inválido."
+
+#: /controllers/comments_controller.php:180
+#: /views/comments/index.ctp:6
+msgid "Recent Comments"
+msgstr "Comentários Recentes"
+
+#: /controllers/comments_controller.php:182
+#: /views/layouts/default.ctp:43
+msgid "Recent Comments for all languages"
+msgstr "Comentários Recentes em todas as linguagens"
+
+#: /controllers/nodes_controller.php:846
+msgid "Invalid Collection"
+msgstr "Coleção Inválida."
+
+#: /controllers/nodes_controller.php:871;976
+msgid "Thanks for your contribution! Your suggestion has been submitted for review."
+msgstr "Obrigado por sua contribuição! Sua sugestão foi enviada para revisão."
+
+#: /controllers/nodes_controller.php:931
+msgid "This function is for comparing different (public) language content"
+msgstr "Essa função é para comparar diferentes (públicos) contéudos entre as linguagens"
+
+#: /controllers/revisions_controller.php:615;620;643
+msgid "Only current and previous revisions can be viewed"
+msgstr "Apenas a revisão anterior e a corrente podem ser vistas"
+
+#: /controllers/revisions_controller.php:624
+msgid "Only possible to compare revisions of the same node"
+msgstr "Só é possível comparar revisões do mesmo nó"
+
+#: /models/node.php:170
+msgid "Default Title"
+msgstr "Título Padrão"
+
+#: /models/node.php:173
+msgid "default_slug"
+msgstr "slug_padrao"
+
+#: /models/node.php:176
+msgid "Default Content"
+msgstr "Conteúdo Padrão"
+
+#: /plugins/users/views/users/login.ctp:8;20
+#: /views/elements/secondary_nav.ctp:23
+msgid "Login"
+msgstr "Login"
+
+#: /plugins/users/views/users/login.ctp:13
+msgid "Forgot your password?"
+msgstr "Esqueceu a senha?"
+
+#: /views/changes/index.ctp:18
+msgid "change submitted by %s, %s"
+msgstr "alteração enviada por %s, %s"
+
+#: /views/changes/index.ctp:24
+#: /views/changes/rss/index.ctp:14
+msgid "accepted"
+msgstr "aprovado"
+
+#: /views/changes/index.ctp:27
+#: /views/changes/rss/index.ctp:17
+msgid "not accepted"
+msgstr "não aprovado"
+
+#: /views/changes/index.ctp:30
+#: /views/changes/rss/index.ctp:20
+msgid "pending"
+msgstr "pendente"
+
+#: /views/changes/index.ctp:35
+msgid "changed from %s to %s by %s, %s"
+msgstr "alterado de %s para %s por %s, %s"
+
+#: /views/changes/index.ctp:41
+msgid "submitted by %s"
+msgstr "enviado por %s"
+
+#: /views/comments/add.ctp:6
+msgid "Comment: %s"
+msgstr "Comentário: %s"
+
+#: /views/comments/index.ctp:4
+msgid "Comments: %s"
+msgstr "Comentários: %s"
+
+#: /views/comments/index.ctp:11
+msgid "No Comments yet!"
+msgstr "Sem comentários ainda!"
+
+#: /views/comments/index.ctp:20
+msgid "This page as a feed"
+msgstr "Essa página é um feed"
+
+#: /views/elements/attachments.ctp:2
+msgid "Images/Files associated with this content"
+msgstr "Imagens/Arquivos associados com este conteúdo"
+
+#: /views/elements/comment.ctp:9
+msgid "unknown"
+msgstr "desconhecido"
+
+#: /views/elements/comment_form.ctp:4
+msgid "Login to add a comment"
+msgstr "Faça o login para adicionar um comentário"
+
+#: /views/elements/comment_form.ctp:17
+msgid "Comment on %s"
+msgstr "Comentário em %s"
+
+#: /views/elements/login_hint.ctp:3
+msgid "Login with your Bakery account"
+msgstr "Faça o login com sua conta do Bakery"
+
+#: /views/elements/login_hint.ctp:4
+msgid "Not got one? Hop on over to %s and sign up"
+msgstr "Não pegou? Pule para %s e registre-se"
+
+#: /views/elements/node_options.ctp:8
+msgid "Edit"
+msgstr "Editar"
+
+#: /views/elements/node_options.ctp:11
+msgid "View just this section"
+msgstr "Visualizar apenas este seção"
+
+#: /views/elements/node_options.ctp:14
+msgid "Comments (%s)"
+msgstr "Comentários (%s)"
+
+#: /views/elements/node_options.ctp:19
+msgid "there is a pending change for this section"
+msgstr "existe uma alteração pendente para esta seção"
+
+#: /views/elements/node_options.ctp:22
+msgid "History"
+msgstr "Histórico"
+
+#: /views/elements/node_options.ctp:32
+msgid "This text may be out of sync with the English version"
+msgstr "Talve esse texto esteja desatualizado com a versão em Inglês"
+
+#: /views/elements/node_options.ctp:39
+msgid "Compare to original content"
+msgstr "Compare com o conteúdo original"
+
+#: /views/elements/paging.ctp:4
+msgid "Page %s"
+msgstr "Página %s"
+
+#: /views/elements/paging.ctp:5
+msgid "<< previous"
+msgstr "<< anterior"
+
+#: /views/elements/paging.ctp:8
+msgid "Next >>"
+msgstr "Próxima >>"
+
+#: /views/elements/search.ctp:22
+#: /views/elements/search_form.ctp:12
+msgid "Search"
+msgstr "Busca"
+
+#: /views/elements/secondary_nav.ctp:10
+msgid "Top Contributors"
+msgstr "Top Colaboradores"
+
+#: /views/elements/secondary_nav.ctp:13
+msgid "todo"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:17
+msgid "Logged in as %s"
+msgstr "Logado como %s"
+
+#: /views/elements/secondary_nav.ctp:18
+msgid "Logout"
+msgstr "Logout"
+
+#: /views/elements/secondary_nav.ctp:26
+msgid "About CakePHP"
+msgstr "Sobre o CakePHP"
+
+#: /views/elements/secondary_nav.ctp:27
+msgid "Donate"
+msgstr "Doar"
+
+#: /views/elements/side_menu.ctp:14
+#: /views/nodes/view_all.ctp:95
+msgid "All in one page"
+msgstr "Tudo em uma página"
+
+#: /views/elements/side_menu.ctp:23
+#: /views/nodes/view_all.ctp:102
+msgid "Suggest a new section here"
+msgstr "Sugira uma nova seção aqui"
+
+#: /views/elements/side_menu.ctp:30
+msgid "Options"
+msgstr "Opções"
+
+#: /views/elements/toc.ctp:13
+#: /views/nodes/toc.ctp:5
+msgid "Table of Contents"
+msgstr "Tabela de Conteúdo"
+
+#: /views/elements/toc.ctp:16
+msgid "see fully expanded table of contents (only)"
+msgstr "veja a tabela de conteúdo totalmente expandida (apenas)"
+
+#: /views/elements/toc.ctp:19
+msgid "Books in "
+msgstr "Livros em "
+
+#: /views/elements/toc.ctp:21
+msgid "Available collections"
+msgstr "Coleções disponíveis"
+
+#: /views/layouts/default.ctp:38
+msgid "Recent comments"
+msgstr "Comentários recentes"
+
+#: /views/layouts/default.ctp:51;55
+msgid "My Submissions"
+msgstr "Minhas Contribuições"
+
+#: /views/layouts/default.ctp:88
+msgid "Welcome to %s"
+msgstr "Bem-vindo ao Cookbook"
+
+#: /views/layouts/error.ctp:6
+msgid "CakePHP: the rapid development php framework:"
+msgstr "CakePHP: o framework de desenvolvimento rápido para php:"
+
+#: /views/layouts/error.ctp:34
+msgid "CakePHP: the rapid development php framework"
+msgstr "CakePHP: o framework de desenvolvimento rápido para php"
+
+#: /views/nodes/add.ctp:17
+#: /views/nodes/edit.ctp:26
+msgid "Please review the guidelines for submitting to the Cookbook to ensure consistency."
+msgstr "Por favor, reveja o guia de como contribuir com o CookBook para garantir consistência"
+
+#: /views/nodes/add.ctp:22
+msgid "Add a new section"
+msgstr "Adicionar uma nova seção"
+
+#: /views/nodes/add.ctp:28
+#: /views/nodes/edit.ctp:32
+msgid "Show me a preview before submitting"
+msgstr "Mostre-me um preview antes de enviar"
+
+#: /views/nodes/add.ctp:30
+msgid "under"
+msgstr "em"
+
+#: /views/nodes/add.ctp:33
+msgid "after"
+msgstr "depois"
+
+#: /views/nodes/add.ctp:39
+#: /views/nodes/edit.ctp:35
+msgid "Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically"
+msgstr "Conteúdos. Código dentro das tag pre vão ser escapadas. Contribuições sem formatação html vão ser formatadas automaticamente"
+
+#: /views/nodes/add.ctp:44
+msgid "Optionally explain in brief why you are proposing this addition (In English Please) :)"
+msgstr "Opcionalmente me explique resumidamente porque você está propondo esta adicão (Em Inglês por favor) :)"
+
+#: /views/nodes/admin_merge.ctp:18
+msgid "You are sure? Please check the preview"
+msgstr "Você tem certeza? Por favor verifique o preview"
+
+#: /views/nodes/compare.ctp:11
+#: /views/revisions/view.ctp:12
+msgid "Differences"
+msgstr "Diferenças"
+
+#: /views/nodes/edit.ctp:40
+msgid "What is the reason for the edit? (In English Please) :)"
+msgstr "Qual é a razão dessa alteração? (Em Inglês por favor) :)"
+
+#: /views/nodes/history.ctp:6
+msgid "See English edits too"
+msgstr "Ver alterações em Inglês também"
+
+#: /views/nodes/stats.ctp:5
+msgid "Here's a shout out to those who have dedicated time, sweat and tears to write, translate and edit the cookbook contents."
+msgstr ""
+
+#: /views/nodes/stats.ctp:7;37
+msgid "Top %s Contributors"
+msgstr "Top Colaboradores %s"
+
+#: /views/nodes/stats.ctp:9;38
+msgid "Last update: %s"
+msgstr ""
+
+#: /views/nodes/stats.ctp:27;63
+msgid "%s (%s current)"
+msgstr ""
+
+#: /views/nodes/stats.ctp:39
+msgid "%s%% translated"
+msgstr ""
+
+#: /views/nodes/stats.ctp:41
+msgid "The cookbook needs you! No submissions for this language!"
+msgstr ""
+
+#: /views/nodes/stats.ctp:44
+msgid "The cookbook needs you! This language will soon be removed if not updated."
+msgstr ""
+
+#: /views/nodes/stats.ctp:46
+msgid "The cookbook needs you! No updates for one month."
+msgstr ""
+
+#: /views/nodes/todo.ctp:7
+msgid "These is no <a href=\"%s\">po file</a> for %s"
+msgstr ""
+
+#: /views/nodes/todo.ctp:9
+msgid "These sections either do not have a translation, or the English text has changed since it was translated"
+msgstr ""
+
+#: /views/nodes/view_all.ctp:40;67
+msgid "See comments for this section"
+msgstr "Ver comentários dessa seção"
+
+#: /views/nodes/view_all.ctp:114;126
+msgid "Change history for %s"
+msgstr "Histórico de alterações para %s"
+
+#: /views/revisions/results.ctp:8
+msgid "Search Results"
+msgstr "Resultados da Busca"
+
+#: /views/revisions/results.ctp:32
+msgid "No results"
+msgstr "Sem resultados"
+
+#: /views/revisions/search.ctp:1
+msgid "Search book.cakephp.org"
+msgstr "Busque book.cakephp.org"
+
+#: /webroot/test.php:75
+msgid "Debug setting does not allow access to this url."
+msgstr ""
+
diff --git a/locale/spa/LC_MESSAGES/default.po b/locale/spa/LC_MESSAGES/default.po
new file mode 100644
index 0000000..5984203
--- /dev/null
+++ b/locale/spa/LC_MESSAGES/default.po
@@ -0,0 +1,466 @@
+# LANGUAGE translation of Book Application
+# Copyright 2008 Andy Dawson <andydawson76@yahoo.co.uk>
+# Generated from files:
+# Revision: 600 app_controller.php
+# Revision: 609 controllers/attachments_controller.php
+# Revision: 600 controllers/changes_controller.php
+# Revision: 639 views/layouts/default.ctp
+# Revision: 600 controllers/comments_controller.php
+# Revision: 600 views/comments/index.ctp
+# Revision: 600 views/changes/index.ctp
+# Revision: 638 views/changes/rss/index.ctp
+# Revision: 600 views/comments/add.ctp
+# Revision: 600 views/elements/attachments.ctp
+# Revision: 600 views/elements/login_hint.ctp
+# Revision: 54 views/elements/node_options.ctp
+# Revision: 600 views/elements/search.ctp
+# Revision: 650 views/elements/secondary_nav.ctp
+# Revision: 54 views/elements/side_menu.ctp
+# Revision: 600 views/elements/toc.ctp
+# Revision: 600 views/layouts/error.ctp
+# Revision: 54 views/nodes/add.ctp
+# Revision: 54 views/nodes/edit.ctp
+# Revision: 600 views/nodes/admin_merge.ctp
+# Revision: 600 views/nodes/history.ctp
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: 2008-11-19 13:34+0100\n"
+"PO-Revision-Date: 2008-11-19 13:38+0100\n"
+"Last-Translator: Andy Dawson <andydawson76@yahoo.co.uk>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: /app_controller.php:93
+msgid "Whoops, not a valid language."
+msgstr "Lo siento, no es un lenguaje válido"
+
+#: /app_controller.php:109
+msgid "Please login to continue"
+msgstr "Por favor inicia la sesión para continuar"
+
+#: /app_controller.php:293
+msgid "All %s matching the term \"%s\""
+msgstr "Todos los %s que concuerdan con el término \"%s\""
+
+#: /controllers/attachments_controller.php:85
+msgid "Attachment controller - no class or foreignKey error."
+msgstr "Attachment controller - no existe la clase o error de foreingKey"
+
+#: /controllers/attachments_controller.php:110
+msgid "Existing Attachment for %s, id %s updated"
+msgstr "Adjunto para %s, id %s actualizado"
+
+#: /controllers/attachments_controller.php:112
+msgid "New Attachment for %s, id %s added"
+msgstr "Nuevo Adjunto para %s, id %s agregado"
+
+#: /controllers/attachments_controller.php:137
+msgid "%s with id %s updated"
+msgstr "%s con id %s actualizado"
+
+#: /controllers/attachments_controller.php:140
+msgid "errors in form"
+msgstr "errores en el formulario"
+
+#: /controllers/changes_controller.php:130
+msgid "Recent Changes for %s"
+msgstr "Cambios recientes para %s"
+
+#: /controllers/changes_controller.php:134
+#: /views/layouts/default.ctp:33
+msgid "Recent Changes for all languages"
+msgstr "Cambios recientes para todos los lenguajes"
+
+#: /controllers/changes_controller.php:136
+#: /views/layouts/default.ctp:28
+msgid "Recent Changes"
+msgstr "Cambios Recientes"
+
+#: /controllers/changes_controller.php:144
+#: /controllers/comments_controller.php:191
+msgid "by %s"
+msgstr "por %s"
+
+#: /controllers/changes_controller.php:149
+msgid "restricted to status: %s"
+msgstr "restringido al estado: %s"
+
+#: /controllers/comments_controller.php:84;116
+msgid "Invalid Node."
+msgstr "Nodo inválido"
+
+#: /controllers/comments_controller.php:93
+msgid "Your comment has been added"
+msgstr "Tu comentario ha sigo agregado"
+
+#: /controllers/comments_controller.php:96
+msgid "Please correct errors below."
+msgstr "Por favor corrija los siguientes errores"
+
+#: /controllers/comments_controller.php:120
+msgid "No permissions to see comments for that section"
+msgstr "No tiene privilegios para ver los comentarios de esa sección"
+
+#: /controllers/comments_controller.php:128
+#: /views/nodes/view_all.ctp:110;120
+msgid "Comments for %s"
+msgstr "Comentarios para %s"
+
+#: /controllers/comments_controller.php:161
+msgid "Invalid Comment."
+msgstr "Comentario inválido"
+
+#: /controllers/comments_controller.php:180
+#: /views/comments/index.ctp:6
+msgid "Recent Comments"
+msgstr "Comentarios Recientes"
+
+#: /controllers/comments_controller.php:182
+#: /views/layouts/default.ctp:43
+msgid "Recent Comments for all languages"
+msgstr "Comentarios Recientes para todos los lenguajes"
+
+#: /controllers/nodes_controller.php:846
+msgid "Invalid Collection"
+msgstr "Colección inválida"
+
+#: /controllers/nodes_controller.php:871;976
+msgid "Thanks for your contribution! Your suggestion has been submitted for review."
+msgstr "¡Gracias por tu contribución! Tu sugerencia ha sido enviada para revisarse"
+
+#: /controllers/nodes_controller.php:931
+msgid "This function is for comparing different (public) language content"
+msgstr "Esta función es para comparar contenidos públicos de lenguajes distintos"
+
+#: /controllers/revisions_controller.php:615;620;643
+msgid "Only current and previous revisions can be viewed"
+msgstr "Solo la versión actual y la anterior pueden ser vistas"
+
+#: /controllers/revisions_controller.php:624
+msgid "Only possible to compare revisions of the same node"
+msgstr "Sólo es posible comparar revisiones del mismo nodo"
+
+#: /models/node.php:170
+msgid "Default Title"
+msgstr "Titulo por Omisión"
+
+#: /models/node.php:173
+msgid "default_slug"
+msgstr "default_slug"
+
+#: /models/node.php:176
+msgid "Default Content"
+msgstr "Contenido por omisión"
+
+#: /plugins/users/views/users/login.ctp:8;20
+#: /views/elements/secondary_nav.ctp:23
+msgid "Login"
+msgstr "Iniciar sesión"
+
+#: /plugins/users/views/users/login.ctp:13
+msgid "Forgot your password?"
+msgstr "¿Olbidaste tu contraseña?"
+
+#: /views/changes/index.ctp:18
+msgid "change submitted by %s, %s"
+msgstr "cambio enviado por %s, %s"
+
+#: /views/changes/index.ctp:24
+#: /views/changes/rss/index.ctp:14
+msgid "accepted"
+msgstr "aceptado"
+
+#: /views/changes/index.ctp:27
+#: /views/changes/rss/index.ctp:17
+msgid "not accepted"
+msgstr "no aceptado"
+
+#: /views/changes/index.ctp:30
+#: /views/changes/rss/index.ctp:20
+msgid "pending"
+msgstr "pendiente"
+
+#: /views/changes/index.ctp:35
+msgid "changed from %s to %s by %s, %s"
+msgstr "cambiado de %s a %s por %s, %s"
+
+#: /views/changes/index.ctp:41
+msgid "submitted by %s"
+msgstr "enviado por %s"
+
+#: /views/comments/add.ctp:6
+msgid "Comment: %s"
+msgstr "Commentario: %s"
+
+#: /views/comments/index.ctp:4
+msgid "Comments: %s"
+msgstr "Comentarios: %s"
+
+#: /views/comments/index.ctp:11
+msgid "No Comments yet!"
+msgstr "¡Aún no hay comentarios!"
+
+#: /views/comments/index.ctp:20
+msgid "This page as a feed"
+msgstr "Esta página tiene un canal"
+
+#: /views/elements/attachments.ctp:2
+msgid "Images/Files associated with this content"
+msgstr "Imagenes/Archivos asociados a este contenido"
+
+#: /views/elements/comment.ctp:9
+msgid "unknown"
+msgstr "desconocido"
+
+#: /views/elements/comment_form.ctp:4
+msgid "Login to add a comment"
+msgstr "Inicia sesión para añadir un comentario"
+
+#: /views/elements/comment_form.ctp:17
+msgid "Comment on %s"
+msgstr "Comentarios para %s"
+
+#: /views/elements/login_hint.ctp:3
+msgid "Login with your Bakery account"
+msgstr "Inicia sesión con tu cuenta Bakery"
+
+#: /views/elements/login_hint.ctp:4
+msgid "Not got one? Hop on over to %s and sign up"
+msgstr "¿No tienes? Ve a %s e inscríbete"
+
+#: /views/elements/node_options.ctp:8
+msgid "Edit"
+msgstr "Editar"
+
+#: /views/elements/node_options.ctp:11
+msgid "View just this section"
+msgstr "Ver sólo esta sección"
+
+#: /views/elements/node_options.ctp:14
+msgid "Comments (%s)"
+msgstr "Comentarios (%s)"
+
+#: /views/elements/node_options.ctp:19
+msgid "there is a pending change for this section"
+msgstr "Hay un cambio pendiente para esta sección"
+
+#: /views/elements/node_options.ctp:22
+msgid "History"
+msgstr "Historia"
+
+#: /views/elements/node_options.ctp:32
+msgid "This text may be out of sync with the English version"
+msgstr "Este tecto puede diverger de la versión original en inglés"
+
+#: /views/elements/node_options.ctp:39
+msgid "Compare to original content"
+msgstr "Comparar con el contenido original"
+
+#: /views/elements/paging.ctp:4
+msgid "Page %s"
+msgstr "Página %s"
+
+#: /views/elements/paging.ctp:5
+msgid "<< previous"
+msgstr "<< anterior"
+
+#: /views/elements/paging.ctp:8
+msgid "Next >>"
+msgstr "Siguiente >>"
+
+#: /views/elements/search.ctp:22
+#: /views/elements/search_form.ctp:12
+msgid "Search"
+msgstr "Buscar"
+
+#: /views/elements/secondary_nav.ctp:10
+msgid "Top Contributors"
+msgstr "Principales contribuidores"
+
+#: /views/elements/secondary_nav.ctp:13
+msgid "todo"
+msgstr ""
+
+#: /views/elements/secondary_nav.ctp:17
+msgid "Logged in as %s"
+msgstr "Registrado como %s"
+
+#: /views/elements/secondary_nav.ctp:18
+msgid "Logout"
+msgstr "Cerrar sesión"
+
+#: /views/elements/secondary_nav.ctp:26
+msgid "About CakePHP"
+msgstr "Acerca de CakePHP"
+
+#: /views/elements/secondary_nav.ctp:27
+msgid "Donate"
+msgstr "Donar"
+
+#: /views/elements/side_menu.ctp:14
+#: /views/nodes/view_all.ctp:95
+msgid "All in one page"
+msgstr "Todo en una página"
+
+#: /views/elements/side_menu.ctp:23
+#: /views/nodes/view_all.ctp:102
+msgid "Suggest a new section here"
+msgstr "Sugerir una nueva sessión aquí"
+
+#: /views/elements/side_menu.ctp:30
+msgid "Options"
+msgstr "opciones"
+
+#: /views/elements/toc.ctp:13
+#: /views/nodes/toc.ctp:5
+msgid "Table of Contents"
+msgstr "Tabla de contenidos"
+
+#: /views/elements/toc.ctp:16
+msgid "see fully expanded table of contents (only)"
+msgstr "ver únicamente la tabla de contenidos expandida"
+
+#: /views/elements/toc.ctp:19
+msgid "Books in "
+msgstr "Libros en"
+
+#: /views/elements/toc.ctp:21
+msgid "Available collections"
+msgstr "Colecciones disponibles"
+
+#: /views/layouts/default.ctp:38
+msgid "Recent comments"
+msgstr "Comentarios recientes"
+
+#: /views/layouts/default.ctp:51;55
+msgid "My Submissions"
+msgstr "Mis contribuciones"
+
+#: /views/layouts/default.ctp:88
+msgid "Welcome to %s"
+msgstr "Bienvenido a %s"
+
+#: /views/layouts/error.ctp:6
+msgid "CakePHP: the rapid development php framework:"
+msgstr "CakePHP: el framework de desarrollo rápido"
+
+#: /views/layouts/error.ctp:34
+msgid "CakePHP: the rapid development php framework"
+msgstr "CakePHP: el framework de desarrollo rápido"
+
+#: /views/nodes/add.ctp:17
+#: /views/nodes/edit.ctp:26
+msgid "Please review the guidelines for submitting to the Cookbook to ensure consistency."
+msgstr "Pro favor revisa las recomendacines sobre como contribuir con el cookbook"
+
+#: /views/nodes/add.ctp:22
+msgid "Add a new section"
+msgstr "Agregar nueva sección"
+
+#: /views/nodes/add.ctp:28
+#: /views/nodes/edit.ctp:32
+msgid "Show me a preview before submitting"
+msgstr "Muéstrame una vista previa antes de enviar"
+
+#: /views/nodes/add.ctp:30
+msgid "under"
+msgstr "debajo de"
+
+#: /views/nodes/add.ctp:33
+msgid "after"
+msgstr "despues de"
+
+#: /views/nodes/add.ctp:39
+#: /views/nodes/edit.ctp:35
+msgid "Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically"
+msgstr "Contenido. Código en etiquetas pre será escapado. Contribuciones sin formato html seran formateadas automáticamente"
+
+#: /views/nodes/add.ctp:44
+msgid "Optionally explain in brief why you are proposing this addition (In English Please) :)"
+msgstr "Opcionalmente explica por qué propones esta adición, se breve. (En inglés por favor ) :)"
+
+#: /views/nodes/admin_merge.ctp:18
+msgid "You are sure? Please check the preview"
+msgstr "¿Estás seguro? Por favor verifica."
+
+#: /views/nodes/compare.ctp:11
+#: /views/revisions/view.ctp:12
+msgid "Differences"
+msgstr "Diferencias"
+
+#: /views/nodes/edit.ctp:40
+msgid "What is the reason for the edit? (In English Please) :)"
+msgstr "¿Cuál es la razón para este cambio? (En inglés por favor) :)"
+
+#: /views/nodes/history.ctp:6
+msgid "See English edits too"
+msgstr "Ver los cambios en inglés también"
+
+#: /views/nodes/stats.ctp:5
+msgid "Here's a shout out to those who have dedicated time, sweat and tears to write, translate and edit the cookbook contents."
+msgstr ""
+
+#: /views/nodes/stats.ctp:7;37
+msgid "Top %s Contributors"
+msgstr "Principales contribuidores %s"
+
+#: /views/nodes/stats.ctp:9;38
+msgid "Last update: %s"
+msgstr "Last update: %s"
+
+#: /views/nodes/stats.ctp:27;63
+msgid "%s (%s current)"
+msgstr "%s (%s current)"
+
+#: /views/nodes/stats.ctp:39
+msgid "%s%% translated"
+msgstr "%s%% translated"
+
+#: /views/nodes/stats.ctp:41
+msgid "The cookbook needs you! No submissions for this language!"
+msgstr "The cookbook needs you! No submissions for this language!"
+
+#: /views/nodes/stats.ctp:44
+msgid "The cookbook needs you! This language will soon be removed if not updated."
+msgstr "The cookbook needs you! This language will soon be removed if not updated."
+
+#: /views/nodes/stats.ctp:46
+msgid "The cookbook needs you! No updates for one month."
+msgstr "The cookbook needs you! No updates for one month."
+
+#: /views/nodes/todo.ctp:7
+msgid "These is no <a href=\"%s\">po file</a> for %s"
+msgstr "These is no <a href=\"%s\">po file</a> for %s"
+
+#: /views/nodes/todo.ctp:9
+msgid "These sections either do not have a translation, or the English text has changed since it was translated"
+msgstr "These sections either do not have a translation, or the English text has changed since it was translated"
+
+#: /views/nodes/view_all.ctp:40;67
+msgid "See comments for this section"
+msgstr "Ver comentarios para esta sección"
+
+#: /views/nodes/view_all.ctp:114;126
+msgid "Change history for %s"
+msgstr "Historia de cambios para %s"
+
+#: /views/revisions/results.ctp:8
+msgid "Search Results"
+msgstr "Resultados de búsqueda"
+
+#: /views/revisions/results.ctp:32
+msgid "No results"
+msgstr "Sin resultados"
+
+#: /views/revisions/search.ctp:1
+msgid "Search book.cakephp.org"
+msgstr "Buscar en book.cakephp.org"
+
+#: /webroot/test.php:75
+msgid "Debug setting does not allow access to this url."
+msgstr "Debug setting does not allow access to this url."
+
diff --git a/models/attachment.php b/models/attachment.php
new file mode 100755
index 0000000..4f17ef1
--- /dev/null
+++ b/models/attachment.php
@@ -0,0 +1,84 @@
+<?php
+/* SVN FILE: $Id: attachment.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for attachment.php
+ *
+ * Long description for attachment.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package mi-base
+ * @subpackage mi-base.app.models
+ * @since v 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Attachment class
+ *
+ * @uses AppModel
+ * @package mi-base
+ * @subpackage mi-base.app.models
+ */
+class Attachment extends AppModel {
+/**
+ * name property
+ *
+ * @var string 'Attachment'
+ * @access public
+ */
+ var $name = 'Attachment';
+/**
+ * displayField property
+ *
+ * @var string 'description'
+ * @access public
+ */
+ var $displayField = 'description';
+/**
+ * validate property
+ *
+ * @var array
+ * @access public
+ */
+ var $validate = array(
+ 'user_id' => array('numeric'),
+ 'class' => array('alphaNumeric'),
+ 'foreign_id' => array('rule' => array('minLength', 1)),
+ );
+/**
+ * actsAs property
+ *
+ * @var array
+ * @access public
+ */
+ var $actsAs = array(
+ 'Polymorphic',
+ 'ImageUpload' => array(
+ 'allowedMime' => '*',
+ 'allowedExt' => '*',
+ 'dirFormat' => '{$class}{DS}{$foreign_id}',
+ 'overwriteExisting' => true,
+ //'factoryMode' => true
+ ),
+ 'Slugged'
+ );
+/**
+ * belongsTo property
+ *
+ * @var array
+ * @access public
+ */
+ var $belongsTo = array('Users.User');
+}
+?>
\ No newline at end of file
diff --git a/models/behaviors/image_upload.php b/models/behaviors/image_upload.php
new file mode 100755
index 0000000..cc0fef1
--- /dev/null
+++ b/models/behaviors/image_upload.php
@@ -0,0 +1,557 @@
+<?php
+/* SVN FILE: $Id: image_upload.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for image_upload.php
+ *
+ * Long description for image_upload.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package base
+ * @subpackage base.models.behaviors
+ * @since v 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+require_once('upload.php');
+/**
+ * ImageUploadBehavior class
+ *
+ * Extending the Upload behavior, adding image specific logic uses directy system calls to
+ * imagemagick - which naturally must be installed for it to work
+ *
+ * @uses UploadBehavior
+ * @package base
+ * @subpackage base.models.behaviors
+ */
+class ImageUploadBehavior extends UploadBehavior {
+/**
+ * name property
+ *
+ * @var string 'ImageUpload'
+ * @access public
+ */
+ var $name = 'ImageUpload';
+/**
+ * mapMethods property
+ *
+ * Map "none-file" calls to the __passThru method which will pickup the filename and call the file version
+ * of the method.
+ * For Flip/Flop/Rotate - operate on the original file and reprocess the versions
+ *
+ * @var array
+ * @access public
+ */
+ var $mapMethods = array(
+ '/convert|crop|polaroid|perspective|reflection|resize|shear|thumbnail|trim/' => '__passThru',
+ '/flip|flop|rotate/' => '__passThruReprocess',
+ );
+/**
+ * current property
+ *
+ * The file currently being processed. stored to allow consecutive operations without deleting the original file
+ *
+ * @var bool false
+ * @access private
+ */
+ var $__current = false;
+/**
+ * setup method
+ *
+ * Override defaults inherited from the upload behavior
+ * Set allowed mimes to image types
+ * Set allowed extension to matching mimes
+ * Set versions to create tiny, small, medium and large thumbs/versions
+ *
+ * @param mixed $model
+ * @param array $config
+ * @return void
+ * @access public
+ */
+ function setup (&$model, $config = array()) {
+ $this->_defaultSettings['allowedMime'] = array('image/jpeg', 'image/gif', 'image/png', 'image/bmp');
+ $this->_defaultSettings['allowedExt'] = array('jpeg', 'jpg', 'gif', 'png', 'bmp');
+ $this->_defaultSettings['versions'] = array(
+ 'thumb' => array(
+ 'callback' => array('resize', 50, 50)
+ ),
+ 'small' => array(
+ 'callback' => array('resize', 75, 75)
+ ),
+ 'medium' => array(
+ 'callback' => array('resize', 150, 150)
+ ),
+ 'large' => array(
+ 'callback' => array(
+ array('resize', 600, 400)
+ )
+ ),
+ );
+ parent::setup($model, $config);
+ }
+/**
+ * convertFile method
+ *
+ * Directly call convert
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function convertFile(&$model, $fullpath = null, $params = array()) {
+ if ($params) {
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+ return false;
+ }
+/**
+ * cropFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function cropFile(&$model, $fullpath = null, $cropTo = null, $params = array()) {
+ if (!$cropTo) {
+ $params['gravity'] = 'Center';
+ $cropTo = '50%\!';
+ }
+ $params = am($params, array('-crop' => $cropTo));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * flipFile method
+ *
+ * Flip (mirror) vertically
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function flipFile(&$model, $fullpath = null, $params = array()) {
+ $params = am($params, array('-flip'));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * flopFile method
+ *
+ * Flop (mirror) horizontally
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function flopFile(&$model, $fullpath = null, $params = array()) {
+ $params = am($params, array('-flop'));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * perspectiveFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param mixed $tl
+ * @param mixed $tr
+ * @param mixed $bl
+ * @param mixed $br
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function perspectiveFile(&$model, $fullpath = null, $tl = null, $tr = null, $bl = null, $br = null, $params = array()) {
+ $dimensions = $this->__imIdentify($model, $fullpath, array('format' => '%wx%h'));
+ if (!$dimensions) {
+ return false;
+ }
+ list($width, $height) = explode('x', $dimensions);
+ if (!$tl) {
+ $tl = '0,0';
+ }
+ if (!$tr) {
+ $tr = $width . ',0';
+ }
+ if (!$bl) {
+ $bl = '0,' . $height;
+ }
+ if (!$br) {
+ $br = $width . ',' . $height;
+ }
+ foreach (array('tl', 'tr', 'bl', 'br') as $var) {
+ if (strpos($$var, '%')) {
+ list($w, $h) = explode(',', $$var);
+ if (strpos($w, '%')) {
+ $w = str_replace('%', '', $w);
+ $w = round($width * $w / 100);
+ }
+ if (strpos($h, '%')) {
+ $h = str_replace('%', '', $h);
+ $h = round($height * $h / 100);
+ }
+ $$var = $w . ',' . $h;
+ }
+ }
+ $params = am($params, array(
+ '-matte',
+ 'virtual-pixel' => 'transparent',
+ 'distort' => "Perspective '0,0 $tl $width,0 $tr 0,$height $bl $width,$height $br'",
+ ));
+ $return = $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * polaroidFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param int $width
+ * @param int $height
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function polaroidFile(&$model, $fullpath = null, $width = 100, $height = 100, $params = array()) {
+ $params = am($params, array('thumbnail' => $width . 'x' . $height, '+polaroid'));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * reflectionFile method
+ *
+ * @TODO not fully implemented/tested
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param mixed $reflectionDepth
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function reflectionFile(&$model, $fullpath, $reflectionDepth = 0.5, $params = array()) {
+ $upsideDown = dirname($fullpath) . DS . rand();
+ $rightWayUp = dirname($fullpath) . DS . rand();
+ copy($fullpath, $rightWayUp);
+ copy($fullpath, $upsideDown);
+ $this->resizeFile($model, $upsideDown, '100%', 200 * $reflectionDepth . '%');
+ $this->flipFile($model, $upsideDown);
+ $dimensions = $this->__imIdentify($model, $upsideDown, array('format' => '%wx%h'));
+ list($width, $height) = explode('x', $dimensions);
+ $height = $height * $reflectionDepth;
+ $this->cropFile($model, $upsideDown, "{$width}x$height");
+ $params = am($params, array('geometry' => '+1+1', 'tile' => '1x2'));
+ $return = $this->__imMontage($model, array($rightWayUp, $upsideDown), $params, $fullpath);
+ }
+/**
+ * resizeFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param int $width
+ * @param int $height
+ * @return void
+ * @access public
+ */
+ function resizeFile(&$model, $fullpath = null, $width = 600, $height = 400, $params = array()) {
+ $params = am($params, array('resize' => $width . 'x' . $height . '>'));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * rotateFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param int $angle
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function rotateFile(&$model, $fullpath = null, $angle = 90, $params = array()) {
+ $params = am($params, array('rotate' => $angle));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * thumbnailFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param int $width
+ * @param int $height
+ * @return void
+ * @access public
+ */
+ function thumbnailFile(&$model, $fullpath = null, $width = 100, $height = 100, $params = array()) {
+ $params = am($params, array('thumbnail' => $width . 'x' . $height . '^', '-auto-orient'));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * shearFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param int $x
+ * @param int $y
+ * @return void
+ * @access public
+ */
+ function shearFile(&$model, $fullpath = null, $x = 0 , $y = 0, $params = array()) {
+ if ($y === false && $x) {
+ $params = am($params, array('shear' => $x));
+ } else {
+ $params = am($params, array('shear' => $x . 'x' . $y));
+ }
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * trimFile method
+ *
+ * @param mixed $model
+ * @param mixed $fullpath
+ * @param array $params
+ * @return void
+ * @access public
+ */
+ function trimFile(&$model, $fullpath = null, $params = array()) {
+ $params = am($params, array('-trim', '+repage'));
+ return $this->__imConvert($model, $fullpath, $params);
+ }
+/**
+ * beforeProcessUpload method
+ *
+ * Add width and height to the data to be saved
+ *
+ * @param mixed $model
+ * @param mixed $data
+ * @return void
+ * @access protected
+ */
+ function _beforeProcessUpload(&$model, &$data) {
+ parent::_beforeProcessUpload($model, $data);
+ extract($this->settings[$model->alias]);
+ list($width, $height) = getimagesize($data[$model->alias]['tempFile']);
+ $data[$model->alias]['width'] = $width;
+ $data[$model->alias]['height'] = $height;
+ return !$this->errors;
+ }
+/**
+ * passThru method
+ *
+ * @param mixed $model
+ * @param mixed $method
+ * @param mixed $firstParam
+ * @return void
+ * @access private
+ */
+ function __passThru(&$model, $method, $firstParam = null) {
+ if (!$firstParam) {
+ return $this->$method($model);
+ }
+ $params = func_get_args();
+ array_shift($params);
+ array_shift($params);
+ $path = '';
+ if (isset($this->settings[$model->alias]['versions'][$params[0]])) {
+ $path = $model->absolutePath($params[0]);
+ } elseif (is_numeric($params[0]) && $model->hasAny(array($model->primaryKey => $params[0]))) {
+ $path = $model->absolutePath($params[0]);
+ } else {
+ $idSchema = $model->schema($model->primaryKey);
+ if ($idSchema['length'] == 36 && $model->hasAny(array($model->primaryKey => $params[0]))) {
+ $path = $model->absolutePath($params[0]);
+ }
+ }
+ if ($path) {
+ $params[0] = $path;
+ } else {
+ $this->errors[] = "method {$method} called but impossible to pass to {$method}File - couldn't determine the file to process";
+ return false;
+ }
+ $method = $method . 'File';
+ switch (count($params)) {
+ case 1:
+ return $this->{$method}($model, $params[0]);
+ case 2:
+ return $this->{$method}($model, $params[0], $params[1]);
+ case 3:
+ return $this->{$method}($model, $params[0], $params[1], $params[2]);
+ case 4:
+ return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3]);
+ case 5:
+ return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3], $params[4]);
+ default:
+ array_unshift($params, $model);
+ return call_user_func_array(array(&$this, $method), $params);
+ break;
+ }
+ }
+/**
+ * passThruReprocess method
+ *
+ * @param mixed $model
+ * @param mixed $method
+ * @param mixed $id
+ * @param array $params
+ * @return void
+ * @access private
+ */
+ function __passThruReprocess(&$model, $method, $id = null) {
+ if (!$id) {
+ if (!$model->id) {
+ return false;
+ }
+ $id = $model->id;
+ }
+ $model->id = $id;
+ $method = $method . 'File';
+ $params = func_get_args();
+ $params[1] = $this->absolutePath($model);
+ $return = call_user_func_array(array(&$this, $method), $params);
+ if ($return) {
+ $return = $this->reprocess($model, true);
+ return $return;
+ }
+ return false;
+ }
+/**
+ * imConvert method
+ *
+ * @param mixed $input (filename)
+ * @param array $params
+ * @param mixed $output (filename)
+ * @param bool $return
+ * @return void
+ * @access private
+ */
+ function __imConvert(&$model, $input, $params = array(), $output = null, $return = false) {
+ if (isset($params['return'])) {
+ $return = $params['return'];
+ unset($params['return']);
+ }
+ if (!$output) {
+ $output = $input;
+ }
+ if ($this->__current != $output) {
+ $this->__current = $output;
+ if (file_exists($output) && $output != $input) {
+ unlink($output);
+ }
+ $input = $this->absolutePath($model);
+ }
+ $dir = dirname($output);
+ if (!file_exists($dir)) {
+ new Folder($dir, true);
+ }
+ $params = $this->__imParseParams($params);
+ $command = "convert $input $params $output";
+ if ($return) {
+ return $command;
+ }
+ $return = exec($command, $exitCode);
+ //return $return;
+ return true;
+ }
+/**
+ * imMontage method
+ *
+ * @param mixed $model
+ * @param mixed $input
+ * @param array $params
+ * @param mixed $output
+ * @return void
+ * @access private
+ */
+ function __imMontage(&$model, $input, $params = array(), $output = null) {
+ if (!$output) {
+ if (is_array($input)) {
+ $output = $input[0];
+ } else {
+ $output = $input;
+ }
+ }
+ if (is_array($input)) {
+ $input = implode($input, ' ');
+ }
+ if ($this->__current != $output) {
+ $this->__current = $output;
+ if (file_exists($output) && $output != $input) {
+ unlink($output);
+ }
+ }
+ $dir = dirname($output);
+ if (!file_exists($dir)) {
+ new Folder($dir, true);
+ }
+ $params = $this->__imParseParams($params);
+ $command = "montage $input $params $output";
+ $return = exec($command, $exitCode);
+ //return $return;
+ return true;
+
+ }
+/**
+ * imInfo method
+ *
+ * @param mixed $model
+ * @param mixed $path
+ * @param array $params
+ * @return void
+ * @access private
+ */
+ function __imIdentify(&$model, $path = null, $params = array()) {
+ if (!$path || !file_exists($path)) {
+ $path = $this->absolutePath($model);
+ }
+ $params = $this->__imParseParams($params);
+ $return = exec('identify ' . $params . ' ' . $path );
+ return $return;
+ }
+
+/**
+ * imParseParams method
+ *
+ * @param mixed $params
+ * @param mixed $key
+ * @return void
+ * @access private
+ */
+ function __imParseParams($params, $key = null) {
+ foreach ($params as $key => $value) {
+ if (is_array($value)) {
+ $params[$key] = $this->__imParseParams($value, $key);
+ } elseif (is_numeric(($key))) {
+ $params[$key] = $value;
+ } elseif (!in_array($key[0], array('-', '+'))) {
+ $this->__imEscape($value);
+ $params[$key] = '-' . $key . ' ' . $value;
+ } else {
+ $this->__imEscape($value);
+ $params[$key] = $key . ' ' . $value;
+ }
+ }
+ return implode($params, ' ');
+ }
+/**
+ * imEscape method
+ *
+ * @param mixed $value
+ * @return void
+ * @access private
+ */
+ function __imEscape(&$value) {
+ $value = str_replace('>', '\>', $value);
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/behaviors/polymorphic.php b/models/behaviors/polymorphic.php
new file mode 100755
index 0000000..d4dc25a
--- /dev/null
+++ b/models/behaviors/polymorphic.php
@@ -0,0 +1,111 @@
+<?php
+/* SVN FILE: $Id: polymorphic.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Polymorphic Behavior.
+ *
+ * Allow the model to be associated with any other model object
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package base
+ * @subpackage base.models.behaviors
+ * @since v 0.1
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * PolymorphicBehavior class
+ *
+ * @uses ModelBehavior
+ * @package base
+ * @subpackage base.models.behaviors
+ */
+class PolymorphicBehavior extends ModelBehavior {
+/**
+ * defaultSettings property
+ *
+ * @var array
+ * @access protected
+ */
+ var $_defaultSettings = array(
+ 'classField' => 'class',
+ 'foreignKey' => 'foreign_id'
+ );
+/**
+ * setup method
+ *
+ * @param mixed $model
+ * @param array $config
+ * @return void
+ * @access public
+ */
+ function setup(&$model, $config = array()) {
+ $this->settings[$model->alias] = am ($this->_defaultSettings, $config);
+ }
+/**
+ * afterFind method
+ *
+ * @param mixed $model
+ * @param mixed $results
+ * @param bool $primary
+ * @access public
+ * @return void
+ */
+ function afterFind (&$model, $results, $primary = false) {
+ extract($this->settings[$model->alias]);
+ if ($primary && isset($results[0][$model->alias][$classField]) && $model->recursive > 0) {
+ foreach ($results as $key => $result) {
+ $associated = array();
+ $class = Inflector::classify($result[$model->alias][$classField]);
+ $foreignId = $result[$model->alias][$foreignKey];
+ if ($class && $foreignId) {
+ $result = $result[$model->alias];
+ if (!isset($model->$class)) {
+ $model->bindModel(array('belongsTo' => array(
+ $class => array(
+ 'conditions' => array($model->alias . '.' . $classField => $class),
+ 'foreignKey' => $foreignKey
+ )
+ )));
+ }
+ $conditions = array($class . '.id' => $foreignId);
+ $recursive = -1;
+ $associated = $model->$class->find('first', compact('conditions', 'recursive'));
+ $name = $model->$class->find('list', compact('conditions'));
+ $associated[$class]['display_field'] = $name[$foreignId];
+ $results[$key][$class] = $associated[$class];
+ }
+ }
+ } elseif(isset($results[$model->alias][$classField])) {
+ $associated = array();
+ $class = $results[$model->alias][$classField];
+ $foreignId = $results[$model->alias][$foreignKey];
+ if ($class && $foreignId) {
+ $result = $results[$model->alias];
+ if (!isset($model->$class)) {
+ $model->bindModel(array('belongsTo' => array(
+ $class => array(
+ 'conditions' => array($model->alias . '.' . $classField => $class),
+ 'foreignKey' => $foreignKey
+ )
+ )));
+ }
+ $associated = $model->$class->find(array($class . '.id' => $foreignId), array('recursive' => -1));
+ $associated[$class]['display_field'] = $associated[$class][$model->$class->displayField];
+ $results[$class] = $associated[$class];
+ }
+ }
+ return $results;
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/behaviors/slugged.php b/models/behaviors/slugged.php
new file mode 100644
index 0000000..2ea1692
--- /dev/null
+++ b/models/behaviors/slugged.php
@@ -0,0 +1,291 @@
+<?php
+/* SVN FILE: $Id: slugged.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for slugged.php
+ *
+ * Part based/inspired by the sluggable behavior of Mariano Iglesias
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package base
+ * @subpackage base.models.behaviors
+ * @since v 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * SluggedBehavior class
+ *
+ * @uses ModelBehavior
+ * @package base
+ * @subpackage base.models.behaviors
+ */
+class SluggedBehavior extends ModelBehavior {
+/**
+ * name property
+ *
+ * @var string 'Slugged'
+ * @access public
+ */
+ var $name = 'Slugged';
+/**
+ * defaultSettings property
+ *
+ * overwrite has 2 values
+ * false - once the slug has been saved, do not change it (use if you are doing lookups based on slugs)
+ * true - if the label field values change, regenerate the slug (use if you are the slug is just window-dressing)
+ * unique has 2 values
+ * false - will not enforce a unique slug, whatever the label is is direclty slugged without checking for duplicates
+ * true - use if you are doing lookups based on slugs (see overwrite)
+ * mode has the following values
+ * ascii - retuns an ascii slug generated using the core Inflector::slug() function
+ * display - a dummy mode which returns a slug legal for display - removes illegal (not unprintable) characters
+ * url - returns a slug appropriate to put in a url
+ * class - a dummy mode which returns a slug appropriate to put in a html class (there are no restrictions)
+ * id - retuns a slug appropriate to use in a html id
+ *
+ * @var array
+ * @access protected
+ */
+ var $_defaultSettings = array(
+ 'label' => null,
+ 'slugField' => 'slug',
+ 'mode' => 'url',
+ 'seperator' => '-',
+ 'length' => 100,
+ 'overwrite' => true,
+ 'unique' => false,
+ 'notices' => true
+ );
+/**
+ * setup method
+ *
+ * Use the model's label field as the default field on which to base the slug, the label can be made up of multiple
+ * fields by specifying an array of fields
+ *
+ * @param mixed $model
+ * @param array $config
+ * @access public
+ * @return void
+ */
+ function setup(&$model, $config = array()){
+ $this->_defaultSettings['label'] = array($model->displayField);
+ $this->settings[$model->alias] = am($this->_defaultSettings, $config);
+ extract ($this->settings[$model->alias]);
+ $label = $this->settings[$model->alias]['label'] = (array)$label;
+ foreach($label as $field) {
+ if ($notices) {
+ if (!$model->hasField($field)){
+ trigger_error('(SluggedBehavior::setup) model ' . $model->name . ' is missing the field ' . $field . ' specified in the setup.', E_USER_WARNING);
+ $model->Behaviors->disable($this->name);
+ }
+ }
+ }
+ }
+/**
+ * beforeSave method
+ *
+ * if a new row, or overwrite is set to true, check for a change to a label field and add the slug to the data
+ * to be saved
+ * If no slug at all is returned (should not be permitted and prevented by validating the label fields) the model
+ * alias will be used as a slug.
+ * If unique is set to true, check for a unique slug and if unavailable suffix the slug with -1, -2, -3 etc.
+ * until a unique slug is found
+ *
+ * @param mixed $model
+ * @access public
+ * @return void
+ */
+ function beforeSave(&$model) {
+ extract ($this->settings[$model->alias]);
+ if (!$model->hasField($slugField)){
+ return true;
+ }
+ if ($overwrite || !$model->id) {
+ $somethingToDo = false;
+ foreach($label as $field) {
+ if (isset($model->data[$model->alias][$field])) {
+ $somethingToDo = true;
+ }
+ }
+ if (!$somethingToDo) {
+ return true;
+ }
+ $slug = array();
+ foreach($label as $field) {
+ if (isset($model->data[$model->alias][$field])) {
+ $slug[] = $model->data[$model->alias][$field];
+ } elseif ($model->id) {
+ $slug[] = $model->field($field);
+ }
+ }
+ $slug = implode($slug, $seperator);
+ $slug = $this->slug($model, $slug);
+ if (!$slug) {
+ $slug = $model->alias;
+ }
+ if ($unique) {
+ $conditions = array($model->alias . '.' . $slugField => $slug);
+ if ($model->id) {
+ $conditions['NOT'][$model->alias . '.' . $model->primaryKey] = $model->id;
+ }
+ $fields = array($model->primaryKey, $slugField);
+ $recursive = -1;
+ $i = 0;
+ $suffix = '';
+ while($model->find('count', compact('condition', 'fields', 'recursive'))) {
+ $i++;
+ $suffix = $seperator . $i;
+ $conditions[$model->alias . '.' . $slugField] = $slug . $suffix;
+ }
+ if ($suffix) {
+ $slug .= $suffix;
+ }
+ }
+ $this->_addToWhitelist($model, array($slugField));
+ $model->data[$model->alias][$slugField] = $slug;
+ }
+ return true;
+ }
+/**
+ * slug method
+ *
+ * For the given string, generate a slug. The replacements used are based on the mode setting, If tidy is false
+ * (only possible if directly called - primarily for tracing and testing) seperators will not be cleaned up
+ * and so slugs like "-----as---df-----" are possible, which by default would otherwise be returned as "as-df".
+ * If the mode is "id" and the first charcter of the regex-ed slug is numeric, it will be prefixed with an x.
+ *
+ * @param mixed $model
+ * @param mixed $string
+ * @param bool $tidy
+ * @return string a slug
+ * @access public
+ */
+ function slug($model, $string, $tidy = true) {
+ extract ($this->settings[$model->alias]);
+ if ($mode == 'ascii') {
+ $slug = Inflector::slug($string, $seperator);
+ } else {
+ $regex = $this->__regex($mode);
+ if ($regex) {
+ $slug = preg_replace('@[' . $regex . ']@Su', $seperator, $string);
+ } else {
+ $slug = $string;
+ }
+ }
+ if ($tidy) {
+ $slug = preg_replace('/' . $seperator . '+/', $seperator, $slug);
+ $slug = trim($slug, $seperator);
+ if ($slug && $mode == 'id' && is_numeric($slug[0])) {
+ $slug = 'x' . $slug;
+ }
+ }
+ if (strlen($slug) > $length) {
+ $slug = substr ($slug, $length);
+ }
+ return $slug;
+ }
+/**
+ * resetSlugs method
+ *
+ * Regenerate all slugs. On large dbs this can take more than 30 seconds - a time limit is set to allow a minimum
+ * 100 updates per second as a preventative measure.
+ *
+ * @param AppModel $model
+ * @param array $conditions
+ * @param int $recursive
+ * @return bool true on success false otherwise
+ * @access public
+ */
+ function resetSlugs(&$model, $conditions = array(), $recursive = -1) {
+ extract ($this->settings[$model->alias]);
+ if (!$model->hasField($slugField)) {
+ return false;
+ }
+ $fields = array_merge((array)$model->primaryKey, $label);
+ $sort = $model->displayField . ' ASC';
+ $count = $model->find('count');
+ set_time_limit (max(30, $count / 100));
+ foreach ($model->find('all', compact('conditions', 'fields', 'sort', 'recursive')) as $row) {
+ $model->create();
+ $model->save($row);
+ }
+ return true;
+ }
+/**
+ * regex method
+ *
+ * Based upon the mode return a partial regex to generate a valid string for the intended use. Note that you
+ * can use almost litterally anything in a url - the limitation is only in what your own application
+ * understands. See the test case for info on how these regex patterns were generated.
+ *
+ * @param string $mode
+ * @return string a partial regex
+ * @access private
+ */
+ function __regex($mode) {
+ $return = '\x00-\x1f\x26\x3c\x7f-\x9f\x{d800}-\x{dfff}\x{fffe}-\x{ffff}';
+ if ($mode == 'display') {
+ return $return;
+ }
+ $return .= preg_quote(' \'"/:?<>.$/:;?@=+&', '@');
+ if ($mode == 'url') {
+ return $return;
+ }
+ $return .= '';
+ if ($mode == 'class') {
+ return $return;
+ }
+ if ($mode == 'id') {
+ return '\x{0000}-\x{002f}\x{003a}-\x{0040}\x{005b}-\x{005e}\x{0060}\x{007b}-\x{007e}\x{00a0}-\x{00b6}' .
+ '\x{00b8}-\x{00bf}\x{00d7}\x{00f7}\x{0132}-\x{0133}\x{013f}-\x{0140}\x{0149}\x{017f}\x{01c4}-\x{01cc}' .
+ '\x{01f1}-\x{01f3}\x{01f6}-\x{01f9}\x{0218}-\x{024f}\x{02a9}-\x{02ba}\x{02c2}-\x{02cf}\x{02d2}-\x{02ff}' .
+ '\x{0346}-\x{035f}\x{0362}-\x{0385}\x{038b}\x{038d}\x{03a2}\x{03cf}\x{03d7}-\x{03d9}\x{03db}\x{03dd}\x{03df}' .
+ '\x{03e1}\x{03f4}-\x{0400}\x{040d}\x{0450}\x{045d}\x{0482}\x{0487}-\x{048f}\x{04c5}-\x{04c6}\x{04c9}-\x{04ca}' .
+ '\x{04cd}-\x{04cf}\x{04ec}-\x{04ed}\x{04f6}-\x{04f7}\x{04fa}-\x{0530}\x{0557}-\x{0558}\x{055a}-\x{0560}' .
+ '\x{0587}-\x{0590}\x{05a2}\x{05ba}\x{05be}\x{05c0}\x{05c3}\x{05c5}-\x{05cf}\x{05eb}-\x{05ef}\x{05f3}-\x{0620}' .
+ '\x{063b}-\x{063f}\x{0653}-\x{065f}\x{066a}-\x{066f}\x{06b8}-\x{06b9}\x{06bf}\x{06cf}\x{06d4}\x{06e9}' .
+ '\x{06ee}-\x{06ef}\x{06fa}-\x{0900}\x{0904}\x{093a}-\x{093b}\x{094e}-\x{0950}\x{0955}-\x{0957}' .
+ '\x{0964}-\x{0965}\x{0970}-\x{0980}\x{0984}\x{098d}-\x{098e}\x{0991}-\x{0992}\x{09a9}\x{09b1}\x{09b3}-\x{09b5}' .
+ '\x{09ba}-\x{09bb}\x{09bd}\x{09c5}-\x{09c6}\x{09c9}-\x{09ca}\x{09ce}-\x{09d6}\x{09d8}-\x{09db}\x{09de}' .
+ '\x{09e4}-\x{09e5}\x{09f2}-\x{0a01}\x{0a03}-\x{0a04}\x{0a0b}-\x{0a0e}\x{0a11}-\x{0a12}\x{0a29}\x{0a31}\x{0a34}' .
+ '\x{0a37}\x{0a3a}-\x{0a3b}\x{0a3d}\x{0a43}-\x{0a46}\x{0a49}-\x{0a4a}\x{0a4e}-\x{0a58}\x{0a5d}\x{0a5f}-\x{0a65}' .
+ '\x{0a75}-\x{0a80}\x{0a84}\x{0a8c}\x{0a8e}\x{0a92}\x{0aa9}\x{0ab1}\x{0ab4}\x{0aba}-\x{0abb}\x{0ac6}\x{0aca}' .
+ '\x{0ace}-\x{0adf}\x{0ae1}-\x{0ae5}\x{0af0}-\x{0b00}\x{0b04}\x{0b0d}-\x{0b0e}\x{0b11}-\x{0b12}\x{0b29}\x{0b31}' .
+ '\x{0b34}-\x{0b35}\x{0b3a}-\x{0b3b}\x{0b44}-\x{0b46}\x{0b49}-\x{0b4a}\x{0b4e}-\x{0b55}\x{0b58}-\x{0b5b}\x{0b5e}' .
+ '\x{0b62}-\x{0b65}\x{0b70}-\x{0b81}\x{0b84}\x{0b8b}-\x{0b8d}\x{0b91}\x{0b96}-\x{0b98}\x{0b9b}\x{0b9d}' .
+ '\x{0ba0}-\x{0ba2}\x{0ba5}-\x{0ba7}\x{0bab}-\x{0bad}\x{0bb6}\x{0bba}-\x{0bbd}\x{0bc3}-\x{0bc5}\x{0bc9}' .
+ '\x{0bce}-\x{0bd6}\x{0bd8}-\x{0be6}\x{0bf0}-\x{0c00}\x{0c04}\x{0c0d}\x{0c11}\x{0c29}\x{0c34}\x{0c3a}-\x{0c3d}' .
+ '\x{0c45}\x{0c49}\x{0c4e}-\x{0c54}\x{0c57}-\x{0c5f}\x{0c62}-\x{0c65}\x{0c70}-\x{0c81}\x{0c84}\x{0c8d}\x{0c91}' .
+ '\x{0ca9}\x{0cb4}\x{0cba}-\x{0cbd}\x{0cc5}\x{0cc9}\x{0cce}-\x{0cd4}\x{0cd7}-\x{0cdd}\x{0cdf}\x{0ce2}-\x{0ce5}' .
+ '\x{0cf0}-\x{0d01}\x{0d04}\x{0d0d}\x{0d11}\x{0d29}\x{0d3a}-\x{0d3d}\x{0d44}-\x{0d45}\x{0d49}\x{0d4e}-\x{0d56}' .
+ '\x{0d58}-\x{0d5f}\x{0d62}-\x{0d65}\x{0d70}-\x{0e00}\x{0e2f}\x{0e3b}-\x{0e3f}\x{0e4f}\x{0e5a}-\x{0e80}\x{0e83}' .
+ '\x{0e85}-\x{0e86}\x{0e89}\x{0e8b}-\x{0e8c}\x{0e8e}-\x{0e93}\x{0e98}\x{0ea0}\x{0ea4}\x{0ea6}\x{0ea8}-\x{0ea9}' .
+ '\x{0eac}\x{0eaf}\x{0eba}\x{0ebe}-\x{0ebf}\x{0ec5}\x{0ec7}\x{0ece}-\x{0ecf}\x{0eda}-\x{0f17}\x{0f1a}-\x{0f1f}' .
+ '\x{0f2a}-\x{0f34}\x{0f36}\x{0f38}\x{0f3a}-\x{0f3d}\x{0f48}\x{0f6a}-\x{0f70}\x{0f85}\x{0f8c}-\x{0f8f}\x{0f96}' .
+ '\x{0f98}\x{0fae}-\x{0fb0}\x{0fb8}\x{0fba}-\x{109f}\x{10c6}-\x{10cf}\x{10f7}-\x{10ff}\x{1101}\x{1104}\x{1108}' .
+ '\x{110a}\x{110d}\x{1113}-\x{113b}\x{113d}\x{113f}\x{1141}-\x{114b}\x{114d}\x{114f}\x{1151}-\x{1153}' .
+ '\x{1156}-\x{1158}\x{115a}-\x{115e}\x{1162}\x{1164}\x{1166}\x{1168}\x{116a}-\x{116c}\x{116f}-\x{1171}\x{1174}' .
+ '\x{1176}-\x{119d}\x{119f}-\x{11a7}\x{11a9}-\x{11aa}\x{11ac}-\x{11ad}\x{11b0}-\x{11b6}\x{11b9}\x{11bb}' .
+ '\x{11c3}-\x{11ea}\x{11ec}-\x{11ef}\x{11f1}-\x{11f8}\x{11fa}-\x{1dff}\x{1e9c}-\x{1e9f}\x{1efa}-\x{1eff}' .
+ '\x{1f16}-\x{1f17}\x{1f1e}-\x{1f1f}\x{1f46}-\x{1f47}\x{1f4e}-\x{1f4f}\x{1f58}\x{1f5a}\x{1f5c}\x{1f5e}' .
+ '\x{1f7e}-\x{1f7f}\x{1fb5}\x{1fbd}\x{1fbf}-\x{1fc1}\x{1fc5}\x{1fcd}-\x{1fcf}\x{1fd4}-\x{1fd5}\x{1fdc}-\x{1fdf}' .
+ '\x{1fed}-\x{1ff1}\x{1ff5}\x{1ffd}-\x{20cf}\x{20dd}-\x{20e0}\x{20e2}-\x{2125}\x{2127}-\x{2129}' .
+ '\x{212c}-\x{212d}\x{212f}-\x{217f}\x{2183}-\x{3004}\x{3006}\x{3008}-\x{3020}\x{3030}\x{3036}-\x{3040}' .
+ '\x{3095}-\x{3098}\x{309b}-\x{309c}\x{309f}-\x{30a0}\x{30fb}\x{30ff}-\x{3104}\x{312d}-\x{4dff}' .
+ '\x{9fa6}-\x{abff}\x{d7a4}-\x{d7ff}\x{e000}-\x{ffff}';
+ }
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/behaviors/upload.php b/models/behaviors/upload.php
new file mode 100755
index 0000000..33354e8
--- /dev/null
+++ b/models/behaviors/upload.php
@@ -0,0 +1,1364 @@
+<?php
+/* SVN FILE: $Id: upload.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for upload.php
+ *
+ * Long description for upload.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package base
+ * @subpackage base.models.behaviors
+ * @since v 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * UploadBehavior class
+ *
+ * A behavior adding validation and automatic processing for file uploads
+ * The design of the behavior is to store meta data for uploaded files in a database table (can just be a file
+ * field in your products table, or a dedicated 'attachments' table) although the behavior can be used with a
+ * table-less model. Unmodified uploaded files are by default stored OUTSIDE the webroot. This allows the
+ * application to use the original file as input to generate any different 'versions' that might be required.
+ * Assuming that generating versions will fail if the file type is not what is expected; this helps defend against
+ * malicious intentions such as phising
+ * Suggested example setup would be:
+ * app
+ * controllers
+ * models
+ * views
+ * uploads <- destination for pristine uploaded files
+ * Post
+ * PostId
+ * uploadedFile.jpg
+ * uploadedFile.pdf
+ * uploadedFile.zip
+ * webroot <- document root
+ * files <- root destination for (none-image) uploaded files
+ * Post
+ * PostId
+ * uploadedFile.pdf
+ * (sanitized)extractedFromZip.doc
+ * (sanitized)extractedFromZip.rtf
+ * (sanitized)uploadedFile.zip
+ * img <- root destination for (image) upload versions
+ * Post
+ * PostId
+ * uploadedFile_small.jpg (see image upload behavior)
+ * uploadedFile_big.jpg (see image upload behavior)
+ *
+ * @uses AppBehavior
+ * @package base
+ * @subpackage base.models.behaviors
+ */
+class UploadBehavior extends ModelBehavior {
+/**
+ * name property
+ *
+ * @var string 'Upload'
+ * @access public
+ */
+ var $name = 'Upload';
+/**
+ * errors property
+ *
+ * Array of (system-type) errors encountered when processing an upload
+ *
+ * @var array
+ * @access public
+ */
+ var $errors = array();
+/**
+ * behaviorMap property
+ *
+ * Map of which more specific upload behaviors exist
+ * Used in factoryMode to automatically load the more specific behavior if a match is found based on the data
+ *
+ * @var array
+ * @access private
+ */
+ var $__behaviorMap = array(
+ 'pdf' => array('extension' => 'pdf'),
+ 'archive' => array(
+ 'extension' => array('bz2', 'gz', 'tar', 'zip'),
+ //'mime' => array('application/zip', 'application/x-tar', 'application/gzip')
+ ),
+ 'image' => array('mime' => 'image/*'),
+ );
+/**
+ * contentMap property
+ *
+ * @var array
+ * @access private
+ */
+ var $__extContentMap = array(
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip',
+ 'csv' => 'application/vnd.ms-excel',
+ 'doc' => 'application/msword',
+ 'gif' => 'image/gif',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'pdf' => 'application/pdf',
+ 'png' => 'image/png',
+ 'psd' => 'image/x-psd',
+ 'sql' => 'text/x-sql',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'txt' => 'text/plain',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xml' => 'application/xml',
+ 'zip' => 'application/x-zip',
+ '*' => 'application/octet-stream'
+ );
+/**
+ * defaultSettings property
+ *
+ * @var array
+ * @access protected
+ */
+ var $_defaultSettings = array(
+ 'dirField' => 'dir',
+ 'fileField' => 'filename',
+ 'extField' => 'ext',
+ 'checksumField' => 'checksum',
+ 'mustUploadFile' => true,
+ 'allowedMime' => '*',
+ 'allowedExt' => '*',
+ 'allowedSize' => '8',// '*' for no limit (in any event limited by php settings)
+ 'allowedSizeUnits' => 'MB',
+ 'overwriteExisting' => true,
+ 'autoCreateVersions' => true,
+ 'baseDir' => '{APP}uploads',
+ 'dirFormat' => '{$class}/{$foreign_id}',// include {$baseDir} to have absolute paths (not recomended)
+ 'fileFormat' => '{$filename}',// include {$dir} to store the dir & filename in one field
+ 'pathReplacements' => array(),
+ 'versions' => array(
+ 'thumb' => array(
+ 'vBaseDir' => '{IMAGES}',
+ 'vDirFormat' => 'types/',
+ 'vFileFormat' => '{$ext}.png',
+ ),
+ /*
+ 'copy' => array(
+ 'vBaseDir' => '{WWW_ROOT}files/',
+ 'vDirFormat' => '{$dir}/',
+ 'vFileFormat' => '{$filename}',
+ 'callback' => array('copy', '{$vAbsolute}')
+ )
+ */
+ ),
+ 'factoryMode' => true,
+ );
+/**
+ * autoConfig property
+ *
+ * Look for a config file for loading settings
+ *
+ * @var bool true
+ * @access public
+ */
+ var $autoConfig = true;
+/**
+ * setup method
+ *
+ * Initialize the component, setup validation rules/messages and check that the base directory is writable
+ * If the base directory is not writable an error is triggered and the behavior is disabled
+ *
+ * @param mixed $model
+ * @param array $config
+ * @return void
+ * @access public
+ */
+ function setup(&$model, $config = array()) {
+ if ($this->autoConfig && function_exists('autoConfig')) {
+ autoConfig($this, $model->alias);
+ }
+ $this->settings[$model->alias] = am ($this->_defaultSettings, $config);
+ extract ($this->settings[$model->alias]);
+ uses('Folder');
+ $baseDir = $this->_replacePseudoConstants($model, $baseDir);
+ if (!file_exists($baseDir)) {
+ new Folder($baseDir, true);
+ if (!file_exists($baseDir)) {
+ trigger_error('UploadBehavior::setup Base directory ' . $baseDir . ' doesn\'t exist and cannot be created.');
+ $model->Behaviors->disable($this->name);
+ return;
+ }
+ } elseif(!is_writable($baseDir)) {
+ trigger_error('UploadBehavior::setup Base directory ' . $baseDir . ' is not writable.');
+ $model->Behaviors->disable($this->name);
+ return;
+ }
+ $this->settings[$model->alias]['baseDir'] = $baseDir;
+ foreach ($this->settings[$model->alias]['versions'] as $key => $settings) {
+ $this->version($model, $key, $settings);
+ }
+ }
+/**
+ * uploadErrors method
+ *
+ * @return void
+ * @access public
+ */
+ function uploadErrors() {
+ return $this->errors;
+ }
+/**
+ * version method
+ *
+ * Get, Add, modify or delete version settings
+ *
+ * @param mixed $model
+ * @param mixed $key
+ * @param array $options
+ * @return void
+ * @access public
+ */
+ function version(&$model, $key = null, $options = array()) {
+ if (!$key) {
+ return $this->settings[$model->alias]['versions'];
+ }
+ if ($options === false) {
+ unset ($this->settings[$model->alias]['versions'][$key]);
+ return true;
+ } elseif ($options) {
+ extract($this->settings[$model->alias]);
+ $options = am(array('vBaseDir' => $baseDir, 'vDirFormat' => $dirFormat, 'vFileFormat' => $fileFormat . '_' . $key), $options);
+ if (isset($this->settings[$model->alias]['versions'][$key])) {
+ $this->settings[$model->alias]['versions'][$key] = am($options, $this->settings[$model->alias]['versions'][$key]);
+ } else {
+ $this->settings[$model->alias]['versions'][$key] = $options;
+ }
+ }
+ return $this->settings[$model->alias]['versions'][$key];
+ }
+/**
+ * absolutePath method
+ *
+ * Convenience method
+ *
+ * @see path
+ * @param mixed $model
+ * @param mixed $id
+ * @param string $to
+ * @return void
+ * @access public
+ */
+ function absolutePath(&$model, $id = null, $to = 'file') {
+ if ($to == 'file' && isset($data[$model->alias]['original'])) {
+ return $data[$model->alias]['original'];
+ }
+ return $this->_path($model, $id, $to, true);
+ }
+/**
+ * afterDelete method
+ *
+ * Reset if running in factoryMode
+ *
+ * @return void
+ * @access public
+ */
+ function afterDelete(&$model) {
+ if ($this->name != 'Upload' && $factoryMode && $model->Behaviors->attached('Upload')) {
+ $model->Behaviors->detatch($this->name);
+ $model->Behaviors->enable('Upload');
+ }
+ }
+/**
+ * afterFind method
+ *
+ * If running in factory mode, and a single result is returned (a read/find) delegate to more specific
+ * behavior if it exists so any methods specific to the type of file to be available
+ * For each result, add the version info for convenience
+ *
+ * @param mixed $model
+ * @param mixed $results
+ * @param boolean $primary
+ * @return void
+ * @access public
+ */
+ function afterFind(&$model, $results, $primary = false) {
+ extract ($this->settings[$model->alias]);
+ if ($factoryMode && $this->name == 'Upload' && count($results) == 1) {
+ $behavior = $this->__detectBehavior($model, $results[0]);
+ if ($behavior && $model->Behaviors->attach($behavior, array('factoryMode' => true))) {
+ $model->Behaviors->disable('Upload');
+ $model->Behaviors->$behavior->setup($model);
+ return $model->Behaviors->$behavior->afterFind($model, $results, $primary);
+ }
+ }
+ if ($model->findQueryType != 'list') {
+ $data = $model->data;
+ foreach ($results as $i => $result) {
+ if (!isset($result[$model->alias][$fileField])
+ || isset($result[$model->alias]['versions'])) {
+ return $results;
+ }
+ $model->id = $result[$model->alias][$model->primaryKey];
+ $model->data = $result;
+ $results[$i][$model->alias]['versions'] = $this->_path($model, null, 'versions');
+ }
+ $model->data = $data;
+ }
+ return $results;
+ }
+/**
+ * afterSave method
+ *
+ * Reset if running in factoryMode
+ *
+ * @param mixed $model
+ * @param mixed $created
+ * @return void
+ * @access public
+ */
+ function afterSave(&$model, $created) {
+ extract ($this->settings[$model->alias]);
+ if ($this->name != 'Upload' && $factoryMode && $model->Behaviors->attached('Upload')) {
+ $model->Behaviors->detach($this->name);
+ $model->Behaviors->enable('Upload');
+ }
+ }
+/**
+ * beforeDelete method
+ *
+ * Before deleting the record, delete the associated file(s)
+ * If running in factory mode, delegate to more specific behavior if it exists
+ *
+ * @param mixed $model
+ * @access public
+ * @return void
+ */
+ function beforeDelete(&$model) {
+ extract ($this->settings[$model->alias]);
+ if ($factoryMode && $this->name == 'Upload') {
+ $behavior = $this->__detectBehavior($model, $model->data);
+ if ($behavior && $model->Behaviors->attach($behavior, array('factoryMode' => true))) {
+ $model->Behaviors->disable('Upload');
+ $model->Behaviors->$behavior->setup($model);
+ $model->Behaviors->$behavior->beforeDelete($model);
+ }
+ }
+ return $this->deleteFiles($model);
+ }
+/**
+ * beforeSave method
+ *
+ * If the file field is an array process the file uploaded. No action if there is no file uploaded
+ * Will prevent saving of 0byte files
+ * If running in factory mode, delegate to more specific behavior if it exists
+ *
+ * @param mixed $model
+ * @access public
+ * @return void
+ */
+ function beforeSave(&$model) {
+ return $this->process($model, $model->data, false);
+ }
+/**
+ * beforeValidate method
+ *
+ * If the associated model is tableless setup the model schema to allow validation errors to be used
+ * If running in factory mode, delegate to more specific behavior if it exists
+ *
+ * @param mixed $model
+ * @return void
+ * @access public
+ */
+ function beforeValidate(&$model) {
+ extract ($this->settings[$model->alias]);
+ if ($factoryMode && $this->name == 'Upload') {
+ $behavior = $this->__detectBehavior($model, $model->data);
+ if ($behavior && $model->Behaviors->attach($behavior, array('factoryMode' => true))) {
+ $model->Behaviors->disable('Upload');
+ $model->Behaviors->attach($behavior);
+ $model->Behaviors->$behavior->setup($model);
+ return $model->Behaviors->$behavior->beforeValidate($model);
+ }
+ }
+ $this->_setupSchema($model);
+ $this->_setupValidation($model);
+ return true;
+ }
+/**
+ * deleteFiles method
+ *
+ * Delete the files for this row - $which can either be 'all', 'original' or 'versions'
+ * Will automatically delete empty folders after processing if permissions allow ( will not
+ * raise an error if not possible to delete empty folders)
+ * If running in factory mode, delegate to more specific behavior if it exists
+ *
+ * @param mixed $model
+ * @param string $which
+ * @return boolean True on success, false on failure
+ * @access public
+ */
+ function deleteFiles(&$model, $id = null, $which = 'all') {
+ if ($id && !is_int($id)) {
+ $idSchema = $model->schema($model->primaryKey);
+ if (!is_numeric($id) && $idSchema['length'] != 36) {
+ $to = $id;
+ $id = null;
+ } elseif (is_array($id)) {
+ extract (array_merge(array('id' => null), $id));
+ }
+ }
+ if (!$id) {
+ $id = $model->id;
+ }
+ extract ($this->settings[$model->alias]);
+ if ($factoryMode && $this->name == 'Upload') {
+ $behavior = $this->__detectBehavior($model, $model->data);
+ if ($behavior && $model->Behaviors->attach($behavior, array('factoryMode' => true))) {
+ $model->Behaviors->attach($behavior);
+ $model->Behaviors->$behavior->setup($model);
+ $return = $model->Behaviors->$behavior->deleteFiles($model, $id, $which);
+ $model->Behaviors->detach($behavior);
+ return $return;
+ }
+ }
+ $paths = $this->_path($model, null, 'all', true);
+ $folders = array();
+ if (in_array($which, array('all', 'versions'))) {
+ foreach ($paths['version'] as $file) {
+ if (file_exists($file) && !unlink($file)) {
+ $this->errors[] = 'Couldn\'t delete file ' . $file;
+ }
+ $folder = dirname($file);
+ if (!in_array($folder, $folders)) {
+ $folders[] = $folder;
+ }
+ }
+ }
+ if (in_array($which, array('all', 'original'))) {
+ if (file_exists($paths['original']) && !unlink($paths['original'])) {
+ $this->errors[] = 'Couldn\'t delete file ' . $paths['original'];
+ }
+ $folder = dirname($file);
+ if (!in_array($folder, $folders)) {
+ $folders[] = $folder;
+ }
+ }
+ foreach ($folders as $folder) {
+ $dir = new Folder($folder);
+ if ($dir->read() == array(array(), array())) {
+ $dir->delete();
+ }
+ }
+ return !$this->errors;
+ }
+/**
+ * checkUploadedAFile method
+ *
+ * Prevent saving a record if no file was uploaded
+ *
+ * @param mixed $model
+ * @param mixed $fieldData
+ * @return void
+ * @access public
+ */
+ function checkUploadedAFile(&$model, $fieldData) {
+ extract ($this->settings[$model->alias]);
+ if (is_array($fieldData[$fileField]) && $fieldData[$fileField]['error'] == 4) {
+ return false;
+ }
+ return true;
+ }
+/**
+ * copy method
+ *
+ * @param mixed $model
+ * @param mixed $id
+ * @param mixed $from
+ * @param mixed $to
+ * @return void
+ * @access public
+ */
+ function copy(&$model, $id = null, $from = null, $to = null) {
+ if (!$to && !$from) {
+ $from = $this->absolutePath($model);
+ $to = $id;
+ } elseif(!$to && strpos($id, DS) !== false) {
+ $to = $from;
+ $from = $id;
+ }
+ $path = dirname($to);
+ new Folder($path, true);
+ return copy($from, $to);
+ }
+/**
+ * checkUploadError method
+ *
+ * @param mixed $model
+ * @param mixed $fieldData
+ * @return boolean true if a file was uploaded successfully, false if an error was encountered
+ * @access public
+ */
+ function checkUploadError (&$model, $fieldData) {
+ extract ($this->settings[$model->alias]);
+ if (isset($fieldData[$fileField]) && is_array($fieldData[$fileField])) {
+ $fieldData = $fieldData[$fileField];
+ } else {
+ return true;
+ }
+ if ($fieldData['size'] && $fieldData['error']) {
+ return false;
+ }
+ return true;
+ }
+/**
+ * checkUploadMime method
+ *
+ * Based on the config settings, check the uploaded mime type and reject if not an allowed mime type
+ * Warning: the mimetype is set by the browser and may be inaccurate/manipulated
+ *
+ * @param mixed $model
+ * @param mixed $fieldData
+ * @return boolean true if a file is an acceptable mime type, false otherwise
+ * @access public
+ */
+ function checkUploadMime (&$model, $fieldData) {
+ extract ($this->settings[$model->alias]);
+ if (isset($fieldData[$fileField]) && is_array($fieldData[$fileField])) {
+ $fieldData = $fieldData[$fileField];
+ } else {
+ return true;
+ }
+ if (!$fieldData['size'] || $allowedMime == '*') {
+ return true;
+ }
+ if (is_array($allowedMime)) {
+ if (in_array($fieldData['type'], $allowedMime)) {
+ return true;
+ }
+ } elseif ($fieldData['type'] == $allowedMime) {
+ return true;
+ }
+ return false;
+ }
+/**
+ * checkUploadSize method
+ *
+ * If the uploaded file exceeds the config settings - reject.
+ * Note that file uploads are limited primarily by php's settings
+ *
+ * @param mixed $model
+ * @param mixed $fieldData
+ * @return boolean true if a file is smaller than the max file size, false otherwise
+ * @access public
+ */
+ function checkUploadSize (&$model, $fieldData) {
+ extract ($this->settings[$model->alias]);
+ if (isset($fieldData[$fileField]) && is_array($fieldData[$fileField])) {
+ $fieldData = $fieldData[$fileField];
+ } else {
+ return true;
+ }
+ if (!$fieldData['size']) {
+ return false;
+ } elseif( $allowedSize == '*') {
+ return true;
+ }
+ $factor = 1;
+ switch ($allowedSizeUnits) {
+ case 'KB':
+ $factor = 1024;
+ case 'MB':
+ $factor = 1024 * 1024;
+ }
+ if ($fieldData['size'] < ($allowedSize * $factor)) {
+ return true;
+ }
+ return false;
+ }
+/**
+ * hasChanged method
+ *
+ * Check if the file changed since it was uploaded - by checking if it exists and whether the checksum
+ * still matches
+ *
+ * @param mixed $model
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function hasChanged(&$model, $id = null) {
+ extract ($this->settings[$model->alias]);
+ if (!$id) {
+ $id = $model->id;
+ }
+ if (!$model->hasField($checksumField)) {
+ return false;
+ }
+ $file = $this->_path($model, $id, 'file', true);
+ if (!file_exists($file)) {
+ return true;
+ }
+ return (md5_file($file) != $model->field($checksumField));
+ }
+/**
+ * metadata method
+ *
+ * Get the metadata directly from the file
+ *
+ * @param mixed $model
+ * @param mixed $id
+ * @param mixed $file
+ * @param mixed $data
+ * @return void
+ * @access public
+ */
+ function metadata(&$model, $id = null, $filename = null, &$data = array()) {
+ extract ($this->settings[$model->alias]);
+ if (!$id) {
+ $id = $model->id;
+ }
+ if (!$filename) {
+ $filename = $this->_path($model, $id, 'file', true);
+ }
+ $bits = explode('.', $filename);
+ if (count($bits) > 1) {
+ $ext = low(array_pop($bits));
+ $data[$model->alias][$extField] = $ext;
+ } else {
+ $ext = false;
+ }
+ if ($ext && isset($this->__extContentMap[$ext])) {
+ $data[$model->alias]['mimetype'] = $this->__extContentMap[$ext];
+ } else {
+ $data[$model->alias]['mimetype'] = $this->__extContentMap['*'];
+ }
+ $data[$model->alias]['extension'] = $ext;
+ if (file_exists($filename)) {
+ $data[$model->alias]['filesize'] = filesize($filename);
+ $data[$model->alias]['checksum'] = md5_file($filename);
+ $data[$model->alias][$fileField] = basename($filename);
+ }
+ return $data[$model->alias];
+ }
+/**
+ * process method
+ *
+ * @param mixed $model
+ * @param array $data
+ * @param bool $direct
+ * @return void
+ * @access public
+ */
+ function process(&$model, &$data = array(), $direct = true) {
+ extract ($this->settings[$model->alias]);
+ if ($data) {
+ $model->data = $data;
+ }
+ if ($direct && !$model->validates()) {
+ return false;
+ }
+ if (!isset($model->data[$model->alias]['tempFile'])) {
+ if (!isset($model->data[$model->alias][$fileField])) {
+ return true;
+ } elseif (!is_array($model->data[$model->alias][$fileField])) {
+ return true;
+ } elseif (!$model->data[$model->alias][$fileField]['size']) {
+ return false;
+ }
+ }
+ if ($factoryMode && $this->name == 'Upload') {
+ $behavior = $this->__detectBehavior($model, $model->data);
+ if ($behavior && $model->Behaviors->attach($behavior, array('factoryMode' => true))) {
+ $model->Behaviors->disable('Upload');
+ $model->Behaviors->attach($behavior);
+ $model->Behaviors->$behavior->setup($model);
+ return $model->Behaviors->$behavior->beforeSave($model);
+ }
+ }
+ $import = true;
+ if (!isset($model->data[$model->alias]['tempFile'])) {
+ $import = false;
+ $model->data[$model->alias]['tempFile'] = $model->data[$model->alias][$fileField]['tmp_name'];
+ }
+ if (!$this->_beforeProcessUpload($model, $model->data)) {
+ return false;
+ }
+ if ($import) {
+ if ($model->data[$model->alias]['tempFile'] != $model->data[$model->alias]['original']) {
+ copy($model->data[$model->alias]['tempFile'], $model->data[$model->alias]['original']);
+ unlink($model->data[$model->alias]['tempFile']);
+ }
+ } else {
+ if(!move_uploaded_file($model->data[$model->alias]['tempFile'], $model->data[$model->alias]['original'])) {
+ $this->errors[] = 'Couldn\'t move the uploaded file.';
+ }
+ }
+ $this->_afterProcessUpload($model, $model->data);
+ return true;
+ }
+/**
+ * relativePath method
+ *
+ * Convenience method
+ *
+ * @see path
+ * @param mixed $model
+ * @param mixed $id
+ * @param string $to
+ * @return void
+ * @access public
+ */
+ function relativePath(&$model, $id = null, $to = 'file') {
+ return $this->_path($model, $id, $to, false);
+ }
+/**
+ * reprocess method
+ *
+ * Does not affect the original upload file
+ * If $clearFolders is true, will delete the containing folder for versions before processing
+ * useful only if files are organized such that all versions for one file are in the same
+ * folder, and that folder only contains files for the same upload
+ * Using the original upload file as the input, regenerate versions and reset size and checksum values
+ * Useful if behavior settings change (e.g. image thumb size is changed system wide) or the original file
+ * is overwritten with an updated version
+ * If running in factory mode, delegate to more specific behavior if it exists
+ *
+ * @param mixed $model
+ * @param mixed $id
+ * @param boolean $clearFolder
+ * @return void
+ * @access public
+ */
+ function reprocess(&$model, $id = null, $clearFolders = false) {
+ if ($id && !is_int($id)) {
+ $idSchema = $model->schema($model->primaryKey);
+ if (!is_numeric($id) && $idSchema['length'] != 36) {
+ $clearFolders = $id;
+ $id = null;
+ } elseif (is_array($id)) {
+ extract (array_merge(array('id' => null), $id));
+ }
+ }
+ if (!$id) {
+ $id = $model->id;
+ }
+ if (!$id) {
+ return false;
+ }
+ extract ($this->settings[$model->alias]);
+ if ($factoryMode && $this->name == 'Upload') {
+ $behavior = $this->__detectBehavior($model, $model->data);
+ if ($behavior && $model->Behaviors->attach($behavior, array('factoryMode' => true))) {
+ $model->Behaviors->attach($behavior);
+ $model->Behaviors->$behavior->setup($model);
+ $return = $model->Behaviors->$behavior->reprocess($model, $id, $clearFolders);
+ $model->Behaviors->detach($behavior);
+ return $return;
+ }
+ }
+ if ($clearFolders) {
+ $path = $this->_path($model, null, 'all', true);
+ $folders = array_unique($path['versionDir']);
+ foreach ($folders as $path) {
+ if (!file_exists($path)) {
+ continue;
+ }
+ $folder = new Folder($path, false);
+ $folder->delete();
+ }
+ }
+ $this->_afterProcessUpload($model, $model->read(null, $id));
+ $data = $this->metaData($model, $id);
+ $data[$model->primaryKey] = $id;
+ $model->save($data);
+ return !$this->errors;
+ }
+/**
+ * afterProcessUpload method
+ *
+ * Process any configured versions
+ *
+ * @param mixed $model
+ * @param mixed $data
+ * @return boolean
+ * @access protected
+ */
+ function _afterProcessUpload(&$model, &$data) {
+ extract($this->settings[$model->alias]);
+ if (isset($data[$model->alias]['original'])) {
+ $original = $data[$model->alias]['original'];
+ } else {
+ $original = $model->absolutePath();
+ }
+ if (file_exists($original)) {
+ foreach($versions as $id => $vData) {
+ $this->_clearReplace($model);
+ $callback = false;
+ extract($vData);
+ if (!$callback) {
+ continue;
+ }
+ $vAbsolute = $vBaseDir;
+ if ($vDirFormat) {
+ $vAbsolute .= DS . $vDirFormat . DS;
+ }
+ $vAbsolute .= $vFileFormat;
+ $vData['vAbsolute'] = $vAbsolute;
+ $this->__addReplace($model, '{$vBaseDir}', $vBaseDir);
+ $this->__addReplace($model, '{$vDirFormat}', $vDirFormat);
+ $this->__addReplace($model, '{$vFileFormat}', $vFileFormat);
+ $this->__addReplace($model, '{$vAbsolute}', $vAbsolute);
+ $vData = $this->_replacePseudoConstants($model, $vData);
+ extract($vData);
+ if (!is_array($callback[0])) {
+ $callback = array($callback);
+ }
+ foreach ($callback as $params) {
+ $method = array_shift($params);
+ array_unshift($params, $id);
+ if (!call_user_func_array(array(&$model, $method), $params)) {
+ array_shift($params);
+ $this->errors[] = 'failed to perform ' . $method . ' (' . implode ($params, ', ') . ')';
+ }
+ }
+ }
+ } else {
+ $this->errors[] = 'Couldn\'t open the original file ' . $original;
+ }
+ return !$this->errors;
+ }
+/**
+ * beforeProcessUpload method
+ *
+ * Anything to process before uploading a file
+ * Set up all the meta data for saving, determine the filename to be saved
+ *
+ * @param mixed $model
+ * @param mixed $data
+ * @access protected
+ * @return void
+ */
+ function _beforeProcessUpload(&$model, &$data) {
+ $this->errors = array();
+ $this->_clearReplace($model);
+ extract ($this->settings[$model->alias]);
+ if (is_array($data[$model->alias][$fileField])) {
+ $file = $data[$model->alias]['tempFile'] = $data[$model->alias][$fileField]['tmp_name'];
+ $filename = $data[$model->alias][$fileField]['name'];
+ $data[$model->alias]['mimetype'] = $data[$model->alias][$fileField]['type'];
+ $data[$model->alias]['filesize'] = $data[$model->alias][$fileField]['size'];
+ } else {
+ $file = $data[$model->alias]['tempFile'];
+ $this->metaData($model, null, $file, $data);
+ $filename = $data[$model->alias][$fileField];
+ }
+ list($filenameOnly, $extension, $filename) = $this->__filename($model, $fileFormat);
+ $dir = $this->__path($model, $dirFormat);
+ uses('Sanitize');
+ $relativePath = $dir . DS . $filename;
+ $path = $baseDir . DS . $relativePath;
+ if(file_exists($path)) {
+ if($overwriteExisting) {
+ if(!unlink($path)) {
+ $this->errors[] = 'The file ' . $relativePath . ' already exists and cannot be deleted.';
+ }
+ } else {
+ $count = 2;
+ while(file_exists($baseDir . $dir . DS . $filenameOnly . '_' . $count . $extension)) {
+ $count++;
+ }
+ $filename = $filenameOnly .= '_' . $count;
+ if ($extension) {
+ $fielname .= '.' . $extension;
+ }
+ list($filenameOnly, $extension, $filename) = $this->__filename($model, $filename);
+ $relativePath = $dir . DS . $filename;
+ $path = $baseDir . DS . $relativePath;
+ }
+ }
+ $conditions = array();
+ if ($dirField) {
+ $conditions[$dirField] = $dir;
+ }
+ if ($fileField) {
+ $conditions[$fileField] = $filename;
+ }
+ if ($conditions && $id = $model->field($model->primaryKey, $conditions)) {
+ if($overwriteExisting) {
+ $model->id = $id;
+ } else {
+ $this->errors[] = 'The file is already in the system';
+ return false;
+ }
+ }
+ $folder = dirname($path);
+ if (!new Folder($folder, true)) {
+ $this->errors[] = 'Could not create the folder ' . $folder;
+ }
+ if ($dirField) {
+ $data[$model->alias][$dirField] = $dir;
+ }
+ if ($fileField) {
+ $data[$model->alias][$fileField] = $filename;
+ }
+ if ($extField) {
+ $data[$model->alias][$extField] = $extension;
+ }
+ if ($checksumField) {
+ $data[$model->alias][$checksumField] = md5_file($file);
+ }
+ $data[$model->alias]['relativePath'] = $relativePath;
+ $data[$model->alias]['original'] = $path;
+ return !$this->errors;
+ }
+/**
+ * clearReplace method
+ *
+ * Remove existing path replacements in preparation for the next (if any) upload
+ *
+ * @param mixed $model
+ * @return void
+ * @access protected
+ */
+ function _clearReplace(&$model) {
+ $this->settings[$model->alias]['pathReplacements'] = array();
+ extract ($this->settings[$model->alias]);
+ $this->settings[$model->alias]['baseDir'] = $this->_replacePseudoConstants($model, $baseDir);
+ $original = $this->absolutePath($model);
+ if ($original) {
+ $this->__addReplace($model, '{$original}', $original);
+ $file = basename($original);
+ $bits = explode('.', $file);
+ if (count($bits) > 1) {
+ $extension = low(array_pop($bits));
+ $this->__addReplace($model, '{$extension}', $extension);
+ $file = implode('.', $bits);
+ $this->__addReplace($model, '{$filenameOnly}', $file);
+ } else {
+ $this->__addReplace($model, '{$extension}', '');
+ $this->__addReplace($model, '{$filenameOnly}', '');
+ }
+ }
+ }
+
+/**
+ * path method
+ *
+ * Return the path to the file, the containing folder (for the original file)
+ * the versions, a specific version or all
+ *
+ * @param mixed $model
+ * @param mixed $id
+ * @param string $to 'file', 'folder', 'versions', or 'all'
+ * @param boolean $absolute
+ * @return mixed string the file or folder path, or array for versions or all
+ * @access protected
+ */
+ function _path(&$model, $id = null, $to = null, $absolute = false) {
+ if ($id && !is_int($id)) {
+ $idSchema = $model->schema($model->primaryKey);
+ if (!is_numeric($id) && $idSchema['length'] != 36) {
+ $absolute = $to;
+ $to = $id;
+ $id = null;
+ } elseif (is_array($id)) {
+ extract (array_merge(array('id' => null), $id));
+ }
+ }
+ if (!$id) {
+ $id = $model->id;
+ }
+ if ($to === null) {
+ $to = 'file';
+ }
+ if (isset($model->data[$model->alias][$model->primaryKey]) &&
+ $model->data[$model->alias][$model->primaryKey] != $id) {
+ $model->read(null, $id);
+ }
+ extract ($this->settings[$model->alias]);
+ if ($factoryMode && $this->name == 'Upload') {
+ $behavior = $this->__detectBehavior($model, $model->data);
+ if ($behavior && $model->Behaviors->attach($behavior, array('factoryMode' => true))) {
+ $model->Behaviors->attach($behavior);
+ $model->Behaviors->$behavior->setup($model);
+ $return = $model->Behaviors->$behavior->_path($model, $id, $to, $absolute);
+ $model->Behaviors->detach($behavior);
+ return $return;
+ }
+ }
+ if (in_array($to, array('file', 'folder', 'all'))) {
+ if ($absolute) {
+ $folder = $baseDir . DS;
+ } else {
+ $folder = '';
+ }
+ if ($dirField && $model->hasField($dirField)) {
+ if (isset($model->data[$model->alias][$dirField])) {
+ $folder .= $model->data[$model->alias][$dirField];
+ } else {
+ $folder .= $model->field($dirField);
+ }
+ } else {
+ $folder .= $this->_replacePseudoConstants($model, $dirFormat);
+ }
+ if ($to == 'folder') {
+ return $folder;
+ }
+ }
+ if (in_array($to, array('file', 'all'))) {
+ if ($absolute) {
+ $original = $folder . DS;
+ } else {
+ $original = '';
+ }
+ if ($fileField && isset($model->data[$model->alias][$fileField])) {
+ if (is_string($model->data[$model->alias][$fileField])) {
+ $original .= $model->data[$model->alias][$fileField];
+ } else {
+ $original .= $model->data[$model->alias][$fileField]['name'];
+ }
+ } else {
+ $original .= $model->field($fileField);
+ }
+ if ($to == 'file') {
+ return $original;
+ }
+ }
+ $this->__filename($model, $model->data[$model->alias][$fileField]);
+ if (in_array($to, array('versions', 'all')) || isset($versions[$to])) {
+ if (isset($versions[$to])) {
+ $versions = array($to => $versions[$to]);
+ }
+ foreach ($versions as $key => $details) {
+ $vBaseDir = $baseDir;
+ $vDirFormat = $dirFormat;
+ $vFileFormat = $fileFormat;
+ extract($details);
+ if ($absolute) {
+ $versionDir[$key] = $this->_replacePseudoConstants($model, $vBaseDir) . DS;
+ } else {
+ $versionDir[$key] = '';
+ }
+ $vDir = $this->_replacePseudoConstants($model, $vDirFormat);
+ if ($vDir) {
+ $version[$key] = $versionDir[$key] .= $vDir . DS;
+ }
+ $version[$key] .= $this->_replacePseudoConstants($model, $vFileFormat);
+ }
+ if ($to == 'versions') {
+ return $version;
+ }
+ }
+ if (isset($version[$to])) {
+ return $version[$to];
+ }
+ return compact('file', 'folder', 'version', 'versionDir');
+ }
+/**
+ * replacePseudoConstants method
+ *
+ * for the passed string look for and replace any pseudo constants.
+ * {CONSTANT} will be replaced with the defined CONSTANT (if it's defined)
+ * {$dataVariable} will be replaced with $this->data['ModelAlias']['dataVariable'] if it is set
+ * {$databaseField} will be replaced with $model->field('databaseField');
+ * {$random} will be replaced with a random 5 digit number, regenerated each time this method is called
+ *
+ * @param mixed $model
+ * @param mixed $string
+ * @return boolean true on success, false on error
+ * @access protected
+ */
+ function _replacePseudoConstants(&$model, $string) {
+ extract($this->settings[$model->alias]);
+ if (is_array($string)) {
+ foreach ($string as $i => $str) {
+ $string[$i] = $this->_replacePseudoConstants($model, $str);
+ }
+ return $string;
+ }
+ $_replacements = $this->settings[$model->alias]['pathReplacements'];
+ $random = uniqid('');
+ $random = substr($random, strlen($random) -5, strlen($random));
+ preg_match_all('@{\$?([^{}]*)}@', $string, $r);
+ foreach ($r[1] as $i => $match) {
+ $_found = false;
+ if (!isset($this->settings[$model->alias]['pathReplacements'][$r[0][$i]])) {
+ if (up($match) == $match) {
+ $constants = get_defined_constants();
+ if (isset($constants[$match])) {
+ $this->__addReplace($model, $r[0][$i], $constants[$match]);
+ $_found = true;
+ }
+ if (!$_found) {
+ $this->errors[] = 'Cannot replace ' . $match . ' as the constant ' . $match . ' is not defined.';
+ }
+ } else {
+ if (isset($$match)) {
+ $this->__addReplace($model, $r[0][$i], $$match);
+ $_found = true;
+ } elseif (isset($model->data[$model->alias][$match])) {
+ $this->__addReplace($model, $r[0][$i], $model->data[$model->alias][$match]);
+ $_found = true;
+ } elseif ($model->id && $model->hasField($match)) {
+ $this->__addReplace($model, $r[0][$i], $model->field($match));
+ $_found = true;
+ }
+ if (!$_found) {
+ $this->errors[] = 'Cannot replace ' . $match . ' as the variable $' . $match . ' cannot be determined.';
+ $this->errors[] = $model->data;
+ }
+ }
+ }
+ }
+ $markers = array_keys($this->settings[$model->alias]['pathReplacements']);
+ $replacements = array_values($this->settings[$model->alias]['pathReplacements']);
+ $this->settings[$model->alias]['pathReplacements'] = $_replacements;
+ return str_replace ($markers, $replacements, $string);
+ }
+/**
+ * setupSchema method
+ *
+ * @TODO How to do this without directly accessing the _schema field
+ * @param mixed $model
+ * @return void
+ * @access protected
+ */
+ function _setupSchema($model) {
+ $schema = $model->schema();
+ extract ($this->settings[$model->alias]);
+ if (!$schema) {
+ $model->_schema = $schema[$fileField] = array(
+ 'type' => 'string',
+ 'null' => null,
+ 'default' => null,
+ 'length' => 100
+ );
+ }
+ }
+/**
+ * setupValidation method
+ *
+ * Add validation rules specific to this behavior. Prepend the behaviors validation rules
+ * To allow the behavior to modify the model's data for any other validation rules
+ *
+ * @param mixed $model
+ * @return void
+ * @access protected
+ */
+ function _setupValidation(&$model) {
+ extract ($this->settings[$model->alias]);
+ if (isset($model->validate[$fileField])) {
+ $existingValidations = $model->validate[$fileField];
+ if (!is_array($existingValidations)) {
+ $existingValidations = array($existingValidations);
+ }
+ } else {
+ $existingValidations = array();
+ }
+ if ($mustUploadFile) {
+ $validations['uploadAFile'] = array(
+ 'on' => 'create',
+ 'rule' => 'checkUploadedAFile',
+ 'message' => 'Please select a file to upload.',
+ 'last' => true
+ );
+ }
+ $validations['uploadError'] = array(
+ 'rule' => 'checkUploadError',
+ 'message' => 'An error was generated during the upload.',
+ 'last' => true
+ );
+ if (is_array($allowedMime)) {
+ $allowedMimes = implode(',', $allowedMime);
+ } else {
+ $allowedMimes = $allowedMime;
+ }
+ $validations['uploadMime'] = array(
+ 'rule' => 'checkUploadMime',
+ 'message' => 'The submitted mime type is not permitted, only ' . $allowedMimes . ' permitted.',
+ 'last' => true
+ );
+ if ($allowedExt != '*') {
+ if (is_array($allowedExt)) {
+ $allowedExts = implode(',', $allowedExt);
+ } else {
+ $allowedExts = $allowedExt;
+ $allowedExt = array($allowedExt);
+ }
+ $validations['uploadExt'] = array(
+ 'rule' => array('extension', $allowedExt),
+ 'message' => 'The submitted file extension is not permitted, only ' . $allowedExts . ' permitted.',
+ 'last' => true
+ );
+ }
+ $validations['uploadSize'] = array(
+ 'rule' => 'checkUploadSize',
+ 'message' => 'The file uploaded is too big, only files less than ' . $allowedSize . ' ' . $allowedSizeUnits .' permitted.',
+ 'last' => true
+ );
+ $model->validate[$fileField] = am($validations, $existingValidations);
+ }
+/**
+ * addReplace method
+ *
+ * Add a find and replace pair
+ *
+ * @param mixed $model
+ * @param mixed $find
+ * @param string $replace
+ * @return void
+ * @access private
+ */
+ function __addReplace(&$model, $find, $replace = '') {
+ if (is_array($find)) {
+ foreach ($find as $f => $r) {
+ $this->__addReplace($model, $f, $r);
+ }
+ return;
+ }
+ if (is_array($replace)) {
+ $replace = array_shift($replace);
+ }
+ $replace = $this->_replacePseudoConstants($model, $replace);
+ $this->settings[$model->alias]['pathReplacements'][$find] = $replace;
+ }
+/**
+ * detectBehavior method
+ *
+ * Based on the passed data, check if a more specific behavior exists and if so return its name
+ *
+ * @param mixed $model
+ * @param mixed $data
+ * @return mixed false if no behavior matches, the name of the matched behavior otherwise
+ * @access private
+ */
+ function __detectBehavior(&$model, &$data = null) {
+ extract ($this->settings[$model->alias]);
+ if (!$data && $model->id) {
+ $_data = $model->data;
+ $data = $model->read();
+ $model->data = $_data;
+ }
+ $mime = false;
+ if (isset($data[$model->alias][$fileField]) && is_array($data[$model->alias][$fileField])) {
+ $mime = $data[$model->alias][$fileField]['type'];
+ } elseif (isset($data[$model->alias]['mimetype'])) {
+ $mime = $data[$model->alias]['mimetype'];
+ }
+ $extension = false;
+ if (isset($data[$model->alias][$fileField]) && is_array($data[$model->alias][$fileField])) {
+ $file = $data[$model->alias][$fileField]['name'];
+ } elseif (isset($data[$model->alias][$fileField])) {
+ $file = $data[$model->alias][$fileField];
+ } else {
+ return false;
+ }
+ $bits = explode('.', $file);
+ if (count($bits) > 1) {
+ $extension = low(array_pop($bits));
+ }
+ $behavior = false;
+ foreach ($this->__behaviorMap as $type => $tests) {
+ $match = 0;
+ foreach ($tests as $field => $value) {
+ switch ($field) {
+ case 'mime':
+ $value = str_replace('*', '', $value);
+ if (strpos($mime, $value) !== false) {
+ $match++;
+ }
+ case 'extension':
+ if (is_string($value)) {
+ if ($extension == $value) {
+ $match++;
+ }
+ } elseif (in_array($extension, $value)) {
+ $match++;
+ }
+ }
+ }
+ if ($match == count($tests)) {
+ $behavior = $type;
+ break;
+ }
+ }
+ if ($behavior) {
+ $behavior = Inflector::classify($behavior) . 'Upload';
+ } else {
+ return false;
+ }
+ $behaviors = Configure::listObjects('behavior');
+ if (in_array($behavior, $behaviors)) {
+ return $behavior;
+ }
+ return false;
+ }
+/**
+ * filename method
+ *
+ * Clean the string and return something suitable to use as a filename
+ * Allows double file extensions (.tar.gz)
+ *
+ * @param mixed $model
+ * @param mixed $string
+ * @return array a 'filename safe' string, the extension, the full filename
+ * @access private
+ */
+ function __filename(&$model, $string) {
+ extract ($this->settings[$model->alias]);
+ if (strpos($string,'{') !== false) {
+ $string = low($this->_replacePseudoConstants($model, $string));
+ }
+ $string = str_replace('__dot__', '.', Inflector::slug(str_replace('.', '__dot__', $string)));
+ $bits = explode('.', $string);
+ if (count($bits) > 1) {
+ $ext = low(array_pop($bits));
+ } else {
+ $ext = false;
+ }
+ $filename = $full = implode('.', $bits);
+ if($ext) {
+ $full .= '.' . $ext;
+ }
+ $this->__addReplace($model, '{$filenameOnly}', $filename);
+ $this->__addReplace($model, '{$extension}', $ext);
+ $this->__addReplace($model, '{$filename}', $full);
+ return array($filename, $ext, $full);
+ }
+/**
+ * path method
+ *
+ * Replace any pseudo constants and create the folder
+ *
+ * @param mixed $model
+ * @param mixed $path
+ * @return string the path to the folder
+ * @access private
+ */
+ function __path (&$model, $path) {
+ extract ($this->settings[$model->alias]);
+ if (strpos($path,'{') !== false) {
+ $path = $this->_replacePseudoConstants($model, $path);
+ }
+ if (!$path) {
+ $this->errors[] = 'Couldn\'t determine the path ' . $dir;
+ return false;
+ } else {
+ if (!(new Folder ($baseDir . DS . $path, true))) {
+ $this->errors[] = 'Couldn\'t create the path ' . $dir;
+ return false;
+ };
+ }
+ $this->__addReplace($model, '{$dir}', $path);
+ return $path;
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/change.php b/models/change.php
new file mode 100644
index 0000000..25ccd9d
--- /dev/null
+++ b/models/change.php
@@ -0,0 +1,76 @@
+<?php
+/* SVN FILE: $Id: change.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for change.php
+ *
+ * Long description for change.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.models
+ * @since 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Change class
+ *
+ * @uses AppModel
+ * @package cookbook
+ * @subpackage cookbook.models
+ */
+class Change extends AppModel {
+/**
+ * name property
+ *
+ * @var string 'Change'
+ * @access public
+ */
+ var $name = 'Change';
+/**
+ * order property
+ *
+ * @var string 'created'
+ * @access public
+ */
+ var $order = 'Change.created';
+/**
+ * belongsTo property
+ *
+ * @var array
+ * @access public
+ */
+ var $belongsTo = array(
+ 'User' => array('className' => 'Users.User', 'fields' => 'username'),
+ 'Author' => array('className' => 'Users.User', 'fields' => 'username'),
+ 'Revision' => array('fields' => array('id', 'node_id', 'user_id', 'lang', 'slug', 'title', 'status')),
+ 'Node' => array('foreignKey' => false, 'conditions' => array('Revision.node_id = Node.id'), 'fields' => array('id', 'sequence'))
+ );
+/**
+ * beforeSave method
+ *
+ * Prevent duplicate log messages
+ *
+ * @return void
+ * @access public
+ */
+ function beforeSave() {
+ extract ($this->data[$this->alias]);
+ if ($this->find('count', array('conditions' => compact('revision_id', 'status_from', 'status_to'), 'recursive' => -1))) {
+ return false;
+ };
+ return true;
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/comment.php b/models/comment.php
new file mode 100644
index 0000000..3afcccc
--- /dev/null
+++ b/models/comment.php
@@ -0,0 +1,90 @@
+<?php
+/* SVN FILE: $Id: comment.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for comment.php
+ *
+ * Long description for comment.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.models
+ * @since 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Comment class
+ *
+ * @uses AppModel
+ * @package cookbook
+ * @subpackage cookbook.models
+ */
+class Comment extends AppModel {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name= 'Comment';
+/**
+ * belongsTo variable
+ *
+ * @var array
+ * @access public
+ */
+ var $belongsTo = array(
+ 'Node',
+ 'Revision' => array(
+ 'foreignKey' => false,
+ 'conditions' => array(
+ 'Revision.status' => 'current',
+ 'Revision.node_id = Comment.node_id',
+ 'Revision.lang = Comment.lang'
+ ),
+ 'fields' => array('slug', 'title')
+ ),
+ 'User' => array('className' => 'Users.User')
+ );
+/**
+ * validate variable
+ *
+ * @var array
+ * @access public
+ */
+ var $validate = array(
+ 'title'=> array(
+ 'required'=> VALID_NOT_EMPTY
+ ),
+ 'body'=>array(
+ 'required'=> VALID_NOT_EMPTY
+ )
+ );
+/**
+ * beforeSave function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeSave() {
+ if (
+ (array_key_exists('lang', $this->data['Comment']) && !$this->data['Comment']['lang']) ||
+ (!$this->id && !array_key_exists('lang', $this->data['Comment']))
+ ) {
+ $this->data['Comment']['lang'] = $this->Node->language;
+ }
+ return true;
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/node.php b/models/node.php
new file mode 100644
index 0000000..6d4766a
--- /dev/null
+++ b/models/node.php
@@ -0,0 +1,975 @@
+<?php
+/* SVN FILE: $Id: node.php 701 2008-11-19 12:12:33Z AD7six $ */
+/**
+ * Short description for node.php
+ *
+ * Long description for node.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.models
+ * @since 1.0
+ * @version $Revision: 701 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-19 13:12:33 +0100 (Wed, 19 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Node class
+ *
+ * @uses AppModel
+ * @package cookbook
+ * @subpackage cookbook.models
+ */
+class Node extends AppModel {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Node';
+/**
+ * displayField variable
+ *
+ * @var string
+ * @access public
+ */
+ var $displayField = 'sequence';
+/**
+ * order property
+ *
+ * @var string 'lft'
+ * @access public
+ */
+ var $order = 'Node.lft';
+/**
+ * viewAllLevel variable
+ *
+ * @var int
+ * @access public
+ */
+ var $viewAllLevel = 5;
+/**
+ * language variable
+ *
+ * @var string
+ * @access public
+ */
+ var $language = 'en';
+/**
+ * bypassSilent variable
+ *
+ * @var bool
+ * @access public
+ */
+ var $bypassSilent = false;
+/**
+ * hasOne variable
+ *
+ * @var array
+ * @access public
+ */
+ var $hasOne = array('Revision' => array('conditions' => array('Revision.lang' => 'en', 'Revision.status' => 'current')));
+/**
+ * hasMany variable
+ *
+ * @var array
+ * @access public
+ */
+ var $hasMany = array('Comment' => array('conditions' => array('Comment.lang' => 'en', 'Comment.published' => true)));
+/**
+ * actsAs variable
+ *
+ * @var array
+ * @access public
+ */
+ var $actsAs = array ('Tree');
+/**
+ * validate property
+ *
+ * @var array
+ * @access public
+ */
+ var $validate = array(
+ 'merge_id' => 'numeric',
+ 'confirmation' => array('equalTo', '0'),
+ );
+/**
+ * addToTree function
+ *
+ * @param mixed $revisionId
+ * @param bool $approve
+ * @access public
+ * @return void
+ */
+ function addToTree ($revisionId, $approve = false) {
+ $id = $this->Revision->field('node_id');
+ if ($id && $this->hasAny(array('id' => $id))) {
+ // already in
+ if ($approve) {
+ $this->id = $id;
+ $this->savefield('status', 1);
+ }
+ return $id;
+ }
+
+ $revision = $this->Revision->find('first', array('id' => $revisionId, 'recursive' => -1));
+ $this->create();
+ $toSave = array('parent_id' => $revision['Revision']['under_node_id']);
+ if ($approve) {
+ $toSave['status'] = 1;
+ }
+ $this->save($toSave);
+ $this->Revision->save(array('node_id' => $this->id));
+
+ if ($revision['Revision']['after_node_id']) {
+ if ($revision['Revision']['under_node_id'] == $revision['Revision']['after_node_id']) {
+ $this->moveUp(null, true);
+ } else {
+ $conditions = array('parent_id' => $revision['Revision']['under_node_id']);
+ $order = 'lft';
+ $children = $this->find('list', compact('conditions', 'order'));
+ $keys = array_flip(array_reverse(array_keys($children)));
+ if (isset($keys[$revision['Revision']['after_node_id']])) {
+ $toMove = $keys[$revision['Revision']['after_node_id']] - 1;
+ if ($toMove) {
+ $this->moveUp(null, $toMove);
+ }
+ }
+ }
+ }
+ $this->reset();
+ return $this->id;
+ }
+/**
+ * afterFind function
+ *
+ * When looking for English content, if there isn't a current revision, populate with default text
+ * When looking for none English content, if there isn't a current revision populate with the English text
+ *
+ * @param mixed $results
+ * @access public
+ * @return void
+ */
+ function afterFind($results) {
+ $before = $results;
+ if ($this->language == 'en') {
+ if (isset($results[0]['Revision'])) {
+ foreach ($results as $i => $result) {
+ if (array_key_exists('title', $result['Revision']) && !$result['Revision']['title']) {
+ $results[$i]['Revision']['title'] = __('Default Title', true);
+ }
+ if (array_key_exists('slug', $result['Revision']) && !$result['Revision']['slug']) {
+ $results[$i]['Revision']['slug'] = __('default_slug', true);
+ }
+ if (array_key_exists('content', $result['Revision']) && !$result['Revision']['content']) {
+ $results[$i]['Revision']['content'] = __('Default Content', true);
+ }
+ if (array_key_exists('lang', $result['Revision']) && !$result['Revision']['lang']) {
+ $results[$i]['Revision']['lang'] = 'en';
+ }
+ }
+ }
+ } else {
+ if (isset($results[0]['Revision'])) {
+ $missing = false;
+ foreach ($results as $i => $result) {
+ if (array_key_exists('title', $result['Revision']) && !$result['Revision']['title']) {
+ $missing = true;
+ break;
+ }
+ if (array_key_exists('slug', $result['Revision']) && !$result['Revision']['slug']) {
+ $missing = true;
+ break;
+ }
+ if (array_key_exists('content', $result['Revision']) && !$result['Revision']['content']) {
+ $missing = true;
+ break;
+ }
+ }
+ if ($missing) {
+ $language = $this->language;
+ $this->setLanguage('en');
+ if ($this->__queryData['order'] == array(null)) {
+ unset($this->__queryData['order']);
+ }
+ $ids = Set::Extract($results, '/Node/id');
+ $engResults = $this->find('all', am($this->__queryData, array('conditions' => array('Node.id' => $ids))));
+ $this->setLanguage($language);
+ foreach ($results as $i => $result) {
+ if (isset($engResults[$i])) {
+ if (array_key_exists('title', $result['Revision']) && !$result['Revision']['title']) {
+ $results[$i]['Revision']['title'] = $engResults[$i]['Revision']['title'];
+ }
+ if (array_key_exists('slug', $result['Revision']) && !$result['Revision']['slug']) {
+ $results[$i]['Revision']['slug'] = $engResults[$i]['Revision']['slug'];
+ }
+ if (array_key_exists('content', $result['Revision']) && !$result['Revision']['content']) {
+ $results[$i]['Revision']['content'] = $engResults[$i]['Revision']['content'];
+ }
+ if (array_key_exists('lang', $result['Revision']) && !$result['Revision']['lang']) {
+ $results[$i]['Revision']['lang'] = 'en';
+ }
+ }
+ }
+ }
+ }
+ }
+ return $results;
+ }
+/**
+ * beforeFind function
+ *
+ * @param mixed $queryData
+ * @access public
+ * @return void
+ */
+ function beforeFind($queryData) {
+ if ($this->language != 'en') {
+ $this->__queryData = $queryData;
+ }
+ return true;
+ }
+/**
+ * beforeSave function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeSave() {
+ if (!$this->id&&isset($this->data['Node']['parent_id'])) {
+ $parent = $this->getPath($this->data['Node']['parent_id'], array('id'));
+ $this->data['Node']['depth'] = count($parent);
+ }
+ return true;
+ }
+/**
+ * findNeighbors function
+ *
+ * @param mixed $nodeId
+ * @param bool $individualNodeView
+ * @access public
+ * @return void
+ */
+ function findNeighbors($nodeId = null, $individualNodeView = false) {
+ $prev = $prevConstraint = $next = $nextConstraint = null;
+ if (!$nodeId) {
+ $nodeId = $this->id;
+ }
+ $currentNode = $this->find('first', array(
+ 'conditions' => array('id' => $nodeId),
+ 'fields' => array ('lft', 'rght','depth'),
+ 'recursive' => -1
+ ));
+ $currentNode = $currentNode['Node'];
+ $viewAllLevel = max($this->viewAllLevel, $currentNode['depth'] + 1);
+ $this->viewAllLevel = $viewAllLevel;
+ $fields = array ('lft', 'rght', 'Node.id', 'Revision.id', 'depth', 'Revision.slug', 'sequence', 'Revision.title');
+ @list ($index, $collection, $book) = $path = $this->getPath($nodeId, $fields, 0);
+ $limits = $book?$book['Node']:($collection?$collection['Node']:$index['Node']);
+ $prevConstraint['Node.lft BETWEEN ? AND ?'] = array($limits['lft'], ($currentNode['lft']-1));
+ $prevConstraint['Node.depth <='] = $viewAllLevel;
+ $prevConstraint['Node.show_in_toc'] = true;
+ $nextConstraint['Node.depth <='] = $viewAllLevel;
+ $nextConstraint['Node.show_in_toc'] = true;
+ if ($individualNodeView) {
+ $nextConstraint['Node.lft BETWEEN ? AND ?'] = array(($currentNode['lft']+1), $limits['rght']);
+ } else {
+ $nextConstraint['Node.lft BETWEEN ? AND ?'] = array(($currentNode['rght']+1), $limits['rght']);
+ }
+ $prev = $this->find($prevConstraint, $fields, 'Node.lft desc', 0);
+ if (!$prev&&isset($path[$currentNode['depth']-1])) {
+ $prev = $path[$currentNode['depth']-1];
+ }
+ $next = $this->find($nextConstraint, $fields, 'Node.lft asc', 0);
+ if (!$next&&isset($path[$currentNode['depth']-2])) {
+ $next = $path[$currentNode['depth']-2];
+ }
+ $return = array ($prev, $next);
+ return $return;
+ }
+/**
+ * book function
+ *
+ * @param mixed $id
+ * @param array $fields
+ * @access public
+ * @return void
+ */
+ function book($id, $fields = array('id')) {
+ return $this->_getNode(2, $id, $fields);
+ }
+/**
+ * collection function
+ *
+ * @param mixed $id
+ * @param array $fields
+ * @access public
+ * @return void
+ */
+ function collection($id, $fields = array('id')) {
+ return $this->_getNode(1, $id, $fields);
+ }
+/**
+ * exportData method
+ *
+ * Get data for export. if no id is passed (admin_export) list all nodes. If an id is passed, retrieve the node and its
+ * children as well as the path to get there - to allow partial yet 'complete' exports/imports
+ *
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function exportData($id = null) {
+ if ($id) {
+ $this->id = $id;
+ $lft = $this->field('lft');
+ $rght = $this->field('rght');
+ $conditions['OR'] = array(
+ array(
+ 'Node.lft >=' => $lft,
+ 'Node.rght <=' => $rght
+ ),
+ array(
+ 'Node.lft <' => $lft,
+ 'Node.rght >' => $rght
+ )
+ );
+ } else {
+ $conditions = array();
+ }
+ $fields = array(
+ 'Node.id',
+ 'Node.parent_id',
+ 'Node.depth',
+ 'Node.show_in_toc',
+ 'Revision.id',
+ 'Revision.lang',
+ 'Revision.title',
+ 'Revision.content',
+ 'Revision.reason',
+ 'Revision.user_id',
+ 'Revision.modified',
+ );
+ $order = 'lft';
+ $recursive = 0;
+ return $this->find('all', compact('fields', 'order', 'recursive', 'conditions'));
+ }
+/**
+ * find function
+ *
+ * @param mixed $conditions
+ * @param array $fields
+ * @param mixed $order
+ * @param mixed $recursive
+ * @access public
+ * @return void
+ */
+ function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
+ if ($conditions != 'list' || !isset($this->hasOne['Revision'])) {
+ return parent::find($conditions, $fields, $order, $recursive);
+ }
+ $query = $fields;
+ $query['fields'] = array('id', 'sequence', 'Revision.title');
+ $query['recursive'] = 0;
+ $results = parent::find('all', $query);
+ if (!$results) {
+ return $results;
+ }
+ $keyPath = "{n}.{$this->alias}.id";
+ $valuePath = array("{0} {1}", "{n}.{$this->alias}.sequence", "{n}.Revision.title");
+ return Set::combine($results, $keyPath, $valuePath);
+ }
+/**
+ * generatetreelist function
+ *
+ * @param mixed $conditions
+ * @access public
+ * @return void
+ */
+ function generatetreelist($conditions = null) {
+ return $this->find('list', array('order' => 'lft', 'conditions' => $conditions));
+ }
+/**
+ * import method
+ *
+ * accepts an xml dump (generated from the nodes controller admin_export function) and syncronizes the current structure
+ * and contents with the file contents.
+ *
+ * @param mixed $xmlFile
+ * @return void
+ * @access public
+ */
+ function import($xml, $options = array(), $thisUser = false) {
+ Configure::write('debug', 2);
+ $schema = $this->schema('id');
+ $delete_missing = false;
+ $allow_moves = false;
+ $auto_approve = false;
+ extract($options);
+ $deletes = $adds = $moves = $mods = 0;
+ $errors = array();
+ uses('Xml');
+ $xml = new Xml($xml);
+ $xml = Set::reverse($xml);
+ $meta = Set::extract($xml, '/Contents/Meta');
+ $nodes = Set::extract($xml, '/Contents/Node');
+ $ids = Set::extract($nodes, '/Node/id');
+ set_time_limit(count($ids) * 2);
+ if ($delete_missing) {
+ $toDelete = $this->find('list', array('conditions' => array('NOT' => array('Node.id' => $ids))));
+ $deletes = count($toDelete);
+ foreach ($toDelete as $id => $_) {
+ $this->delete($id);
+ }
+ }
+ $first = $this->field('id', array('Node.parent_id' => null));
+ if (!$first) {
+ $allow_moves = true;
+ $auto_approve = true;
+ }
+ $importLang = isset($meta[0]['Meta']['lang'])?$meta[0]['Meta']['lang']:'en';
+ $this->setLanguage($importLang);
+ $message = array();
+ $counters = array();
+ $webroot = Router::url('/');
+ $i = 0;
+ foreach ($nodes as $i => $row) {
+ $parent = isset($row['Node']['parent_id'])?$row['Node']['parent_id']:null;
+ if ($i == 0) {
+ if ($first && $first != $row['Node']['id']) {
+ return array(false, array('This import file is incompatible with the current install'), array());
+ }
+ if (isset($row['Node']['position'])) {
+ $counters[$parent] = $row['Node']['position'];
+ }
+ }
+ if (!$parent && $i > 0) {
+ $errors[] = 'Duplicate root node detected Id: ' . $row['Node']['id'] . ', halting processing';
+ break;
+ }
+ $current = $this->find('first', array(
+ 'conditions' => array('Node.id' => $row['Node']['id']),
+ 'recursive' => 0,
+ 'fields' => array('Node.parent_id', 'Node.show_in_toc', 'Node.lft',
+ 'Revision.id', 'Revision.title', 'Revision.content')
+ ));
+ $showInToc = null;
+ if (isset($row['Node']['show_in_toc'])) {
+ $showInToc = $row['Node']['show_in_toc'];
+ } elseif (isset($row['Node']['ShowInToc'])) {
+ $showInToc = false;
+ }
+ if (!$current) {
+ /* This code shouldn't be necessary, and is ONLY necessary at all for uuid installs
+ * it adds the node to the tree as the last node to allow moving it around.
+ * without this code the uuid changes on save
+ */
+ if ($schema['length'] == 36) {
+ $max = $this->query('SELECT MAX(`rght`) as `rght` FROM `nodes`', false);
+ $max = $max[0][0]['rght'];
+ $lft = $max + 1;
+ $rght = $lft + 1;
+ $this->query('INSERT INTO `nodes` (`id`, `lft`, `rght`) VALUES (\'' . $row['Node']['id'] . "', $lft, $rght)");
+ }
+ /* This code shouldn't be necessary end */
+ $adds++;
+ }
+ if (!$current ||
+ ($current['Node']['parent_id'] != $parent && $allow_moves)) {
+ $this->id = $row['Node']['id'];
+ $toSave = array('id' => $row['Node']['id'], 'parent_id' => $parent);
+ if ($showInToc !== null) {
+ $toSave['show_in_toc'] = $showInToc;
+ }
+ $this->save($toSave);
+ if ($showInToc !== null && !$showInToc) {
+ die;
+ }
+ if ($current) {
+ $moves++;
+ }
+ } elseif ($showInToc !== null && $current['Node']['show_in_toc'] != $showInToc) {
+ $this->id = $row['Node']['id'];
+ $this->saveField('show_in_toc', $showInToc);
+ }
+ if (!isset($counters[$parent])) {
+ $counters[$parent] = 0;
+ } else {
+ $counters[$parent]++;
+ }
+ if ($current) {
+ $previousSiblings = $this->find('count', array('recursive' => -1,
+ 'conditions' => array('parent_id' => $parent, 'lft <' => $current['Node']['lft'])));
+ } else {
+ $previousSiblings = $this->find('count', array('recursive' => -1,
+ 'conditions' => array('parent_id' => $parent, 'id !=' => $row['Node']['id'])));
+ }
+ $difference = $previousSiblings - $counters[$parent];
+ if ($difference) {
+ if ($difference > 0) {
+ $this->moveUp($row['Node']['id'], $difference);
+ } else {
+ $this->moveDown($row['Node']['id'], $difference);
+ }
+ }
+ if (!isset($row['Node']['Revision']['id']) || !$row['Node']['Revision']['id']) {
+ if (isset($row['Node']['Revision']['lang']) && $row['Node']['Revision']['lang'] != $importLang) {
+ $message['language'] = 'Some sections in the import have not been translated, import English version to inherit original contents';
+ }
+ continue;
+ }
+ $title = $row['Node']['Revision']['title'];
+ $content = isset($row['Node']['Revision']['content'])?$row['Node']['Revision']['content']:'';
+ if ($webroot != '/') {
+ $content = preg_replace('@(href|src)=(\'|")/@', '\\1=\\2' . $webroot, $content);
+ }
+ $exists = $this->Revision->find('first', array('conditions' => compact('title', 'content')));
+ $different = false;
+ $compare = preg_replace("/[\r\n\t ]/", '', $current['Revision']['title']);
+ $import = preg_replace("/[\r\n\t ]/", '', $title);
+ if ($compare != $import) {
+ $different = true;
+ } else {
+ $compare = preg_replace("/[\r\n\t ]/", '', $current['Revision']['content']);
+ $import = preg_replace("/[\r\n\t ]/", '', $content);
+ if ($compare != $import) {
+ $different = true;
+ }
+ }
+ if (!$exists && $different) {
+ $reason = isset($row['Node']['Revision']['reason'])?$row['Node']['Revision']['reason']:'Revision Imported';
+ $user_id = isset($row['Node']['Revision']['user_id'])?$row['Node']['Revision']['user_id']:$thisUser;
+ $this->Revision->create();
+ $toSave = array(
+ 'node_id' => $row['Node']['id'],
+ 'under_node_id' => $parent,
+ 'status' => 'pending',
+ 'user_id' => $user_id,
+ 'title' => $title,
+ 'content' => $content,
+ 'reason' => $reason
+ );
+ if (!$this->Revision->save($toSave)) {
+ $errors[] = 'Could not save revision ' . $title;
+ }
+ if ($auto_approve || !$current) {
+ $this->Revision->publish($this->Revision->id, $reason . ' (auto approved)');
+ }
+ if ($current) {
+ $mods++;
+ }
+ } elseif ($exists && $auto_approve) {
+ if ($exists['Revision']['status'] != 'current') {
+ $this->Revision->create();
+ $reason = isset($exists['Revision']['reason'])?$exists['Revision']['reason']:'Revision Imported';
+ $this->Revision->publish($exists['Revision']['id'], $reason . ' (auto approved)');
+ $mods++;
+ }
+ }
+ }
+ $didAnything = $deletes + $adds + $moves + $mods;
+ $message[] = 'File imported, ' . $i + 1 . ' nodes/revisions processed (of ' . count($ids) . ' present in the input file)';
+ if ($errors) {
+ $messageM = count($errors) . ' errors encountered! ';
+ $messageM .= implode(', ', $errors);
+ $message[] = $messageM;
+ }
+ if (!$didAnything) {
+ $message[] = 'No changes detected';
+ } else {
+ if ($deletes) {
+ $message[] = $deletes . ' nodes deleted: (' . implode(', ', $toDelete) . ')';
+ }
+ if ($adds) {
+ $messageM = $adds . ' new nodes & revisions created';
+ if ($auto_approve) {
+ $messageM .= ' (all automatically approved)';
+ }
+ $message[] = $messageM;
+ }
+ if ($moves) {
+ $message[] = $moves . ' nodes moved';
+ }
+ if ($mods) {
+ $messageM = $mods . ' edits imported';
+ if ($auto_approve) {
+ $messageM .= ' (automatically approved)';
+ }
+ $message[] = $messageM;
+ }
+ $this->reset();
+ }
+ return array(true, $message, compact('adds', 'deletes', 'moves', 'mods', 'errors'));
+ }
+/**
+ * initialize method
+ *
+ * If the database is empty, populate it with some sample content
+ * Debug messages are supressed during execution unless $debug is true
+ *
+ * @param int $books
+ * @param int $sections
+ * @param bool $debug
+ * @return void
+ * @access public
+ */
+ function initialize($collections = 2, $books = 2, $sections = 2, $debug = false) {
+ if (!$debug) {
+ $debug = Configure::read();
+ Configure::write('debug', 0);
+ }
+ if ($this->find('count') || $this->Revision->find('count')) {
+ return false;
+ }
+ $this->create();
+ $this->save(array('parent_id' => null));
+ $id = $this->id;
+ $toSave = array(
+ 'node_id' => $id,
+ 'title' => 'Your Collections',
+ 'content' => 'Edit the collection index to change this text',
+ 'status' => 'current',
+ 'lang' => 'en');
+ $this->Revision->create();
+ $this->Revision->save($toSave);
+ for ($i=1; $i<=$collections; $i++) {
+ $this->__initCollection($i, $books, $sections, $id);
+ }
+ $this->reset();
+ if ($debug) {
+ Configure::write('debug', $debug);
+ }
+ }
+/**
+ * initCollection method
+ *
+ * @param mixed $i
+ * @param mixed $books
+ * @param mixed $sections
+ * @param mixed $id
+ * @return void
+ * @access private
+ */
+ function __initCollection($i, $books, $sections, $id) {
+ $toSave = array('status' => 'current', 'lang' => 'en', 'content' => 'a collection of books');
+ $this->create();
+ $this->save(array('parent_id' => $id));
+ $id = $this->id;
+ $this->Revision->create();
+ $this->Revision->save(am($toSave, array('node_id' => $id, 'title' => 'Collection ' . $i)));
+ for ($i=1; $i<=$books; $i++) {
+ $this->__initBook($i, $sections, $id);
+ }
+ }
+/**
+ * initBook method
+ *
+ * @param mixed $i
+ * @param mixed $sections
+ * @param mixed $id
+ * @return void
+ * @access private
+ */
+ function __initBook($i, $sections, $id) {
+ $toSave = array('status' => 'current', 'lang' => 'en', 'content' => 'a book about... ' . $i);
+ $this->create();
+ $this->save(array('parent_id' => $id));
+ $id = $this->id;
+ $this->Revision->create();
+ $this->Revision->save(am($toSave, array('node_id' => $id, 'title' => 'Book ' . $i)));
+ for ($i=1; $i<=$sections; $i++) {
+ $sid = $this->__initSection($i, $id);
+ for ($j=1; $j<=$sections; $j++) {
+ $ssid = $this->__initSection($j, $sid);
+ for ($k=1; $k<=$sections; $k++) {
+ $this->__initSection($k, $ssid);
+ }
+ }
+ }
+ }
+/**
+ * initSection method
+ *
+ * @param mixed $i
+ * @param mixed $id
+ * @return void
+ * @access private
+ */
+ function __initSection($i, $id) {
+ $this->create();
+ $this->save(array('parent_id' => $id));
+ $id = $this->id;
+ $this->Revision->create();
+ $toSave = array('status' => 'current', 'lang' => 'en', 'content' => 'Section ' . $id . ' content');
+ $this->Revision->save(am($toSave, array('node_id' => $id, 'title' => 'Section id ' . $id)));
+ return $this->Revision->id;
+ }
+/**
+ * merge method
+ *
+ * @param mixed $id
+ * @param mixed $mergeId
+ * @return void
+ * @access public
+ */
+ function merge($id, $mergeId) {
+ $recursive = -1;
+ $conditions['Revision.node_id'] = $mergeId;
+ $conditions['Revision.status'] = 'current';
+ $fields = array('lang', 'title');
+ $titles = $this->Revision->find('list', compact('recursive', 'conditions', 'fields', 'order'));
+
+ $conditions['Revision.node_id'] = $id;
+ $fields = array('lang', 'content');
+ $contents = $this->Revision->find('list', compact('recursive', 'conditions', 'fields', 'order'));
+ $fields = array('lang', 'title');
+ $oldTitles = $this->Revision->find('list', compact('recursive', 'conditions', 'fields', 'order'));
+ $toSave = array();
+ foreach ($contents as $lang => $content) {
+ $title = isset($titles[$lang])?$titles[$lang]:$titles['en'];
+ $toSave[$lang] = array(
+ 'node_id' => $mergeId,
+ 'title' => $title,
+ 'content' => $content,
+ 'lang' => $lang,
+ 'reason' => 'Merging "' . $oldTitles['en'] . '" content into "' . $title . '"',
+ 'user_id' => $this->currentUserId
+ );
+ if ($lang != 'en') {
+ $toSave[$lang]['flags'] = 'englishChanged';
+ }
+ }
+ foreach ($titles as $lang => $title) {
+ if (isset($toSave[$lang])) {
+ continue;
+ }
+ $toSave[$lang] = array(
+ 'node_id' => $mergeId,
+ 'title' => $title,
+ 'content' => $contents['en'],
+ 'lang' => $lang,
+ 'reason' => 'Merging "' . $oldTitles['en'] . '" content into "' . $title . '"',
+ 'user_id' => $this->currentUserId,
+ 'flags' => 'englishChanged'
+ );
+ }
+ $this->Revision->recursive = -1;
+ $this->Comment->updateAll(array('Comment.node_id' => "'$mergeId'"), array('Comment.node_id' => $id));
+ $this->Revision->updateAll(array('Revision.node_id' => "'$mergeId'"), array('Revision.node_id' => $id));
+ $this->Revision->updateAll(
+ array('Revision.status' => '"previous"'),
+ array('Revision.status' => 'current', 'Revision.node_id' => $id)
+ );
+ $parentId = $this->field('parent_id', array('id' => $id));
+ $this->removeFromTree($id);
+ $this->reset($parentId);
+ foreach ($toSave as $revision) {
+ $this->Revision->create();
+ $this->Revision->save($revision);
+ $this->Revision->publish($this->Revision->id, $revision['reason']);
+ }
+ return true;
+ }
+/**
+ * moveUp method
+ *
+ * After calling the tree behavior method, reset the sequences
+ *
+ * @param mixed $id
+ * @param mixed $steps
+ * @param bool $auto
+ * @return void
+ * @access public
+ */
+ function moveUp($id = null, $steps = null, $auto = true) {
+ if ($this->Behaviors->Tree->moveUp($this, $id, $steps) && $auto) {
+ $this->resetSequences($this->field('parent_id'));
+ }
+ return;
+ }
+/**
+ * moveDown method
+ *
+ * After calling the tree behavior method, reset the sequences
+ *
+ * @param mixed $id
+ * @param mixed $steps
+ * @param bool $auto
+ * @return void
+ * @access public
+ */
+ function moveDown($id = null, $steps = null, $auto = true) {
+ if ($this->Behaviors->Tree->moveDown($this, $id, $steps) && $auto) {
+ $this->resetSequences($this->field('parent_id'));
+ }
+ return;
+ }
+
+/**
+ * reset function
+ *
+ * @param mixed $parentId
+ * @access public
+ * @return void
+ */
+ function reset($parentId = null) {
+ $this->resetDepths($parentId);
+ $this->resetSequences($parentId);
+ }
+/**
+ * resetDepths function
+ *
+ * @param mixed $parentId
+ * @access public
+ * @return void
+ */
+ function resetDepths($parentId = null) {
+ if ($parentId) {
+ $conditions['Node.lft >'] = $this->field('lft', array('id' => $parentId));
+ $conditions['Node.rght <'] = $this->field('rght', array('id' => $parentId));
+ } else {
+ $conditions = array();
+ }
+ $nodes = $this->find('list', compact('conditions'));
+ foreach ($nodes as $nodeId => $node) {
+ $this->id = $nodeId;
+ $parent = $this->getPath($nodeId, array('id'));
+ $this->saveField('depth', count($parent) - 1);
+ }
+ return true;
+ }
+/**
+ * resetSequences function
+ *
+ * @param mixed $parentId
+ * @param string $prefix
+ * @param bool $start
+ * @access public
+ * @return void
+ */
+ function resetSequences($parentId = null, $prefix = '', $start = true) {
+ if ($prefix == '' && $start == true) {
+ $this->id = $parentId;
+ $depth = $this->field('depth');
+ $prefix = $this->field('sequence');
+ }
+ $this->recursive = -1;
+ $nodes = $this->findAllByParent_id($parentId, null, 'lft ASC');
+ $prefix = $prefix?$prefix.'.':$prefix;
+ $index = $parentId?1:'';
+ if ($nodes) {
+ foreach ($nodes as $node) {
+ $node = $node['Node'];
+ if ($node['depth'] > 2) {
+ if ($node['show_in_toc']) {
+ $this->create();
+ $this->id = $node['id'];
+ $this->saveField('sequence', $prefix.$index);
+ $this->resetSequences($node['id'], $prefix.$index, false);
+ $index++;
+ } else {
+ $this->updateAll(array('sequence' => null, 'show_in_toc' => 0), array('lft >=' => $node['lft'], 'rght <=' => $node['rght']));
+ }
+ } else {
+ $this->resetSequences($node['id'], '', false);
+ }
+ }
+ }
+ return true;
+ }
+/**
+ * setLanguage function
+ *
+ * @param string $lang
+ * @access public
+ * @return void
+ */
+ function setLanguage($lang = 'en') {
+ $bind['hasOne']['Revision']['conditions']['Revision.lang'] = $lang;
+ $bind['hasOne']['Revision']['conditions']['Revision.status'] = 'current';
+ $bind['hasMany']['Comment']['conditions']['Comment.lang'] = $lang;
+ $bind['hasMany']['Comment']['conditions']['Comment.published'] = true;
+ $this->language = $lang;
+ $this->bindModel($bind, false);
+ }
+/**
+ * getId function
+ *
+ * @param mixed $id
+ * @param mixed $depth
+ * @param array $fields
+ * @access protected
+ * @return void
+ */
+ function _getId($id, $depth = null, $fields = array('id')) {
+ $conditions = array();
+ if ($depth) {
+ $conditions['Node.depth'] = $depth;
+ }
+ if (is_numeric($id)) {
+ $conditions['Revision.node_id'] = $id;
+ } else {
+ $conditions['Revision.slug'] = $id;
+ }
+ $result = $this->find($conditions, array ('id'), null, 0);
+ if ($result['Node']['id']) {
+ if ($fields == array('id')) {
+ return $result['Node']['id'];
+ }
+ return $result['Node'];
+ }
+ return false;
+ }
+/**
+ * getNode function
+ *
+ * @param mixed $depth
+ * @param mixed $id
+ * @param array $fields
+ * @access protected
+ * @return void
+ */
+ function _getNode($depth, $id, $fields = array('id')) {
+ $cId = $this->_getId($id, $depth);
+ if ($cId && $fields == array('id')) {
+ return $cId;
+ } else {
+ $id = $this->_getId($id);
+ }
+ $data = $this->find(array('Node.id' => $id), array('lft', 'rght'), null, 0);
+ $conditions['Node.lft <'] = $data['Node']['lft'];
+ $conditions['Node.rght >'] = $data['Node']['rght'];
+ $conditions['Node.depth'] = $depth;
+ $result = $this->find($conditions, $fields, null, 0);
+ if (isset($result['Node'])) {
+ if (count($fields) == 1) {
+ return $result['Node'][$fields[0]];
+ }
+ return $result['Node'];
+ }
+ return false;
+ }
+/**
+ * clearCache function
+ *
+ * @param mixed $type
+ * @access protected
+ * @return void
+ */
+ function _clearCache($type = null) {
+ clearCache(null, 'views');
+ clearCache(null, 'views', ''); // clear elements
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/revision.php b/models/revision.php
new file mode 100644
index 0000000..34e2dfb
--- /dev/null
+++ b/models/revision.php
@@ -0,0 +1,559 @@
+<?php
+/* SVN FILE: $Id: revision.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for revision.php
+ *
+ * Long description for revision.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.models
+ * @since 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Revision class
+ *
+ * @uses AppModel
+ * @package cookbook
+ * @subpackage cookbook.models
+ */
+class Revision extends AppModel {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Revision';
+/**
+ * displayField variable
+ *
+ * @var string
+ * @access public
+ */
+ var $displayField = 'title';
+/**
+ * order property
+ *
+ * @var string 'created DESC'
+ * @access public
+ */
+ var $order = 'Revision.created DESC';
+/**
+ * viewAllLevel variable
+ *
+ * @var int
+ * @access public
+ */
+ var $viewAllLevel = 3;
+/**
+ * belongsTo variable
+ *
+ * @var array
+ * @access public
+ */
+ var $belongsTo = array('User' => array('className' => 'Users.User'), 'Node');
+/**
+ * actsAs variable
+ *
+ * @var array
+ * @access public
+ */
+ var $actsAs = array (
+ 'Slugged' => array('length' => 150, 'overwrite' => true, 'unique' => false, 'mode' => 'id'),
+ 'Searchable.Searchable'
+ );
+/**
+ * validate variable
+ *
+ * @var array
+ * @access public
+ */
+ var $validate = array(
+ 'preview' => array('rule' => array('equalTo', '0')),
+ 'title' => array(
+ //array('rule' => 'noHtml', 'message' => 'No Html in section titles'),
+ 'missing' => '/[^\\s]/'
+ ),
+ 'content' => array(
+ 'missing' => array('rule' => '/[^\\s]/', 'last' => true),
+ array('rule' => 'noHeaders', 'message' => 'Please create subsections instead of headers in content'),
+ ),
+ );
+/**
+ * itsAnAdd property
+ *
+ * @var bool false
+ * @access public
+ */
+ var $itsAnAdd = false;
+/**
+ * afterSave method
+ *
+ * @param mixed $created
+ * @return void
+ * @access public
+ */
+ function afterSave($created) {
+ if (!$created) {
+ return;
+ }
+ $comment = $this->field('reason');
+ if (!$comment) {
+ $comment = 'Edit Submitted';
+ }
+ $change['status_from'] = 'new';
+ $change['status_to'] = 'pending';
+ $change['revision_id'] = $this->id;
+ $change['author_id'] = $this->field('user_id');
+ $change['comment'] = $comment;
+ $change['user_id'] = $this->field('user_id');
+ $Change = ClassRegistry::init('Change');
+ $Change->create();
+ $Change->save($change);
+ }
+/**
+ * beforeSave function
+ *
+ * @access public
+ * @return void
+ */
+ function beforeSave() {
+ if (
+ (array_key_exists('lang', $this->data['Revision']) && !$this->data['Revision']['lang']) ||
+ (!$this->id && !array_key_exists('lang', $this->data['Revision']))
+ ) {
+ $this->data['Revision']['lang'] = $this->Node->language;
+ }
+ return true;
+ }
+/**
+ * beforeValidate method
+ *
+ * @return void
+ * @access public
+ */
+ function beforeValidate() {
+ if (isset($this->data['Revision']['content'])) {
+ $contents = $this->data['Revision']['content'];
+ $firstTag = strpos($contents, '<');
+ if ($firstTag > 10 || $firstTag === false) {
+ preg_match_all('@<\?php([\\s\\S]*?)\?>@i', $contents, $codeSegments, PREG_PATTERN_ORDER);
+ if ($codeSegments[1]) {
+ foreach ($codeSegments[1] as $id => $text) {
+ $contents = str_replace($text, '{{{segment' . $id . '}}}', $contents);
+ }
+ }
+ $contents = str_replace('<', '<', $contents);
+ $contents = str_replace('>', '>', $contents);
+ $contents = explode("\r\n", $contents);
+ foreach ($contents as $i => $para) {
+ $para = preg_replace("/^[\r\t\n ]*|[\r\t\n ]*$/", '', $para);
+ $para = trim($para);
+ if (!$para) {
+ unset ($contents[$i]);
+ }
+ }
+ if ($contents) {
+ $this->data['Revision']['content'] = $contents = '<p>' . implode($contents, "</p>\n<p>") . '</p>';
+ }
+ if ($codeSegments[1]) {
+ foreach ($codeSegments[1] as $id => $text) {
+ $contents = str_replace('{{{segment' . $id . '}}}', $text, $contents);
+ }
+ }
+ }
+ if ($contents) {
+ $contents = preg_replace('/<p>\W*Note:?\s*/', '<p class="note">', $contents);
+ $contents = preg_replace('/<p>\W*Figure:?\s*/', '<p class="caption">Figure: ', $contents);
+ $contents = preg_replace('/<p>\W*Table:?\s*/', '<p class="caption">Table: ', $contents);
+ $contents = preg_replace('/<p><\?php/', '<pre><?php', $contents);
+ $contents = preg_replace('/\?><\/p>/', '?></pre>', $contents);
+ preg_match_all('@<pre[^>]*>([\\s\\S]*?)</pre>@i', $contents, $result, PREG_PATTERN_ORDER);
+ if (!empty($result['0'])) {
+ $count = count($result['0']);
+ for($i = 0; $i < $count; $i++) {
+ $replaced = str_replace('<', '<', $result['1'][$i]); // ensure escaping
+ $replaced = str_replace('>', '>', $replaced); // ensure escaping
+ $contents = str_replace($result[1][$i], $replaced, $contents);
+ }
+ }
+ $this->data['Revision']['content'] = $contents;
+ }
+ }
+ return true;
+ }
+/**
+ * pending function
+ *
+ * @param mixed $nodeId
+ * @access public
+ * @return void
+ */
+ function pending($nodeId = null) {
+ $currentRevision = $this->field('id');
+ $conditions = array('Revision.status' => 'pending', 'Revision.node_id' => $nodeId, 'Revision.lang' => $this->Node->language);
+ $fields = array('id');
+ $recursive = -1;
+ $return = $this->find('all', compact('conditions', 'fields', 'recursive'));
+ $return = Set::extract($return, '{n}.Revision.id', '{n}.Revision.id');
+ return $return;
+ }
+/**
+ * publish function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function publish($id = null, $comment = 'Publishing this change', $flagTranslations = false) {
+ if ($id) {
+ $this->id = $id;
+ } else {
+ $id = $this->id;
+ }
+ $nodeId = $this->Node->addToTree($id, true);
+ $this->Node->id = $nodeId;
+ $this->Node->saveField('status', '1');
+
+ $conditions = array('Revision.node_id' => $nodeId, 'Revision.lang' => $this->field('lang'), 'Revision.status' => 'current', 'NOT' => array('Revision.id' => $id));
+ $revisions = $this->find('list', array('conditions' => $conditions));
+ foreach($revisions as $revision => $_title){
+ $update = array(
+ 'id' => $revision,
+ 'status' => 'previous'
+ );
+ $this->create($update);
+ $this->save();
+ $this->delete_from_index($revision);
+ }
+ $this->id = $id;
+ $change['status_from'] = $this->field('status');
+ $change['status_to'] = 'published';
+ $change['revision_id'] = $this->id;
+ $change['author_id'] = $this->field('user_id');
+ $change['comment'] = $comment;
+ $change['user_id'] = $this->currentUserId;
+ $return = $this->saveField('status', 'current');
+ $data = $this->read();
+ if ($data['Revision']['lang'] == 'en' && $flagTranslations) {
+ $conditions = array();
+ $conditions['Revision.node_id'] = $data['Revision']['node_id'];
+ $conditions['Revision.status'] = array('current', 'pending');
+ $conditions['NOT']['Revision.lang'] = 'en';
+ $hasTranslations = $this->find('count', compact('conditions'));
+ if ($hasTranslations) {
+ $revisions = $this->find('list', compact('conditions'));
+ foreach ($revisions as $revision => $title) {
+ $this->flag($revision, 'englishChanged');
+ }
+ }
+ } else {
+ $isSignificant = false;
+ }
+
+ $Change = ClassRegistry::init('Change');
+ $Change->create();
+ $Change->save($change);
+ return $return;
+ }
+/**
+ * reject method
+ *
+ * @param mixed $id
+ * @return void
+ * @access public
+ */
+ function reject($id, $comment = 'Change not accepted') {
+ $change['status_from'] = $this->field('status');
+ $change['status_to'] = 'rejected';
+ $change['revision_id'] = $this->id;
+ $change['author_id'] = $this->field('user_id');
+ $change['comment'] = $comment;
+ $change['user_id'] = $this->currentUserId;
+ $return = $this->saveField('status', 'reject');
+ if ($return) {
+ $Change = ClassRegistry::init('Change');
+ $Change->save($change);
+ }
+ return $return;
+ }
+/**
+ * hide function
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function hide($id) {
+ $this->id = $id;
+ $this->saveField('status', 'pending');
+
+ $conditions = array('Revision.node_id' => $id, 'Revision.lang' => $this->field('lang'), 'Revision.status' => 'pending', 'NOT' => array('Revision.id' => $id));
+ $fields = array('id');
+ $order = 'Revision.modified DESC';
+ $previous = $this->find('first', compact('conditions', 'fields', 'order'));
+ if ($previous) {
+ $this->id = $previous['Revision']['id'];
+ $this->saveField('status', 'current');
+ } else {
+ $nodeId = $this->field('node_id');
+ $this->Node->id = $nodeId;
+ $this->Node->saveField('status', 0);
+ }
+ $this->delete_from_index($id);
+ return true;
+ }
+/**
+ * flag method
+ *
+ * @param mixed $id
+ * @param string $flag
+ * @return void
+ * @access public
+ */
+ function flag($id, $flag = '') {
+ $flags = $this->field('flags', $id);
+ if ($flags) {
+ $flags = explode(',', $flags);
+ } else {
+ $flags = array();
+ }
+ if (!in_array($flag, $flags)) {
+ $flags[] = $flag;
+ $this->id = $id;
+ $this->saveField('flags', implode(',', $flags));
+ $this->create();
+ }
+ }
+/**
+ * reset function
+ *
+ * For each node and each language, if there are no current or previous revisions - skip, nothing to do
+ * If there is a current revision - skip, nothing to do
+ * Otherwise, make the most recent approved (previous) revision the current content
+ *
+ * @access public
+ * @return void
+ */
+ function reset() {
+ $this->recursive = -1;
+ $nodes = $this->find('list', array(
+ 'fields' => array('node_id', 'node_id'),
+ 'conditions' => array('node_id >' => 0)
+ ));
+ $order = 'Revision.id DESC';
+ $fields = array('id');
+ foreach ($nodes as $id) {
+ $langs = $this->find('list', array(
+ 'fields' => array('lang', 'lang'),
+ 'conditions' => array('node_id' => $id)
+ ));
+ foreach ($langs as $lang) {
+ $conditions = array(
+ 'node_id' => $id,
+ 'lang' => $lang,
+ 'status' => array('current', 'previous')
+ );
+ if (!$this->find('count', compact('conditions'))) {
+ continue;
+ }
+ $conditions['Revision.status'] = 'current';
+ if ($this->find('count', compact('conditions'))) {
+ continue;
+ }
+ $conditions['Revision.status'] = 'previous';
+ $last = $this->find('first', compact('conditions', 'order', 'fields'));
+ $this->updateAll(array('status' => '"current"'), array('Revision.id' => $last['Revision']['id']));
+ }
+ }
+ }
+/**
+ * resetSlugs function
+ *
+ * Warning - This method is intensive if run on the whole table - if called with no params before starting, the
+ * searchable behavior is disabled if it is attached; Then for each live revision, the title is resaved to trigger
+ * the slug behavior to update the slug. By comparing before and after, and only if $updateSearchIndex is set to true
+ * (the default), then search index is then updated with a high time limit. In this way if updating the index cause the
+ * function to time out the slugs should have at least already been updated.
+ *
+ * @param mixed $id
+ * @param bool $updateSearchIndex
+ * @access public
+ * @return array the revisions which have been updated
+ */
+ function resetSlugs($id = null, $updateSearchIndex = true) {
+ if ($this->Behaviors->attached('Searchable') && !$id) {
+ $this->Behaviors->disable('Searchable');
+ }
+ $order = 'node_id';
+ if ($id) {
+ $conditions['Revision.id'] = $id;
+ } else {
+ $conditions['Revision.status'] = 'current';
+ }
+ $data = $this->find('list', compact('order', 'conditions'));
+ if (count($data) > 3000) {
+ set_time_limit (count($data) / 100);
+ }
+ $fields = array('slug');
+ $slugsBefore = $this->find('list', compact('order', 'conditions', 'fields'));
+ foreach ($data as $id => $title) {
+ $this->id = $id;
+ $this->save(array('title' => $title));
+ }
+ $slugsAfter = $this->find('list', compact('order', 'conditions', 'fields'));
+ $diff = array_diff($slugsAfter, $slugsBefore);
+ $updatedRevisions = array_keys($diff);
+ if ($this->Behaviors->attached('Searchable')) {
+ $this->Behaviors->enable('Searchable');
+ if ($updatedRevisions && $updateSearchIndex) {
+ if (count($updatedRevisions) > 30) {
+ set_time_limit (count($updatedRevisions));
+ }
+ $counter = 1;
+ define('NOW', getMicrotime());
+ foreach ($updatedRevisions as $id) {
+ debug ($counter++ . ' :' . round(getMicrotime() - NOW, 4));
+ $this->add_to_index($id);
+ }
+ }
+ }
+ return $updatedRevisions;
+ }
+/**
+ * checkWellFormed function
+ *
+ * @TODO implement
+ * @param mixed $content
+ * @access protected
+ * @return void
+ */
+ function checkWellFormed($content) {
+ // To be called as part of validation routine
+ }
+/**
+ * getId function
+ *
+ * @param mixed $id
+ * @param mixed $depth
+ * @param array $fields
+ * @access protected
+ * @return void
+ */
+ function _getId($id, $depth = null, $fields = array('id')) {
+ /*
+ $conditions['Node.sequence'] = $id;
+ $result = $this->find($conditions, array ('id', 'slug'), null, -1);
+ */
+ $conditions = array();
+ if ($depth) {
+ $conditions['Node.depth'] = $depth;
+ }
+ if (is_numeric($id)) {
+ $conditions['Revision.node_id'] = $id;
+ } else {
+ $conditions['Revision.slug'] = $id;
+ }
+ $result = $this->find($conditions, array ('id'), null, 0);
+ if ($result['Node']['id']) {
+ if ($fields == array('id')) {
+ return $result['Node']['id'];
+ }
+ return $result['Node'];
+ }
+ return false;
+ }
+/**
+ * clearCache function
+ *
+ * @param mixed $type
+ * @access protected
+ * @return void
+ */
+ function _clearCache($type = null) {
+ if ($this->field('status') == 'current') {
+ clearCache(null, 'views');
+ clearCache(null, 'views', ''); // clear elements
+ }
+ }
+
+/**
+ * find_index function
+ *
+ * this is the find funtion used by the searchable behavior for the index generation
+ * Optimized to /NOT/ look for the book and collection for each row when building the index
+ * This optimization logic only works if results are returned ordered by Node.lft
+ *
+ * @param string $type
+ * @param array $options
+ * @access public
+ * @return void
+ */
+ function find_index($type = 'all', $options = array()){
+ $this->unbindModel(array('belongsTo' => array('User')));
+ $params = Set::merge(array('conditions' => array('Revision.status' => 'current', 'Node.id >' => 0), 'order' => 'Node.lft ASC', 'recursive' => 0), $options);
+ $params['order'] = 'Node.lft';
+ $results = $this->find('all', $params);
+ $collection = null;
+ $book = null;
+ foreach($results as &$result){
+ if ($result['Node']['depth'] == 1) {
+ $collection = $result['Node']['id'];
+ $book = null;
+ } elseif ($result['Node']['depth'] == 2) {
+ $book = $result['Node']['id'];
+ }
+ $result['Revision']['collection'] = $collection;
+ $result['Revision']['book'] = $book;
+ if (!$collection) {
+ if ($result['Node']['depth'] > 0) {
+ $result['Revision']['collection'] =
+ $this->Node->collection($result['Revision']['node_id']);
+ }
+ }
+ if (!$book) {
+ if ($result['Node']['depth'] > 1) {
+ $result['Revision']['collection'] =
+ $this->Node->collection($result['Revision']['node_id']);
+ }
+ }
+ }
+ if($type =='first' && count($results)){
+ $results = $results[0];
+ }
+ return $results;
+ }
+/**
+ * noHeaders method
+ *
+ * @param mixed $vals
+ * @return void
+ * @access public
+ */
+ function noHeaders($vals) {
+ if ($this->itsAnAdd == true) {
+ return true;
+ }
+ foreach ($vals as $val) {
+ if (strpos($val, '<h') !== false) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+?>
\ No newline at end of file
diff --git a/plugins/searchable/models/behaviors/searchable.php b/plugins/searchable/models/behaviors/searchable.php
new file mode 100644
index 0000000..054ac5e
--- /dev/null
+++ b/plugins/searchable/models/behaviors/searchable.php
@@ -0,0 +1,417 @@
+<?php
+/* SVN FILE: $Id: searchable.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for searchable.php
+ *
+ * Long description for searchable.php
+ *
+ * PHP 5
+ *
+ * Copyright (c) 2008, Marcin Domanski
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Marcin Domanski
+ * @link www.kabturek.info
+ * @package
+ * @subpackage projects.cookbook.models.behaviors
+ * @since v 0.1
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * SearchableBehavior class
+ *
+ * @uses ModelBehavior
+ * @package
+ * @subpackage searchable.models.behaviors.searchable
+ */
+class SearchableBehavior extends ModelBehavior {
+/**
+ * config variable
+ *
+ * @var mixed
+ * @access public
+ */
+ var $config = null;
+/**
+ * Index variable
+ *
+ * @var mixed
+ * @access public
+ */
+ var $Index = null;
+/**
+ * index_file variable
+ *
+ * @var mixed
+ * @access public
+ */
+ var $index_file;
+/**
+ * terms variable
+ *
+ * @var array
+ * @access public
+ */
+ var $terms = array();
+/**
+ * hits_count variable
+ *
+ * @var int
+ * @access public
+ */
+ var $hits_count = 0;
+/**
+ * setup function
+ *
+ * @param mixed $Model
+ * @param array $settings
+ * @access public
+ * @return void
+ */
+ function setup(&$Model, $settings = array()) {
+ if (!function_exists('iconv')) {
+ function iconv($inCharset, $outCharset, $string) {
+ return $string;
+ }
+ function iconv_strlen($string) {
+ //return strlen(utf8_decode($string));
+ return mb_strlen($string, 'UTF-8');
+ }
+ function iconv_substr($string, $offset, $length) {
+ return mb_substr($string, $offset, $length, 'UTF-8');
+ }
+ }
+ if(function_exists('ini_set')){
+ ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . APP. 'vendors');
+ }
+ // import the Zend_Search_Lucene library
+ App::import('Vendor', 'Zend/Search/Lucene', array('file' => 'Zend/Search/Lucene.php'));
+ // load app/config/lucene.php
+ config('lucene');
+ if(class_exists('Lucene_Config')) {
+ $this->config = &new Lucene_Config;
+ }
+ $config = 'default';
+ if(!empty($settings['config'])){
+ $config = $settings['config'];
+ }
+ $this->settings = $this->config->{$config};
+ $this->index_file = TMP.$this->settings['index_file'];
+ unset($this->settings['index_file']);
+ }
+/**
+ * afterSave callback
+ *
+ * Check whether the current rows data should be in the index, add if it should, call add_to_index
+ *
+ * @param mixed $Model
+ * @param mixed $created
+ * @access public
+ * @return void
+ */
+ function afterSave(&$Model, $created){
+ if (!$created) {
+ $this->delete_from_index($Model, $Model->id);
+ }
+ if (isset($this->settings[$Model->alias]['find_options']['conditions'])) {
+ $conditions = $this->settings[$Model->alias]['find_options']['conditions'];
+ $conditions[$Model->alias . '.' . $Model->primaryKey] = $Model->id;
+ if (!$Model->find('count', compact('conditions'))) {
+ return;
+ }
+ }
+ $this->add_to_index($Model, $Model->id);
+ }
+/**
+ * afterDelete callback
+ *
+ * @param mixed $Model
+ * @access public
+ * @return void
+ */
+ function afterDelete(&$Model){
+ $this->delete_from_index($Model, $Model->id);
+ }
+/**
+ * open_index function
+ * opens the index for manipulation/searching
+ *
+ * @access public
+ * @return void
+ */
+ function open_index(&$Model) {
+ if (empty($this->Index)) {
+ try {
+ $this->Index = Zend_Search_Lucene::open($this->index_file);
+ return $this->Index;
+ } catch (Zend_Search_Lucene_Exception $e) {
+ $this->log("Unable to open the index: ". $e->getMessage(), 'searchable');
+ $this->create_index($Model);
+ }
+ } else {
+ return $this->Index;
+ }
+ return false;
+ }
+/**
+ * create_index method
+ *
+ * @param mixed $Model
+ * @return void
+ * @access public
+ */
+ function create_index($Model){
+ try {
+ $this->Index = Zend_Search_Lucene::create($this->index_file);
+ return $this->Index;
+ } catch(Zend_Search_Lucene_Exception $e) {
+ $this->log("Unable to create the index: ". $e->getMessage(), 'searchable');
+ return false;
+ }
+ }
+
+/**
+ * search function
+ * searches the index
+ *
+ * @param mixed $Model
+ * @param mixed $query
+ * @param int $limit
+ * @param int $page
+ * @access public
+ * @return void
+ */
+ function search(&$Model, $query, $limit = 20, $page = 1) {
+ // open the index
+ if(!$this->open_index($Model)){
+ return false;
+ }
+ try {
+ // set the default encoding
+ Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8');
+ // zend search results limiting (We will use the LimitIterator)
+ // we can use it for some maximum value like 1000 if its likely that there could be more results
+ Zend_Search_Lucene::setResultSetLimit(1000);
+ // set the parser default operator to AND
+ Zend_Search_Lucene_Search_QueryParser::setDefaultOperator(Zend_Search_Lucene_Search_QueryParser::B_AND);
+ // utf-8 num analyzer
+ Zend_Search_Lucene_Analysis_Analyzer::setDefault(
+ new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());
+ // parse the query
+ $Query = Zend_Search_Lucene_Search_QueryParser::parse($query);
+ $Terms = $Query->getQueryTerms();
+ foreach($Terms as $Term){
+ $this->terms[] = $Term->text;
+ }
+ // do the search
+ $Hits = new ArrayObject($this->Index->find($Query));
+ } catch (Zend_Search_Lucene_Exception $e) {
+ $this->log("Zend_Search_Lucene error: ". $e->getMessage(), 'searchable');
+ }
+ $this->hits_count = count($Hits);
+
+ if (!count($Hits)) {
+ return null;
+ }
+ $Hits = new LimitIterator($Hits->getIterator(), ($page - 1) * $limit, $limit);
+ $results = array();
+ foreach ($Hits as $Hit) {
+ $Document = $Hit->getDocument();
+ $fields = $Document->getFieldNames();
+ $result = array();
+ foreach($fields as $field){
+ $result['Result'][$field] = $Document->{$field};
+ }
+ $results[] = $result;
+ }
+ return $results;
+ }
+/**
+ * terms function
+ * return the search terms
+ *
+ * @access public
+ * @return void
+ */
+ function terms(){
+ return $this->terms;
+ }
+/**
+ * hits_count function
+ * return the number of results
+ *
+ * @access public
+ * @return void
+ */
+ function hits_count(){
+ return $this->hits_count;
+ }
+/**
+ * deletes an model row from the index
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function delete_from_index(&$Model, $id){
+ // open the index
+ if(!$this->open_index($Model)){
+ return false;
+ }
+ try{
+ $query = new Zend_Search_Lucene_Search_Query_MultiTerm();
+ $query->addTerm(new Zend_Search_Lucene_Index_Term($id, 'cake_id'), true);
+ $query->addTerm(new Zend_Search_Lucene_Index_Term($Model->alias, 'cake_model'), true);
+ $Hits = $this->Index->find($query);
+ foreach($Hits as $Hit){
+ $this->Index->delete($Hit->id);
+ if (Configure::read()) {
+ $this->log('deleted index id:'.$id, 'searchable');
+ }
+ if(count($Hits)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } catch (Zend_Search_Lucene_Exception $e) {
+ $this->log('Lucene exception:'. $e->getMessage(), 'searchable');
+ }
+
+ }
+/**
+ * add_to_index function
+ *
+ * deletes any exising index entries, and adds a document to the index
+ *
+ * @TODO Is this working properly
+ * @param mixed $Model
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ function add_to_index(&$Model, $id){
+ if(!empty($this->settings[$Model->alias])){
+ if(!$this->open_index($Model)){
+ return false;
+ }
+ try{
+ $query = new Zend_Search_Lucene_Search_Query_MultiTerm();
+ $query->addTerm(new Zend_Search_Lucene_Index_Term($id, 'cake_id'), true);
+ $query->addTerm(new Zend_Search_Lucene_Index_Term($Model->alias, 'cake_model'), true);
+ $Hits = $this->Index->find($query);
+ // TODO there are never any hits - why
+ if(count($Hits)){
+ $this->delete_from_index($Model, $id);
+ }
+
+ if(method_exists($Model,'find_index')){
+ $result = $Model->find_index('first', array('conditions' => array($Model->alias.'.id' => $id)));
+ } else {
+ $result = $Model->find('first',array('conditions' => array($Model->alias.'.id' => $id)));
+ }
+ if(!empty($result)){
+ $doc = new Zend_Search_Lucene_Document();
+
+ // add the model field
+ $doc->addField(Zend_Search_Lucene_Field::Keyword('cake_model', $Model->alias, 'utf-8'));
+ foreach($this->settings[$Model->alias]['fields'] as $field_name => $options){
+ if(!empty($options['prepare']) && function_exists($options['prepare'])){
+ $result[$Model->alias][$field_name] = call_user_func($options['prepare'], $result[$Model->alias][$field_name]);
+ }
+ $alias = !empty($options['alias']) ? $options['alias'] : $field_name;
+ $doc->addField(Zend_Search_Lucene_Field::$options['type']($alias, $result[$Model->alias][$field_name], 'utf-8'));
+ }
+ $this->Index->addDocument($doc);
+ $this->Index->commit();
+ if (Configure::read()) {
+ $Hits = $this->Index->find($query);
+ // TODO why isn't it possible to find what was just added
+ if(!count($Hits)){
+ debug ('Tried to add to the search index, no errors but couldnt find what was just added!');
+ }
+ $this->log('added to index id:'.$id, 'searchable');
+ }
+ }
+ } catch (Zend_Search_Lucene_Exception $e) {
+ $this->log('Lucene exception:'. $e->getMessage(), 'searchable');
+ }
+ }
+ }
+/**
+ * build_index method
+ *
+ * Rebuild the entire search index.
+ *
+ * $mergefactor - increase this value for faster indexing. Reduce this value to tax the server less
+ * $maxBufferDocs - increase this value for faster indexing. Reduce this value to tax the server less
+ *
+ * @param mixed $Model
+ * @param int $mergeFactor
+ * @param int $maxBufferDocs
+ * @return void
+ * @access public
+ */
+ function build_index($Model, $mergeFactor = 2000, $maxBufferDocs = 500){
+ if (Configure::read()) {
+ $this->log('Starting to build index.', 'searchable');
+ $start = getMicrotime();
+ }
+ if(!$this->create_index($Model)){
+ return false;
+ }
+ $this->Index->setMergeFactor($mergeFactor);
+ $this->Index->setMaxBufferedDocs($maxBufferDocs);
+ foreach($this->settings as $model => $model_options){
+ App::import('Model', $model);
+ $model = new $model();
+ if(empty($model_options['find_options'])) {
+ $model_options['find_options'] = array();
+ }
+
+ if(method_exists($model,'find_index')){
+ $results = $model->find_index('all', $model_options['find_options']);
+ } else {
+ $results = $model->find('all', $model_options['find_options']);
+ }
+ if (Configure::read()) {
+ $time = round(getMicrotime() - $start, 1);
+ $count = count($results);
+ $this->log('Found ' . $count . ' ' . $model->name . ' results ' . $time . 's', 'searchable');
+ }
+ foreach($results as $i => $result) {
+ if (Configure::read()) {
+ $time = round(getMicrotime() - $start, 1);
+ $this->log('Processing ' . $model->name . ' result ' . ($i + 1) . '/' . $count .
+ ' (id:' . $result[$model->alias][$model->primaryKey] . ', ' .
+ $result[$model->alias][$model->displayField] . ') ' . $time .
+ 's', 'searchable');
+ }
+ $doc = new Zend_Search_Lucene_Document();
+
+ // add the model field
+ $doc->addField(Zend_Search_Lucene_Field::Keyword('cake_model', $model->name, 'utf-8'));
+ foreach($model_options['fields'] as $field_name => $options){
+ if(!empty($options['prepare']) && function_exists($options['prepare'])){
+ $result[$model->name][$field_name] =
+ call_user_func($options['prepare'], $result[$model->name][$field_name]);
+ }
+ $alias = !empty($options['alias']) ? $options['alias'] : $field_name;
+ $doc->addField(Zend_Search_Lucene_Field::$options['type']($alias,
+ $result[$model->name][$field_name], 'utf-8'));
+ }
+ $this->Index->addDocument($doc);
+ }
+ if (Configure::read()) {
+ $time = round(getMicrotime() - $start, 1);
+ $this->log('Finished processing ' . Inflector::pluralize($model->name) . ' ' .$time . 's',
+ 'searchable');
+ }
+ }
+ $this->Index->commit();
+ }
+}
+?>
\ No newline at end of file
diff --git a/plugins/searchable/searchable_app_controller.php b/plugins/searchable/searchable_app_controller.php
new file mode 100644
index 0000000..24a265e
--- /dev/null
+++ b/plugins/searchable/searchable_app_controller.php
@@ -0,0 +1,5 @@
+<?php
+class SearchableAppController extends AppController {
+
+}
+?>
\ No newline at end of file
diff --git a/plugins/searchable/searchable_app_model.php b/plugins/searchable/searchable_app_model.php
new file mode 100644
index 0000000..15fa7aa
--- /dev/null
+++ b/plugins/searchable/searchable_app_model.php
@@ -0,0 +1,7 @@
+<?php
+
+class SearchableAppModel extends AppModel {
+
+}
+
+?>
\ No newline at end of file
diff --git a/plugins/searchable/vendors/shells/search.php b/plugins/searchable/vendors/shells/search.php
new file mode 100644
index 0000000..fe7cf86
--- /dev/null
+++ b/plugins/searchable/vendors/shells/search.php
@@ -0,0 +1,155 @@
+<?php
+class SearchShell extends Shell {
+
+// var $tasks = array('search', 'index');
+
+ function startup() {
+ if(function_exists('ini_set')){
+ ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . APP. 'vendors');
+ }
+ App::import('Vendor', 'Zend/Search/Lucene', array('file' => 'Zend/Search/Lucene.php'));
+ config('lucene');
+ if(class_exists('Lucene_Config')) {
+ $this->config = &new Lucene_Config;
+ }
+ $config = 'default';
+ if(!empty($this->params['config'])){
+ $config = $this->params['config'];
+ }
+ $this->settings = $this->config->{$config};
+ $this->index_file = $this->settings['index_file'];
+ unset($this->settings['index_file']);
+
+ }
+
+ function help() {
+ $this->out("The Search Shell gives the ability to build and search the index.");
+ $this->hr();
+ $this->out("Usage: cake schema <command> <arg1> <arg2>...");
+ $this->hr();
+ $this->out('Params:');
+ $this->out("\n\t-connection <config>\n\t\tset db config <config>. uses 'default' if none is specified");
+ $this->out('Commands:');
+ $this->out("\n\tsearch help\n\t\tshows this help message.");
+ $this->out("\n\tsearch build_index\n\t\tinitilize the index.");
+ $this->out("\n\tsearch query\n\t\tquery the index.");
+ $this->out("\n\tsearch optimize\n\t\toptimizes the index.");
+ $this->out("");
+ $this->stop();
+ }
+
+/**
+ * rebuild the index
+ *
+ * @access public
+ * @return void
+ */
+ function build_index(){
+ $index = $this->__open(true);
+ $index->setMergeFactor(2000);
+ $index->setMaxBufferedDocs(500);
+ $start = time();
+ foreach($this->settings as $model => $model_options){
+ App::import('Model', $model);
+ $model = new $model();
+ if(empty($model_options['find_options'])) {
+ $model_options['find_options'] = array();
+ }
+
+ if(method_exists($model,'find_index')){
+ $results = $model->find_index('all', $model_options['find_options']);
+ } else {
+ $results = $model->find('all', $model_options['find_options']);
+ }
+ $this->log($model->name.' find time: '.(time()-$start));
+ $start = time();
+
+ $count = count($results);
+ $i =1;
+ foreach($results as $result){
+ printf("%.1f", $i/$count * 100);
+ $this->out("");
+ $i++;
+
+ $this->out('Processing '.$model->name.' #'.$result[$model->name]['id']);
+ $doc = new Zend_Search_Lucene_Document();
+
+ // add the model field
+ $doc->addField(Zend_Search_Lucene_Field::Keyword('cake_model', $model->name, 'utf-8'));
+ foreach($model_options['fields'] as $field_name => $options){
+ if(!empty($options['prepare']) && function_exists($options['prepare'])){
+ $result[$model->name][$field_name] = call_user_func($options['prepare'], $result[$model->name][$field_name]);
+ }
+ $alias = !empty($options['alias']) ? $options['alias'] : $field_name;
+ $doc->addField(Zend_Search_Lucene_Field::$options['type']($alias, $result[$model->name][$field_name], 'utf-8'));
+ }
+ $index->addDocument($doc);
+ $this->out('Processed '.$model->name.' #'.$result[$model->name]['id']);
+ }
+ $this->log($model->name.' adding time: '.(time()-$start));
+ $start = time();
+ }
+ $this->optimize($index);
+ $index->commit();
+ $this->log('Optimize+commit time: '.(time()-$start));
+ }
+/**
+ * query function
+ *
+ * @access public
+ * @return void
+ */
+ function query(){
+ $query = implode(' ',$this->args);
+ $index = $this->__open(false);
+ $hits = $index->find($query);
+ $this->out('Query results:');
+ $this->out("Score\tModel\tID\tTitle");
+ foreach ($hits as $hit) {
+ $this->out(printf("%.2f",$hit->score)."\t".$hit->cake_model." \t".$hit->cake_id."\t".$hit->title);
+ $this->out('id '.$hit->id);
+ }
+ }
+
+
+/**
+ * optimizes the index
+ * TODO: add some loggin/printing
+ *
+ * @param mixed $index
+ * @access public
+ * @return void
+ */
+ function optimize($index = null){
+ if(empty($index)){
+ $index = $this->__open(false);
+ }
+ $this->out("Optimizing index...");
+ $index->optimize();
+ $this->out("Optimized index.");
+ }
+/**
+ * opens or creates an index
+ *
+ * @param bool $create
+ * @access private
+ * @return void
+ */
+ function __open($create = false){
+ try {
+ return Zend_Search_Lucene::open($this->index_file);
+ } catch (Zend_Search_Lucene_Exception $e) {
+ if($create){
+ try {
+ return Zend_Search_Lucene::create($this->index_file);
+ } catch(Zend_Search_Lucene_Exception $e) {
+ echo "Unable to create the index: ". $e->getMessage();
+ }
+ } else {
+ echo "Unable to open the index: ". $e->getMessage();
+ }
+ }
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/plugins/searchable/views/helpers/search.php b/plugins/searchable/views/helpers/search.php
new file mode 100644
index 0000000..f9784b0
--- /dev/null
+++ b/plugins/searchable/views/helpers/search.php
@@ -0,0 +1,64 @@
+<?php
+class SearchHelper extends AppHelper {
+
+ var $name = 'Search';
+
+ function highlight($terms, $string, $crop = 40, $delimiter = '...', $max_chars = 400){
+ if(!is_array($terms)){
+ $terms = array($terms);
+ }
+ $excerpt = '';
+ if($crop) {
+ $string = strip_tags($string);
+ // get the term positions
+ $positions = array();
+ foreach($terms as $term){
+ $offset = 0;
+ while(strlen($string) > $offset){
+ $position = stripos($string, $term, $offset);
+ if($position !== FALSE){
+ $positions[] = $position;
+ $offset = $position + strlen($term);
+ } else {
+ break;
+ }
+
+ }
+ }
+ sort($positions);
+ unset($position);
+ if(!empty($positions)) {
+ if(($positions[0] - $crop) > 0){
+ $part_start = $positions[0] - $crop;
+ } else {
+ $part_start = 0;
+ }
+ for($pos = 0; $pos <= (count($positions) - 2); $pos++){
+ if(($positions[$pos] + (2 *$crop)) < $positions[$pos + 1]){
+ $excerpt .= mb_substr($string, $part_start, $positions[$pos] - $part_start + $crop, 'UTF8'). $delimiter.' '.$delimiter;
+ $part_start = $positions[$pos + 1] - $crop;
+ }
+ }
+ if((end($positions) + (2 *$crop)) < count($string)){
+ $excerpt .= mb_substr($string, $part_start, $positions[$pos] - $part_start + $crop, 'UTF8'). $delimiter;
+ } else {
+ $excerpt .= mb_substr($string, end($positions) - $crop, strlen($string), 'UTF8');
+ }
+
+ }
+ if(!empty($excerpt) && mb_substr($string, 0, 4, 'UTF8') != mb_substr($excerpt, 0, 4, 'UTF8')){
+ $excerpt = '...'.$excerpt;
+ }
+ $string = $excerpt;
+ }
+ if($max_chars && strlen($string) > $max_chars){
+ $string = mb_substr($string, 0, $max_chars, 'UTF8');
+ }
+ $i = 0;
+ foreach($terms as $term){
+ $string = str_ireplace($term, '<span class="search_term_'.$i.'">'.$term.'</span>', $string);
+ $i++;
+ }
+ return $string;
+ }
+}?>
\ No newline at end of file
diff --git a/plugins/users/controllers/components/bakery.php b/plugins/users/controllers/components/bakery.php
new file mode 100644
index 0000000..7521d9f
--- /dev/null
+++ b/plugins/users/controllers/components/bakery.php
@@ -0,0 +1,74 @@
+<?php
+/*
+define('ADMIN', '800');
+define('EDITOR', '700');
+define('MODERATOR', '600');
+define('COMMENTER', '300');
+define('READ', '200');
+define('NONE', '100');
+define('INVALID', '0');
+*/
+class BakeryComponent extends Object {
+
+ var $components = array('Auth');
+
+ function initialize() {
+ $this->Auth->loginAction = '/users/login';
+ $this->Auth->logoutRedirect = '/';
+
+ $this->Auth->fields = array('username'=> 'username', 'password'=>'psword');
+ $this->Auth->authorize = 'object';
+ $this->Auth->object = $this;
+ $this->Auth->authenticate = $this;
+ }
+
+ function startup(&$controller) {
+ if($auth = $this->Auth->user()) {
+ if (!empty($auth['User']) && empty($auth['User']['Level'])) {
+ $model = $this->Auth->getModel();
+ $model->recursive = 0;
+ $user = $model->read(array('User.id', 'User.username', 'User.email', 'Level.*', 'Group.*'), $auth['User']['id']);
+ $this->Auth->Session->write('Auth.User', $user['User']);
+ $this->Auth->Session->write('Auth.User.Level', $user['Level']['value']);
+ $this->Auth->Session->write('Auth.User.Group', $user['Group']['name']);
+ }
+ }
+ }
+
+ function hashPasswords($data) {
+ if(!empty($data['User']['psword'])) {
+ $data['User']['psword'] = Security::hash($data['User']['psword']);
+ }
+ return $data;
+ }
+
+ function isAuthorized($user, $controller, $action) {
+ if($this->Auth->user('Level') == ADMIN) {
+ return true;
+ }
+
+ if($action === 'admin_delete') {
+ if($this->Auth->user('Level') >= EDITOR) {
+ return true;
+ }
+ return false;
+ }
+
+ if (in_array($controller, array('Nodes', 'Comments'))) {
+ if($this->Auth->user('Level') >= COMMENTER) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function beforeRender(&$controller) {
+ $user = $this->Auth->user();
+ if(empty($user)) {
+ $user = array('User' => array('Level' => READ, 'Group' => null));
+ }
+ $controller->set('auth', $user);
+ }
+}
+?>
\ No newline at end of file
diff --git a/plugins/users/controllers/users_controller.php b/plugins/users/controllers/users_controller.php
new file mode 100644
index 0000000..4616a06
--- /dev/null
+++ b/plugins/users/controllers/users_controller.php
@@ -0,0 +1,194 @@
+<?php
+/* SVN FILE: $Id: users_controller.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * Cake Foundation
+ *
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under the CAKE SOFTWARE FOUNDATION LICENSE(CSFL) version 1.0
+ * Redistributions of files must retain the above copyright notice.
+ * You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at:
+ * License page: http://www.cakefoundation.org/licenses/csfl/
+ * Copyright page: http://www.cakefoundation.org/copyright/
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package csf
+ * @subpackage csf.plugins.users.controllers
+ * @since CSF v 1.0.0.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.cakefoundation.org/licenses/csfl/ The CSFL License
+ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * @package csf
+ * @subpackage csf.plugins.users.controllers
+ * @since CSF v 1.0.0.0
+ *
+ */
+class UsersController extends AppController {
+
+ var $name = 'Users';
+ var $components = array('Email');
+
+ var $helpers = array('Html', 'Form');
+
+ function beforeFilter() {
+ parent::beforeFilter();
+ $this->Auth->allow('logout', 'reset', 'verify');
+ }
+
+ function beforeRender() {
+ parent::beforeRender();
+ $this->set('loginFields', $this->Auth->fields);
+ }
+
+ function login() {
+ /* this causes crazy redirects. I dont know why we need it since it is handled by AuthComponent
+ if ($this->Auth->user('id')) {
+ $this->redirect($this->referer('/'), null, true);
+ }
+ if (!$this->Session->check('Auth.from')) {
+ $this->Session->write('Auth.from', $this->Session->read('referer'));
+ }
+ unset($this->data['User']['psword']);
+ */
+ if($this->Auth->user('id'))
+ {
+ if(!empty($this->data['User']['redirect'])){
+ $this->redirect($this->data['User']['redirect'], null, true);
+ } else {
+ $this->redirect($this->Auth->redirect());
+ }
+ }
+ }
+
+ function logout() {
+ $this->Session->destroy();
+ $this->redirect('/', null, true);
+ }
+
+ function reset() {
+ if(!empty($this->data['User']['email'])) {
+
+ $email = $this->data['User']['email'];
+ if(empty($email)) {
+ $this->Session->setFlash('Please enter an email.');
+ $this->set('error',array('email_missing' => true));
+ $this->render(); exit;
+ }
+
+ $this->User->recursive = -1;
+ if ($this->User->findCount(array('User.email' => $email), -1)) {
+ $this->User->saveTempPassword($email);
+ $user = $this->User->find(array('User.email' => $email));
+ $this->Email->to = $user['User']['email'];
+ $this->Email->from = Configure::read('Site.email');
+ $this->Email->subject = Configure::read('Site.name') . ' new password request' ;
+ $this->Email->template = null;
+
+ $content[] = 'A request to reset you password has been submitted.';
+ $content[] = 'Please visit the following url to have your temporary password sent';
+ $content[] = Router::url('/users/verify/reset/'.$user['User']['email_token'], true);
+
+ if($this->Email->send($content)) {
+ $this->Session->setFlash('You should receive an email with further instruction shortly');
+ $this->set($user);
+ $this->redirect('/', null, true);
+ }
+ } else {
+ $this->User->invalidate('email', 'The email you entered was not found');
+ }
+ }
+ }
+
+ function verify($type = 'email') {
+ if(isset($this->passedArgs['1'])){
+ $token = $this->passedArgs['1'];
+ } else {
+ $this->Session->setFlash('Invalid verification token.');
+ $this->render(); exit;
+ }
+ if($type === 'email') {
+ $data = $this->User->validateToken($token);
+ } elseif($type === 'reset') {
+ $data = $this->User->validateToken($token, true);
+ } else {
+ $this->Session->setFlash('There url you accessed is no longer valid');
+ $this->redirect(array('action' => 'login'));
+ }
+
+ $password = $data['User']['psword'];
+ $data = $this->Auth->hashPasswords($data);
+
+ if($data !== false){
+ $email = $data['User']['email'];
+ unset($data['User']['email']);
+ if($this->User->save($data, false)) {
+ if($type === 'reset'){
+ $this->Email->to = $email;
+ $this->Email->from = Configure::read('Site.email');
+ $this->Email->subject = Configure::read('Site.name') . ' password reset' ;
+ $this->Email->template = null;
+ $content[] = 'Your password has been reset';
+ $content[] = 'Please login using';
+ $content[] = 'Username: ' . $data['User']['username'];
+ $content[] = 'Password: ' . $password;
+ $this->Email->send($content);
+ $this->Session->setFlash('Your password was sent to your registered email account');
+ } else {
+ $this->Session->setFlash('Your Email was validated, Please Login');
+ $this->redirect(array('action' => 'login'));
+ }
+ } else {
+ $this->Session->setFlash('There was an error trying to validate, check your email and the url entered');
+ }
+ } else {
+ $this->Session->setFlash('There url you accessed is no longer valid');
+ $this->redirect(array('action' => 'login'));
+ }
+ }
+/**
+ * Admin methods
+ *
+ **/
+
+ function admin_login() {
+ $this->login();
+ $this->render('login');
+ }
+
+ function admin_logout() {
+ $this->logout();
+ }
+
+ function admin_index() {
+ $this->User->recursive = 0;
+ $this->set('users', $this->paginate());
+ }
+
+ function admin_view($id = null) {
+ if (!$id) {
+ $this->Session->setFlash('Invalid User.');
+ $this->redirect(array('action'=>'index'), null, true);
+ }
+ $this->set('user', $this->User->read(null, $id));
+ }
+}
+?>
\ No newline at end of file
diff --git a/plugins/users/models/group.php b/plugins/users/models/group.php
new file mode 100644
index 0000000..45554d7
--- /dev/null
+++ b/plugins/users/models/group.php
@@ -0,0 +1,68 @@
+<?php
+/* SVN FILE: $Id: group.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * Cake Bakery
+ *
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under the CAKE SOFTWARE FOUNDATION LICENSE(CSFL) version 1.0
+ * Redistributions of files must retain the above copyright notice.
+ * You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at:
+ * License page: http://www.cakefoundation.org/licenses/csfl/
+ * Copyright page: http://www.cakefoundation.org/copyright/
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package bakery
+ * @subpackage bakery.models
+ * @since Bakery v 1.0.0.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.cakefoundation.org/licenses/csfl/ The CSFL License
+ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * @package bakery
+ * @subpackage bakery.models
+ * @since Bakery v 1.0.0.0
+ *
+ */
+class Group extends UsersAppModel {
+/**
+ * Enter description here...
+ *
+ * @var string
+ */
+ var $name = 'Group';
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ */
+ var $validate = array('name' => VALID_NOT_EMPTY);
+
+ var $belongsTo = array('Level' => array('className' => 'Users.Level'));
+
+/**
+ * Enter description here...
+ *
+ * @var array
+ */
+ var $hasMany = array('User'=> array('className' => 'Users.User'));
+}
+?>
\ No newline at end of file
diff --git a/plugins/users/models/level.php b/plugins/users/models/level.php
new file mode 100644
index 0000000..06e811b
--- /dev/null
+++ b/plugins/users/models/level.php
@@ -0,0 +1,62 @@
+<?php
+/* SVN FILE: $Id: level.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * Cake Bakery
+ *
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under the CAKE SOFTWARE FOUNDATION LICENSE(CSFL) version 1.0
+ * Redistributions of files must retain the above copyright notice.
+ * You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at:
+ * License page: http://www.cakefoundation.org/licenses/csfl/
+ * Copyright page: http://www.cakefoundation.org/copyright/
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package bakery
+ * @subpackage bakery.models
+ * @since Bakery v 1.0.0.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.cakefoundation.org/licenses/csfl/ The CSFL License
+ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * @package bakery
+ * @subpackage bakery.models
+ * @since Bakery v 1.0.0.0
+ *
+ */
+class Level extends UsersAppModel {
+/**
+ * Enter description here...
+ *
+ * @var string
+ */
+ var $name = 'Level';
+/**
+ * Enter description here...
+ *
+ * @var array
+ */
+ var $hasMany = array('Group'=>
+ array('className' => 'Users.Group'),
+ 'User'=>
+ array('className' => 'Users.User'));
+}
+?>
\ No newline at end of file
diff --git a/plugins/users/models/profile.php b/plugins/users/models/profile.php
new file mode 100644
index 0000000..d4c9084
--- /dev/null
+++ b/plugins/users/models/profile.php
@@ -0,0 +1,59 @@
+<?php
+/* SVN FILE: $Id: profile.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * Cake Bakery
+ *
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under the CAKE SOFTWARE FOUNDATION LICENSE(CSFL) version 1.0
+ * Redistributions of files must retain the above copyright notice.
+ * You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at:
+ * License page: http://www.cakefoundation.org/licenses/csfl/
+ * Copyright page: http://www.cakefoundation.org/copyright/
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package bakery
+ * @subpackage bakery.models
+ * @since Bakery v 1.0.0.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.cakefoundation.org/licenses/csfl/ The CSFL License
+ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * @package bakery
+ * @subpackage bakery.models
+ * @since Bakery v 1.0.0.0
+ *
+ */
+class Profile extends UsersAppModel {
+
+ var $name = 'Profile';
+
+ var $belongsTo = array('User' => array('className' => 'Users.User'));
+/**
+ var $validate = array(
+ 'user_icon' => array(
+ 'rule' => array('url'),
+ 'message' => 'Please provide a valid URL'
+ )
+ );
+*/
+}
+?>
\ No newline at end of file
diff --git a/plugins/users/models/user.php b/plugins/users/models/user.php
new file mode 100644
index 0000000..1256a05
--- /dev/null
+++ b/plugins/users/models/user.php
@@ -0,0 +1,156 @@
+<?php
+/* SVN FILE: $Id: user.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * Cake Foundation
+ *
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under the CAKE SOFTWARE FOUNDATION LICENSE(CSFL) version 1.0
+ * Redistributions of files must retain the above copyright notice.
+ * You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at:
+ * License page: http://www.cakefoundation.org/licenses/csfl/
+ * Copyright page: http://www.cakefoundation.org/copyright/
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package csf
+ * @subpackage csf.plugins.users.models
+ * @since CSF v 1.0.0.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.cakefoundation.org/licenses/csfl/ The CSFL License
+ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * @package csf
+ * @subpackage csf.plugins.users.models
+ * @since CSF v 1.0.0.0
+ *
+ */
+class User extends UsersAppModel {
+
+ var $name = 'User';
+
+ var $displayField = 'username';
+
+ var $belongsTo = array('Group' => array('className' => 'Users.Group'), 'Level' => array('className' => 'Users.Level'));
+
+ var $hasOne = array('Users.Profile');
+
+ var $hasMany = array('Revision', 'Comment');
+
+ function beforeValidate() {
+ if(!$this->id) {
+ $this->recursive = -1;
+ if ($this->findCount(array('User.username' => $this->data['User']['username'])) > 0) {
+ $this->invalidate('username_unique');
+ }
+ $this->recursive = -1;
+ if ($this->findCount(array('User.email' => $this->data['User']['email'])) > 0) {
+ $this->invalidate('email_unique');
+ }
+ }
+ return true;
+ }
+
+ function beforeSave() {
+ if(!$this->id) {
+ $this->data['User']['email_token'] = $this->__generateToken();
+ $this->data['User']['email_token_expires'] = date('Y-m-d H:i:s', time() + (86400 * 2));
+ }
+ return true;
+ }
+
+ function saveTempPassword($email){
+ $this->id = $this->field('id', array('User.email' => $email));
+ $this->data['User']['temppassword'] = $this->__genpassword();
+ $this->data['User']['email_token'] = $this->__generateToken();
+ $sixtyMins = time() + 43000;
+ $this->data['User']['email_token_expires'] = date('Y-m-d H:i:s', $sixtyMins);
+ if($this->save($this->data)){
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function validateToken($id = null, $reset = false) {
+ $this->recursive = '-1';
+ $match = $this->find(array('User.email_token' => $id), 'id, username, email, temppassword, email_token_expires');
+ if(!empty($match)){
+ $expires = strtotime($match['User']['email_token_expires']);
+ if($expires > time()){
+ $data['User']['id'] = $match['User']['id'];
+ $data['User']['username'] = $match['User']['username'];
+ $data['User']['email'] = $match['User']['email'];
+ $data['User']['email_authenticated'] = '1';
+
+ if($reset === true) {
+ $data['User']['psword'] = $match['User']['temppassword'];
+ $data['User']['temppassword'] = null;
+ }
+ $data['User']['email_token'] = null;
+ $data['User']['email_token_expires'] = null;
+ }
+ return $data;
+ }
+ return false;
+ }
+
+ /* Private Methods */
+ function __password($compareTo, $password, $check = true){
+ $security = Security::getInstance();
+ $salt = Configure::read('Security.salt');
+ if($check === true){
+ if($security->hash($salt . $password) === $compareTo){
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ $genPassword = $security->hash($salt . $password);
+ return $genPassword;
+ }
+ }
+
+ function __genpassword($length = 10) {
+ srand((double)microtime()*1000000);
+ $password = '';
+ $vowels = array("a", "e", "i", "o", "u");
+ $cons = array("b", "c", "d", "g", "h", "j", "k", "l", "m", "n", "p", "r", "s", "t", "u", "v", "w", "tr",
+ "cr", "br", "fr", "th", "dr", "ch", "ph", "wr", "st", "sp", "sw", "pr", "sl", "cl");
+ for($i = 0; $i < $length; $i++){
+ $password .= $cons[mt_rand(0, 31)] . $vowels[mt_rand(0, 4)];
+ }
+ return substr($password, 0, $length);
+ }
+
+ function __generateToken() {
+ $possible = "0123456789abcdfghijklmnopqrstvwxyz";
+ $id = "";
+ $i = 0;
+ while ($i < 20) {
+ $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
+ if (!strstr($id, $char)) {
+ $id .= $char;
+ $i++;
+ }
+ }
+ return $id;
+ }
+}
\ No newline at end of file
diff --git a/plugins/users/users_app_controller.php b/plugins/users/users_app_controller.php
new file mode 100644
index 0000000..3f45d3f
--- /dev/null
+++ b/plugins/users/users_app_controller.php
@@ -0,0 +1,48 @@
+<?php
+/* SVN FILE: $Id: users_app_controller.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * Cake Foundation
+ *
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under the CAKE SOFTWARE FOUNDATION LICENSE(CSFL) version 1.0
+ * Redistributions of files must retain the above copyright notice.
+ * You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at:
+ * License page: http://www.cakefoundation.org/licenses/csfl/
+ * Copyright page: http://www.cakefoundation.org/copyright/
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package csf
+ * @subpackage csf.plugins.users
+ * @since CSF v 1.0.0.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.cakefoundation.org/licenses/csfl/ The CSFL License
+ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * @package csf
+ * @subpackage csf.plugins.users
+ * @since CSF v 1.0.0.0
+ *
+ */
+class UsersAppController extends AppController {
+
+
+}
\ No newline at end of file
diff --git a/plugins/users/users_app_model.php b/plugins/users/users_app_model.php
new file mode 100644
index 0000000..1fe0bba
--- /dev/null
+++ b/plugins/users/users_app_model.php
@@ -0,0 +1,51 @@
+<?php
+/* SVN FILE: $Id: users_app_model.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * Cake Foundation
+ *
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under the CAKE SOFTWARE FOUNDATION LICENSE(CSFL) version 1.0
+ * Redistributions of files must retain the above copyright notice.
+ * You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at:
+ * License page: http://www.cakefoundation.org/licenses/csfl/
+ * Copyright page: http://www.cakefoundation.org/copyright/
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package csf
+ * @subpackage csf.plugins.users
+ * @since CSF v 1.0.0.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.cakefoundation.org/licenses/csfl/ The CSFL License
+ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * @package csf
+ * @subpackage csf.plugins.users
+ * @since CSF v 1.0.0.0
+ *
+ */
+class UsersAppModel extends AppModel {
+
+ function __construct($one = null, $two = null, $three = null) {
+ $this->useDbConfig = Configure::read('Site.database');
+ parent::__construct($one, $two, $three);
+ }
+}
\ No newline at end of file
diff --git a/plugins/users/views/elements/user.ctp b/plugins/users/views/elements/user.ctp
new file mode 100644
index 0000000..329cfef
--- /dev/null
+++ b/plugins/users/views/elements/user.ctp
@@ -0,0 +1,10 @@
+<?php if (!empty($authUser)) :?>
+<div class="plugin-users">
+ <span class="username">
+ <?php echo $html->link($authUser['User']['username'], array('controller' => 'users', 'action' => 'view'));?>
+ </span>
+ <span class="logout">
+ <?php echo $html->link('logout', array('controller' => 'users', 'action' => 'logout'));?>
+ </span>
+</div>
+<?php endif;?>
\ No newline at end of file
diff --git a/plugins/users/views/users/login.ctp b/plugins/users/views/users/login.ctp
new file mode 100644
index 0000000..32685f1
--- /dev/null
+++ b/plugins/users/views/users/login.ctp
@@ -0,0 +1,22 @@
+<?php /* SVN FILE: $Id: login.ctp 659 2008-09-10 14:52:21Z AD7six $ */ ?>
+<div class="login">
+<?php
+ echo $form->create('User', array('url' => '/users/login'));
+ echo $form->hidden('redirect', array('value' => $session->read('Auth.redirect')));
+?>
+ <fieldset>
+ <legend><?php __('Login') ?></legend>
+<?php
+ foreach($loginFields as $label => $field):
+ $after = null;
+ if ($label == 'password') {
+ $after = '<p>' . $html->link(__('Forgot your password?', true), array('admin'=> false, 'action' => 'reset')) .'</p>';
+ }
+ echo $form->input($field, array('label' => Inflector::humanize($label), 'after' => $after));
+ endforeach;
+?>
+ </fieldset>
+<?php
+ echo $form->end(__('Login', true));
+?>
+</div>
\ No newline at end of file
diff --git a/plugins/users/views/users/reset.ctp b/plugins/users/views/users/reset.ctp
new file mode 100644
index 0000000..1879493
--- /dev/null
+++ b/plugins/users/views/users/reset.ctp
@@ -0,0 +1,6 @@
+<?php /* SVN FILE: $Id: reset.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<?php
+ echo $form->create('User', array('action' => 'reset'));
+ echo $form->inputs(array('email','legend' => 'Reset Password'));
+ echo $form->end('Submit');
+?>
\ No newline at end of file
diff --git a/plugins/users/views/users/verify.ctp b/plugins/users/views/users/verify.ctp
new file mode 100644
index 0000000..4eaf2f5
--- /dev/null
+++ b/plugins/users/views/users/verify.ctp
@@ -0,0 +1 @@
+<?php /* SVN FILE: $Id: verify.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
\ No newline at end of file
diff --git a/tests/cases/behaviors/empty b/tests/cases/behaviors/empty
new file mode 100755
index 0000000..e69de29
diff --git a/tests/cases/components/empty b/tests/cases/components/empty
new file mode 100755
index 0000000..e69de29
diff --git a/tests/cases/controllers/empty b/tests/cases/controllers/empty
new file mode 100755
index 0000000..e69de29
diff --git a/tests/cases/helpers/empty b/tests/cases/helpers/empty
new file mode 100755
index 0000000..e69de29
diff --git a/tests/cases/models/empty b/tests/cases/models/empty
new file mode 100755
index 0000000..e69de29
diff --git a/tests/cases/models/node.test.php b/tests/cases/models/node.test.php
new file mode 100644
index 0000000..0591402
--- /dev/null
+++ b/tests/cases/models/node.test.php
@@ -0,0 +1,147 @@
+<?php
+/* SVN FILE: $Id: node.test.php 694 2008-11-05 13:59:08Z AD7six $ */
+/**
+ * Short description for node.test.php
+ *
+ * Long description for node.test.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package cookbook
+ * @subpackage cookbook.tests.cases.models
+ * @since v 1.0
+ * @version $Revision: 694 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 14:59:08 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Model', 'Node');
+/**
+ * NodeTestCase class
+ *
+ * @uses CakeTestCase
+ * @package cookbook
+ * @subpackage cookbook.tests.cases.models
+ */
+class NodeTestCase extends CakeTestCase {
+/**
+ * Node property
+ *
+ * @var mixed null
+ * @access public
+ */
+ var $Node = null;
+/**
+ * fixtures property
+ *
+ * @var array
+ * @access public
+ */
+ var $fixtures = array('app.node');
+/**
+ * start method
+ *
+ * @return void
+ * @access public
+ */
+ function start() {
+ parent::start();
+ $this->Node =& ClassRegistry::init('Node');
+ }
+/**
+ * testNodeInstance method
+ *
+ * @return void
+ * @access public
+ */
+ function testNodeInstance() {
+ $this->assertTrue(is_a($this->Node, 'Node'));
+ }
+/**
+ * testNodeFind method
+ *
+ * Initial test to ensure the fixture is loaded correctly, and find('list' is working correctly
+ *
+ * @return void
+ * @access public
+ */
+ function testNodeFind() {
+ $this->Node->recursive = -1;
+ $expected = array(
+ 1 => ' Your Collections',
+ 2 => ' Collection 1',
+ 3 => ' Book 1',
+ 4 => '1 Section id 4',
+ 5 => '1.1 Section id 5',
+ 6 => '1.1.1 Section id 6',
+ 7 => '1.1.2 Section id 7',
+ 8 => '1.2 Section id 8',
+ 9 => '1.2.1 Section id 9',
+ 10 => '1.2.2 Section id 10',
+ 11 => '2 Section id 11',
+ 12 => '2.1 Section id 12',
+ 13 => '2.1.1 Section id 13',
+ 14 => '2.1.2 Section id 14',
+ 15 => '2.2 Section id 15',
+ 16 => '2.2.1 Section id 16',
+ 17 => '2.2.2 Section id 17',
+ 18 => ' Book 2',
+ 19 => '1 Section id 19',
+ 20 => '1.1 Section id 20',
+ 21 => '1.1.1 Section id 21',
+ 22 => '1.1.2 Section id 22',
+ 23 => '1.2 Section id 23',
+ 24 => '1.2.1 Section id 24',
+ 25 => '1.2.2 Section id 25',
+ 26 => '2 Section id 26',
+ 27 => '2.1 Section id 27',
+ 28 => '2.1.1 Section id 28',
+ 29 => '2.1.2 Section id 29',
+ 30 => '2.2 Section id 30',
+ 31 => '2.2.1 Section id 31',
+ 32 => '2.2.2 Section id 32',
+ 33 => ' Collection 2',
+ 34 => ' Book 1',
+ 35 => '1 Section id 35',
+ 36 => '1.1 Section id 36',
+ 37 => '1.1.1 Section id 37',
+ 38 => '1.1.2 Section id 38',
+ 39 => '1.2 Section id 39',
+ 40 => '1.2.1 Section id 40',
+ 41 => '1.2.2 Section id 41',
+ 42 => '2 Section id 42',
+ 43 => '2.1 Section id 43',
+ 44 => '2.1.1 Section id 44',
+ 45 => '2.1.2 Section id 45',
+ 46 => '2.2 Section id 46',
+ 47 => '2.2.1 Section id 47',
+ 48 => '2.2.2 Section id 48',
+ 49 => ' Book 2',
+ 50 => '1 Section id 50',
+ 51 => '1.1 Section id 51',
+ 52 => '1.1.1 Section id 52',
+ 53 => '1.1.2 Section id 53',
+ 54 => '1.2 Section id 54',
+ 55 => '1.2.1 Section id 55',
+ 56 => '1.2.2 Section id 56',
+ 57 => '2 Section id 57',
+ 58 => '2.1 Section id 58',
+ 59 => '2.1.1 Section id 59',
+ 60 => '2.1.2 Section id 60',
+ 61 => '2.2 Section id 61',
+ 62 => '2.2.1 Section id 62',
+ 63 => '2.2.2 Section id 63'
+ );
+ $results = $this->Node->find('list');
+ $this->assertEqual($results, $expected);
+ }
+}
+?>
\ No newline at end of file
diff --git a/tests/fixtures/comment_fixture.php b/tests/fixtures/comment_fixture.php
new file mode 100644
index 0000000..5a2fcef
--- /dev/null
+++ b/tests/fixtures/comment_fixture.php
@@ -0,0 +1,73 @@
+<?php
+/* SVN FILE: $Id: comment_fixture.php 693 2008-11-05 13:01:32Z AD7six $ */
+/**
+ * Short description for comment_fixture.php
+ *
+ * Long description for comment_fixture.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ * @since v 1.0
+ * @version $Revision: 693 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 14:01:32 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * CommentFixture class
+ *
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ */
+class CommentFixture extends CakeTestFixture {
+ /**
+ * name property
+ *
+ * @var string 'Comment'
+ * @access public
+ */
+ var $name = 'Comment';
+ /**
+ * fields property
+ *
+ * @var array
+ * @access public
+ */
+ var $fields = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'node_id' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 10),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'revision_id' => array('type'=>'integer', 'null' => true, 'default' => '0', 'length' => 10),
+ 'lang' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 2),
+ 'title' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 150),
+ 'author' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'email' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'url' => array('type'=>'string', 'null' => true, 'default' => NULL),
+ 'body' => array('type'=>'text', 'null' => true, 'default' => NULL),
+ 'published' => array('type'=>'boolean', 'null' => false, 'default' => '1'),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+ /**
+ * records property
+ *
+ * @var array
+ * @access public
+ */
+ var $records = array(
+ array('id' => '1', 'node_id' => '1', 'user_id' => '1', 'revision_id' => '1', 'lang' => 'en', 'title' => 'Title',
+ 'body' => 'Body', 'published' => '1', ),
+ );
+}
+?>
\ No newline at end of file
diff --git a/tests/fixtures/node_fixture.php b/tests/fixtures/node_fixture.php
new file mode 100644
index 0000000..24a6490
--- /dev/null
+++ b/tests/fixtures/node_fixture.php
@@ -0,0 +1,955 @@
+<?php
+/* SVN FILE: $Id: node_fixture.php 693 2008-11-05 13:01:32Z AD7six $ */
+/**
+ * Short description for node_fixture.php
+ *
+ * Long description for node_fixture.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ * @since v 1.0
+ * @version $Revision: 693 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 14:01:32 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * NodeFixture class
+ *
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ */
+class NodeFixture extends CakeTestFixture {
+/**
+ * name property
+ *
+ * @var string 'Node'
+ * @access public
+ */
+ var $name = 'Node';
+/**
+ * fields property
+ *
+ * @var array
+ * @access public
+ */
+ var $fields = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'lft' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'rght' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'status' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 2),
+ 'comment_level' => array('type'=>'integer', 'null' => false, 'default' => '200', 'length' => 4),
+ 'edit_level' => array('type'=>'integer', 'null' => false, 'default' => '200', 'length' => 4),
+ 'show_in_toc' => array('type'=>'boolean', 'null' => false, 'default' => '1'),
+ 'depth' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 2),
+ 'sequence' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 20),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array(
+ 'PRIMARY' => array('column' => 'id', 'unique' => 1),
+ 'LFT_RGHT' => array('column' => array('lft', 'rght'), 'unique' => 0),
+ 'RGHT_LFT' => array('column' => array('rght', 'lft'), 'unique' => 0)
+ ));
+/**
+ * records property
+ *
+ * @var array
+ * @access public
+ */
+ var $records = array(
+ array(
+ 'id' => '1',
+ 'lft' => '1',
+ 'rght' => '126',
+ 'parent_id' => '',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '0',
+ 'sequence' => '',
+ 'created' => '2008-11-05 12:49:17',
+ 'modified' => '2008-11-05 12:49:40',
+ ),
+ array(
+ 'id' => '2',
+ 'lft' => '2',
+ 'rght' => '63',
+ 'parent_id' => '1',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '1',
+ 'sequence' => '',
+ 'created' => '2008-11-05 12:49:17',
+ 'modified' => '2008-11-05 12:49:40',
+ ),
+ array(
+ 'id' => '3',
+ 'lft' => '3',
+ 'rght' => '32',
+ 'parent_id' => '2',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '2',
+ 'sequence' => '',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:40',
+ ),
+ array(
+ 'id' => '4',
+ 'lft' => '4',
+ 'rght' => '17',
+ 'parent_id' => '3',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '1',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '5',
+ 'lft' => '5',
+ 'rght' => '10',
+ 'parent_id' => '4',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.1',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '6',
+ 'lft' => '6',
+ 'rght' => '7',
+ 'parent_id' => '5',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.1',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '7',
+ 'lft' => '8',
+ 'rght' => '9',
+ 'parent_id' => '5',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.2',
+ 'created' => '2008-11-05 12:49:19',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '8',
+ 'lft' => '11',
+ 'rght' => '16',
+ 'parent_id' => '4',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.2',
+ 'created' => '2008-11-05 12:49:19',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '9',
+ 'lft' => '12',
+ 'rght' => '13',
+ 'parent_id' => '8',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.1',
+ 'created' => '2008-11-05 12:49:19',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '10',
+ 'lft' => '14',
+ 'rght' => '15',
+ 'parent_id' => '8',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.2',
+ 'created' => '2008-11-05 12:49:20',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '11',
+ 'lft' => '18',
+ 'rght' => '31',
+ 'parent_id' => '3',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '2',
+ 'created' => '2008-11-05 12:49:20',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '12',
+ 'lft' => '19',
+ 'rght' => '24',
+ 'parent_id' => '11',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.1',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '13',
+ 'lft' => '20',
+ 'rght' => '21',
+ 'parent_id' => '12',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.1',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '14',
+ 'lft' => '22',
+ 'rght' => '23',
+ 'parent_id' => '12',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.2',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '15',
+ 'lft' => '25',
+ 'rght' => '30',
+ 'parent_id' => '11',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.2',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '16',
+ 'lft' => '26',
+ 'rght' => '27',
+ 'parent_id' => '15',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.1',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '17',
+ 'lft' => '28',
+ 'rght' => '29',
+ 'parent_id' => '15',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.2',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '18',
+ 'lft' => '33',
+ 'rght' => '62',
+ 'parent_id' => '2',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '2',
+ 'sequence' => '',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:40',
+ ),
+ array(
+ 'id' => '19',
+ 'lft' => '34',
+ 'rght' => '47',
+ 'parent_id' => '18',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '1',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '20',
+ 'lft' => '35',
+ 'rght' => '40',
+ 'parent_id' => '19',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.1',
+ 'created' => '2008-11-05 12:49:23',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '21',
+ 'lft' => '36',
+ 'rght' => '37',
+ 'parent_id' => '20',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.1',
+ 'created' => '2008-11-05 12:49:23',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '22',
+ 'lft' => '38',
+ 'rght' => '39',
+ 'parent_id' => '20',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.2',
+ 'created' => '2008-11-05 12:49:24',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '23',
+ 'lft' => '41',
+ 'rght' => '46',
+ 'parent_id' => '19',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.2',
+ 'created' => '2008-11-05 12:49:25',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '24',
+ 'lft' => '42',
+ 'rght' => '43',
+ 'parent_id' => '23',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.1',
+ 'created' => '2008-11-05 12:49:25',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '25',
+ 'lft' => '44',
+ 'rght' => '45',
+ 'parent_id' => '23',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.2',
+ 'created' => '2008-11-05 12:49:25',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '26',
+ 'lft' => '48',
+ 'rght' => '61',
+ 'parent_id' => '18',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '2',
+ 'created' => '2008-11-05 12:49:25',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '27',
+ 'lft' => '49',
+ 'rght' => '54',
+ 'parent_id' => '26',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.1',
+ 'created' => '2008-11-05 12:49:26',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '28',
+ 'lft' => '50',
+ 'rght' => '51',
+ 'parent_id' => '27',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.1',
+ 'created' => '2008-11-05 12:49:26',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '29',
+ 'lft' => '52',
+ 'rght' => '53',
+ 'parent_id' => '27',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.2',
+ 'created' => '2008-11-05 12:49:26',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '30',
+ 'lft' => '55',
+ 'rght' => '60',
+ 'parent_id' => '26',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.2',
+ 'created' => '2008-11-05 12:49:27',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '31',
+ 'lft' => '56',
+ 'rght' => '57',
+ 'parent_id' => '30',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.1',
+ 'created' => '2008-11-05 12:49:27',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '32',
+ 'lft' => '58',
+ 'rght' => '59',
+ 'parent_id' => '30',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.2',
+ 'created' => '2008-11-05 12:49:27',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '33',
+ 'lft' => '64',
+ 'rght' => '125',
+ 'parent_id' => '1',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '1',
+ 'sequence' => '',
+ 'created' => '2008-11-05 12:49:28',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '34',
+ 'lft' => '65',
+ 'rght' => '94',
+ 'parent_id' => '33',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '2',
+ 'sequence' => '',
+ 'created' => '2008-11-05 12:49:28',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '35',
+ 'lft' => '66',
+ 'rght' => '79',
+ 'parent_id' => '34',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '1',
+ 'created' => '2008-11-05 12:49:28',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '36',
+ 'lft' => '67',
+ 'rght' => '72',
+ 'parent_id' => '35',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.1',
+ 'created' => '2008-11-05 12:49:29',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '37',
+ 'lft' => '68',
+ 'rght' => '69',
+ 'parent_id' => '36',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.1',
+ 'created' => '2008-11-05 12:49:29',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '38',
+ 'lft' => '70',
+ 'rght' => '71',
+ 'parent_id' => '36',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.2',
+ 'created' => '2008-11-05 12:49:29',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '39',
+ 'lft' => '73',
+ 'rght' => '78',
+ 'parent_id' => '35',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.2',
+ 'created' => '2008-11-05 12:49:29',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '40',
+ 'lft' => '74',
+ 'rght' => '75',
+ 'parent_id' => '39',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.1',
+ 'created' => '2008-11-05 12:49:30',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '41',
+ 'lft' => '76',
+ 'rght' => '77',
+ 'parent_id' => '39',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.2',
+ 'created' => '2008-11-05 12:49:30',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '42',
+ 'lft' => '80',
+ 'rght' => '93',
+ 'parent_id' => '34',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '2',
+ 'created' => '2008-11-05 12:49:31',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '43',
+ 'lft' => '81',
+ 'rght' => '86',
+ 'parent_id' => '42',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.1',
+ 'created' => '2008-11-05 12:49:31',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '44',
+ 'lft' => '82',
+ 'rght' => '83',
+ 'parent_id' => '43',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.1',
+ 'created' => '2008-11-05 12:49:31',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '45',
+ 'lft' => '84',
+ 'rght' => '85',
+ 'parent_id' => '43',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.2',
+ 'created' => '2008-11-05 12:49:32',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '46',
+ 'lft' => '87',
+ 'rght' => '92',
+ 'parent_id' => '42',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.2',
+ 'created' => '2008-11-05 12:49:32',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '47',
+ 'lft' => '88',
+ 'rght' => '89',
+ 'parent_id' => '46',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.1',
+ 'created' => '2008-11-05 12:49:32',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '48',
+ 'lft' => '90',
+ 'rght' => '91',
+ 'parent_id' => '46',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.2',
+ 'created' => '2008-11-05 12:49:33',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '49',
+ 'lft' => '95',
+ 'rght' => '124',
+ 'parent_id' => '33',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '2',
+ 'sequence' => '',
+ 'created' => '2008-11-05 12:49:33',
+ 'modified' => '2008-11-05 12:49:41',
+ ),
+ array(
+ 'id' => '50',
+ 'lft' => '96',
+ 'rght' => '109',
+ 'parent_id' => '49',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '1',
+ 'created' => '2008-11-05 12:49:33',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '51',
+ 'lft' => '97',
+ 'rght' => '102',
+ 'parent_id' => '50',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.1',
+ 'created' => '2008-11-05 12:49:33',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '52',
+ 'lft' => '98',
+ 'rght' => '99',
+ 'parent_id' => '51',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.1',
+ 'created' => '2008-11-05 12:49:34',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '53',
+ 'lft' => '100',
+ 'rght' => '101',
+ 'parent_id' => '51',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.1.2',
+ 'created' => '2008-11-05 12:49:34',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '54',
+ 'lft' => '103',
+ 'rght' => '108',
+ 'parent_id' => '50',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '1.2',
+ 'created' => '2008-11-05 12:49:35',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '55',
+ 'lft' => '104',
+ 'rght' => '105',
+ 'parent_id' => '54',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.1',
+ 'created' => '2008-11-05 12:49:35',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '56',
+ 'lft' => '106',
+ 'rght' => '107',
+ 'parent_id' => '54',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '1.2.2',
+ 'created' => '2008-11-05 12:49:35',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '57',
+ 'lft' => '110',
+ 'rght' => '123',
+ 'parent_id' => '49',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '3',
+ 'sequence' => '2',
+ 'created' => '2008-11-05 12:49:36',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '58',
+ 'lft' => '111',
+ 'rght' => '116',
+ 'parent_id' => '57',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.1',
+ 'created' => '2008-11-05 12:49:37',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '59',
+ 'lft' => '112',
+ 'rght' => '113',
+ 'parent_id' => '58',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.1',
+ 'created' => '2008-11-05 12:49:37',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '60',
+ 'lft' => '114',
+ 'rght' => '115',
+ 'parent_id' => '58',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.1.2',
+ 'created' => '2008-11-05 12:49:38',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '61',
+ 'lft' => '117',
+ 'rght' => '122',
+ 'parent_id' => '57',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '4',
+ 'sequence' => '2.2',
+ 'created' => '2008-11-05 12:49:38',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '62',
+ 'lft' => '118',
+ 'rght' => '119',
+ 'parent_id' => '61',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.1',
+ 'created' => '2008-11-05 12:49:39',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ array(
+ 'id' => '63',
+ 'lft' => '120',
+ 'rght' => '121',
+ 'parent_id' => '61',
+ 'status' => '0',
+ 'comment_level' => '200',
+ 'edit_level' => '200',
+ 'show_in_toc' => '1',
+ 'depth' => '5',
+ 'sequence' => '2.2.2',
+ 'created' => '2008-11-05 12:49:40',
+ 'modified' => '2008-11-05 12:49:42',
+ ),
+ );
+}
+?>
\ No newline at end of file
diff --git a/tests/fixtures/revision_fixture.php b/tests/fixtures/revision_fixture.php
new file mode 100644
index 0000000..5cf0c6a
--- /dev/null
+++ b/tests/fixtures/revision_fixture.php
@@ -0,0 +1,1147 @@
+<?php
+/* SVN FILE: $Id: revision_fixture.php 693 2008-11-05 13:01:32Z AD7six $ */
+/**
+ * Short description for revision_fixture.php
+ *
+ * Long description for revision_fixture.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ * @since v 1.0
+ * @version $Revision: 693 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 14:01:32 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * RevisionFixture class
+ *
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ */
+class RevisionFixture extends CakeTestFixture {
+/**
+ * name property
+ *
+ * @var string 'Revision'
+ * @access public
+ */
+ var $name = 'Revision';
+/**
+ * fields property
+ *
+ * @var array
+ * @access public
+ */
+ var $fields = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'node_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'index'),
+ 'under_node_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'after_node_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'status' => array('type'=>'string', 'null' => false, 'default' => 'pending', 'length' => 30),
+ 'user_id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10),
+ 'lang' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 3),
+ 'slug' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 50),
+ 'title' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 200),
+ 'content' => array('type'=>'text', 'null' => true, 'default' => NULL),
+ 'type' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 50),
+ 'reason' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 300),
+ 'flags' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 100),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'indexes' => array(
+ 'PRIMARY' => array('column' => 'id', 'unique' => 1),
+ 'node_id' => array('column' => array('node_id', 'lang', 'status'), 'unique' => 0)
+ ));
+
+/**
+ * records property
+ *
+ * @var array
+ * @access public
+ */
+ var $records = array(
+ array(
+ 'id' => '1',
+ 'node_id' => '1',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Your-Collections',
+ 'title' => 'Your Collections',
+ 'content' => '<p>Edit the collection index to change this text</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:17',
+ 'modified' => '2008-11-05 12:49:17',
+ ),
+ array(
+ 'id' => '2',
+ 'node_id' => '2',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Collection-1',
+ 'title' => 'Collection 1',
+ 'content' => '<p>a collection of books</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:18',
+ ),
+ array(
+ 'id' => '3',
+ 'node_id' => '3',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Book-1',
+ 'title' => 'Book 1',
+ 'content' => '<p>a book about... 1</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:18',
+ ),
+ array(
+ 'id' => '4',
+ 'node_id' => '4',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-4',
+ 'title' => 'Section id 4',
+ 'content' => '<p>Section 4 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:18',
+ ),
+ array(
+ 'id' => '5',
+ 'node_id' => '5',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-5',
+ 'title' => 'Section id 5',
+ 'content' => '<p>Section 5 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:18',
+ ),
+ array(
+ 'id' => '6',
+ 'node_id' => '6',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-6',
+ 'title' => 'Section id 6',
+ 'content' => '<p>Section 6 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:18',
+ 'modified' => '2008-11-05 12:49:18',
+ ),
+ array(
+ 'id' => '7',
+ 'node_id' => '7',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-7',
+ 'title' => 'Section id 7',
+ 'content' => '<p>Section 7 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:19',
+ 'modified' => '2008-11-05 12:49:19',
+ ),
+ array(
+ 'id' => '8',
+ 'node_id' => '8',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-8',
+ 'title' => 'Section id 8',
+ 'content' => '<p>Section 8 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:19',
+ 'modified' => '2008-11-05 12:49:19',
+ ),
+ array(
+ 'id' => '9',
+ 'node_id' => '9',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-9',
+ 'title' => 'Section id 9',
+ 'content' => '<p>Section 9 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:19',
+ 'modified' => '2008-11-05 12:49:19',
+ ),
+ array(
+ 'id' => '10',
+ 'node_id' => '10',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-10',
+ 'title' => 'Section id 10',
+ 'content' => '<p>Section 10 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:20',
+ 'modified' => '2008-11-05 12:49:20',
+ ),
+ array(
+ 'id' => '11',
+ 'node_id' => '11',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-11',
+ 'title' => 'Section id 11',
+ 'content' => '<p>Section 11 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:20',
+ 'modified' => '2008-11-05 12:49:20',
+ ),
+ array(
+ 'id' => '12',
+ 'node_id' => '12',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-12',
+ 'title' => 'Section id 12',
+ 'content' => '<p>Section 12 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:21',
+ ),
+ array(
+ 'id' => '13',
+ 'node_id' => '13',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-13',
+ 'title' => 'Section id 13',
+ 'content' => '<p>Section 13 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:21',
+ ),
+ array(
+ 'id' => '14',
+ 'node_id' => '14',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-14',
+ 'title' => 'Section id 14',
+ 'content' => '<p>Section 14 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:21',
+ ),
+ array(
+ 'id' => '15',
+ 'node_id' => '15',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-15',
+ 'title' => 'Section id 15',
+ 'content' => '<p>Section 15 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:21',
+ 'modified' => '2008-11-05 12:49:21',
+ ),
+ array(
+ 'id' => '16',
+ 'node_id' => '16',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-16',
+ 'title' => 'Section id 16',
+ 'content' => '<p>Section 16 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:22',
+ ),
+ array(
+ 'id' => '17',
+ 'node_id' => '17',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-17',
+ 'title' => 'Section id 17',
+ 'content' => '<p>Section 17 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:22',
+ ),
+ array(
+ 'id' => '18',
+ 'node_id' => '18',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Book-2',
+ 'title' => 'Book 2',
+ 'content' => '<p>a book about... 2</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:22',
+ ),
+ array(
+ 'id' => '19',
+ 'node_id' => '19',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-19',
+ 'title' => 'Section id 19',
+ 'content' => '<p>Section 19 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:22',
+ 'modified' => '2008-11-05 12:49:22',
+ ),
+ array(
+ 'id' => '20',
+ 'node_id' => '20',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-20',
+ 'title' => 'Section id 20',
+ 'content' => '<p>Section 20 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:23',
+ 'modified' => '2008-11-05 12:49:23',
+ ),
+ array(
+ 'id' => '21',
+ 'node_id' => '21',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-21',
+ 'title' => 'Section id 21',
+ 'content' => '<p>Section 21 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:23',
+ 'modified' => '2008-11-05 12:49:23',
+ ),
+ array(
+ 'id' => '22',
+ 'node_id' => '22',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-22',
+ 'title' => 'Section id 22',
+ 'content' => '<p>Section 22 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:24',
+ 'modified' => '2008-11-05 12:49:24',
+ ),
+ array(
+ 'id' => '23',
+ 'node_id' => '23',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-23',
+ 'title' => 'Section id 23',
+ 'content' => '<p>Section 23 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:25',
+ 'modified' => '2008-11-05 12:49:25',
+ ),
+ array(
+ 'id' => '24',
+ 'node_id' => '24',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-24',
+ 'title' => 'Section id 24',
+ 'content' => '<p>Section 24 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:25',
+ 'modified' => '2008-11-05 12:49:25',
+ ),
+ array(
+ 'id' => '25',
+ 'node_id' => '25',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-25',
+ 'title' => 'Section id 25',
+ 'content' => '<p>Section 25 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:25',
+ 'modified' => '2008-11-05 12:49:25',
+ ),
+ array(
+ 'id' => '26',
+ 'node_id' => '26',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-26',
+ 'title' => 'Section id 26',
+ 'content' => '<p>Section 26 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:26',
+ 'modified' => '2008-11-05 12:49:26',
+ ),
+ array(
+ 'id' => '27',
+ 'node_id' => '27',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-27',
+ 'title' => 'Section id 27',
+ 'content' => '<p>Section 27 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:26',
+ 'modified' => '2008-11-05 12:49:26',
+ ),
+ array(
+ 'id' => '28',
+ 'node_id' => '28',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-28',
+ 'title' => 'Section id 28',
+ 'content' => '<p>Section 28 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:26',
+ 'modified' => '2008-11-05 12:49:26',
+ ),
+ array(
+ 'id' => '29',
+ 'node_id' => '29',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-29',
+ 'title' => 'Section id 29',
+ 'content' => '<p>Section 29 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:26',
+ 'modified' => '2008-11-05 12:49:26',
+ ),
+ array(
+ 'id' => '30',
+ 'node_id' => '30',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-30',
+ 'title' => 'Section id 30',
+ 'content' => '<p>Section 30 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:27',
+ 'modified' => '2008-11-05 12:49:27',
+ ),
+ array(
+ 'id' => '31',
+ 'node_id' => '31',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-31',
+ 'title' => 'Section id 31',
+ 'content' => '<p>Section 31 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:27',
+ 'modified' => '2008-11-05 12:49:27',
+ ),
+ array(
+ 'id' => '32',
+ 'node_id' => '32',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-32',
+ 'title' => 'Section id 32',
+ 'content' => '<p>Section 32 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:27',
+ 'modified' => '2008-11-05 12:49:27',
+ ),
+ array(
+ 'id' => '33',
+ 'node_id' => '33',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Collection-2',
+ 'title' => 'Collection 2',
+ 'content' => '<p>a collection of books</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:28',
+ 'modified' => '2008-11-05 12:49:28',
+ ),
+ array(
+ 'id' => '34',
+ 'node_id' => '34',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Book-1',
+ 'title' => 'Book 1',
+ 'content' => '<p>a book about... 1</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:28',
+ 'modified' => '2008-11-05 12:49:28',
+ ),
+ array(
+ 'id' => '35',
+ 'node_id' => '35',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-35',
+ 'title' => 'Section id 35',
+ 'content' => '<p>Section 35 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:28',
+ 'modified' => '2008-11-05 12:49:28',
+ ),
+ array(
+ 'id' => '36',
+ 'node_id' => '36',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-36',
+ 'title' => 'Section id 36',
+ 'content' => '<p>Section 36 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:29',
+ 'modified' => '2008-11-05 12:49:29',
+ ),
+ array(
+ 'id' => '37',
+ 'node_id' => '37',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-37',
+ 'title' => 'Section id 37',
+ 'content' => '<p>Section 37 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:29',
+ 'modified' => '2008-11-05 12:49:29',
+ ),
+ array(
+ 'id' => '38',
+ 'node_id' => '38',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-38',
+ 'title' => 'Section id 38',
+ 'content' => '<p>Section 38 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:29',
+ 'modified' => '2008-11-05 12:49:29',
+ ),
+ array(
+ 'id' => '39',
+ 'node_id' => '39',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-39',
+ 'title' => 'Section id 39',
+ 'content' => '<p>Section 39 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:30',
+ 'modified' => '2008-11-05 12:49:30',
+ ),
+ array(
+ 'id' => '40',
+ 'node_id' => '40',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-40',
+ 'title' => 'Section id 40',
+ 'content' => '<p>Section 40 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:30',
+ 'modified' => '2008-11-05 12:49:30',
+ ),
+ array(
+ 'id' => '41',
+ 'node_id' => '41',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-41',
+ 'title' => 'Section id 41',
+ 'content' => '<p>Section 41 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:30',
+ 'modified' => '2008-11-05 12:49:30',
+ ),
+ array(
+ 'id' => '42',
+ 'node_id' => '42',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-42',
+ 'title' => 'Section id 42',
+ 'content' => '<p>Section 42 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:31',
+ 'modified' => '2008-11-05 12:49:31',
+ ),
+ array(
+ 'id' => '43',
+ 'node_id' => '43',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-43',
+ 'title' => 'Section id 43',
+ 'content' => '<p>Section 43 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:31',
+ 'modified' => '2008-11-05 12:49:31',
+ ),
+ array(
+ 'id' => '44',
+ 'node_id' => '44',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-44',
+ 'title' => 'Section id 44',
+ 'content' => '<p>Section 44 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:31',
+ 'modified' => '2008-11-05 12:49:31',
+ ),
+ array(
+ 'id' => '45',
+ 'node_id' => '45',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-45',
+ 'title' => 'Section id 45',
+ 'content' => '<p>Section 45 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:32',
+ 'modified' => '2008-11-05 12:49:32',
+ ),
+ array(
+ 'id' => '46',
+ 'node_id' => '46',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-46',
+ 'title' => 'Section id 46',
+ 'content' => '<p>Section 46 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:32',
+ 'modified' => '2008-11-05 12:49:32',
+ ),
+ array(
+ 'id' => '47',
+ 'node_id' => '47',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-47',
+ 'title' => 'Section id 47',
+ 'content' => '<p>Section 47 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:32',
+ 'modified' => '2008-11-05 12:49:32',
+ ),
+ array(
+ 'id' => '48',
+ 'node_id' => '48',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-48',
+ 'title' => 'Section id 48',
+ 'content' => '<p>Section 48 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:33',
+ 'modified' => '2008-11-05 12:49:33',
+ ),
+ array(
+ 'id' => '49',
+ 'node_id' => '49',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Book-2',
+ 'title' => 'Book 2',
+ 'content' => '<p>a book about... 2</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:33',
+ 'modified' => '2008-11-05 12:49:33',
+ ),
+ array(
+ 'id' => '50',
+ 'node_id' => '50',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-50',
+ 'title' => 'Section id 50',
+ 'content' => '<p>Section 50 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:33',
+ 'modified' => '2008-11-05 12:49:33',
+ ),
+ array(
+ 'id' => '51',
+ 'node_id' => '51',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-51',
+ 'title' => 'Section id 51',
+ 'content' => '<p>Section 51 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:34',
+ 'modified' => '2008-11-05 12:49:34',
+ ),
+ array(
+ 'id' => '52',
+ 'node_id' => '52',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-52',
+ 'title' => 'Section id 52',
+ 'content' => '<p>Section 52 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:34',
+ 'modified' => '2008-11-05 12:49:34',
+ ),
+ array(
+ 'id' => '53',
+ 'node_id' => '53',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-53',
+ 'title' => 'Section id 53',
+ 'content' => '<p>Section 53 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:34',
+ 'modified' => '2008-11-05 12:49:34',
+ ),
+ array(
+ 'id' => '54',
+ 'node_id' => '54',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-54',
+ 'title' => 'Section id 54',
+ 'content' => '<p>Section 54 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:35',
+ 'modified' => '2008-11-05 12:49:35',
+ ),
+ array(
+ 'id' => '55',
+ 'node_id' => '55',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-55',
+ 'title' => 'Section id 55',
+ 'content' => '<p>Section 55 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:35',
+ 'modified' => '2008-11-05 12:49:35',
+ ),
+ array(
+ 'id' => '56',
+ 'node_id' => '56',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-56',
+ 'title' => 'Section id 56',
+ 'content' => '<p>Section 56 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:35',
+ 'modified' => '2008-11-05 12:49:35',
+ ),
+ array(
+ 'id' => '57',
+ 'node_id' => '57',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-57',
+ 'title' => 'Section id 57',
+ 'content' => '<p>Section 57 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:36',
+ 'modified' => '2008-11-05 12:49:36',
+ ),
+ array(
+ 'id' => '58',
+ 'node_id' => '58',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-58',
+ 'title' => 'Section id 58',
+ 'content' => '<p>Section 58 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:37',
+ 'modified' => '2008-11-05 12:49:37',
+ ),
+ array(
+ 'id' => '59',
+ 'node_id' => '59',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-59',
+ 'title' => 'Section id 59',
+ 'content' => '<p>Section 59 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:37',
+ 'modified' => '2008-11-05 12:49:37',
+ ),
+ array(
+ 'id' => '60',
+ 'node_id' => '60',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-60',
+ 'title' => 'Section id 60',
+ 'content' => '<p>Section 60 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:38',
+ 'modified' => '2008-11-05 12:49:38',
+ ),
+ array(
+ 'id' => '61',
+ 'node_id' => '61',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-61',
+ 'title' => 'Section id 61',
+ 'content' => '<p>Section 61 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:38',
+ 'modified' => '2008-11-05 12:49:38',
+ ),
+ array(
+ 'id' => '62',
+ 'node_id' => '62',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-62',
+ 'title' => 'Section id 62',
+ 'content' => '<p>Section 62 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:39',
+ 'modified' => '2008-11-05 12:49:39',
+ ),
+ array(
+ 'id' => '63',
+ 'node_id' => '63',
+ 'under_node_id' => '',
+ 'after_node_id' => '',
+ 'status' => 'current',
+ 'user_id' => 1,
+ 'lang' => 'en',
+ 'slug' => 'Section-id-63',
+ 'title' => 'Section id 63',
+ 'content' => '<p>Section 63 content</p>',
+ 'type' => '',
+ 'reason' => '',
+ 'flags' => '',
+ 'created' => '2008-11-05 12:49:40',
+ 'modified' => '2008-11-05 12:49:40',
+ ),
+ );
+}
+?>
\ No newline at end of file
diff --git a/tests/fixtures/user_fixture.php b/tests/fixtures/user_fixture.php
new file mode 100644
index 0000000..aef2030
--- /dev/null
+++ b/tests/fixtures/user_fixture.php
@@ -0,0 +1,85 @@
+<?php
+/* SVN FILE: $Id: user_fixture.php 693 2008-11-05 13:01:32Z AD7six $ */
+/**
+ * Short description for user_fixture.php
+ *
+ * Long description for user_fixture.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ * @since v 1.0
+ * @version $Revision: 693 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 14:01:32 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * UserFixture class
+ *
+ * @package cookbook
+ * @subpackage cookbook.tests.fixtures
+ */
+class UserFixture extends CakeTestFixture {
+/**
+ * name property
+ *
+ * @var string 'User'
+ * @access public
+ */
+ var $name = 'User';
+/**
+ * fields property
+ *
+ * @var array
+ * @access public
+ */
+ var $fields = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'group_id' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 10),
+ 'level_id' => array('type'=>'integer', 'null' => false, 'default' => '0', 'length' => 10),
+ 'realname' => array('type'=>'string', 'null' => false),
+ 'username' => array('type'=>'string', 'null' => false, 'key' => 'unique'),
+ 'email' => array('type'=>'string', 'null' => false, 'key' => 'unique'),
+ 'psword' => array('type'=>'string', 'null' => false),
+ 'temppassword' => array('type'=>'string', 'null' => false),
+ 'tos' => array('type'=>'boolean', 'null' => false, 'default' => '0'),
+ 'mail_comments' => array('type'=>'boolean', 'null' => false, 'default' => '1'),
+ 'email_authenticated' => array('type'=>'boolean', 'null' => true, 'default' => NULL),
+ 'email_token' => array('type'=>'string', 'null' => false, 'length' => 45),
+ 'email_token_expires' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
+ 'display_name' => array('type'=>'boolean', 'null' => true, 'default' => '0'),
+ 'indexes' => array(
+ 'PRIMARY' => array('column' => 'id', 'unique' => 1),
+ 'USERNAME_UNIQUE_INDEX' => array('column' => 'username', 'unique' => 1),
+ 'EMAIL_UNIQUE_INDEX' => array('column' => 'email', 'unique' => 1)
+ ));
+/**
+ * records property
+ *
+ * @var array
+ * @access public
+ */
+ var $records = array(
+ array(
+ 'id' => '1',
+ 'group_id' => '1',
+ 'level_id' => '1',
+ 'realname' => 'Test User',
+ 'username' => 'test',
+ 'email' => 'test@example.com',
+ ),
+ );
+}
+?>
\ No newline at end of file
diff --git a/tests/groups/empty b/tests/groups/empty
new file mode 100755
index 0000000..e69de29
diff --git a/vendors/highlight.php b/vendors/highlight.php
new file mode 100644
index 0000000..8095262
--- /dev/null
+++ b/vendors/highlight.php
@@ -0,0 +1,160 @@
+<?php
+/* SVN FILE: $Id: highlight.php 689 2008-11-05 10:30:07Z AD7six $ */
+
+/**
+ * Class to style php code as an ordered list.
+ *
+ * Originally from http://shiflett.org/blog/oct/formatting-and-highlighting-php-code-listings
+ * Some minor modifications to allow it to work with php4.
+ *
+ * PHP versions 4 and 5
+ *
+ * @filesource
+ * @package vendors
+ * @since Noswad site version 3
+ * @version $Revision: 689 $
+ * @created 26/01/2007
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ */
+
+/*
+ * Default CSS to follow:
+ body {
+ margin: 2em;
+ padding: 0;
+ border: 0;
+ font: 1em verdana, helvetica, sans-serif;
+ color: #000;
+ background: #fff;
+ text-align: center;
+ }
+ ol.code {
+ width: 90%;
+ margin: 0 5%;
+ padding: 0;
+ font-size: 0.75em;
+ line-height: 1.8em;
+ overflow: hidden;
+ color: #939399;
+ text-align: left;
+ list-style-position: inside;
+ border: 1px solid #d3d3d0;
+ }
+ ol.code li {
+ float: left;
+ clear: both;
+ width: 99%;
+ white-space: nowrap;
+ margin: 0;
+ padding: 0 0 0 1%;
+ background: #fff;
+ }
+ ol.code li.even { background: #f3f3f0; }
+ ol.code li code {
+ font: 1.2em courier, monospace;
+ color: #c30;
+ white-space: pre;
+ padding-left: 0.5em;
+ }
+ .code .comment { color: #939399; }
+ .code .default { color: #44c; }
+ .code .keyword { color: #373; }
+ .code .string { color: #c30; }
+ */
+class highlight {
+
+ function highlight () {
+ $this->__construct();
+ }
+
+ function __construct() {
+ ini_set('highlight.comment', 'comment');
+ ini_set('highlight.default', 'default');
+ ini_set('highlight.keyword', 'keyword');
+ ini_set('highlight.string', 'string');
+ ini_set('highlight.html', 'html');
+ }
+
+ function process($code= "") {
+ $code= highlight_string($code, TRUE);
+ /* Clean Up */
+ if (phpversion() >= 5) {
+ $code= substr($code, 33, -15);
+ $code= str_replace('<span style="color: ', '<span class="', $code);
+ } else {
+ $code= substr($code, 25, -15);
+ $code= str_replace('<font color=', '<span class=', $code);
+ $code= str_replace('</font>', '</span>', $code);
+ }
+ $code= str_replace(' ', ' ', $code);
+ $code= str_replace('&', '&', $code);
+ $code= str_replace('<br />', "\n", $code);
+ $code= trim($code);
+
+ /* Normalize Newlines */
+ $code= str_replace("\r", "\n", $code);
+ $code= preg_replace("!\n\n+!", "\n", $code);
+
+ $lines= explode("\n", $code);
+ while(strip_tags($lines[count($lines) -1]) == '') {
+ $lines[count($lines) -2] .= $lines[count($lines) -1];
+ unset($lines[count($lines) -1]);
+ }
+
+ /* Previous Style */
+ $previous= FALSE;
+
+ /* Output Listing */
+ $return= " <ol class=\"code\">\n";
+ foreach ($lines as $key => $line) {
+ if (substr($line, 0, 7) == '</span>') {
+ $previous= FALSE;
+ $line= substr($line, 7);
+ }
+
+ if (empty ($line)) {
+ $line= ' ';
+ }
+
+ if ($previous) {
+ $line= "<span class=\"$previous\">" . $line;
+ }
+
+ /* Set Previous Style */
+ if (strpos($line, '<span') !== FALSE) {
+ switch (substr($line, strrpos($line, '<span') + 13, 1)) {
+ case 'c' :
+ $previous= 'comment';
+ break;
+ case 'd' :
+ $previous= 'default';
+ break;
+ case 'k' :
+ $previous= 'keyword';
+ break;
+ case 's' :
+ $previous= 'string';
+ break;
+ }
+ }
+
+ /* Unset Previous Style Unless Span Continues */
+ if (substr($line, -7) == '</span>') {
+ $previous= FALSE;
+ }
+ elseif ($previous) {
+ $line .= '</span>';
+ }
+
+ if ($key % 2) {
+ $return .= " <li class=\"even\"><code>$line</code></li>\n";
+ } else {
+ $return .= " <li><code>$line</code></li>\n";
+ }
+ }
+ $return .= " </ol>\n";
+ return $return;
+ }
+}
+?>
\ No newline at end of file
diff --git a/vendors/shells/search.php b/vendors/shells/search.php
new file mode 100644
index 0000000..92144b8
--- /dev/null
+++ b/vendors/shells/search.php
@@ -0,0 +1,221 @@
+<?php
+/* SVN FILE: $Id: search.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for search.php
+ *
+ * Long description for search.php
+ *
+ * PHP 5
+ *
+ * Copyright (c) 2008, Marcin Domanski
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Marcin Domanski
+ * @link www.kabturek.info
+ * @package
+ * @subpackage projects.cookbook.models.behaviors
+ * @since v 0.1
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * SearchShell class
+ *
+ * @uses Shell
+ * @package cookbook
+ * @subpackage cookbook.vendors.shells
+ */
+class SearchShell extends Shell {
+/**
+ * main method
+ *
+ * @return void
+ * @access public
+ */
+ function main() {
+ $this->help();
+ }
+/**
+ * startup method
+ *
+ * @return void
+ * @access public
+ */
+ function startup() {
+ if (!function_exists('iconv')) {
+ function iconv($inCharset, $outCharset, $string) {
+ return $string;
+ }
+ function iconv_strlen($string) {
+ //return strlen(utf8_decode($string));
+ return mb_strlen($string, 'UTF-8');
+ }
+ function iconv_substr($string, $offset, $length) {
+ return mb_substr($string, $start, $length, 'UTF-8');
+ }
+ }
+ if(function_exists('ini_set')){
+ ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . APP. 'vendors');
+ }
+ App::import('Vendor', 'Zend/Search/Lucene', array('file' => 'Zend/Search/Lucene.php'));
+ config('lucene');
+ if(class_exists('Lucene_Config')) {
+ $this->config = &new Lucene_Config;
+ }
+ $config = 'default';
+ if(!empty($this->params['config'])){
+ $config = $this->params['config'];
+ }
+ $this->settings = $this->config->{$config};
+ $this->index_file = TMP.$this->settings['index_file'];
+ unset($this->settings['index_file']);
+
+ }
+/**
+ * help method
+ *
+ * @return void
+ * @access public
+ */
+ function help() {
+ $this->out("The Search Shell gives the ability to build and search the index.");
+ $this->hr();
+ $this->out("Usage: cake schema <command> <arg1> <arg2>...");
+ $this->hr();
+ $this->out('Params:');
+ $this->out("\n\t-connection <config>\n\t\tset db config <config>. uses 'default' if none is specified");
+ $this->out('Commands:');
+ $this->out("\n\tsearch help\n\t\tshows this help message.");
+ $this->out("\n\tsearch build_index\n\t\tinitilize the index.");
+ $this->out("\n\tsearch query\n\t\tquery the index.");
+ $this->out("\n\tsearch optimize\n\t\toptimizes the index.");
+ $this->out("");
+ $this->stop();
+ }
+/**
+ * stop method
+ *
+ * @return void
+ * @access public
+ */
+ function stop(){
+ return true;
+ }
+
+/**
+ * rebuild the index
+ *
+ * @access public
+ * @return void
+ */
+ function build_index(){
+ $index = $this->__open(true);
+ // uncomment to get faster indexing time (uses much ram and cpu)
+ $index->setMergeFactor(2000);
+ $index->setMaxBufferedDocs(500);
+ $this->out('Building search index...');
+ $this->out('This may take a while depending on the db size.');
+ $start = time();
+ foreach($this->settings as $model => $model_options){
+ App::import('Model', $model);
+ $model = new $model();
+ if(empty($model_options['find_options'])) {
+ $model_options['find_options'] = array();
+ }
+
+ if(method_exists($model,'find_index')){
+ $results = $model->find_index('all', $model_options['find_options']);
+ } else {
+ $results = $model->find('all', $model_options['find_options']);
+ }
+ $this->log($model->name.' find time: '.(time()-$start));
+ $start = time();
+
+ $count = count($results);
+ $i =1;
+ foreach($results as $result){
+ $this->out($model->name.' :'.printf("%.1f", $i/$count * 100));
+ $i++;
+
+ $doc = new Zend_Search_Lucene_Document();
+
+ // add the model field
+ $doc->addField(Zend_Search_Lucene_Field::Keyword('cake_model', $model->name, 'utf-8'));
+ foreach($model_options['fields'] as $field_name => $options){
+ if(!empty($options['prepare']) && function_exists($options['prepare'])){
+ $result[$model->name][$field_name] = call_user_func($options['prepare'], $result[$model->name][$field_name]);
+ }
+ $alias = !empty($options['alias']) ? $options['alias'] : $field_name;
+ $doc->addField(Zend_Search_Lucene_Field::$options['type']($alias, $result[$model->name][$field_name], 'utf-8'));
+ }
+ $index->addDocument($doc);
+ }
+ $this->log($model->name.' adding time: '.(time()-$start));
+ $start = time();
+ }
+ $this->optimize($index);
+ $index->commit();
+ $this->log('Optimize+commit time: '.(time()-$start));
+ }
+/**
+ * query function
+ *
+ * @access public
+ * @return void
+ */
+ function query(){
+ $query = implode(' ',$this->args);
+ $index = $this->__open(false);
+ $hits = $index->find($query);
+ $this->out('Query results:');
+ $this->out("Score\tModel\tID\tTitle");
+ foreach ($hits as $hit) {
+ $this->out(printf("%.2f",$hit->score)."\t".$hit->cake_model." \t".$hit->cake_id."\t".$hit->title);
+ $this->out('id '.$hit->id);
+ }
+ }
+
+
+/**
+ * optimizes the index
+ * TODO: add some loggin/printing
+ *
+ * @param mixed $index
+ * @access public
+ * @return void
+ */
+ function optimize($index = null){
+ if(empty($index)){
+ $index = $this->__open(false);
+ }
+ $this->out("Optimizing index...");
+ $index->optimize();
+ $this->out("Optimized index.");
+ }
+/**
+ * opens or creates an index
+ *
+ * @param bool $create
+ * @access private
+ * @return void
+ */
+ function __open($create = false){
+ try {
+ return Zend_Search_Lucene::open($this->index_file);
+ } catch (Zend_Search_Lucene_Exception $e) {
+ if($create){
+ try {
+ return Zend_Search_Lucene::create($this->index_file);
+ } catch(Zend_Search_Lucene_Exception $e) {
+ echo "Unable to create the index: ". $e->getMessage();
+ }
+ } else {
+ echo "Unable to open the index: ". $e->getMessage();
+ }
+ }
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/vendors/shells/templates/views/index.ctp b/vendors/shells/templates/views/index.ctp
new file mode 100755
index 0000000..0978160
--- /dev/null
+++ b/vendors/shells/templates/views/index.ctp
@@ -0,0 +1,66 @@
+<?php /* SVN FILE: $Id: index.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+echo '<?php /* SVN FILE: $Id' . '$ */ ?>' . "\r\n";
+$singularHumanName = Inflector::Humanize( Inflector::Underscore($singularHumanName));
+$pluralHumanName = Inflector::Humanize( Inflector::Underscore($pluralHumanName));
+$namedString = implode ('\', \'', $fields);
+$singularVar[0] = up($singularVar[0]);
+$keyFields = array();
+if (isset($associations['belongsTo'])) {
+ foreach ($associations['belongsTo'] as $alias => $details) {
+ $keyFields[$details['foreignKey']] = array(
+ 'alias' => $alias,
+ 'displayField' => $details['displayField'],
+ 'foreignKey' => $details['foreignKey']
+ );
+ }
+}
+?>
+<h1><?php echo $pluralHumanName; ?></h1>
+<div class="container">
+<?php echo "<?php\r\n"; ?>
+<?php //echo "\$pass = \$this->passedArgs;\r\n"; ?>
+<?php echo "\$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', \$this->action); // temp\r\n"; ?>
+<?php echo "\$paginator->options(array('url' => \$pass));\r\n"; ?>
+<?php echo "?>\r\n"; ?>
+<table>
+<?php echo "<?php\r\n"; ?>
+<?php echo "\$th = array(\r\n"; ?>
+<?php foreach ($fields as $field): ?>
+<?php if (isset($keyFields[$field])) : ?>
+ <?php echo "\$paginator->sort('" . Inflector::Humanize( Inflector::Underscore($keyFields[$field]['alias'])) . "', '{$keyFields[$field]['alias']}.{$keyFields[$field]['displayField']}'),\r\n"; ?>
+<?php elseif(!in_array($schema[$field]['type'], array('text')) && !in_array($field, array('password', 'deleted', 'slug', 'temppassword', 'email_token'))) : ?>
+ <?php echo "\$paginator->sort('{$field}'),\r\n"; ?>
+<?php endif; ?>
+<?php endforeach; ?>
+ <?php echo "'actions'\r\n"; ?>
+<?php echo ");\r\n"; ?>
+<?php echo "echo \$html->tableHeaders(\$th);\r\n"; ?>
+<?php echo "foreach (\$data as \$row) {\r\n"; ?>
+ <?php echo "extract(\$row);\r\n" ?>
+ <?php echo "\$actions = array();\r\n" ?>
+ <?php echo "\$actions[] = \$html->link('V', array('action' => 'view', \${$modelClass}['id']), array('title' => 'view'));\r\n" ?>
+ <?php echo "\$actions[] = \$html->link('E', array('action' => 'edit', \${$modelClass}['id']), array('title' => 'edit'));\r\n" ?>
+ <?php echo "\$actions[] = \$html->link('X', array('action' => 'delete', \${$modelClass}['id']), array('title' => 'delete'));\r\n" ?>
+ <?php echo "\$actions = implode(' - ', \$actions);\r\n" ?>
+ <?php echo "\$tr = array(\r\n"; ?>
+<?php foreach ($fields as $field): ?>
+<?php if (isset($keyFields[$field])): ?>
+ <?php echo "\${$keyFields[$field]['alias']}?\$html->link(\${$keyFields[$field]['alias']}['{$keyFields[$field]['displayField']}'], am(\$pass, array('page' => 1, '$field' => \${$modelClass}['$field']))):'',\r\n"; ?>
+<?php elseif ($field == $primaryKey || $field == $displayField) : ?>
+ <?php echo "\$html->link(\${$modelClass}['$field'], array('action' => 'view', \${$modelClass}['$primaryKey'])),\r\n"; ?>
+<?php elseif (!in_array($schema[$field]['type'], array('text')) && !in_array($field, array('password', 'deleted', 'slug', 'temppassword', 'email_token'))) : ?>
+<?php if (in_array($field, array('ip', 'signup_ip'))) : ?>
+ <?php echo "\$html->link(long2ip(\${$modelClass}['$field']), am(\$pass, array('page' => 1, '$field' => \${$modelClass}['$field']))),\r\n"; ?>
+<?php else : ?>
+ <?php echo "\$html->link(\${$modelClass}['$field'], am(\$pass, array('page' => 1, '$field' => \${$modelClass}['$field']))),\r\n"; ?>
+<?php endif; ?>
+<?php endif; ?>
+<?php endforeach; ?>
+ <?php echo "\$actions\r\n"; ?>
+ <?php echo ");\r\n"; ?>
+ <?php echo "echo \$html->tableCells(\$tr, array('class' => 'odd'), array('class' => 'even'));\r\n"; ?>
+<?php echo "}\r\n"; ?>
+<?php echo "?>\r\n"; ?>
+</table>
+<?php echo "<?php echo \$this->element('paging'); ?>"; ?>
+</div>
\ No newline at end of file
diff --git a/views/attachments/admin_add.ctp b/views/attachments/admin_add.ctp
new file mode 100644
index 0000000..fbe6a94
--- /dev/null
+++ b/views/attachments/admin_add.ctp
@@ -0,0 +1,11 @@
+<?php /* SVN FILE: $Id: admin_add.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+echo $form->create(null, array('id' => 'image_upload', 'type' => 'file', 'url' => '/' . $this->params['url']['url']));
+$inputs = array(
+ 'legend' => 'Upload a file/image',
+ 'filename' => array ('type' => 'file'),
+ 'description' => array ()
+);
+echo $form->inputs($inputs);
+echo $form->submit('upload');
+echo $form->end();
+?>
\ No newline at end of file
diff --git a/views/attachments/admin_edit.ctp b/views/attachments/admin_edit.ctp
new file mode 100755
index 0000000..1d0ac8c
--- /dev/null
+++ b/views/attachments/admin_edit.ctp
@@ -0,0 +1,10 @@
+<?php /* SVN FILE: $Id: admin_edit.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<?php
+$action = in_array($this->action, array('add', 'admin_add'))?'Add':'Edit';
+$action = Inflector::humanize($action);
+echo $form->create();
+echo $form->inputs(array(
+ 'legend' => $action . ' Image',
+));
+echo $form->end('Submit');
+?>
\ No newline at end of file
diff --git a/views/attachments/admin_import.ctp b/views/attachments/admin_import.ctp
new file mode 100755
index 0000000..89b2445
--- /dev/null
+++ b/views/attachments/admin_import.ctp
@@ -0,0 +1,11 @@
+<?php /* SVN FILE: $Id: admin_import.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+echo $form->create(null, array('type' => 'file', 'url' => '/' . $this->params['url']['url']));
+echo $form->inputs(array(
+ 'legend' => 'Import files from another install, or restore to a previous backup',
+ 'file' => array('type' => 'file', 'label' => 'XML file to import'),
+ 'backup' => array('options' => $backups, 'label' => 'Or choose a previous backup', 'empty' => true),
+ 'take_backup' => array('type' => 'checkbox', 'checked' => 'checked', 'label' => 'Backup current attachments before import?'),
+));
+echo $form->submit();
+echo $form->end();
+?>
diff --git a/views/attachments/admin_index.ctp b/views/attachments/admin_index.ctp
new file mode 100755
index 0000000..dd6eec9
--- /dev/null
+++ b/views/attachments/admin_index.ctp
@@ -0,0 +1,46 @@
+<?php /* SVN FILE: $Id: admin_index.ctp 666 2008-09-23 18:03:17Z AD7six $ */ ?>
+<h2>All Attachments</h2>
+<table>
+<?php
+$pass = $this->passedArgs;
+$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', $this->action); // temp
+$paginator->options(array('url' => $pass));
+$th = array(
+ $paginator->sort('id'),
+ $paginator->sort('pic'),
+ $paginator->sort('size', 'filesize'),
+ $paginator->sort('checksum'),
+ 'associated',
+);
+echo $html->tableHeaders($th);
+foreach ($data as $row) {
+ extract($row['Attachment']);
+ if (isset($row[$class])) {
+ $other = $row['Attachment']['class'];
+ $otherController = Inflector::pluralize($other);
+ if (isset($row[$other]['id'])) {
+ $associated = $html->link($row[$other]['display_field'],array('admin' => false, 'controller' => low($otherController), 'action' => 'view', $row[$other]['id']));
+ } else {
+ $associated = 'none';
+ }
+ } else {
+ $associated = 'none';
+ }
+ if ($width && $height) {
+ $size = $width . ' x ' . $height;
+ } else {
+ $size = $number->toReadableSize($filesize);
+ }
+ $thumb = $html->image('/img' . $versions['thumb'], array('alt' => 'filename: ' . $filename . ' ' . $description));
+ $tr = array (
+ $html->link($id, array('action' => 'view', $id)),
+ $html->link($thumb, array('action' => 'view', $id), null, null, false),
+ $size,
+ $checksum,
+ $associated,
+ );
+ echo $html->tableCells($tr);
+}
+?>
+</table>
+<?php echo $this->element('paging'); ?>
\ No newline at end of file
diff --git a/views/attachments/admin_view.ctp b/views/attachments/admin_view.ctp
new file mode 100755
index 0000000..5171b77
--- /dev/null
+++ b/views/attachments/admin_view.ctp
@@ -0,0 +1,24 @@
+<?php /* SVN FILE: $Id: admin_view.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<h2>Image</h2>
+<table>
+<?php
+ extract($data);
+ echo $html->tableCells(array('id',$Image['id']));
+ echo $html->tableCells(array('User',$User?$html->link($User['first_name'], array('controller' => 'users', 'action' => 'view', $Image['user_id'])):''));
+ echo $html->tableCells(array('class',$Image['class']));
+ echo $html->tableCells(array('foreign_id',$Image['foreign_id']));
+ echo $html->tableCells(array('original',$Image['original']));
+ echo $html->tableCells(array('filename',$Image['filename']));
+ echo $html->tableCells(array('dir',$Image['dir']));
+ echo $html->tableCells(array('mimetype',$Image['mimetype']));
+ echo $html->tableCells(array('filesize',$Image['filesize']));
+ echo $html->tableCells(array('height',$Image['height']));
+ echo $html->tableCells(array('width',$Image['width']));
+ echo $html->tableCells(array('thumb',$Image['thumb']));
+ echo $html->tableCells(array('description',$Image['description']));
+ echo $html->tableCells(array('slug',$Image['slug']));
+ echo $html->tableCells(array('status',$Image['status']));
+ echo $html->tableCells(array('created',$Image['created']));
+ echo $html->tableCells(array('modified',$Image['modified']));
+?>
+</table>
\ No newline at end of file
diff --git a/views/attachments/xml/admin_export.ctp b/views/attachments/xml/admin_export.ctp
new file mode 100644
index 0000000..7582a5d
--- /dev/null
+++ b/views/attachments/xml/admin_export.ctp
@@ -0,0 +1,12 @@
+<?php /* SVN FILE: $Id: admin_export.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+$uuid = String::uuid();
+$from = array_pop(explode('-', $uuid));
+$content = $xml->serialize(array('meta' => array('from' => $from, 'on' => date('Y-m-d H:i'))), array('format' => 'tags'));
+foreach ($data as $row) {
+ extract($row);
+ extract($Attachment);
+ $Attachment['source'] = base64_encode(file_get_contents(APP . 'uploads' . DS . $dir . DS . $filename));
+ $content .= $xml->serialize(array('Attachment' => $Attachment), array('format' => 'tags'));
+}
+echo $xml->elem('contents', null, $content);
+?>
diff --git a/views/changes/index.ctp b/views/changes/index.ctp
new file mode 100644
index 0000000..0607b49
--- /dev/null
+++ b/views/changes/index.ctp
@@ -0,0 +1,48 @@
+<?php /* SVN FILE: $Id: index.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<div class="container">
+<h2>Change Log</h2>
+<ul id="results">
+<?php
+$pass = $this->passedArgs;
+$paginator->options(array('url' => $pass));
+foreach ($data as $row) {
+ extract($row);
+ echo '<li>';
+ if (in_array($Revision['status'], array('current', 'previous'))) {
+ echo '<h3>' . $html->link($Revision['title'], array('controller' => 'revisions', 'action' => 'view', $Revision['id'])) . '</h3>';
+ } else {
+ echo '<h3>' . $Revision['title'] . '</h3>';
+ }
+ echo '<ul>';
+ if ($Change['status_from'] == 'new') {
+ echo '<li>' . sprintf(__('change submitted by %s, %s', true),
+ isset($User['username'])?$User['username']:'unknown',
+ $time->niceShort($Change['created'])) . '</li>';
+ } else {
+ switch ($Change['status_to']) {
+ case 'accepted';
+ $to = __('accepted', true);
+ break;
+ case 'rejected';
+ $to = __('not accepted', true);
+ break;
+ case 'pending';
+ $to = __('pending', true);
+ break;
+ default:
+ $to = __($Change['status_to'], true);
+ }
+ echo '<li>' . sprintf(__('changed from %s to %s by %s, %s', true),
+ $Change['status_from'],
+ $to,
+ isset($User['username'])?$User['username']:'unknown',
+ $time->niceShort($Change['created'])) . '</li>';
+ $author = isset($Author['username'])?$Author['username']:'unknown';
+ echo '<li>' . sprintf(__('submitted by %s', true), $author) . '</li>';
+ }
+ echo '<li>' . $html->clean($Change['comment']) . '</li>';
+ echo '</ul></li>';
+}
+?>
+</ul>
+<?php echo $this->element('paging'); ?></div>
\ No newline at end of file
diff --git a/views/changes/rss/index.ctp b/views/changes/rss/index.ctp
new file mode 100755
index 0000000..4103a9e
--- /dev/null
+++ b/views/changes/rss/index.ctp
@@ -0,0 +1,44 @@
+<?php /* SVN FILE: $Id: index.ctp 638 2008-09-01 23:24:26Z AD7six $ */
+ transformRSS (null, $html);
+ echo $rss->items($data, 'transformRSS');
+
+ function transformRSS($row, &$_html = false) {
+ static $html;
+ if ($_html) {
+ $html = $_html;
+ return;
+ }
+ extract($row);
+ switch ($Change['status_to']) {
+ case 'accepted';
+ $to = __('accepted', true);
+ break;
+ case 'rejected';
+ $to = __('not accepted', true);
+ break;
+ case 'pending';
+ $to = __('pending', true);
+ break;
+ default:
+ $to = __($Change['status_to'], true);
+ }
+
+ $desc = '<ul>';
+ $author = isset($Author['username'])?$Author['username']:'unknown';
+ $desc .= '<li>Submitted by: ' . $author . '</li>';
+ if ($Change['status_to'] != 'pending'){
+ $desc .= '<li>Changed by: ' . $User['username'] . '</li>';
+ }
+ $comment = $html->clean(trim($Change['comment']));
+ if ($comment) {
+ $desc .= '<li>' . $comment . '</li>';
+ }
+ $desc .= '</ul>';
+ return array(
+ 'title' => $to . ' - ' . $Revision['title'],
+ 'link' => array('controller' => 'revisions', 'action' => 'view', $Revision['id'], $Revision['slug']),
+ 'description' => $desc,
+ 'pubDate' => date('r', strtotime($Change['created'])),
+ );
+ }
+?>
\ No newline at end of file
diff --git a/views/comments/add.ctp b/views/comments/add.ctp
new file mode 100644
index 0000000..e9ad2b2
--- /dev/null
+++ b/views/comments/add.ctp
@@ -0,0 +1,14 @@
+<?php /* SVN FILE: $Id: add.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<div class="comment">
+<?php
+echo $form->create('Comment',array('url' => '/' . $this->params['url']['url']));
+echo $form->inputs(array (
+ 'legend' => sprintf(__('Comment: %s', true), $node['Revision']['title']),
+ 'parent_id' => array('type' => 'hidden'),
+ 'title',
+ 'body' => array ('cols' => 100, 'rows' => 10)
+));
+echo $form->submit('save');
+echo $form->end();
+?>
+</div>
\ No newline at end of file
diff --git a/views/comments/admin_edit.ctp b/views/comments/admin_edit.ctp
new file mode 100644
index 0000000..2cd4f0e
--- /dev/null
+++ b/views/comments/admin_edit.ctp
@@ -0,0 +1,23 @@
+<?php /* SVN FILE: $Id: admin_edit.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<h1>Comments - edit Comment </h1>
+<div class="form-container">
+<?php
+$action = in_array($this->action, array('add', 'admin_add'))?'Add':'Edit';
+$action = Inflector::humanize($action);
+echo $form->create();
+echo $form->inputs(array(
+ 'legend' => false,
+ 'id',
+ 'node_id' => array('empty' => true),
+ 'user_id' => array('empty' => true),
+ 'class',
+ 'lang',
+ 'title',
+ 'author',
+ 'email',
+ 'url',
+ 'body',
+ 'published',
+));
+echo $form->end('Submit');
+?></div>
\ No newline at end of file
diff --git a/views/comments/admin_index.ctp b/views/comments/admin_index.ctp
new file mode 100644
index 0000000..56b191b
--- /dev/null
+++ b/views/comments/admin_index.ctp
@@ -0,0 +1,59 @@
+<?php /* SVN FILE: $Id: admin_index.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<h1>Comments</h1>
+<div class="container">
+<?php
+$pass = $this->passedArgs;
+$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', $this->action); // temp
+$paginator->options(array('url' => $pass));
+?>
+<table>
+<?php
+$th = array(
+ 'Book',
+ $paginator->sort('Section', 'Node.sequence'),
+ $paginator->sort('Comment Title', 'title'),
+ $paginator->sort('lang'),
+ $paginator->sort('User', 'User.username'),
+ $paginator->sort('email'),
+ $paginator->sort('published'),
+ $paginator->sort('created'),
+ 'actions'
+);
+echo $html->tableHeaders($th);
+foreach ($data as $row) {
+ extract($row);
+ $collection = $book = '-';
+ foreach ($collections as $c) {
+ if ($c['Node']['lft'] <= $Node['lft'] && $c['Node']['rght'] >= $Node['rght']) {
+ $collection = $html->link($c['Revision']['title'], am($pass, array('restrict_to' => $c['Node']['id'])));
+ $collection = $html->link($c['Revision']['title'], array('restrict_to' => $c['Node']['id']));
+ break;
+ }
+ }
+ foreach ($books as $b) {
+ if ($b['Node']['lft'] <= $Node['lft'] && $b['Node']['rght'] >= $Node['rght']) {
+ $book = $html->link($b['Revision']['title'], am($pass, array('restrict_to' => $b['Node']['id'])));
+ break;
+ }
+ }
+ $actions = array();
+ $actions[] = $html->link('V', array('action' => 'view', $Comment['id']), array('title' => 'view'));
+ $actions[] = $html->link('E', array('action' => 'edit', $Comment['id']), array('title' => 'edit'));
+ $actions[] = $html->link('X', array('action' => 'delete', $Comment['id']), array('title' => 'delete'));
+ $actions = implode(' - ', $actions);
+ $tr = array(
+ $book . ' (' . $collection . ')',
+ $Node?$html->link($Node['sequence'] . ' ' . $Revision['title'], am($pass, array('page' => 1, 'node_id' => $Comment['node_id']))):'',
+ $html->link($Comment['title'], array('action' => 'view', $Comment['id'])),
+ $html->link($Comment['lang'], am($pass, array('page' => 1, 'lang' => $Comment['lang']))),
+ $User?$html->link($User['username'], am($pass, array('page' => 1, 'user_id' => $Comment['user_id']))):'',
+ $html->link($Comment['email'], am($pass, array('page' => 1, 'email' => $Comment['email']))),
+ $html->link($Comment['published'], am($pass, array('page' => 1, 'published' => $Comment['published']))),
+ $html->link($Comment['created'], am($pass, array('page' => 1, 'created' => $Comment['created']))),
+ $actions
+ );
+ echo $html->tableCells($tr, array('class' => 'odd'), array('class' => 'even'));
+}
+?>
+</table>
+<?php echo $this->element('paging'); ?></div>
\ No newline at end of file
diff --git a/views/comments/admin_view.ctp b/views/comments/admin_view.ctp
new file mode 100644
index 0000000..c4bbb62
--- /dev/null
+++ b/views/comments/admin_view.ctp
@@ -0,0 +1,20 @@
+<?php /* SVN FILE: $Id: admin_view.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<h2>Comment</h2>
+<table>
+<?php
+ extract($data);
+ echo $html->tableCells(array('id',$Comment['id']));
+ echo $html->tableCells(array('Node', $Node?$html->link($Node['sequence'], array('controller' => 'nodes', 'action' => 'view', $Comment['node_id'])):''));
+ echo $html->tableCells(array('User', $User?$html->link($User['username'], array('controller' => 'users', 'action' => 'view', $Comment['user_id'])):''));
+ echo $html->tableCells(array('class',$Comment['class']));
+ echo $html->tableCells(array('lang',$Comment['lang']));
+ echo $html->tableCells(array('title',$Comment['title']));
+ echo $html->tableCells(array('author',$Comment['author']));
+ echo $html->tableCells(array('email',$Comment['email']));
+ echo $html->tableCells(array('url',$Comment['url']));
+ echo $html->tableCells(array('body',$Comment['body']));
+ echo $html->tableCells(array('published',$Comment['published']));
+ echo $html->tableCells(array('created',$Comment['created']));
+ echo $html->tableCells(array('modified',$Comment['modified']));
+?>
+</table>
\ No newline at end of file
diff --git a/views/comments/index.ctp b/views/comments/index.ctp
new file mode 100644
index 0000000..6428ceb
--- /dev/null
+++ b/views/comments/index.ctp
@@ -0,0 +1,21 @@
+<?php /* SVN FILE: $Id: index.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<h2><?php
+if (isset($node)) {
+ echo $html->link(sprintf(__('Comments: %s', true), htmlspecialchars($node['Revision']['title'])), array('id' => $this->params['id']));
+} else {
+ __('Recent Comments');
+}
+?></h2>
+<?php
+if (!$data) {
+ echo '<p>' . __('No Comments yet!', true) . '</p>';
+} else {
+ foreach ($data as $count => $row) {
+ echo $this->element('comment', array('data' => $row, 'count' => $count + 1));
+ }
+}
+if (isset($node)) {
+ echo $this->element('comment_form');
+}
+$html->meta('rss', am($this->params['pass'], array('ext' => 'rss')), array('title' => __('This page as a feed', true)), false);
+?>
\ No newline at end of file
diff --git a/views/comments/rss/index.ctp b/views/comments/rss/index.ctp
new file mode 100755
index 0000000..c8fca4b
--- /dev/null
+++ b/views/comments/rss/index.ctp
@@ -0,0 +1,22 @@
+<?php /* SVN FILE: $Id: index.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+ transformRSS (null, $html, $this);
+ echo $rss->items($data, 'transformRSS');
+
+ function transformRSS($row, &$_html = false, $_view = false) {
+ static $html;
+ static $_this;
+ if ($_html) {
+ $html = $_html;
+ $_this = $_view;
+ return;
+ }
+ extract($row);
+ return array(
+ 'title' => $Node['sequence'] . ' ' . $Revision['title'] . ' - ' . $html->clean(htmlspecialchars($Comment['title'])),
+ 'link' => array('controller' => 'comments', 'action' => 'index', $Comment['node_id'], 'lang' => $Comment['lang'], '#'
+ => "comment_{$Comment['id']}"),
+ 'description' => $_this->element('comment', array('data' => $row)),
+ 'pubDate' => date('r', strtotime($Comment['created'])),
+ );
+ }
+?>
\ No newline at end of file
diff --git a/views/comments/rss/recent.ctp b/views/comments/rss/recent.ctp
new file mode 100755
index 0000000..6239097
--- /dev/null
+++ b/views/comments/rss/recent.ctp
@@ -0,0 +1,21 @@
+<?php /* SVN FILE: $Id: recent.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+ transformRSS (null, $html, $this);
+ echo $rss->items($data, 'transformRSS');
+
+ function transformRSS($row, &$_html = false, $_view = false) {
+ static $html;
+ static $_this;
+ if ($_html) {
+ $html = $_html;
+ $_this = $_view;
+ return;
+ }
+ extract($row);
+ return array(
+ 'title' => $Comment['title'],
+ 'link' => array('controller' => 'comments', 'action' => 'view', $Comment['id']),
+ 'description' => $_this->element('comment', array('data' => $row['Comment'])),
+ 'pubDate' => date('r', strtotime($Comment['created'])),
+ );
+ }
+?>
\ No newline at end of file
diff --git a/views/comments/view.ctp b/views/comments/view.ctp
new file mode 100644
index 0000000..4831f43
--- /dev/null
+++ b/views/comments/view.ctp
@@ -0,0 +1,3 @@
+<?php /* SVN FILE: $Id: view.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+echo $this->element('comment',array('data'=> $data['Comment'], 'count' => 1));
+?>
\ No newline at end of file
diff --git a/views/elements/attachments.ctp b/views/elements/attachments.ctp
new file mode 100644
index 0000000..8c43ce0
--- /dev/null
+++ b/views/elements/attachments.ctp
@@ -0,0 +1,28 @@
+<?php /* SVN FILE: $Id: attachments.ctp 666 2008-09-23 18:03:17Z AD7six $ */ ?>
+<div id='thumbs' class='clearfix'><p><?php __('Images/Files associated with this content') ?></p><?php
+$out = array();
+if (isset($attachments)) {
+ foreach ($attachments as $row) {
+ extract ($row['Attachment']);
+ extract ($versions);
+ if (isset($large)) {
+ $path = '/img/' . $large;
+ } else {
+ $path = '/files/' . $dir . '/' . $filename;
+ }
+ $thumb = $html->image($thumb);
+ $div = '<div>';
+ $div .= $html->link($thumb, $path, array('style' => 'float:left;min-height:50px;max-width:50px'), null, false);
+ $div .= '<p style="margin-left:55px"><img src="' . $html->url($path) . '" alt="' . $description . '" />';
+ $div .= "<br /><p class='caption'>$description</p>";
+ $div .= '</div>';
+ $out [] = $div;
+ }
+}
+if ($out) {
+ echo '<p>' . implode($out, '</p><p>') . '</p>';
+} else {
+ echo '<p>None</p>';
+}
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/collections.ctp b/views/elements/collections.ctp
new file mode 100644
index 0000000..536aefe
--- /dev/null
+++ b/views/elements/collections.ctp
@@ -0,0 +1,27 @@
+<?php /* SVN FILE: $Id: collections.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<div id="main_nav">
+<ul class="navigation">
+<?php
+$collections = cache('views/collection_' . $this->params['lang']);
+if ($collections) {
+ $collections = unserialize($collections);
+} else {
+ $collections = $this->requestAction('/nodes/collections/' . $this->params['lang']);
+}
+$currentCollection = isset($currentPath[1])?$currentPath[1]:array('Node' => array('id' => false));
+foreach ($collections as $row) {
+ extract($row);
+ if ($currentCollection['Node']['id'] == $Node['id']) {
+ $options = array('class' => 'active');
+ } else {
+ $options = array();
+ }
+ $links[] = $html->link($Revision['title'],
+ array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'view', $Node['id'], $Revision['slug']),
+ $options
+ );
+}
+echo '<li>' . implode($links, '</li><li>') . '</li>';
+?>
+</ul>
+</div>
\ No newline at end of file
diff --git a/views/elements/comment.ctp b/views/elements/comment.ctp
new file mode 100755
index 0000000..1b015ce
--- /dev/null
+++ b/views/elements/comment.ctp
@@ -0,0 +1,33 @@
+<?php /* SVN FILE: $Id: comment.ctp 673 2008-10-06 14:05:17Z AD7six $ */
+if (1==2 && $data['user_id']==$data['Revision']['user_id']) {
+ $class = " highlight";
+} else {
+ $class = "";
+}
+extract($data);
+extract($Comment);
+$name = __('unknown', true);
+if (isset($commenters[$user_id])) {
+ $name = $commenters[$user_id];
+}
+echo "<div id='comment_{$id}' class=\"comment$class\">";
+echo "<p class=\"commentmeta\">";
+echo 'By ' . $name . ' ' . $time->timeAgoInWords($created);
+echo "</p>";
+echo "<p class=\"commenttitle\">";
+if ($this->action == 'recent') {
+ echo $html->link('#', array('action' => 'index', $Node['id'], $Revision['slug'], '#' => "comment_{$id}")) . ' - ';
+} elseif(!empty($count)) {
+ echo $html->link($count, "#comment_{$id}") . ' - ';
+}
+if ($this->action == 'recent') {
+ echo $html->link($Node['sequence'] . ' ' . $Revision['title'], array('controller' => 'nodes', 'action' => 'view', $Node['id'],
+ $Revision['slug'], 'lang' => $lang)) . ' - ';
+}
+ echo htmlspecialchars($title);
+echo "</p>";
+echo "<div class=\"commentbody\">";
+echo '<p>' . implode(explode("\n", htmlspecialchars($body)), '</p><p>') . '</p>';
+echo "</div>";
+echo "</div>";
+?>
\ No newline at end of file
diff --git a/views/elements/comment_form.ctp b/views/elements/comment_form.ctp
new file mode 100644
index 0000000..9d287ea
--- /dev/null
+++ b/views/elements/comment_form.ctp
@@ -0,0 +1,24 @@
+<?php /* SVN FILE: $Id: comment_form.ctp 661 2008-09-10 14:53:56Z AD7six $ */
+if (!isset($auth['User']['id'])) {
+ echo '<div class="comment"><p class="commenttitle"><em>';
+ echo $html->link(__('Login to add a comment', true), array('controller' => 'comments', 'action' => 'add'));
+ echo '</em></p></div>';
+ return;
+}
+?>
+<div class="comment">
+<?php
+if (!isset($node)) {
+ $node = $data['Node'];
+}
+echo $form->create('Comment',array('id' => 'CommentAddForm' . $node['Node']['id'],
+ 'url' => array('controller' => 'comments', 'action' => 'add', $node['Node']['id'], $node['Revision']['slug'])));
+echo $form->inputs(array (
+ 'legend' => sprintf(__('Comment on %s', true), $node['Revision']['title']),
+ 'title',
+ 'body' => array ('cols' => 100, 'rows' => 10)
+ ));
+echo $form->submit('save');
+echo $form->end();
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/crumbs.ctp b/views/elements/crumbs.ctp
new file mode 100755
index 0000000..ec45e22
--- /dev/null
+++ b/views/elements/crumbs.ctp
@@ -0,0 +1,14 @@
+<?php /* SVN FILE: $Id: crumbs.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<div class="crumbs">
+<?php
+if ($this->name == 'Revisions') {
+ echo $this->element('crumbs/revisions');
+} elseif ($this->action == 'admin_toc') {
+ echo $this->element('crumbs/admin_toc');
+//} elseif ($this->action == 'toc') {
+// echo $this->element('crumbs/toc');
+} else {
+ echo $this->element('crumbs/nodes');
+}
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/crumbs/admin_toc.ctp b/views/elements/crumbs/admin_toc.ctp
new file mode 100644
index 0000000..a898855
--- /dev/null
+++ b/views/elements/crumbs/admin_toc.ctp
@@ -0,0 +1,8 @@
+<?php /* SVN FILE: $Id: admin_toc.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+if (isset($crumbPath)&&($crumbPath)) {
+ foreach($crumbPath as $linkInfo) {
+ $html->addCrumb($linkInfo['Revision']['title'],array($linkInfo['Node']['id']));
+ }
+ echo $html->getCrumbs();
+}
+?>
\ No newline at end of file
diff --git a/views/elements/crumbs/nodes.ctp b/views/elements/crumbs/nodes.ctp
new file mode 100644
index 0000000..0d23a02
--- /dev/null
+++ b/views/elements/crumbs/nodes.ctp
@@ -0,0 +1,15 @@
+<?php /* SVN FILE: $Id: nodes.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+if (isset($crumbPath) && count($crumbPath) > 1) {
+ //$html->addCrumb('Collection Index',array('action'=>'index'));
+ if (isset($this->params['admin']) && $this->params['admin']) {
+ foreach($crumbPath as $linkInfo) {
+ $html->addCrumb($linkInfo['Revision']['title'],array($linkInfo['Node']['id'],$linkInfo['Revision']['slug']));
+ }
+ } else {
+ foreach($crumbPath as $linkInfo) {
+ $html->addCrumb($linkInfo['Revision']['title'],array($linkInfo['Node']['id'],$linkInfo['Revision']['slug']));
+ }
+ }
+ echo $html->getCrumbs();
+}
+?>
\ No newline at end of file
diff --git a/views/elements/crumbs/revisions.ctp b/views/elements/crumbs/revisions.ctp
new file mode 100644
index 0000000..9549d53
--- /dev/null
+++ b/views/elements/crumbs/revisions.ctp
@@ -0,0 +1,8 @@
+<?php /* SVN FILE: $Id: revisions.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+if (isset($crumbPath)&&($crumbPath)) {
+ foreach($crumbPath as $linkInfo) {
+ $html->addCrumb($linkInfo['Revision']['title'],array('action'=>'view',$linkInfo['Node']['id']));
+ }
+ echo $html->getCrumbs();
+}
+?>
\ No newline at end of file
diff --git a/views/elements/crumbs/toc.ctp b/views/elements/crumbs/toc.ctp
new file mode 100644
index 0000000..3658ef6
--- /dev/null
+++ b/views/elements/crumbs/toc.ctp
@@ -0,0 +1,14 @@
+<?php /* SVN FILE: $Id: toc.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+if (isset($crumbPath)&&($crumbPath)) {
+ //$html->addCrumb('Collection Index',array('action'=>'index'));
+ foreach($crumbPath as $linkInfo) {
+ if ($linkInfo['Node']['depth'] <= $viewAllLevel) {
+ $html->addCrumb($linkInfo['Revision']['title'],array('action'=>'view',$linkInfo['Node']['id'],$linkInfo['Revision']['slug']));
+ $lastNode = $linkInfo;
+ } else {
+ $html->addCrumb($linkInfo['Revision']['title'],array('action'=>'view',$lastNode['Node']['id'],$lastNode['Revision']['slug'], '#' => $linkInfo['Revision']['slug']));
+ }
+ }
+ echo $html->getCrumbs();
+}
+?>
\ No newline at end of file
diff --git a/views/elements/development.ctp b/views/elements/development.ctp
new file mode 100644
index 0000000..5b667eb
--- /dev/null
+++ b/views/elements/development.ctp
@@ -0,0 +1,17 @@
+<?php /* SVN FILE: $Id: development.ctp 658 2008-09-10 14:51:09Z AD7six $ */
+$cakeDebug['Params'] = $this->params;
+$cakeDebug['Session'] = $session->read();
+$cookie = ClassRegistry::getObject('Component.Cookie');
+if ($cookie) {
+ $cakeDebug['Cookie'] = $cookie->read();
+} else {
+ $cakeDebug['Cookie'] = $_COOKIE;
+}
+if ($this->data) {
+ $cakeDebug['ValidationErrors'] = $this->validationErrors;
+ $cakeDebug['ViewVars'] = $this->viewVars;
+}
+echo '<pre class="cakeDebug">';
+echo 'Debug Info' . "\r\n";
+pr ($cakeDebug);
+echo '</pre>';
\ No newline at end of file
diff --git a/views/elements/filter.ctp b/views/elements/filter.ctp
new file mode 100644
index 0000000..9f4dced
--- /dev/null
+++ b/views/elements/filter.ctp
@@ -0,0 +1,51 @@
+<?php /* SVN FILE: $Id: filter.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<p>
+<?php
+if (!isset($filters)) {
+ return;
+}
+echo $html->link('Toggle Filter', '#', array('id' => 'toggleFilter'));
+$currentFilter = $session->read($modelClass . '.filter');
+if ($currentFilter) {
+ $out = ' - currently filtering for :';
+ foreach ($currentFilter as $field => $filter) {
+ if (is_array($filter)) {
+ $filter = 'In ' . implode(', ', $filter);
+ } elseif ($filter === null) {
+ $currentFilters[] = $field . ' IS NULL';
+ continue;
+ } elseif ($filter === 'NOT NULL') {
+ $currentFilters[] = $field . ' IS NOT NULL';
+ continue;
+ }
+ $currentFilters[] = $field . ' ' . $filter;
+ }
+ echo $out . implode(', ', $currentFilters);
+}
+?>
+</p>
+<div id="resultFilter">
+<?php
+$_data = $form->data;
+$form->data = $session->read($modelClass . '.filterForm');
+echo $form->create(null, array('url' => '/' . $this->params['url']['url']));
+foreach ($filters as $filter => $settings) {
+ if (!is_array($settings)) {
+ $filter = $settings;
+ }
+ $settings = am(array('filterOptions' => $filterOptions), $settings);
+ $selectOptions = am(array('empty' => true, 'div' => false, 'label' => $filter, 'options' => $settings['filterOptions']));
+ unset($settings['filterOptions']);
+ $select = $form->input($filter . '_type', $selectOptions);
+ $inputOptions = am(array('div' => false, 'label' => false, 'empty' => true), $settings);
+ if ($filter == 'id') {
+ $inputOptions['type'] = 'text';
+ }
+ $input = $form->input($filter, $inputOptions);
+ $out = $select . $input;
+ echo $html->div('input', $out);
+}
+echo $form->end('apply filter');
+$form->data = $_data;
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/login.ctp b/views/elements/login.ctp
new file mode 100644
index 0000000..0525cfc
--- /dev/null
+++ b/views/elements/login.ctp
@@ -0,0 +1,2 @@
+<?php /* SVN FILE: $Id: login.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+include(APP . 'plugins' . DS . 'users' . DS . 'views' . DS . 'users' . DS . 'login.ctp'); ?>
\ No newline at end of file
diff --git a/views/elements/login_hint.ctp b/views/elements/login_hint.ctp
new file mode 100644
index 0000000..b3e091d
--- /dev/null
+++ b/views/elements/login_hint.ctp
@@ -0,0 +1,5 @@
+<?php /* SVN FILE: $Id: login_hint.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<div class="context-menu">
+<h4><?php __('Login with your Bakery account') ?></h4>
+<p><?php echo sprintf(__('Not got one? Hop on over to %s and sign up', true), $html->link('The Bakery', 'http://bakery.cakephp.org'))?></p>
+</div>
\ No newline at end of file
diff --git a/views/elements/markitup.ctp b/views/elements/markitup.ctp
new file mode 100644
index 0000000..a0de745
--- /dev/null
+++ b/views/elements/markitup.ctp
@@ -0,0 +1,10 @@
+<?php /* SVN FILE: $Id: markitup.ctp 673 2008-10-06 14:05:17Z AD7six $ */
+$javascript->link('markitup/jquery.markitup.pack.js', false);
+$javascript->link('markitup/sets/default/set.js', false);
+$html->css(array('/js/markitup/skins/markitup/style.css', '/js/markitup/sets/default/style.css'), null, array(), false);
+?>
+<script type="text/javascript" >
+ $(document).ready(function() {
+ $("<?php echo $process?>").markItUp(mySettings);
+ });
+</script>
\ No newline at end of file
diff --git a/views/elements/menu/generic.ctp b/views/elements/menu/generic.ctp
new file mode 100644
index 0000000..913861e
--- /dev/null
+++ b/views/elements/menu/generic.ctp
@@ -0,0 +1,185 @@
+<?php /* SVN FILE: $Id: generic.ctp 683 2008-10-25 23:05:10Z AD7six $ */
+if (!isset($session)) {
+ return;
+}
+$auth = $session->read('Auth');
+if (!isset($auth['User']['Level'])) {
+ $auth['User']['Level'] = 0;
+}
+if (isset($this->params['admin'])) {
+ if ($auth['User']['Level'] >= EDITOR) {
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Nodes',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'index', 'admin' => true)
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Revisions',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'pending', 'admin' => true)
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Comments',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'comments', 'action' => 'index', 'admin' => true)
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Images/files',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'attachments', 'action' => 'index', 'admin' => true)
+ ));
+
+ if ($this->name == 'Nodes') {
+ $nodeId = null;
+ if (isset($currentPath[0])) {
+ $nodeId = $currentPath[count($currentPath) - 1]['Node']['id'];
+ }
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'TOC',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'toc', 'admin' => true,
+ $nodeId),
+ 'under' => 'Nodes'
+ ));
+ } elseif ($this->name == 'Revisions') {
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'All',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'index', 'admin' => true),
+ 'under' => 'Revisions'
+ ));
+
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Pending',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'pending', 'admin' => true),
+ 'under' => 'Revisions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Recently published',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'index', 'admin' => true,
+ 'status' => 'current', 'sort' => 'created', 'direction' => 'desc', 'lang:' . $this->params['lang'], 'lang' => false),
+ 'under' => 'Revisions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Invalid',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'invalid'),
+ 'under' => 'Revisions'
+ ));
+
+ } elseif ($this->name == 'Comments') {
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Recent',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'comments', 'action' => 'index', 'admin' => true,
+ 'sort' => 'created', 'direction' => 'asc'),
+ 'under' => 'Comments'
+ ));
+
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Unpublished',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'comments', 'action' => 'index', 'admin' => true,
+ 'published' => 0),
+ 'under' => 'Comments'
+ ));
+
+ }
+
+ }
+ if ($auth['User']['Level'] >= ADMIN) {
+ if ($this->name == 'Nodes') {
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Admin functions',
+ 'url' => false,
+ 'under' => 'Nodes'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Verify Tree',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'verify_tree', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Recover Tree',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'recover_tree', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Reset Depths',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'reset_depths', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Reset Sequences',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'reset_sequences', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Export',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'export', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Import',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'nodes', 'action' => 'import', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ } elseif ($this->name == 'Revisions') {
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Admin functions',
+ 'url' => false,
+ 'under' => 'Revisions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Reset Slugs',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'reset_slugs', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Build Search Index',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'build_index', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Delete and Build Search Index',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'build_index', 'admin' => true, 'reset'),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Check and fix current revisions',
+ 'url' => array('prefix' => null, 'plugin' => null, 'controller' => 'revisions', 'action' => 'reset'),
+ 'under' => 'Admin functions'
+ ));
+ } elseif ($this->name == 'Comments') {
+
+ } elseif ($this->name == 'Attachments') {
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Export',
+ 'url' => array('prefix' => null, 'plugin' => null, 'action' => 'export', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ $menu->add(array(
+ 'section' => 'Main Options',
+ 'title' => 'Import',
+ 'url' => array('prefix' => null, 'plugin' => null, 'action' => 'import', 'admin' => true),
+ 'under' => 'Admin functions'
+ ));
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/views/elements/node_navigation.ctp b/views/elements/node_navigation.ctp
new file mode 100755
index 0000000..63a0b39
--- /dev/null
+++ b/views/elements/node_navigation.ctp
@@ -0,0 +1,40 @@
+<?php /* SVN FILE: $Id: node_navigation.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<div class="node-nav">
+<?php
+$prevUrl = $nextUrl = false;
+if (isset($neighbours[0]['Node']['depth'])) {
+ if ($this->name == 'Revisions') {
+ $prevUrl = array('action' => 'view', $neighbours[0]['Revision']['id']);
+ } else {
+ $prevUrl = array('action' => 'view', $neighbours[0]['Node']['id'], $neighbours[0]['Revision']['slug']);
+ }
+ $prevTitle = '<< ' . $neighbours[0]['Revision']['title'];
+ $prevOptions = array(
+ 'title' => $neighbours[0]['Revision']['title']
+ );
+
+ if($prevUrl) {
+ echo '<span class="prev">';
+ echo $html->link($prevTitle, $prevUrl, $prevOptions);
+ echo '</span>';
+ }
+}
+if (isset($neighbours[0]['Node']['depth']) && isset($neighbours[1]['Node']['depth'])) {
+ echo ' | ';
+}
+if (isset($neighbours[1]['Node']['depth'])) {
+ if ($this->name == 'Revisions') {
+ $nextUrl = array('action' => 'view', $neighbours[1]['Revision']['id']);
+ } else {
+ $nextUrl = array('action' => 'view', $neighbours[1]['Node']['id'], $neighbours[1]['Revision']['slug']);
+ }
+ $nextTitle = $neighbours[1]['Revision']['title']. ' >>';
+ $nextOptions = array('title' => $neighbours[1]['Revision']['title']);
+ if ($nextUrl) {
+ echo '<span class="next">';
+ echo $html->link($nextTitle, $nextUrl, $nextOptions);
+ echo '</span>';
+ }
+}
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/node_options.ctp b/views/elements/node_options.ctp
new file mode 100644
index 0000000..e6b6d25
--- /dev/null
+++ b/views/elements/node_options.ctp
@@ -0,0 +1,45 @@
+<?php /* SVN FILE: $Id: node_options.ctp 705 2008-11-19 12:15:50Z AD7six $ */
+if (isset($this->params['admin'])) {
+ return;
+}
+extract($data);
+$options = array();
+if ($Node['edit_level'] <= $auth['User']['Level']) {
+ $out[] = $html->link(__('Edit', true), array('action'=>'edit',$Node['id'], $Revision['slug']));
+}
+if ($Node['depth'] >= $viewAllLevel) {
+ $out[] = $html->link(__('View just this section', true), array('action'=>'view',$Node['id'], $Revision['slug']));
+}
+if ($Node['comment_level'] <= $auth['User']['Level']) {
+ $out[] = $html->link(sprintf(__('Comments (%s)', true), count($Comment)), '#comments-' . $Node['id'], array('class' => 'show-comment'));
+}
+$flags = explode(',', trim($Revision['flags']));
+$flagLis = '';
+if (in_array($Node['id'], $pendingUpdates)) {
+ $flagLis .= '<li class="flag pending">' . $html->link(__('there is a pending change for this section', true),
+ array('controller' => 'changes', 'action' => 'index', $Node['id'])) . '</li>';
+} {
+ $out[] = $html->link(__('History', true), array('action' => 'history', $Node['id'], $Revision['slug']));
+}
+$compare = true;
+foreach($flags as $flag) {
+ if (trim($flag) == '') {
+ continue;
+ }
+ if ($flag == 'englishChanged') {
+ $compare = false;
+ $flagLis .= '<li class="flag englishChanged">' .
+ $html->link(__('This text may be out of sync with the English version', true),
+ array('action' => 'compare', $Node['id'], $Revision['slug'])) . '</li>';
+ } else {
+ $flagLis .= '<li class="flag warning">' . __($flag, true) . '</li>';
+ }
+}
+if ($this->params['lang'] != 'en' && $Revision['id'] && $compare) {
+ $out[] = $html->link(__('Compare to original content', true), array('action' => 'compare', $Node['id'], $Revision['slug']));
+}
+
+if ($out) {
+ echo '<ul class="node-options"><li>' . implode($out, '</li><li>') . '</li>' . $flagLis . '</ul>';
+}
+?>
\ No newline at end of file
diff --git a/views/elements/paging.ctp b/views/elements/paging.ctp
new file mode 100755
index 0000000..b0ba558
--- /dev/null
+++ b/views/elements/paging.ctp
@@ -0,0 +1,10 @@
+<?php /* SVN FILE: $Id: paging.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<div class="paging">
+<?php
+echo '<p>' . sprintf(__('Page %s', true), $paginator->counter()) .'</p>';
+echo $paginator->prev(__('<< previous', true), array(), null, array('class'=>'disabled'));
+$numbers = $paginator->numbers();
+echo $numbers ? ' | ' . $numbers . ' | ' : ' | ';
+echo $paginator->next(__('Next >>', true), array(), null, array('class'=>'disabled'));
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/preview.ctp b/views/elements/preview.ctp
new file mode 100644
index 0000000..0872708
--- /dev/null
+++ b/views/elements/preview.ctp
@@ -0,0 +1,60 @@
+<?php /* SVN FILE: $Id: preview.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+if (isset($highlight)) {
+ $highlight->auto = false;
+}
+$messages = array(
+ 'Perfect!' => 'How\'d you like them apples?',
+ 'Done!' => 'How\'s that for a slice of fried gold?',
+ 'Tastey!' => 'How\'s that for a slice of cake?',
+ 'Yeah!' => 'Are we done yet?',
+);
+$confirm = array_rand($messages);
+$message = $messages[$confirm];
+$previewText = $form->error('Revision.preview', $message);
+if (!$previewText) {
+ return;
+}
+$errors = $this->validationErrors;
+unset($errors['Revision']['preview']);
+if (empty($errors['Revision'])) {
+ unset ($errors['Revision']);
+} else {
+ return;
+}
+?>
+<fieldset>
+ <?php echo $previewText ?>
+ <div id='preview'>
+ <h2><?php echo htmlspecialchars($data['Revision']['title']) ?> </h2>
+ <div class="view"><?php
+ if (isset($highlight)) {
+ echo $highlight->auto($data['Revision']['content']);
+ } else {
+ echo $data['Revision']['content'];
+ }
+ ?></div>
+ </div>
+<?php
+if (empty($errors)) {
+ $inputs = array(
+ 'legend' => false,
+ 'fieldset' => false,
+ 'Revision.id' => array('type' => 'hidden'),
+ 'Revision.node_id' => array('type' => 'hidden'),
+ 'Revision.under_node_id' => array('type' => 'hidden'),
+ 'Revision.after_node_id' => array('type' => 'hidden'),
+ 'Revision.lang' => array('type' => 'hidden'),
+ 'Revision.title' => array('type' => 'hidden'),
+ 'Revision.content2' => array('type' => 'hidden', 'value' => $data['Revision']['content']),
+ 'Revision.preview' => array('type' => 'hidden', 'value' => '0'),
+ 'Revision.reason',
+ );
+ if (isset ($data['Node']['show_in_toc'])) {
+ $inputs['Node.show_in_toc'] = array('type' => 'hidden', 'value' => $data['Node']['show_in_toc']);
+ }
+ echo $form->create(null, array('url' => '/' .$this->params['url']['url']));
+ echo $form->inputs($inputs);
+ echo $form->end($confirm . ' Save it');
+}
+?>
+</fieldset>
diff --git a/views/elements/search.ctp b/views/elements/search.ctp
new file mode 100644
index 0000000..c6af76e
--- /dev/null
+++ b/views/elements/search.ctp
@@ -0,0 +1,24 @@
+<?php /* SVN FILE: $Id: search.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<div id="site_search"><?php
+$currentCollection = 2;
+if (isset($currentPath[1])) {
+ list($_, $currentCollection) = $currentPath;
+ if (isset($currentCollection['Node']['id'])) {
+ $currentCollection = $currentCollection['Node']['id'];
+ }
+}
+$query = isset($this->params['named']['query'])?$this->params['named']['query']:'';
+if (isset($this->params['admin'])) {
+ echo $form->create(null, array('action' => 'search', 'id' => 'search'));
+} else {
+ echo $form->create('Search', array('url' => '/search', 'id' => 'search'));
+}
+echo $form->inputs(array(
+ 'legend' => false,
+ 'query' => array('label' => false, 'div' => false, 'value' => $query, 'class' => 'query'),
+ 'collection' => array('type' => 'hidden', 'value' => $currentCollection),
+ 'lang' => array('type' => 'hidden', 'value' => $this->params['lang']),
+));
+echo $form->submit(__('Search', true), array('div' => false, 'id' => 'search_submit_btn'));
+echo $form->end();
+?></div>
\ No newline at end of file
diff --git a/views/elements/search_form.ctp b/views/elements/search_form.ctp
new file mode 100644
index 0000000..1465b93
--- /dev/null
+++ b/views/elements/search_form.ctp
@@ -0,0 +1,14 @@
+<div class="form">
+<?php
+echo $form->create('Search', array('url' => array('controller' => 'revisions', 'action' => 'search')));
+echo $form->label('Search.query');
+echo $form->text('Search.query', array('value' => $query));
+echo $form->label('Search.collection');
+echo $form->radio('Search.collection', array( 304 =>'1.1', 2 => '1.2'), array('default' => 2));
+if($this->params['lang'] != 'en') {
+echo $form->label('Search.lang');
+echo $form->radio('Search.lang', array( $this->params['lang'] => $this->params['lang'], 'en' => 'en'), array('default' => $this->params['lang']));
+}
+echo $form->end(__('Search', true));
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/secondary_nav.ctp b/views/elements/secondary_nav.ctp
new file mode 100644
index 0000000..dde1180
--- /dev/null
+++ b/views/elements/secondary_nav.ctp
@@ -0,0 +1,29 @@
+<?php /* SVN FILE: $Id: secondary_nav.ctp 705 2008-11-19 12:15:50Z AD7six $ */ ?>
+<div id="secondary_nav">
+<ul class="navigation">
+ <li><?php
+ if ($this->params['lang'] != 'en') {
+ $stats = '/' . $this->params['lang'] . '/stats#' . $this->params['lang'];
+ } else {
+ $stats = '/stats';
+ }
+ echo $html->link(__('Top Contributors', true), $stats);
+ ?></li>
+ <li><?php
+ echo $html->link(__('todo', true), array('controller' => 'nodes', 'action' => 'todo'));
+ ?></li>
+ <?php
+ if ($session->read('Auth.User.id')) {
+ echo '<li>' . $html->link(sprintf(__('Logged in as %s', true), $session->read('Auth.User.username')), '#') . '</li>';
+ echo '<li>' . $html->link(__('Logout', true), array('controller' => 'users', 'action' => 'logout')) . '</li>';
+ if ($session->read('Auth.User.Level') === ADMIN) {
+ echo '<li>' . $html->link('Admin', '/admin') . '</li>';
+ }
+ } else {
+ echo '<li>' . $html->link(__('Login', true), '/users/login') . '</li>';
+ }
+ ?>
+ <li><a href="http://cakephp.org/"><?php __('About CakePHP') ?></a></li>
+ <li><a href="http://cakefoundation.org/pages/donations"><?php __('Donate') ?></a></li>
+ </ul>
+</div>
\ No newline at end of file
diff --git a/views/elements/side_menu.ctp b/views/elements/side_menu.ctp
new file mode 100644
index 0000000..5152706
--- /dev/null
+++ b/views/elements/side_menu.ctp
@@ -0,0 +1,42 @@
+<?php /* SVN FILE: $Id: side_menu.ctp 672 2008-10-06 14:03:23Z AD7six $ */
+if ($this->name == 'Users') {
+ echo $this->element('login_hint');
+}
+$sections = $menu->sections();
+if (!$this->name && !empty($this->data['Node'])) {
+ // rendered from cache
+ include CONFIGS . 'routes.php';
+ extract ($this->data['Node']);
+ $slug = !empty($this->params['pass'][1])?$this->params['pass'][1]:null;
+ if ($depth > 1) {
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => __('All in one page', true),
+ 'url' => array('controller' => 'nodes', 'action' => 'single_page', $id, $slug)
+ ));
+ }
+ $authLevel = $session->read('Auth.User.Level');
+ $authLevel = $authLevel?$authLevel:200;
+ if ($edit_level <= $authLevel) {
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => __('Suggest a new section here', true),
+ 'url' => array('controller' => 'nodes', 'action' => 'add', $id, $slug)
+ ));
+ }
+ $sections = $menu->sections();
+}
+if (in_array('Options', $sections)) {
+ echo '<div class="context-menu"><h4>' . __('Options', true) . '</h4>';
+ echo $menu->generate('Options');
+ echo '</div>';
+ $keys = array_flip($sections);
+ unset ($sections[$keys['Options']]);
+}
+foreach ($sections as $section) {
+ echo '<div class="context-menu ' . $section . '"><h4>' . $section . '</h4>';
+ $menu->settings($section, array('class' => $section));
+ echo $menu->generate($section);
+ echo '</div>';
+}
+?>
\ No newline at end of file
diff --git a/views/elements/sites_nav.ctp b/views/elements/sites_nav.ctp
new file mode 100644
index 0000000..5e6ae10
--- /dev/null
+++ b/views/elements/sites_nav.ctp
@@ -0,0 +1,12 @@
+<?php /* SVN FILE: $Id: sites_nav.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<div id="sites_nav">
+ <ul class="navigation">
+ <li class="current"><a href="http://cakephp.org/">CakePHP</a></li>
+ <li><a href="http://api.cakephp.org/">API</a></li>
+ <li><a href="http://book.cakephp.org/">Docs</a></li>
+ <li><a href="http://bakery.cakephp.org/">Bakery</a></li>
+ <li><a href="http://live.cakephp.org/">Live</a></li>
+ <li><a href="http://cakeforge.org/">Forge</a></li>
+ <li><a href="https://trac.cakephp.org/">Trac</a></li>
+ </ul>
+</div>
\ No newline at end of file
diff --git a/views/elements/toc.ctp b/views/elements/toc.ctp
new file mode 100644
index 0000000..bae6667
--- /dev/null
+++ b/views/elements/toc.ctp
@@ -0,0 +1,34 @@
+<?php /* SVN FILE: $Id: toc.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<div id="toc" class="context-menu toc tree">
+<?php
+$url = array('admin' => false, 'controller' => 'nodes', 'action' => 'toc', $data['Node']['Node']['id'], 'lang' => $this->params['lang']);
+$url = str_replace($this->base, '', Router::url($url));
+$data = $this->requestAction($url, array('currentPath' => $currentPath, 'currentNode' => $currentNode));
+$i = false;
+$title = '';
+if (isset($currentPath[2])) {
+ $i = 2;
+ $j = count($currentPath) - 1;
+ $k = max($j -1, 2);
+ $title .= $html->link(__('Table of Contents', true), array(
+ 'action' => 'toc', $currentPath[$j]['Node']['id'], $currentPath[$j]['Revision']['slug'],
+ '#' => $currentPath[$k]['Revision']['slug'] . '-' . $currentPath[$k]['Node']['id']
+ ), array('title' => __('see fully expanded table of contents (only)', true))) . ' : ';
+} elseif (isset($currentPath[1])) {
+ $i = 1;
+ $title .= __('Books in ', true);
+} else {
+ $title .= __('Available collections', true);
+}
+if ($i) {
+ $title .= $html->link($currentPath[$i]['Revision']['title'],
+ array('controller' => 'nodes', 'action' => 'view', $currentPath[$i]['Node']['id'], $currentPath[$i]['Revision']['slug']));
+}
+echo '<h4>' . $title . '</h4>';
+$selected = array();
+if (isset($currentNode['lft'])) {
+ $selected = array($currentNode['lft'], $currentNode['rght']);
+}
+echo $tree->generate($data, array ('element' => 'toc/public_item', 'model' => 'Node', 'selected' => $selected));
+?>
+</div>
\ No newline at end of file
diff --git a/views/elements/toc/admin_item.ctp b/views/elements/toc/admin_item.ctp
new file mode 100644
index 0000000..e11620c
--- /dev/null
+++ b/views/elements/toc/admin_item.ctp
@@ -0,0 +1,42 @@
+<?php /* SVN FILE: $Id: admin_item.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+extract($data);
+extract($Revision);
+extract($Node);
+echo $html->link($sequence . ' ' . $title, array($id), array ('title' => 'view \'' . $title . '\'.')) . ' ';
+$links = array();
+$links[] = $html->link('view', array ('admin' => false, 'action' => 'view', $id, $slug), array ('title' => 'View public content'));
+$links[] = $html->link('edit', array ('admin' => false, 'action' => 'edit', $id, $slug), array ('title' => 'Edit content'));
+$links[] = $html->link('history', array ('controller' => 'revisions', 'action' => 'history', $id, $slug), array ('title' => 'See History'));
+if ($auth['User']['Level'] > COMMENTER) {
+ $links[] = $html->link('props', array ('action' => 'edit', $id), array ('title' => 'Edit the stuctural properties'));
+ if ($Node['parent_id']) {
+ if ($Node['depth'] > 1) {
+ $links[] = $html->link('←', array ('action' => 'promote', $id), array ('title' => 'Promote, along with any children'));
+ }
+ }
+}
+if (!$firstChild) {
+ $links[] = $html->link('↑', array ('action' => 'move_up', $id), array ('title' => 'Move Previous - Up the tree'));
+ $links[] = $html->link('↑↑↑', array ('action' => 'move_up', $id, 100), array ('title' => 'Move First - Up the tree'));
+}
+if (!$lastChild) {
+ $links[] = $html->link('↓', array ('action' => 'move_down', $id), array ('title' => 'Move After - Down the tree'));
+ $links[] = $html->link('↓↓↓', array ('action' => 'move_down', $id, 100), array ('title' => 'Move Last - Down the tree'));
+}
+if ($Node['parent_id']) {
+ $links[] = $html->link('move anywhere', array ('action' => 'move', $id), array ('title' => 'Move Somewhere else'));
+}
+$links[] = $html->link('merge', array ('action' => 'merge', $id), array ('title' => 'Move this content to a different place'));
+if ($auth['User']['Level'] >= COMMENTER) {
+ $links[] = $html->link('delete', array ('action' => 'delete', $id), array ('title' => 'delete node \'' . $title . '\' and all children.'));
+ if ($Node['parent_id']) {
+ if ($hasChildren) {
+ $links[] = $html->link('remove', array ('action' => 'remove', $id, 'true'), array ('title' => 'remove node \'' . $title . '\' from the tree reparenting children and delete \'' . $title . '\'.'));
+ }
+ }
+}
+
+if ($links) {
+ echo '<ul class=\'tree-options\'><li>' . implode ($links, '</li><li>') . '</li></ul>';
+}
+?>
\ No newline at end of file
diff --git a/views/elements/toc/public_item.ctp b/views/elements/toc/public_item.ctp
new file mode 100644
index 0000000..9e5a81d
--- /dev/null
+++ b/views/elements/toc/public_item.ctp
@@ -0,0 +1,29 @@
+<?php /* SVN FILE: $Id: public_item.ctp 616 2008-08-20 17:13:45Z AD7six $ */
+$params = array();
+extract($data);
+if (!isset($pathIds) && isset ($currentPath)) {
+ $pathIds = Set::extract($currentPath, '{n}.Node.id');
+ $this->set('pathIds', $pathIds);
+}
+$params['id'] = 'toc-' . $Revision['slug'] . '-' . $Node['id'];
+if (isset($pathIds)) {
+ if ($Node['id'] == $pathIds[count($pathIds) - 1]) {
+ //$tree->addItemAttribute('class', 'selected');
+ $params['class'] = 'selected';
+ }
+}
+if ($this->action == 'single_page' && $Node['lft'] >= $currentNode['lft'] && $Node['rght'] <= $currentNode['rght']) {
+ echo $html->link($Node['sequence'] . ' ' . $Revision['title'], '#' . $Revision['slug'] . '-' . $Node['id'],
+ $params);
+ return;
+}
+//if ($Node['depth'] < $viewAllLevel || !isset($lastNode)) {
+ echo $html->link($Node['sequence'] . ' ' . $Revision['title'],
+ array('action'=>'view', $Node['id'], $Revision['slug']), $params);
+/* $this->set('lastNode', $data);
+} else {
+ echo $html->link($Node['sequence'] . ' ' . $Revision['title'],
+ array('action'=>'view', $lastNode['Node']['id'], $lastNode['Revision']['slug'], '#' => $Revision['slug'] . '-' . $Node['id']), $params);
+}
+ */
+?>
\ No newline at end of file
diff --git a/views/helpers/diff.php b/views/helpers/diff.php
new file mode 100644
index 0000000..447da06
--- /dev/null
+++ b/views/helpers/diff.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * DiffHelper class
+ *
+ * This class is a wrapper for PEAR Text_Diff with modified renderers from Horde
+ * You need the stable Text_Diff from PEAR and (if you want to use them) two
+ * renderers attached with this helper (sidebyside.php and character.php)
+ *
+ * To use this helper you either have to a) have pear libraries in your path
+ * b) can use ini_set to set the path (default is app/vendors/)
+ * c) change all requires in Text_Diff ;)
+ *
+ * @uses AppHelper
+ * @author Marcin Domanski aka kabturek <blog@kabturek.info>
+ * @package Dressing
+ * @subpackage .dressing.views.helpers
+ */
+class DiffHelper extends AppHelper {
+
+/**
+ * name of the helper
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Diff';
+
+/**
+ * what engine should Text_Diff use.
+ * Avaible: auto (chooses best), native, xdiff
+ *
+ * @var string
+ * @access public
+ */
+ var $engine = 'auto';
+
+/**
+ * what renderer to use ?
+ * for avaible renderers look in Text/Diff/Renderer/*
+ * Standard: unified, context, inline
+ * Additional: sidebyside
+ *
+ * @var string
+ * @access public
+ */
+ var $renderer = 'sidebyside';
+
+/**
+ * Do you want to use the Character diff renderer additionally to the sidebyside renderer ?
+ * sidebyside renderer is the only one supporting the additional renderer
+ *
+ * @var bool
+ * @access public
+ */
+ var $character_diff = true;
+
+/**
+ * If the params are strings on what characters do you want to explode the string?
+ * Can be an array if you want to explode on multiple chars
+ *
+ * @var mixed
+ * @access public
+ */
+ var $explode_on = "\r\n";
+
+/**
+ * How many context lines do you want to see around the changed line?
+ *
+ * @var int
+ * @access public
+ */
+ var $context_lines = 4;
+
+
+/**
+ * construct function
+ *
+ * @param mixed $one
+ * @param mixed $two
+ * @param mixed $three
+ * @access private
+ * @return void
+ */
+ function __construct($one = null, $two = null, $three = null) {
+ parent::__construct($one, $two, $three);
+ if(function_exists('ini_set')){
+ ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . APP. 'vendors');
+ }
+ App::import('Vendor', 'Text', array('file' => 'Text/Diff.php'));
+ App::import('Vendor', 'Renderer', array('file' => 'Text/Diff/Renderer.php'));
+ }
+/**
+ * compare function
+ * Compares two strings/arrays using the specified method and renderer
+ *
+ * @param mixed $original
+ * @param mixed $changed
+ * @access public
+ * @return void
+ */
+ function compare($original, $changed){
+ if(!is_array($original)){
+ $original = $this->__explode($original);
+ }
+ if(!is_array($changed)){
+ $changed = $this->__explode($changed);
+ }
+ $rendererClassName = 'Text_Diff_Renderer_'.$this->renderer;
+ if(!class_exists($rendererClassName)) {
+ App::import('Vendor', $this->renderer.'Renderer', array('file' => 'Text/Diff/Renderer/'.$this->renderer.'.php'));
+ }
+ $renderer = new $rendererClassName(array('context_lines' => $this->context_lines, 'character_diff' =>$this->character_diff));
+ $diff = new Text_Diff($this->engine, array($original, $changed));
+ return $this->output($renderer->render($diff));
+ }
+
+/**
+ * explodes the string into an array
+ *
+ * @param string $text
+ * @access private
+ * @return void
+ */
+ function __explode($text){
+ if(is_array($this->explode_on)){
+ foreach($this->explode_on as $explode_on){
+ $text = explode($explode_on, $text);
+ }
+ return $text;
+ }
+ return explode($this->explode_on, $text);
+ }
+}
+?>
\ No newline at end of file
diff --git a/views/helpers/highlight.php b/views/helpers/highlight.php
new file mode 100644
index 0000000..12f24b4
--- /dev/null
+++ b/views/helpers/highlight.php
@@ -0,0 +1,121 @@
+<?php
+/* SVN FILE: $Id: highlight.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for highlight.php
+ *
+ * Long description for highlight.php
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
+ * @link http://www.cakephp.org
+ * @package cookbook
+ * @subpackage cookbook.views.helpers
+ * @since 1.0
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * HighlightHelper class
+ *
+ * @uses AppHelper
+ * @package cookbook
+ * @subpackage cookbook.views.helpers
+ */
+class HighlightHelper extends AppHelper {
+/**
+ * name variable
+ *
+ * @var string
+ * @access public
+ */
+ var $name = 'Highlight';
+/**
+ * auto variable
+ *
+ * @var bool
+ * @access public
+ */
+ var $auto = true;
+/**
+ * construct function
+ *
+ * @param mixed $one
+ * @param mixed $two
+ * @param mixed $three
+ * @access private
+ * @return void
+ */
+ function __construct($one = null, $two = null, $three = null) {
+ parent::__construct($one, $two, $three);
+ App::import('Vendor', 'highlight');
+ $this->highlight = new highlight();
+ }
+/**
+ * afterRender function
+ *
+ * @access public
+ * @return void
+ */
+ function afterRender() {
+ if ($this->auto) {
+ $text = @ ob_get_clean();
+ ob_start();
+ echo $this->auto($text);
+ }
+ }
+/**
+ * auto function
+ *
+ * @param mixed $text
+ * @access public
+ * @return void
+ */
+ function auto($text) {
+ $this->auto = false; // avoid double processing
+ preg_match_all('/(<pre>)([\\s\\S]*?)(<\\/pre>)/i', $text, $result, PREG_PATTERN_ORDER);
+ if (!empty($result['0'])) {
+ $count = count($result['0']);
+ for($i = 0; $i < $count; $i++) {
+ $result['2'][$i] = str_replace('<', '<', $result['2'][$i]); // ensure escaping
+ $highlighted = '<pre class="code">' . $result['2'][$i] . '</pre>';
+
+ $stripStart = false;
+ if (strpos($result['2'][$i], '<?') === false) {
+ $stripStart = true;
+ // add 2 dummy lines so that the odd-even is in sync when removed
+ $result['2'][$i] = '<?php ' . "\r\n" . '$x=$y;' . "\r\n" . $result['2'][$i];
+ }
+ $highlighted .= $this->process(html_entity_decode($result['2'][$i]));
+
+ if ($stripStart) {
+ $highlighted = str_replace('<li><code><span class="default"><?php </span></code></li>', '', $highlighted);
+ $highlighted = str_replace('<li class="even"><code><span class="default">$x</span><span class="keyword">=</span><span class="default">$y</span><span class="keyword">;</span></code></li>', '', $highlighted);
+ }
+ $text = str_replace($result[0][$i], $highlighted, $text);
+ }
+ }
+ // Prevent cache poisoning
+ $text = str_replace('<?', '<?', $text);
+ return $text;
+ }
+/**
+ * process function
+ *
+ * @param mixed $text
+ * @access public
+ * @return void
+ */
+ function process($text) {
+ return $this->highlight->process($text);
+ }
+}
+?>
\ No newline at end of file
diff --git a/views/helpers/menu.php b/views/helpers/menu.php
new file mode 100644
index 0000000..7219a6d
--- /dev/null
+++ b/views/helpers/menu.php
@@ -0,0 +1,585 @@
+<?php
+/* SVN FILE: $Id: menu.php 699 2008-11-19 12:11:38Z AD7six $ */
+/**
+ * Short description for menu.php
+ *
+ * Long description for menu.php
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package base
+ * @subpackage base.views.helpers
+ * @since v 1.0
+ * @version $Revision: 699 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-19 13:11:38 +0100 (Wed, 19 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * MenuHelper class
+ *
+ * @uses AppHelper
+ * @package base
+ * @subpackage base.views.helpers
+ */
+class MenuHelper extends AppHelper {
+/**
+ * name property
+ *
+ * @var string 'Menu'
+ * @access public
+ */
+ var $name = 'Menu';
+/**
+ * helpers property
+ *
+ * @var array
+ * @access public
+ */
+ var $helpers = array('Html', 'Tree');
+/**
+ * defaultSettings property
+ *
+ * @var array
+ * @access private
+ */
+ var $__defaultSettings = array(
+ 'order' => null,
+ 'indent' => null,
+ 'genericElement' => 'menu/generic',
+ 'hereMode' => 'active', // active // text // false[do nothing]
+ 'hereKey' => null, // the key for the item to mark as active
+ 'activeMode' => 'url', // url // controller[name] // action[and controller name] // false [do nothing]
+ 'uniqueKey' => 'title',
+ 'onlyActiveChildren' => false,
+ 'overwrite' => false,
+ 'showWarnings' => true
+ );
+/**
+ * settings property
+ *
+ * @var array
+ * @access public
+ */
+ var $settings = array();
+/**
+ * data property
+ *
+ * Holds the menu data as they get built. References flatData.
+ *
+ * @var array
+ * @access private
+ */
+ var $__data = array();
+/**
+ * flatData property
+ *
+ * A flat list of menu data
+ *
+ * @var array
+ * @access private
+ */
+ var $__flatData = array();
+/**
+ * here property
+ *
+ * Place holder for router normalized "here"
+ *
+ * @var string ''
+ * @access private
+ */
+ var $__here = '';
+/**
+ * beforeRender method
+ *
+ * If genericElement is set, 'render' the named element. This can be used to prevent repeating menu logic if
+ * for example there are some menu items which don't change based on the specific view file
+ *
+ * @access public
+ * @return void
+ */
+ function beforeRender() {
+ if (!isset($this->params['requested']) && $this->__defaultSettings['genericElement']) {
+ $view =& ClassRegistry:: getObject('view');
+ if ($view) {
+ echo $view->element($this->__defaultSettings['genericElement']);
+ }
+ }
+ return true;
+ }
+/**
+ * settings method
+ *
+ * Define "here" and initialize or change settings
+ *
+ * @param string $section
+ * @param array $settings
+ * @access public
+ * @return void
+ */
+ function settings($section = 'main', $settings = array()) {
+ if (!$this->__here) {
+ if (isset($this->params['url']['url'])) {
+ $this->__here = Router::normalize('/' . $this->params['url']['url']);
+ } else {
+ $this->__here = '/';
+ }
+ }
+ if (!isset($this->settings[$section])) {
+ foreach ($settings as $key => $_) {
+ if (!isset($this->__defaultSettings[$key])) {
+ unset ($settings[$key]);
+ }
+ }
+ $settings = array_merge($this->__defaultSettings, $settings);
+ $this->settings[$section] = $settings;
+ } elseif ($settings) {
+ $this->settings[$section] = array_merge($this->settings[$section], $settings);
+ }
+ if (is_null($this->settings[$section]['order'])) {
+ $this->settings[$section]['order'] = count($this->settings);
+ }
+ return $this->settings[$section];
+ }
+/**
+ * addm method
+ *
+ * Add Multiple menu items at once - use array syntax
+ *
+ * @param string $section
+ * @param array $data
+ * @access public
+ * @return void
+ */
+ function addm($section = 'main', $data = array()) {
+ if (is_array($section)) {
+ $section = 'main';
+ $data = $section;
+ }
+ foreach ($data as $row) {
+ $this->add(array_merge(array('section' => $section), $row));
+ }
+ }
+/**
+ * Add a menu item.
+ *
+ * Add a menu item syntax examples:
+ * $menu->add($title, $url); adds an entry with $title and $url to the menu named "main"
+ * $menu->add('main', $title, $url); as above but explicit
+ * $menu->add('context', $title, $url); add an entry with $title and $url to the menu named "context"
+ * $menu->add('context', $title, $url, 'subSection'); add an entry with $title and $url to subsection "subSection for the menu named "context"
+ * $menu->add(array('url' => $url, 'title' => $title, 'options' => array('escapeTitle' => false))); array syntax, not escaping title
+ * $menu->add(array('url' => $url, 'title' => $title, 'options' => array('htmlAttributes' => array('id' => 'foo'))); array syntax, setting id for link
+ *
+ * @param string $section
+ * @param mixed $title
+ * @param mixed $url
+ * @param mixed $under
+ * @param array $options
+ * @param array $settings
+ * @access public
+ * @return void
+ */
+ function add($section = 'main', $title = null, $url = null, $under = null, $options = array(), $settings = array()) {
+ $here = $inPath = $activeChild = $sibling = false;
+ if (is_array($section)) {
+ $settings = $section;
+ extract(array_merge(array('section' => 'main'), $section));
+ } elseif (($section && $url !== false) || (is_string ($url) && $url[0] != 'h' && $url[0] != '/'&& $url[0] != '#') || is_array($under)) {
+ if ($under) {
+ $options = $under;
+ }
+ $settings = array();
+ $options = $under;
+ $under = $url;
+ $url = $title;
+ $title = $section;
+ $section = 'main';
+ }
+ if (!isset($this->settings[$section])) {
+ $this->settings($section, $settings);
+ }
+ extract(array_merge($this->settings[$section], $settings));
+ if (isset($$uniqueKey)) {
+ if (is_array($$uniqueKey)) {
+ if ($uniqueKey == 'url') {
+ $key = Router::normalize($$uniqueKey);
+ } else {
+ $key = serialize($$uniqueKey);
+ }
+ } else {
+ $key = $$uniqueKey;
+ }
+ } else {
+ $key = $title;
+ }
+ if (is_array($under)) {
+ if ($uniqueKey == 'url') {
+ $under = Router::normalize($under);
+ } else {
+ $under = serialize($under);
+ }
+ }
+ list($here, $markActive, $url) = $this->__setHere($section, $url, $key, $activeMode, $hereMode, $options);
+ $children = array();
+ if ($under) {
+ if (!isset($this->__flatData[$section][$under])) {
+ $parent = array('title' => null, 'url' => false, 'options' => array(), 'here' => false,
+ 'under' => false, 'inPath' => false, 'activeChild' => false, 'sibling' => false, 'markActive' => false,
+ 'children' => array());
+ $parent[$uniqueKey] = strpos('{', $under)?unserialize($under):$under;
+ $this->__flatData[$section][$under] = $parent;
+ $this->__data[$section][$under] =& $this->__flatData[$section][$under];
+ }
+ $this->__flatData[$section][$key] = compact('title', 'url', 'options', 'under', 'here', 'inPath', 'activeChild', 'sibling',
+ 'markActive', 'children');
+ $this->__flatData[$section][$under]['children'][$key] =& $this->__flatData[$section][$key];
+ } elseif (!isset($this->__flatData[$section][$key]) || $overwrite) {
+ $this->__flatData[$section][$key] = compact('title', 'url', 'options', 'under', 'here', 'inPath', 'activeChild', 'sibling',
+ 'markActive', 'children');
+ $this->__data[$section][$key] =& $this->__flatData[$section][$key];
+ } elseif ($showWarnings) {
+ $altKey = $uniqueKey == 'title'?'url':'title';
+ trigger_error ('MenuHelper::add<br /> Duplicate menu item detected for item "' . $title . '" in menu ' . $section . '.' .
+ '<br />You can change the field used to detect duplicates which is currently set to ' . $uniqueKey . ',' .
+ ' can be changed to ' . $altKey . '.');
+ }
+ if ($hereMode == 'text' && $here == true) {
+ $this->__flatData[$section][$key]['url'] = false;
+ }
+ }
+/**
+ * del method
+ *
+ * Delete a menu item. Specify the section name alone to delete the entire section.
+ * Specify the section and key to delete a single menu item.
+ * Specify just the key to delete an entry from the main (or only) menu section.
+ *
+ * @param mixed $section
+ * @param mixed $key
+ * @return void
+ * @access public
+ */
+ function del($section, $key = null) {
+ if (is_null($key)) {
+ if (isset($this->__flatData[$section])) {
+ unset ($this->__flatData[$section]);
+ unset ($this->__data[$section]);
+ return;
+ }
+ $key = $section;
+ $section = 'main';
+ }
+ unset ($this->__flatData[$section][$key]);
+ unset ($this->__data[$section][$key]);
+ }
+/**
+ * sections method
+ *
+ * Return the names of all sections currently stored by the helper
+ *
+ * @access public
+ * @return mixed array of menu sections if no order passed. name of the section name matching the order if passed.
+ */
+ function sections ($order = null) {
+ $sequence = array();
+ foreach ($this->settings as $key => $settings) {
+ if ($order !== null && $settings['order'] == $order) {
+ return $key;
+ } elseif (!isset($sequence[$settings['order']])) {
+ $sequence[$settings['order']] = $key;
+ } else {
+ $sequence[$settings['order'] . rand()] = $key;
+ }
+ }
+ if ($order !== null) {
+ return false;
+ }
+ ksort($sequence);
+ return $sequence;
+ }
+/**
+ * generate menu method
+ *
+ * generate menu syntax examples:
+ * echo $menu->generate(); echo the main menu
+ * echo $menu->generate('menu'); as above but explicit
+ * echo $menu->generate('menu', array('element' => 'menus/item'); use an element for each item's content
+ * echo $menu->generate('menu', array('callback' => 'menuItem'); use loose method menuItem for each item's content
+ * echo $menu->generate('menu', array('callback' => array(&$object, 'method'); call $object->method($data) for each item's content
+ *
+ * @param mixed $section the section name or the numerical order
+ * @param array $settings to be passed to the tree helper
+ * @param bool $createEmpty
+ * @access public
+ * @return void
+ */
+ function generate ($section = 'main', $settings = array(), $createEmpty = true, $debug = false) {
+ if (is_array($section)) {
+ extract(array_merge(array('section' => 'main'), $section));
+ }
+ if (!isset($this->settings[$section])) {
+ if (is_numeric($section)) {
+ $order = $section;
+ $match = false;
+ foreach ($this->settings as $section => $_) {
+ if ($_['order'] == $order) {
+ $match = true;
+ break;
+ }
+ }
+ if (!$match) {
+ return;
+ }
+ }
+ return false;
+ }
+ $settings = array_merge($this->settings[$section], $settings);
+ $settings = array_merge(array('callback' => array(&$this, 'menuItem'), 'model' => false, 'class' => 'menu'), $settings);
+ extract ($settings);
+ if (isset($this->__data[$section])) {
+ if ($onlyActiveChildren) {
+ $pkey = false;
+ if (isset($this->settings[$section]['hereKey'])) {
+ $key = $this->settings[$section]['hereKey'];
+ $pkey = $this->__flatData[$section][$key]['under'];
+ unset($this->settings[$section]['hereKey']);
+ if (isset($this->__flatData[$section][$key]['children'])) {
+ foreach ($this->__flatData[$section][$key]['children'] as $i => $_i) {
+ $this->__flatData[$section][$key]['children'][$i]['activeChild'] = true;
+ }
+ }
+ $under = $this->__flatData[$section][$key]['under'];
+ while ($under) {
+ $this->__flatData[$section][$under]['inPath'] = true;
+ $under = $this->__flatData[$section][$under]['under'];
+ }
+ }
+ foreach ($this->__flatData[$section] as $i => $row) {
+ if (!$row['under'] && !$row['here']) {
+ $this->__flatData[$section][$i]['sibling'] = true;
+ } elseif ($row['under'] == $pkey && !$row['activeChild'] && !$row['here']) {
+ $this->__flatData[$section][$i]['sibling'] = true;
+ } elseif (!($row['here'] || $row['inPath']|| $row['activeChild'] || $row['sibling'])) {
+ unset($this->__flatData[$section][$i]);
+ }
+ }
+ $this->__cleanData($this->__data[$section], $section);
+ }
+ $data = $this->__data[$section];
+ if ($debug) {
+ $settings = array(
+ 'callback' => array(&$this, 'debugItem'),
+ 'debug' => true,
+ 'indent' => true,
+ 'model' => false,
+ 'class' => 'menu',
+ 'itemType' => false,
+ 'type' => false
+ );
+ } else {
+ unset ($this->settings[$section]);
+ unset ($this->__data[$section]);
+ unset ($this->__flatData[$section]);
+ }
+ } elseif ($createEmpty) {
+ return '<ul><!-- Empty menu --></ul>';
+ } else {
+ return false;
+ }
+ $return = $this->Tree->generate($data, $settings);
+ if ($debug) {
+ return '<pre><h2> Menu Section:' . $section . '</h2>' . $return . '</pre>';
+ }
+ return $return;
+ }
+/**
+ * debug method
+ *
+ * @param mixed $section
+ * @return void
+ * @access public
+ */
+ function debug($section = null) {
+ if (!$section) {
+ foreach ($this->settings as $section) {
+ $this->debug($section);
+ }
+ return;
+ }
+ return $this->generate(array('section' => $section, 'debug' => true));
+ }
+/**
+ * internal callback
+ *
+ * Used to return the output from the html helper using the parameters for this menu option
+ *
+ * @param array $data
+ * @access public
+ * @return void
+ */
+ function debugItem($data = array()) {
+ foreach (array_keys($data) as $key) {
+ if ($key != 'data') {
+ $debug[$key] = (string)$data[$key];
+ }
+ }
+ $htmlAttributes = array();
+ $markActive = false;
+ $confirmMessage = false;
+ $escapeTitle = true;
+ extract ($data);
+ extract ($data);
+ if ($options) {
+ extract ($options);
+ }
+ if ($markActive) {
+ $this->Tree->addItemAttribute('class', 'active');
+ if (isset ($htmlAttributes['class'])) {
+ $htmlAttributes['class'] .= ' active';
+ } else {
+ $htmlAttributes['class'] = 'active';
+ }
+ }
+ $return = str_pad('title', 25, ' ') . ':' . $title . "\r\n";
+ $return .= str_pad('url', 25, ' ') . ':' . str_replace(Router::url('/'), '/', Router::url($url)) . "\r\n";
+ $return .= str_pad('here?', 25, ' ') . ':' . ($here?'yes':'no');
+ $return .= "\r\n";
+ //$return .= str_pad('in path?', 25, ' ') . ':' . ($inPath?'yes':'no') . "\r\n";
+ //$return .= str_pad('active child?', 25, ' ') . ':' . ($activeChild?'yes':'no') . "\r\n";
+ //$return .= str_pad('active sibling?', 25, ' ') . ':' . ($sibling?'yes':'no') . "\r\n";
+ $return .= str_pad('first child?', 25, ' ') . ':' . ($firstChild?'yes':'no') . "\r\n";
+ $return .= str_pad('last child?', 25, ' ') . ':' . ($lastChild?'yes':'no') . "\r\n";
+ $return .= str_pad('has children?', 25, ' ') . ':' . ($hasChildren?'yes':'no');
+ if ($hasChildren) {
+ $bits = array();
+ if ($numberOfDirectChildren) {
+ $bits[] = 'direct: ' . $numberOfDirectChildren;
+ }
+ /* Unreachable, but present just incase ported somewhere else */
+ if ($numberOfTotalChildren) {
+ $bits[] = 'total: ' . $numberOfTotalChildren;
+ $bits[] = 'visible?: ' . ($hasVisibleChildren?'yes':'no');
+ }
+ /* Unreachable end */
+ $return .= ' (' . implode(', ', $bits) . ")";
+ }
+ $return .= "\r\n";
+ $return .= str_pad('depth', 25, ' ') . ':' . $depth . "\r\n";
+ return $return;
+ }
+/**
+ * internal callback
+ *
+ * Used to return the output from the html helper using the parameters for this menu option
+ *
+ * @param array $data
+ * @access public
+ * @return void
+ */
+ function menuItem($data = array()) {
+ $htmlAttributes = array();
+ $markActive = false;
+ $confirmMessage = false;
+ $escapeTitle = true;
+ extract ($data);
+ extract ($data);
+ if ($options) {
+ extract ($options);
+ }
+ if ($markActive) {
+ $this->Tree->addItemAttribute('class', 'active');
+ if (isset ($htmlAttributes['class'])) {
+ $htmlAttributes['class'] .= ' active';
+ } else {
+ $htmlAttributes['class'] = 'active';
+ }
+ }
+ if ($url === false) {
+ return $title;
+ } else {
+ return $this->Html->link($title, $url, $htmlAttributes, $confirmMessage, $escapeTitle);
+ }
+ }
+/**
+ * setHere method
+ *
+ * Used internally to detect whether the current menu item links to the page currently
+ * being rendered and modify the url if appropriate
+ *
+ * @param mixed $section
+ * @param mixed $url
+ * @param mixed $activeMode
+ * @param mixed $hereMode
+ * @access private
+ * @return array($here, $markActive, $url)
+ */
+ function __setHere($section, $url, $key, $activeMode, $hereMode, $options) {
+ $view =& ClassRegistry:: getObject('view');
+ if (isset($this->settings[$section]['hereKey']) || !$view) {
+ return array(false, false, $url);
+ }
+ $here = $markActive = false;
+ if (!empty($options['markActive'])) {
+ $here = true;
+ } elseif ($activeMode == 'url' && Router::normalize($url) == $this->__here) {
+ $here = true;
+ } elseif (is_array($url) &&
+ (!isset($url['controller']) ||
+ Inflector::underscore($url['controller']) == Inflector::underscore($view->name))) {
+ if ($activeMode == 'controller') {
+ $here = true;
+ } elseif ($activeMode == 'action' &&
+ (!isset($url['action']) || $url['action'] == Inflector::underscore($view->action))) {
+ $here = true;
+ }
+ }
+ if ($here) {
+ $this->settings[$section]['hereKey'] = $key;
+ if ($hereMode == 'text') {
+ $url = false;
+ } elseif ($hereMode == 'active') {
+ $markActive = true;
+ }
+ }
+ if ($here && $hereMode == 'active') {
+ $this->Tree->addItemAttribute('class', 'active');
+ if (isset ($htmlAttributes['class'])) {
+ $htmlAttributes['class'] .= ' active';
+ } else {
+ $htmlAttributes['class'] = 'active';
+ }
+ }
+
+ return array($here, $markActive, $url);
+ }
+/**
+ * cleanData method
+ *
+ * Shouldn't really be necessary. Ensures that any item(s) which have been suppressed by the "only show active"
+ * logic are removed
+ *
+ * @param mixed $array
+ * @param mixed $section
+ * @access private
+ * @return void
+ */
+ function __cleanData(&$array, $section) {
+ foreach ($array as $key => $row) {
+ if (!isset($this->__flatData[$section][$key])) {
+ unset ($array[$key]);
+ } elseif (isset($row['children']) && $row['children']) {
+ $this->__cleanData($array[$key]['children'], $section);
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/views/helpers/tree.php b/views/helpers/tree.php
new file mode 100644
index 0000000..08cd883
--- /dev/null
+++ b/views/helpers/tree.php
@@ -0,0 +1,481 @@
+<?php
+/* SVN FILE: $Id: tree.php 699 2008-11-19 12:11:38Z AD7six $ */
+/**
+ * Tree Helper.
+ *
+ * Long description for tree.php
+ *
+ * Used the generate nested representations of hierarchial data
+ *
+ * Copyright (c) 2008, Andy Dawson
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2008, Andy Dawson
+ * @link www.ad7six.com
+ * @package base
+ * @subpackage base.views.helpers
+ * @since v 1.0
+ * @version $Revision: 699 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-19 13:11:38 +0100 (Wed, 19 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * TreeHelper class
+ *
+ * Helper to generate tree representations of MPTT or recursively nested data
+ *
+ * @uses AppHelper
+ * @package base
+ * @subpackage base.views.helpers
+ */
+class TreeHelper extends AppHelper {
+/**
+ * name property
+ *
+ * @var string 'Tree'
+ * @access public
+ */
+ var $name = 'Tree';
+/**
+ * settings property
+ *
+ * @var array
+ * @access private
+ */
+ var $__settings = array();
+/**
+ * typeAttributes property
+ *
+ * @var array
+ * @access private
+ */
+ var $__typeAttributes = array();
+/**
+ * typeAttributesNext property
+ *
+ * @var array
+ * @access private
+ */
+ var $__typeAttributesNext = array();
+/**
+ * itemAttributes property
+ *
+ * @var array
+ * @access private
+ */
+ var $__itemAttributes = array();
+/**
+ * helpers variable
+ *
+ * @var array
+ * @access public
+ */
+ var $helpers = array ('Html');
+/**
+ * Tree generation method.
+ *
+ * Accepts the results of
+ * find('all', array('fields' => array('lft', 'rght', 'whatever'), 'order' => 'lft ASC'));
+ * children(); // if you have the tree behavior of course!
+ * or findAllThreaded(); and generates a tree structure of the data.
+ *
+ * Settings (2nd parameter):
+ * 'model' => name of the model (key) to look for in the data array. defaults to the first model for the current
+ * controller. If set to false 2d arrays will be allowed/expected.
+ * 'alias' => the array key to output for a simple ul (not used if element or callback is specified)
+ * 'type' => type of output defaults to ul
+ * 'itemType => type of item output default to li
+ * 'id' => id for top level 'type'
+ * 'class' => class for top level 'type'
+ * 'element' => path to an element to render to get node contents.
+ * 'callback' => callback to use to get node contents. e.g. array(&$anObject, 'methodName') or 'floatingMethod'
+ * 'autoPath' => array($left, $right [$classToAdd = 'active']) if set any item in the path will have the class $classToAdd added. MPTT only.
+ * 'left' => name of the 'lft' field if not lft. only applies to MPTT data
+ * 'right' => name of the 'rght' field if not lft. only applies to MPTT data
+ * 'depth' => used internally when running recursively, can be used to override the depth in either mode.
+ * 'firstChild' => used internally when running recursively.
+ * 'splitDepth' => if multiple "parallel" types are required, instead of one big type, nominate the depth to do so here
+ * example: useful if you have 30 items to display, and you'd prefer they appeared in the source as 3 lists of 10 to be able to
+ * style/float them.
+ * 'splitCount' => the number of "parallel" types. defaults to 3
+ *
+ * @param array $data data to loop on
+ * @param array $settings
+ * @return string html representation of the passed data
+ * @access public
+ */
+ function generate ($data, $settings = array ()) {
+ $this->__settings = array_merge(array(
+ 'model' => null,
+ 'alias' => 'name',
+ 'type' => 'ul',
+ 'itemType' => 'li',
+ 'id' => false,
+ 'class' => false,
+ 'element' => false,
+ 'callback' => false,
+ 'autoPath' => false,
+ 'left' => 'lft',
+ 'right' => 'rght',
+ 'depth' => 0,
+ 'firstChild' => true,
+ 'indent' => null,
+ 'splitDepth' => false,
+ 'splitCount' => 3,
+ 'totalNodes' => false
+ ), (array)$settings);
+ if ($this->__settings['autoPath'] && !isset($this->__settings['autoPath'][2])) {
+ $this->__settings['autoPath'][2] = 'active';
+ }
+ extract($this->__settings);
+ if ($indent === null && Configure::read()) {
+ $indent = true;
+ }
+ $view =& ClassRegistry:: getObject('view');
+ if ($model === null) {
+ $model = Inflector::classify($view->params['models'][0]);
+ }
+ if (!$model) {
+ $model = '_NULL_';
+ }
+ $this->__itemAttributes = $this->__typeAttributes = $this->__typeAttributesNext = array();
+ $stack = array();
+ if ($depth == 0) {
+ if ($class) {
+ $this->addTypeAttribute('class', $class, null, 'previous');
+ }
+ if ($id) {
+ $this->addTypeAttribute('id', $id, null, 'previous');
+ }
+ }
+ $return = '';
+ if ($indent) {
+ $return = "\r\n";
+ }
+ $__addType = true;
+ $this->__settings['totalNodes'] = count($data);
+ $keys = array_keys($data);
+ foreach ($data as $i => $result) {
+ /* Allow 2d data arrays */
+ if ($model == '_NULL_') {
+ $_result = $result;
+ $result[$model] = $_result;
+ }
+ /* BulletProof */
+ if (!isset($result[$model][$left]) && !isset($result['children'])) {
+ $result['children'] = array();
+ }
+ /* Close open items as appropriate */
+ while ($stack && ($stack[count($stack)-1] < $result[$model][$right])) {
+ array_pop($stack);
+ if ($indent) {
+ $whiteSpace = str_repeat("\t",count($stack));
+ $return .= "\r\n" . $whiteSpace . "\t";
+ }
+ if ($type) {
+ $return .= '</' . $type . '>';
+ }
+ if ($itemType) {
+ $return .= '</' . $itemType . '>';
+ }
+ }
+ /* Some useful vars */
+ $hasChildren = $firstChild = $lastChild = $hasVisibleChildren = false;
+ $numberOfDirectChildren = $numberOfTotalChildren = null;
+ if (isset($result['children'])) {
+ if ($result['children']) {
+ $hasChildren = $hasVisibleChildren = true;
+ $numberOfDirectChildren = count($result['children']);
+ }
+ $key = array_search($i, $keys);
+ if ($key == 0) {
+ $firstChild = true;
+ }
+ if ($key == count($keys) - 1) {
+ $lastChild = true;
+ }
+ } elseif (isset($result[$model][$left])) {
+ if ($result[$model][$left] != ($result[$model][$right] - 1)) {
+ $hasChildren = true;
+ $numberOfTotalChildren = ($result[$model][$right] - $result[$model][$left] - 1) / 2;
+ if (isset($data[$i + 1]) && $data[$i + 1][$model][$right] < $result[$model][$right]) {
+ $hasVisibleChildren = true;
+ }
+ }
+ if (!isset($data[$i - 1]) || ($data[$i - 1][$model][$left] == ($result[$model][$left] - 1))) {
+ $firstChild = true;
+ }
+ if (!isset($data[$i + 1]) || ($stack && $stack[count($stack) - 1] == ($result[$model][$right] + 1))) {
+ $lastChild = true;
+ }
+ }
+ $elementData = array(
+ 'data' => $result,
+ 'depth' => $depth?$depth:count($stack),
+ 'hasChildren' => $hasChildren,
+ 'numberOfDirectChildren' => $numberOfDirectChildren,
+ 'numberOfTotalChildren' => $numberOfTotalChildren,
+ 'firstChild' => $firstChild,
+ 'lastChild' => $lastChild,
+ 'hasVisibleChildren' => $hasVisibleChildren
+ );
+ $this->__settings = array_merge($this->__settings, $elementData);
+ /* Main Content */
+ if ($element) {
+ $content = $view->element($element,$elementData);
+ } elseif ($callback) {
+ list($content) = array_map($callback, array($elementData));
+ } else {
+ $content = $result[$model][$alias];
+ }
+ if (!$content) {
+ continue;
+ }
+ $whiteSpace = str_repeat("\t", $depth);
+ if ($indent && strpos($content, "\r\n", 1)) {
+ $content = str_replace("\r\n", "\n" . $whiteSpace . "\t", $content);
+ }
+ /* Prefix */
+ if ($__addType) {
+ if ($indent) {
+ $return .= "\r\n" . $whiteSpace;
+ }
+ if ($type) {
+ $typeAttributes = $this->__attributes($type, array('data' => $elementData));
+ $return .= '<' . $type . $typeAttributes . '>';
+ }
+ }
+ if ($indent) {
+ $return .= "\r\n" . $whiteSpace . "\t";
+ }
+ if ($itemType) {
+ $itemAttributes = $this->__attributes($itemType, $elementData);
+ $return .= '<' . $itemType . $itemAttributes . '>';
+ }
+ $return .= $content;
+ /* Suffix */
+ $__addType = false;
+ if ($hasVisibleChildren) {
+ if ($numberOfDirectChildren) {
+ $settings['depth'] = $depth + 1;
+ $return .= $this->__suffix();
+ $return .= $this->generate($result['children'], $settings);
+ if ($itemType) {
+ $return .= '</' . $itemType . '>';
+ }
+ } elseif ($numberOfTotalChildren) {
+ $__addType = true;
+ $stack[] = $result[$model][$right];
+ }
+ } else {
+ if ($itemType) {
+ $return .= '</' . $itemType . '>';
+ }
+ $return .= $this->__suffix();
+ }
+ }
+ /* Cleanup */
+ while ($stack) {
+ array_pop($stack);
+ if ($indent) {
+ $whiteSpace = str_repeat("\t",count($stack));
+ $return .= "\r\n" . $whiteSpace . "\t";
+ }
+ if ($type) {
+ $return .= '</' . $type . '>';
+ }
+ if ($itemType) {
+ $return .= '</' . $itemType . '>';
+ }
+ }
+
+ if ($indent) {
+ $return .= "\r\n";
+ }
+ if ($type) {
+ $return .= '</' . $type . '>';
+ if ($indent) {
+ $return .= "\r\n";
+ }
+ }
+ return $return;
+ }
+/**
+ * addItemAttribute function
+ *
+ * Called to modify the attributes of the next <item> to be processed
+ * Note that the content of a 'node' is processed before generating its wrapping <item> tag
+ *
+ * @param string $id
+ * @param string $key
+ * @param mixed $value
+ * @access public
+ * @return void
+ */
+ function addItemAttribute($id = '', $key = '', $value = null) {
+ if (!is_null($value)) {
+ $this->__itemAttributes[$id][$key] = $value;
+ } elseif (!(isset($this->__itemAttributes[$id]) && in_array($key, $this->__itemAttributes[$id]))) {
+ $this->__itemAttributes[$id][] = $key;
+ }
+ }
+/**
+ * addTypeAttribute function
+ *
+ * Called to modify the attributes of the next <type> to be processed
+ * Note that the content of a 'node' is processed before generating its wrapping <type> tag (if appropriate)
+ * An 'interesting' case is that of a first child with children. To generate the output
+ * <ul> (1)
+ * <li>XYZ (3)
+ * <ul> (2)
+ * <li>ABC...
+ * ...
+ * </ul>
+ * ...
+ * The processing order is indicated by the numbers in brackets.
+ * attributes are allways applied to the next type (2) to be generated
+ * to set properties of the holding type - pass 'previous' for the 4th param
+ * i.e.
+ * // Hide children (2)
+ * $tree->addTypeAttribute('style', 'display', 'hidden');
+ * // give top level type (1) a class
+ * $tree->addTypeAttribute('class', 'hasHiddenGrandChildren', null, 'previous');
+ *
+ * @param string $id
+ * @param string $key
+ * @param mixed $value
+ * @access public
+ * @return void
+ */
+ function addTypeAttribute($id = '', $key = '', $value = null, $previousOrNext = 'next') {
+ $var = '__typeAttributes';
+ $firstChild = isset($this->__settings['firstChild'])?$this->__settings['firstChild']:true;
+ if ($previousOrNext == 'next' && $firstChild) {
+ $var = '__typeAttributesNext';
+ }
+ if (!is_null($value)) {
+ $this->{$var}[$id][$key] = $value;
+ } elseif (!(isset($this->{$var}[$id]) && in_array($key, $this->{$var}[$id]))) {
+ $this->{$var}[$id][] = $key;
+ }
+ }
+
+/**
+ * supressChildren method
+ *
+ * @return void
+ * @access public
+ */
+ function supressChildren() {
+ }
+/**
+ * suffix method
+ *
+ * Used to close and reopen a ul/ol to allow easier listings
+ *
+ * @access private
+ * @return void
+ */
+ function __suffix() {
+/**
+ * splitCount property
+ *
+ * @static
+ * @var int 0
+ * @access private
+ */
+ static $__splitCount = 0;
+/**
+ * splitCounter property
+ *
+ * @static
+ * @var int 0
+ * @access private
+ */
+ static $__splitCounter = 0;
+ extract($this->__settings);
+ if ($splitDepth || $splitCount) {
+ if (!$splitDepth) {
+ $__splitCount = $totalNodes / $splitCount;
+ $rounded = (int)$__splitCount;
+ if ($rounded < $__splitCount) {
+ $__splitCount = $rounded + 1;
+ }
+ } elseif ($depth == $splitDepth -1) {
+ $total = $numberOfDirectChildren?$numberOfDirectChildren:$numberOfTotalChildren;
+ if ($total) {
+ $__splitCounter = 0;
+ $__splitCount = $total / $splitCount;
+ $rounded = (int)$__splitCount;
+ if ($rounded < $__splitCount) {
+ $__splitCount = $rounded + 1;
+ }
+ }
+ }
+ if (!$splitDepth || $depth == $splitDepth) {
+ $__splitCounter++;
+ if ($type && ($__splitCounter % $__splitCount) == 0 && !$lastChild) {
+ return '</' . $type . '><' . $type . '>';
+ }
+ }
+ }
+ return;
+ }
+/**
+ * attributes function
+ *
+ * Logic to apply styles to tags.
+ *
+ * @param mixed $rType
+ * @param array $elementData
+ * @access private
+ * @return void
+ */
+ function __attributes($rType, $elementData = array(), $clear = true) {
+ extract($this->__settings);
+ if ($rType == $type) {
+ $attributes = $this->__typeAttributes;
+ if ($clear) {
+ $this->__typeAttributes = $this->__typeAttributesNext;
+ $this->__typeAttributesNext = array();
+ }
+ } else {
+ $attributes = $this->__itemAttributes;
+ $this->__itemAttributes = array();
+ if ($clear) {
+ $this->__itemAttributes = array();
+ }
+ }
+ if ($autoPath && $depth) {
+ if ($this->__settings['data'][$model][$left] < $autoPath[0] && $this->__settings['data'][$model][$right] > $autoPath[1]) {
+ $attributes['class'][] = $autoPath[2];
+ } elseif (isset($autoPath[3]) && $this->__settings['data'][$model][$left] == $autoPath[0]) {
+ $attributes['class'][] = $autoPath[3];
+ }
+ }
+ if ($attributes) {
+ foreach ($attributes as $type => $values) {
+ foreach ($values as $key => $val) {
+ if (is_array($val)) {
+ $attributes[$type][$key] = '';
+ foreach ($val as $vKey => $v) {
+ $attributes[$type][$key][$vKey] .= $vKey . ':' . $v;
+ }
+ $attributes[$type][$key] = implode(';', $attributes[$type][$key]);
+ }
+ if (is_string($key)) {
+ $attributes[$type][$key] = $key . ':' . $val . ';';
+ }
+ }
+ $attributes[$type] = $type . '="' . implode(' ', $attributes[$type]) . '"';
+ }
+ return ' ' . implode(' ', $attributes);
+ }
+ return '';
+ }
+}
+?>
\ No newline at end of file
diff --git a/views/layouts/ajax.ctp b/views/layouts/ajax.ctp
new file mode 100644
index 0000000..2bf9d9b
--- /dev/null
+++ b/views/layouts/ajax.ctp
@@ -0,0 +1,2 @@
+<?php /* SVN FILE: $Id: ajax.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+echo $content_for_layout; ?>
\ No newline at end of file
diff --git a/views/layouts/default.ctp b/views/layouts/default.ctp
new file mode 100644
index 0000000..dab55fb
--- /dev/null
+++ b/views/layouts/default.ctp
@@ -0,0 +1,166 @@
+<?php /* SVN FILE: $Id: default.ctp 702 2008-11-19 12:13:02Z AD7six $ */ ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<?php echo $html->charset('UTF-8');
+$app = cache('views/app_name_' . $this->params['lang']);
+if ($app) {
+ $app = unserialize($app);
+} else {
+ $app = $this->requestAction('/nodes/app_name/' . $this->params['lang']);
+}
+if ($this->here == $this->webroot) {
+ $title_for_layout = $app['tag_line'];
+}
+echo $html->meta(
+ 'keywords',
+ 'CakePHP Documentation, ' . str_replace(' :: ', ', ', $title_for_layout)
+);
+
+?>
+<title><?php echo ($title_for_layout?$title_for_layout:$app['tag_line']) . ' :: ' . $app['name'];?></title>
+<link rel="icon" href="<?php echo $this->webroot;?>favicon.ico" type="image/x-icon" />
+<link rel="shortcut icon" href="<?php echo $this->webroot;?>favicon.ico" type="image/x-icon" />
+<?php
+echo $html->meta(
+ 'rss',
+ array('admin' => false, 'plugin' => false, 'controller' => 'changes', 'action' => 'index', 'ext' => 'rss'),
+ array('title' => __('Recent Changes', true))
+);
+echo $html->meta(
+ 'rss',
+ array('admin' => false, 'plugin' => false, 'controller' => 'changes', 'action' => 'index', 'language' => '*', 'ext' => 'rss'),
+ array('title' => __('Recent Changes for all languages', true))
+);
+echo $html->meta(
+ 'rss',
+ array('admin' => false, 'plugin' => false, 'controller' => 'comments', 'action' => 'recent', 'ext' => 'rss'),
+ array('title' => __('Recent comments', true))
+);
+echo $html->meta(
+ 'rss',
+ array('admin' => false, 'plugin' => false, 'controller' => 'comments', 'action' => 'recent', 'language' => '*', 'ext' => 'rss'),
+ array('title' => __('Recent Comments for all languages', true))
+);
+?><cake:nocache><?php
+if ($session->read('Auth.User.id')) {
+ $userName = $session->read('Auth.User.username');
+ echo $html->meta(
+ 'rss',
+ array('admin' => false, 'plugin' => false, 'controller' => 'changes', 'action' => 'index', 'user' => $userName, 'language' => '*', 'ext' => 'rss'),
+ array('title' => __('My Submissions', true))
+ );
+ $menu->add(array(
+ 'section' => 'Feeds',
+ 'title' => __('My Submissions', true),
+ 'url' => array('admin' => false, 'plugin' => false, 'controller' => 'changes', 'action' => 'index', 'user' => $userName, 'language' => '*', 'ext' => 'rss')
+ ));
+}
+?></cake:nocache><?php
+if (!isset($this->params['lang']) || $this->params['lang'] == 'en') {
+ $base = $html->url('/');
+} else {
+ $base = $html->url('/' . $this->params['lang'] . '/');
+}
+echo $javascript->codeBlock("var baseUrl = '{$base}';"); ?>
+<?php
+echo $html->css(array('cake.generic.css?v=537', 'cake.cookbook.css?v=537'), 'stylesheet', array('media' => 'screen'));
+echo $html->css('print.css?v=537', 'stylesheet', array('media' => 'print'));
+if (isset ($javascript)) {
+ echo $javascript->link('jquery/jquery.min.js');
+ echo $javascript->link('scripts.js?v=537');
+}
+echo $scripts_for_layout;
+if (Configure::read()) {
+ echo $html->css('development');
+ echo $javascript->link('development');
+}
+?>
+</head>
+<body>
+ <div id="container">
+ <div id="header">
+ <h1><?php
+$link = '/';
+if ($this->params['lang'] != 'en') {
+ $link .= $this->params['lang'];
+}
+echo $html->link(sprintf(__('Welcome to %s', true), $app['name']), $link);
+ ?></h1>
+ </div>
+ <?php echo $this->element('collections'); ?>
+ <cake:nocache> <?php echo $this->element('secondary_nav'); ?></cake:nocache>
+ <?php
+ echo $this->element('sites_nav');
+ echo $this->element('search');
+ ?>
+ <div id="content">
+ <div id="body">
+ <?php echo $this->element('crumbs'); ?>
+ <cake:nocache> <?php
+ if($session->check('Message.auth')):
+ $session->flash('auth');
+ endif;
+
+ if($session->check('Message.flash')):
+ $session->flash();
+ endif;
+ ?></cake:nocache>
+ <?php echo $content_for_layout; ?>
+ </div>
+ <div id="side"><?php
+if ($this->name == 'Nodes' && isset($data['Node']['Node']) && !$isAdmin) {
+ $url = $html->url(array('admin' => false, 'controller' => 'nodes', 'action' => 'toc', $data['Node']['Node']['id']));
+ echo $this->element('toc');
+}
+ ?><cake:nocache> <?php echo $this->element('side_menu'); ?></cake:nocache>
+ </div>
+ <div class="clear"></div>
+ </div>
+
+ <div id="footer">
+ <p>
+ <span>Also available in:
+ <?php
+ $url = $this->passedArgs;
+ foreach (Configure::read('Languages.all') as $lang) {
+ $url['lang'] = $lang;
+ $languages[] = $html->link($lang, $url);
+ }
+ echo implode (' · ', $languages);
+ ?></span>
+ <?php echo ' ';
+ echo $html->link(
+ $html->image('cake.power.gif', array('alt'=>"CakePHP: the PHP Rapid Development Framework")),
+ 'http://www.cakephp.org/',
+ null,
+ null,
+ false
+ );
+ ?>
+ <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
+ <?php echo $html->image('license.png', array('alt' => "Creative Commons License")); ?>
+ </a>
+ </p>
+ <p>© <a href="http://cakefoundation.org">Cake Software Foundation, Inc.</a></p>
+ </div>
+ </div>
+<?php
+if(env('SERVER_ADDR') != '127.0.0.1'):?>
+<script type="text/javascript">
+var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+</script>
+<script type="text/javascript">
+var pageTracker = _gat._getTracker("UA-743287-3");
+pageTracker._initData();
+pageTracker._trackPageview();
+</script>
+<?php endif;
+$this->set('data', false); $this->cache->data = false;
+if (Configure::read()) {
+ echo $this->element('development');
+}
+?>
+</body>
+</html>
\ No newline at end of file
diff --git a/views/layouts/error.ctp b/views/layouts/error.ctp
new file mode 100644
index 0000000..04f39b7
--- /dev/null
+++ b/views/layouts/error.ctp
@@ -0,0 +1,43 @@
+<?php /* SVN FILE: $Id: error.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>
+ <?php __('CakePHP: the rapid development php framework:'); ?>
+ <?php echo $title_for_layout; ?>
+ </title>
+ <?php
+ echo $html->charset();
+ echo $html->meta('icon');
+
+ echo $html->css('cake.generic');
+
+ echo $scripts_for_layout;
+ ?>
+</head>
+<body>
+ <div id="container">
+ <?php echo $this->element('header');?>
+
+ <div id="content">
+ <?php
+ if ($session->check('Message.flash')):
+ $session->flash();
+ endif;
+ ?>
+
+ <?php echo $content_for_layout; ?>
+
+ </div>
+ <div id="footer">
+ <?php echo $html->link(
+ $html->image('cake.power.gif', array('alt'=> __("CakePHP: the rapid development php framework", true), 'border'=>"0")),
+ 'http://www.cakephp.org/',
+ array('target'=>'_new'), null, false
+ );
+ ?>
+ </div>
+ </div>
+ <?php echo $cakeDebug; ?>
+</body>
+</html>
\ No newline at end of file
diff --git a/views/layouts/flash.ctp b/views/layouts/flash.ctp
new file mode 100644
index 0000000..7ee878f
--- /dev/null
+++ b/views/layouts/flash.ctp
@@ -0,0 +1,22 @@
+<?php /* SVN FILE: $Id: flash.ctp 30 2007-01-14 03:20:46Z phpnut $ */ ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><?php echo $page_title?></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<?php if(Configure::read() == 0) { ?>
+<meta http-equiv="Refresh" content="<?php echo $pause?>;url=<?php echo $url?>"/>
+<?php } ?>
+<style><!--
+P { text-align:center; font:bold 1.1em sans-serif }
+A { color:#444; text-decoration:none }
+A:HOVER { text-decoration: underline; color:#44E }
+--></style>
+</head>
+
+<body>
+
+<p><a href="<?php echo $url?>"><?php echo $message?></a></p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/views/layouts/rss/default.ctp b/views/layouts/rss/default.ctp
new file mode 100644
index 0000000..a980dd3
--- /dev/null
+++ b/views/layouts/rss/default.ctp
@@ -0,0 +1,22 @@
+<?php /* SVN FILE: $Id: default.ctp 611 2008-08-19 15:39:29Z AD7six $ */ ?>
+<cake:nocache><?php header('Content-type: application/xhtml-xml'); ?></cake:nocache><?php
+echo $rss->header();
+
+if (!isset($channel)) {
+ $channel = array();
+}
+if (!isset($channel['title'])) {
+ $channel['title'] = 'Cookbook :: ' . $title_for_layout;
+}
+
+echo $rss->document(
+ $rss->channel(
+ array(), $channel, $content_for_layout
+ )
+);
+if (!$this->cacheAction) {
+ $this->cacheAction = CACHE_DURATION;
+}
+$this->data = null;
+Configure::write('debug', 0);
+?>
\ No newline at end of file
diff --git a/views/layouts/xml/default.ctp b/views/layouts/xml/default.ctp
new file mode 100644
index 0000000..2f406a7
--- /dev/null
+++ b/views/layouts/xml/default.ctp
@@ -0,0 +1,10 @@
+<?php /* SVN FILE: $Id: default.ctp 611 2008-08-19 15:39:29Z AD7six $ */ ?>
+<cake:nocache><?php header('Content-type: text/xml'); ?></cake:nocache><?php
+e($xml->header());
+echo $content_for_layout;
+if (!$this->cacheAction) {
+ $this->cacheAction = CACHE_DURATION;
+}
+$this->data = null;
+Configure::write('debug', 0);
+?>
\ No newline at end of file
diff --git a/views/nodes/add.ctp b/views/nodes/add.ctp
new file mode 100755
index 0000000..935efad
--- /dev/null
+++ b/views/nodes/add.ctp
@@ -0,0 +1,51 @@
+<?php /* SVN FILE: $Id: add.ctp 672 2008-10-06 14:03:23Z AD7six $ */ ?>
+<div class="nodes add">
+<?php
+$contents = '';
+if (isset($this->data['Revision']['content'])) {
+ $contents = $this->data['Revision']['content'];
+ preg_match_all('@<pre[^>]*>([\\s\\S]*?)</pre>@i', $contents, $result, PREG_PATTERN_ORDER);
+ if (!empty($result['0'])) {
+ $count = count($result['0']);
+ for($i = 0; $i < $count; $i++) {
+ $replaced = str_replace('<', '<', $result['1'][$i]);
+ $replaced = str_replace('>', '>', $replaced);
+ $contents = str_replace($result[1][$i], $replaced, $contents);
+ }
+ }
+}
+echo $html->link(__('Please review the guidelines for submitting to the Cookbook to ensure consistency.', true),
+ array('controller' => 'nodes', 'action' => 'view', 482, 'contributing-to-the-cookbook'));
+echo $this->element('preview');
+echo $form->create(null, array('url' => '/' .$this->params['url']['url']));
+
+ $inputs['legend'] = __('Add a new section', true);
+
+ if ($session->read('Auth.User.Level') == ADMIN) {
+ $inputs['Node.show_in_toc'] = array('type' => 'checkbox');
+ }
+
+ $inputs['Revision.preview'] = array('type'=>'checkbox', 'label' => __('Show me a preview before submitting', true), 'error' => false);
+
+ $inputs['Revision.under_node_id'] = array('label'=> __('under', true), 'options' => $parents);
+
+ if (isset($afters)) {
+ $inputs['Revision.after_node_id'] = array('label'=> __('after', true), 'options' => $afters);
+ }
+
+ $inputs[] = 'Revision.title';
+
+ $inputs['Revision.content'] = array(
+ 'label' => __('Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically', true),
+ 'cols' => 100, 'rows' => 30,
+ 'value' => $contents
+ );
+
+ $inputs['Revision.reason'] = array('label' => __('Optionally explain in brief why you are proposing this addition (In English Please) :)', true));
+
+ echo $form->inputs($inputs);
+
+echo $form->end('save');
+?>
+</div>
+<?php echo $this->element('markitup', array('process' => 'textarea')); ?>
\ No newline at end of file
diff --git a/views/nodes/admin_edit.ctp b/views/nodes/admin_edit.ctp
new file mode 100644
index 0000000..0a14068
--- /dev/null
+++ b/views/nodes/admin_edit.ctp
@@ -0,0 +1,25 @@
+<?php /* SVN FILE: $Id: admin_edit.ctp 672 2008-10-06 14:03:23Z AD7six $ */ ?>
+<div>
+<h2>Properties for <?php echo $this->data['Revision']['title']; ?></h2>
+<?php
+$parent = $currentPath[count($currentPath) - 2]['Revision']['title'];
+$auth = array(
+ ADMIN => 'Admin',
+ EDITOR => 'Editor',
+ MODERATOR => 'Moderator',
+ COMMENTER => 'Commenter',
+ READ => 'Read (to allow the link to the action to be publicly visible)'
+);
+echo $this->element('preview');
+echo $form->create(null, array('url' => '/' . $this->params['url']['url']));
+echo $form->inputs(array (
+ 'Node.comment_level' => array('options' => $auth),
+ 'Node.edit_level' => array('options' => $auth),
+ 'Node.show_in_toc' => array('type' => 'checkbox', 'label' => 'Show this section in the TOC? Setting to no will prevent this section from having a
+ sequence number and make this section (and any sub sections) always appear inline when viewing "' . $parent . '"'),
+ 'Node.show_subsections_inline' => array('type' => 'checkbox', 'label' => 'Show subsections inline? This will set all subsections to not appear in the TOC (No effect if left unchecked).'),
+));
+echo $form->submit('save');
+echo $form->end();
+?>
+</div>
diff --git a/views/nodes/admin_import.ctp b/views/nodes/admin_import.ctp
new file mode 100644
index 0000000..042a8fe
--- /dev/null
+++ b/views/nodes/admin_import.ctp
@@ -0,0 +1,14 @@
+<?php /* SVN FILE: $Id: admin_import.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+echo $form->create(null, array('type' => 'file', 'url' => '/' . $this->params['url']['url']));
+echo $form->inputs(array(
+ 'legend' => 'Import contents from another install, or restore to a previous backup',
+ 'file' => array('type' => 'file', 'label' => 'XML file to import'),
+ 'backup' => array('options' => $backups, 'label' => 'Or choose a previous backup', 'empty' => true),
+ 'delete_missing' => array('type' => 'checkbox', 'label' => 'Delete any nodes in this install that are not in the import file?'),
+ 'allow_moves' => array('type' => 'checkbox', 'label' => 'Allow moving nodes around?'),
+ 'auto_approve' => array('type' => 'checkbox', 'label' => 'Auto approve changes?'),
+ 'take_backup' => array('type' => 'checkbox', 'checked' => 'checked', 'label' => 'Backup current contents before import?'),
+));
+echo $form->submit();
+echo $form->end();
+?>
diff --git a/views/nodes/admin_index.ctp b/views/nodes/admin_index.ctp
new file mode 100755
index 0000000..6525ec4
--- /dev/null
+++ b/views/nodes/admin_index.ctp
@@ -0,0 +1,83 @@
+<?php /* SVN FILE: $Id: admin_index.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<h1>Nodes</h1>
+<div class="container">
+<?php
+$pass = $this->passedArgs;
+$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', $this->action); // temp
+$paginator->options(array('url' => $pass));
+?>
+<table>
+<?php
+$th = array(
+ $paginator->sort('id'),
+ 'Book',
+ //$paginator->sort('lft'),
+ //$paginator->sort('rght'),
+ //$paginator->sort('parent_id'),
+ //$paginator->sort('comment_level'),
+ //$paginator->sort('edit_level'),
+ //$paginator->sort('view_level'),
+ //$paginator->sort('depth'),
+ $paginator->sort('sequence'),
+ $paginator->sort('Title', 'Revision.title'),
+ //$paginator->sort('created'),
+ //$paginator->sort('modified'),
+ 'last Author',
+ 'flags',
+ 'actions'
+);
+echo $html->tableHeaders($th);
+foreach ($data as $row) {
+ extract($row);
+ $collection = $book = '-';
+ foreach ($collections as $c) {
+ if ($c['Node']['lft'] <= $Node['lft'] && $c['Node']['rght'] >= $Node['rght']) {
+ $collection = $html->link($c['Revision']['title'], am($pass, array('restrict_to' => $c['Node']['id'])));
+ $collection = $html->link($c['Revision']['title'], array('restrict_to' => $c['Node']['id']));
+ break;
+ }
+ }
+ foreach ($books as $b) {
+ if ($b['Node']['lft'] <= $Node['lft'] && $b['Node']['rght'] >= $Node['rght']) {
+ $book = $html->link($b['Revision']['title'], am($pass, array('restrict_to' => $b['Node']['id'])));
+ break;
+ }
+ }
+ $author = isset($users[$Revision['user_id']])?$html->link($users[$Revision['user_id']], am($pass, array('Revision.user_id' => $Revision['user_id']))):'';
+ $status = array();
+ if (in_array($Node['id'], $pendingUpdates)) {
+ $status[] = $html->link('change pending', array('controller' => 'revisions', 'action' => 'history', $Node['id'], 'status' =>
+ 'pending'));
+ }
+ $status = implode ($status, ' ');
+ $actions = array();
+ $actions[] = $html->link('P', array('admin' => false, 'action' => 'view', $Node['id'], $Revision['title']), array('title' => 'see public
+ version'));
+ $actions[] = $html->link('V', array('action' => 'view', $Node['id']), array('title' => 'view'));
+ $actions[] = $html->link('E', array('admin' => false, 'action' => 'edit', $Node['id']), array('title' => 'edit'));
+ $actions[] = $html->link('X', array('action' => 'delete', $Node['id']), array('title' => 'delete'));
+ $actions = implode(' - ', $actions);
+ $tr = array(
+ $html->link($Node['id'], array('action' => 'view', $Node['id'])),
+ $book . ' (' . $collection . ')',
+ //$html->link($Node['lft'], am($pass, array('page' => 1, 'lft' => $Node['lft']))),
+ //$html->link($Node['rght'], am($pass, array('page' => 1, 'rght' => $Node['rght']))),
+ //$html->link($Node['parent_id'], am($pass, array('page' => 1, 'parent_id' => $Node['parent_id']))),
+ //$html->link($Node['status'], am($pass, array('page' => 1, 'status' => $Node['status']))),
+ //$html->link($Node['comment_level'], am($pass, array('page' => 1, 'comment_level' => $Node['comment_level']))),
+ //$html->link($Node['edit_level'], am($pass, array('page' => 1, 'edit_level' => $Node['edit_level']))),
+ //$html->link($Node['view_level'], am($pass, array('page' => 1, 'view_level' => $Node['view_level']))),
+ //$html->link($Node['depth'], am($pass, array('page' => 1, 'depth' => $Node['depth']))),
+ $html->link($Node['sequence'], am($pass, array('restrict_to' => $Node['id']))),
+ $html->link($Revision['title'], array('action' => 'view', $Node['id'])),
+ //$html->link($Node['created'], am($pass, array('page' => 1, 'created' => $Node['created']))),
+ //$html->link($Node['modified'], am($pass, array('page' => 1, 'modified' => $Node['modified']))),
+ $author,
+ $status,
+ $actions
+ );
+ echo $html->tableCells($tr, array('class' => 'odd'), array('class' => 'even'));
+}
+?>
+</table>
+<?php echo $this->element('paging'); ?></div>
\ No newline at end of file
diff --git a/views/nodes/admin_merge.ctp b/views/nodes/admin_merge.ctp
new file mode 100644
index 0000000..f37dc76
--- /dev/null
+++ b/views/nodes/admin_merge.ctp
@@ -0,0 +1,37 @@
+<?php /* SVN FILE: $Id: admin_merge.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+if (isset($preview)) :
+?>
+<div id='preview' class="nodes view">
+ <h2><?php echo htmlspecialchars($preview['title']) ?> </h2>
+ <div class="body"><?php
+ if (isset($highlight)) {
+ echo $highlight->auto($preview['content']);
+ } else {
+ echo $preview['content'];
+ }
+ ?></div>
+</div>
+<?php
+endif;
+echo $form->create('Node', array('url' => '/' . $this->params['url']['url']));
+if (array_key_exists('merge_id', $this->data['Node'])) {
+ echo $form->input('confirmation', array('type' => isset($this->data['Node']['merge_id'])?'checkbox':'hidden', 'label' => __('You are sure? Please check the preview', true), 'error' => false));
+}
+echo $form->inputs(array(
+ 'legend' => 'Merging contents',
+ 'id' => array('options' => $nodes, 'label' => 'Take the content from here', 'value' => $data['Node']['id']),
+ 'merge_id' => array('options' => $nodes, 'label' => 'And merge it with', 'value' =>
+ isset($data['Node']['merge_id'])?$data['Node']['merge_id']:$data['Node']['id']),
+));
+echo $form->submit('merge it');
+echo $form->end();
+?>
+<script type="text/javascript">
+ url = '<?php echo Router::url('merge'); ?>';
+ $('#NodeId').change(function() {
+ if (typeof $(this).attr('value') != 'undefined') {
+ url += '/' + $(this).attr('value');
+ window.location = url;
+ }
+ });
+</script>
\ No newline at end of file
diff --git a/views/nodes/admin_move.ctp b/views/nodes/admin_move.ctp
new file mode 100644
index 0000000..b367bf7
--- /dev/null
+++ b/views/nodes/admin_move.ctp
@@ -0,0 +1,18 @@
+<?php /* SVN FILE: $Id: admin_move.ctp 600 2008-08-07 17:55:23Z AD7six $ */
+echo $form->create('Node', array('url' => '/' . $this->params['url']['url']));
+echo $form->inputs(array(
+ 'id' => array('options' => $nodes, 'label' => 'Move this', 'value' => $data['Node']['id']),
+ 'parent_id' => array('options' => $nodes, 'label' => 'Under this'),
+));
+echo $form->submit('move it');
+echo $form->end();
+?>
+<script type="text/javascript">
+ url = '<?php echo Router::url('move'); ?>';
+ $('#NodeId').change(function() {
+ if (typeof $(this).attr('value') != 'undefined') {
+ url += '/' + $(this).attr('value');
+ window.location = url;
+ }
+ });
+</script>
\ No newline at end of file
diff --git a/views/nodes/admin_toc.ctp b/views/nodes/admin_toc.ctp
new file mode 100644
index 0000000..d82d570
--- /dev/null
+++ b/views/nodes/admin_toc.ctp
@@ -0,0 +1,5 @@
+<?php /* SVN FILE: $Id: admin_toc.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<h2><?php echo $data[0]['Revision']['title']; ?> Table of Contents</h2>
+<div class="view">
+<?php echo $tree->generate($data, array ('element' => 'toc/admin_item', 'class' => 'tree')); ?>
+</div>
\ No newline at end of file
diff --git a/views/nodes/compare.ctp b/views/nodes/compare.ctp
new file mode 100644
index 0000000..5e3c84f
--- /dev/null
+++ b/views/nodes/compare.ctp
@@ -0,0 +1,14 @@
+<div class="nodes view">
+<?php
+$compare = array();
+foreach ($data as $key => $row) {
+ extract ($row);
+ echo '<h2>{' . up($Revision['lang']) . '} - ' . $Node['sequence'] . ' ' . htmlspecialchars($Revision['title']) . '</h2>';
+ //echo $html->clean($currentNode['Revision']['content']);
+ echo '<div class="summary">' . $Revision['content'] . '</div>';
+ $compare[$key] = '<title>' . $Revision['title'] . "</title>\r\n" . $Revision['content'];
+}
+echo '<h2>' . __('Differences', true) . '</h2>';
+echo $diff->compare(htmlspecialchars($compare['compare']), htmlspecialchars($compare['original']));
+?>
+</div>
\ No newline at end of file
diff --git a/views/nodes/edit.ctp b/views/nodes/edit.ctp
new file mode 100755
index 0000000..c1780a1
--- /dev/null
+++ b/views/nodes/edit.ctp
@@ -0,0 +1,50 @@
+<?php /* SVN FILE: $Id: edit.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<div class="nodes form">
+<?php
+$contents = '';
+if (isset($this->data['Revision']['content'])) {
+ $contents = $this->data['Revision']['content'];
+ preg_match_all('@<pre[^>]*>([\\s\\S]*?)</pre>@i', $contents, $result, PREG_PATTERN_ORDER);
+ if (!empty($result['0'])) {
+ $count = count($result['0']);
+ for($i = 0; $i < $count; $i++) {
+ $replaced = str_replace('<', '<', $result['1'][$i]);
+ $replaced = str_replace('>', '>', $replaced);
+ $contents = str_replace($result[1][$i], $replaced, $contents);
+ }
+ }
+}
+echo $this->element('attachments', array('path' => IMAGES . 'Node/' . $this->data['Revision']['node_id']));
+if ($session->read('Auth.User.Level') == ADMIN && $this->action == 'edit') {
+ $menu->addm('Admin Functions', array(
+ array('title' => 'Edit Node Properties', 'url' => array('admin' => true, 'action' => 'edit', $this->data['Revision']['node_id'])),
+ array('title' => 'TOC', 'url' => array('admin' => true, 'action' => 'toc', $this->data['Revision']['node_id'])),
+ array('title' => 'Merge', 'url' => array('admin' => true, 'action' => 'merge', $this->data['Revision']['node_id'])),
+ array('title' => 'Upload Image/File', 'url' => array('admin' => true, 'controller' => 'attachments', 'action' => 'add', 'Node', $this->data['Revision']['node_id'])),
+ ));
+}
+echo $html->link(__('Please review the guidelines for submitting to the Cookbook to ensure consistency.', true),
+ array('controller' => 'nodes', 'action' => 'view', 482, 'contributing-to-the-cookbook'));
+echo $this->element('preview');
+echo $form->create(null, array('url' => '/' . $this->params['url']['url']));
+$inputs = array (
+ 'Revision.node_id' => array('type' => 'hidden'),
+ 'Revision.preview' => array('type' => 'checkbox', 'label' => __('Show me a preview before submitting', true), 'error' => false),
+ 'Revision.title',
+ 'Revision.content' => array (
+ 'label' => __('Contents. Code in pre tags will be escaped. Submissions with no html formatting will be formatted automatically', true),
+ 'cols' => 100,
+ 'rows' => 30,
+ 'value' => $contents
+ ),
+ 'Revision.reason' => array('label' => __('What is the reason for the edit? (In English Please) :)', true)),
+);
+if ($session->read('Auth.User.Level') == ADMIN) {
+ $inputs = am(array('Node.show_in_toc' => array('type' => 'checkbox')), $inputs);
+}
+echo $form->inputs($inputs);
+echo $form->submit('save');
+echo $form->end();
+?>
+</div>
+<?php echo $this->element('markitup', array('process' => 'textarea')); ?>
\ No newline at end of file
diff --git a/views/nodes/english_todo.ctp b/views/nodes/english_todo.ctp
new file mode 100644
index 0000000..40512fe
--- /dev/null
+++ b/views/nodes/english_todo.ctp
@@ -0,0 +1,21 @@
+<?php /* SVN FILE: $Id: english_todo.ctp 707 2008-11-19 12:18:03Z AD7six $ */ ?>
+<?php /* SVN FILE: $Id: english_todo.ctp 707 2008-11-19 12:18:03Z AD7six $ */ ?>
+<div class="nodes view">
+<div class="summary">
+<p>These sections have been marked as needing updating</p>
+</div>
+<?php
+foreach ($data as $id => $row) {
+ extract ($row);
+ $sequence = $Node['sequence'];
+ $sequence = $sequence?$sequence:'#';
+ echo "<h2 id=\"{$Revision['slug']}-{$Node['id']}\">" .
+ $html->link($sequence, '#' . $Revision['slug'] . '-' . $Node['id']) . ' ' . htmlspecialchars($Revision['title']) . "</h2>";
+
+ echo '<div class="options">';
+ echo $this->element('node_options', array('data' => $row));
+ echo '</div>';
+}
+?>
+</div>
+<?php echo $this->element('paging');
\ No newline at end of file
diff --git a/views/nodes/history.ctp b/views/nodes/history.ctp
new file mode 100644
index 0000000..12eb5d6
--- /dev/null
+++ b/views/nodes/history.ctp
@@ -0,0 +1,46 @@
+<?php /* SVN FILE: $Id: history.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<h1>History</h1>
+<?php if ($this->params['lang'] != 'en') {
+ $url = $this->passedArgs;
+ $url[] = 1;
+ echo '<p>' . $html->link(__('See English edits too', true), $url) . '</p>';
+} ?>
+<div class="container">
+<table>
+<?php
+$pass = $this->passedArgs;
+$paginator->options(array('url' => $pass));
+$th = array(
+ 'Revision Id',
+ 'Language',
+ 'User',
+ 'Note',
+ 'Status',
+ 'Submitted'
+);
+$firstTranslation = true;
+echo $html->tableHeaders($th);
+foreach ($data as $row) {
+ extract($row);
+ $defaultReason = 'edit';
+ if ($Revision['lang'] != 'en') {
+ $defaultReason = 'edit/translation';
+ }
+ if ($Revision['status'] == 'pending') {
+ $link = $Revision['id'];
+ } else {
+ $link = $html->link($Revision['id'], array('controller' => 'revisions', 'action' => 'view', $Revision['id']));
+ }
+ $tr = array(
+ $link,
+ $Revision['lang'],
+ isset($users[$Revision['user_id']])?$users[$Revision['user_id']]:'-',
+ $Revision['reason']?$Revision['reason']:$defaultReason,
+ $Revision['status'],
+ $time->niceShort($Revision['created'])
+ );
+ echo $html->tableCells($tr, array('class' => 'odd'), array('class' => 'even'));
+}
+?>
+</table>
+<?php echo $this->element('paging'); ?></div>
\ No newline at end of file
diff --git a/views/nodes/rss/view_all.ctp b/views/nodes/rss/view_all.ctp
new file mode 100644
index 0000000..c491de3
--- /dev/null
+++ b/views/nodes/rss/view_all.ctp
@@ -0,0 +1,19 @@
+<?php /* SVN FILE: $Id: view_all.ctp 604 2008-08-11 14:46:35Z AD7six $ */
+ transformRSS (null, $html);
+ echo $rss->items($data, 'transformRSS');
+
+ function transformRSS($row, &$_html = false) {
+ static $html;
+ if ($_html) {
+ $html = $_html;
+ return;
+ }
+ extract($row);
+ return array(
+ 'title' => $Revision['title'],
+ 'link' => array('action' => 'view', $Node['id'], $Revision['slug']),
+ 'description' => $Revision['content'],
+ 'pubDate' => date('r', strtotime($Revision['modified'])),
+ );
+ }
+?>
diff --git a/views/nodes/stats.ctp b/views/nodes/stats.ctp
new file mode 100644
index 0000000..10ff97f
--- /dev/null
+++ b/views/nodes/stats.ctp
@@ -0,0 +1,71 @@
+<?php /* SVN FILE: $Id: stats.ctp 708 2008-11-19 12:39:19Z AD7six $ */ ?>
+<div class="nodes view">
+<h2>Statistics</h2>
+<div class="summary">
+<p><?php echo __('Here\'s a shout out to those who have dedicated time, sweat and tears to write, translate and edit the cookbook contents.', true) ?></p>
+</div>
+<h3><?php echo sprintf(__('Top %s Contributors', true), 'EN') ?></h3>
+<div class="options"><ul class="node-options">
+ <li><?php echo sprintf(__('Last update: %s', true), $time->niceShort($data['en']['last_update'])) ?></li>
+</ul></div>
+<div class="summary">
+<?php
+foreach ($data['en']['top_contributors'] as $row) {
+ if (isset($users[$row['Revision']['user_id']])) {
+ $nick = $users[$row['Revision']['user_id']]['User']['username'];
+ if ($users[$row['Revision']['user_id']]['Profile']['url']) {
+ $url = $users[$row['Revision']['user_id']]['Profile']['url'];
+ } else {
+ $url = 'http://bakery.cakephp.org/profiles/view/' . $row['Revision']['user_id'] . '/' . $nick;
+ }
+ } else {
+ $nick = 'user_' . $row['Revision']['user_id'];
+ $url = 'http://bakery.cakephp.org/profiles/view/' . $row['Revision']['user_id'] . '/' . $nick;
+ }
+ $menu->add(array(
+ 'section' => 'en',
+ 'title' => sprintf(__('%s (%s current)', true), $nick, $row[0]['count']),
+ 'url' => $url
+ ));
+}
+unset ($counts['en']);
+echo '<br style="clear:left" />';
+echo '<div class="userstats">' . $menu->generate('en', array('class' => 'stats', 'splitCount' => 3)) . '</div>';
+echo '</div>';
+foreach ($counts as $lang => $count) {
+ $row = $data[$lang];
+ echo '<h3 id="' . $lang . '">' . $html->link(sprintf(__('Top %s Contributors', true), up($lang)), '#' . $lang) . '</h3><div class="options"><ul class="node-options">';
+ echo '<li>' . sprintf(__('Last update: %s', true), $time->niceShort($row['last_update'])) . '</li>';
+ echo '<li>' . sprintf(__('%s%% translated', true), (int)($count / $nodes * 100)) . '</li></ul></div><div class="summary">';
+ if (!$row['last_update']) {
+ echo '<p class="warning">' . __('The cookbook needs you! No submissions for this language!', true) . '</p>';
+ } else {
+ if (!$time->wasWithinLast('2 month', $row['last_update'])) {
+ echo '<p class="warning">' . __('The cookbook needs you! This language will soon be removed if not updated.', true) . '</p>';
+ } elseif (!$time->wasWithinLast('1 month', $row['last_update'])) {
+ echo '<p class="note">' . __('The cookbook needs you! No updates for one month.', true) . '</p>';
+ }
+ }
+ foreach ($row['top_contributors'] as $row) {
+ if (isset($users[$row['Revision']['user_id']])) {
+ $nick = $users[$row['Revision']['user_id']]['User']['username'];
+ if ($users[$row['Revision']['user_id']]['Profile']['url']) {
+ $url = $users[$row['Revision']['user_id']]['Profile']['url'];
+ } else {
+ $url = 'http://bakery.cakephp.org/profiles/view/' . $row['Revision']['user_id'] . '/' . $nick;
+ }
+ } else {
+ $nick = 'user_' . $row['Revision']['user_id'];
+ $url = 'http://bakery.cakephp.org/profiles/view/' . $row['Revision']['user_id'] . '/' . $nick;
+ }
+ $menu->add(array(
+ 'section' => $lang,
+ 'title' => sprintf(__('%s (%s current)', true), $nick, $row[0]['count']),
+ 'url' => $url
+ ));
+ }
+ echo '<div class="userstats">' . $menu->generate($lang, array('class' => 'stats', 'splitCount' => 3)) . '</div>';
+ echo '</div>';
+}
+?>
+</div>
\ No newline at end of file
diff --git a/views/nodes/toc.ctp b/views/nodes/toc.ctp
new file mode 100644
index 0000000..faabc77
--- /dev/null
+++ b/views/nodes/toc.ctp
@@ -0,0 +1,12 @@
+<?php /* SVN FILE: $Id: toc.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<div class="toc">
+<?php
+echo '<h2>' . $data[0]['Revision']['title'] . '</h2>';
+echo '<h3>' . __('Table of Contents', true) . '</h3><div class="tree">';
+$selected = array();
+if (isset($currentPath)) {
+ $currentNode = array_pop($currentPath);
+ $selected = array($currentNode['Node']['lft'], $currentNode['Node']['rght'], 'active', 'selected');
+}
+echo $tree->generate($data, array ('element' => 'toc/public_item', 'class' => 'tree', 'model' => 'Node', 'autoPath' => $selected));?>
+</div></div>
\ No newline at end of file
diff --git a/views/nodes/todo.ctp b/views/nodes/todo.ctp
new file mode 100644
index 0000000..90531bb
--- /dev/null
+++ b/views/nodes/todo.ctp
@@ -0,0 +1,26 @@
+<?php /* SVN FILE: $Id: todo.ctp 707 2008-11-19 12:18:03Z AD7six $ */ ?>
+<div class="nodes view">
+<div class="summary">
+<?php
+$i18n = I18n::getInstance();
+if (!file_exists(APP . 'locale' . DS . $i18n->l10n->locale . DS . 'LC_MESSAGES' . DS . 'default.po')) {
+ echo '<p class="note">' . sprintf(__('These is no <a href="%s">po file</a> for %s', true), $html->url('/default.pot'), $i18n->l10n->language) . '</p>';
+}
+echo __('These sections either do not have a translation, or the English text has changed since it was translated') . '</p>';
+?>
+</div>
+<?php
+foreach ($data as $id => $row) {
+ extract ($row);
+ $sequence = $Node['sequence'];
+ $sequence = $sequence?$sequence:'#';
+ echo "<h2 id=\"{$Revision['slug']}-{$Node['id']}\">" .
+ $html->link($sequence, '#' . $Revision['slug'] . '-' . $Node['id']) . ' ' . htmlspecialchars($Revision['title']) . "</h2>";
+
+ echo '<div class="options">';
+ echo $this->element('node_options', array('data' => $row));
+ echo '</div>';
+}
+?>
+</div>
+<?php echo $this->element('paging');
\ No newline at end of file
diff --git a/views/nodes/view_all.ctp b/views/nodes/view_all.ctp
new file mode 100644
index 0000000..6f9a517
--- /dev/null
+++ b/views/nodes/view_all.ctp
@@ -0,0 +1,129 @@
+<?php /* SVN FILE: $Id: view_all.ctp 706 2008-11-19 12:16:42Z AD7six $ */
+if ($this->params['isAjax']) {
+ echo $this->element('crumbs');
+
+ if($session->check('Message.auth')):
+ $session->flash('auth');
+ endif;
+
+ if($session->check('Message.flash')):
+ $session->flash();
+ endif;
+}
+?>
+<div class="nodes view">
+<?php
+ $currentNode = current($data);
+ $this->set('currentNode', $currentNode['Node']);
+ $attributes = '';
+ echo "<h2 id= \"{$currentNode['Revision']['slug']}-{$currentNode['Node']['id']}\">" .
+ $currentNode['Node']['sequence'] . ' ' . htmlspecialchars($currentNode['Revision']['title']) . "</h2>";
+
+ echo '<div class="options">';
+ echo $this->element('node_options', array('data' => $currentNode));
+ echo '</div>';
+ if (trim(html_entity_decode(strip_tags(str_replace(' ', '', $currentNode['Revision']['content'])))) != '') {
+ echo '<div class="summary">';
+ // TODO Identify why this is problematic
+ //echo $html->clean($currentNode['Revision']['content']);
+ echo $currentNode['Revision']['content'];
+ echo '</div>';
+ echo $html->meta(
+ 'description',
+ $text->truncate(strip_tags($currentNode['Revision']['content']), 150),
+ array(),
+ false
+ );
+ }
+ echo '<div class="comments" id="comments-' . $currentNode['Node']['id'] . '">';
+ echo '<div class="comment">';
+ echo $html->link(__('See comments for this section', true), array('controller' => 'comments', 'action' => 'index', $currentNode['Node']['id']));
+ echo '</div></div>';
+
+ array_shift ($data);
+ foreach ($data as $id => $row) {
+ extract ($row);
+ $level = 2 - $currentNode['Node']['depth'] + $Node['depth'];
+ $level = min ($level, 6);
+
+ $sequence = $Node['sequence'];
+ $sequence = $sequence?$sequence:'#';
+ echo "<h$level id=\"{$Revision['slug']}-{$Node['id']}\">" .
+ $html->link($sequence, '#' . $Revision['slug'] . '-' . $Node['id']) . ' ' . htmlspecialchars($Revision['title']) . "</h$level>";
+
+ echo '<div class="options">';
+ echo $this->element('node_options', array('data' => $row));
+ echo '</div>';
+
+ if (trim(html_entity_decode(strip_tags(str_replace(' ', '', $Revision['content'])))) != '') {
+ echo '<div class="body">';
+ // TODO Identify why this is problematic
+ //echo $html->clean($Revision['content']);
+ echo $Revision['content'];
+ echo '</div>';
+ }
+ echo '<div class="comments" id="comments-' . $Node['id'] . '">';
+ echo '<div class="comment">';
+ echo $html->link(__('See comments for this section', true), array('controller' => 'comments', 'action' => 'index', $Node['id']));
+ echo '</div></div>';
+ }
+?>
+</div>
+<?php echo $this->element('node_navigation');
+if (isset($this->params['admin'])) {
+ extract($currentPath[count($currentPath)-1]);
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'History',
+ 'url' => array('controller' => 'revisions', 'action' => 'history', $currentNode['Node']['id'], 'lang:' . $this->params['lang'])
+ ));
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Comments',
+ 'url' => array('controller' => 'comments', 'action' => 'index', $currentNode['Node']['id'], 'lang:' . $this->params['lang'])
+ ));
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Move around',
+ 'url' => array('action' => 'toc', $currentNode['Node']['id'])
+ ));
+
+}
+if ($currentNode['Node']['depth'] > 1 && $currentNode['Node']['depth'] < $viewAllLevel) {
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => __('All in one page', true),
+ 'url' => array('action' => 'single_page', $currentNode['Node']['id'], $currentNode['Revision']['slug'])
+ ));
+}
+if ($currentNode['Node']['edit_level'] <= $auth['User']['Level']) {
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => __('Suggest a new section here', true),
+ 'url' => array('admin' => false, 'action' => 'add', $currentNode['Node']['id'], $currentNode['Revision']['slug'])
+ ));
+}
+extract($this->data);
+$html->meta(
+ 'rss',
+ array('plugin' => false, 'controller' => 'comments', 'action' => 'index', $Node['id'], $Revision['slug'], 'ext' => 'rss'),
+ array('title' => sprintf(__('Comments for %s', true), $Revision['title']))
+ , false);
+$html->meta('rss',
+ array('plugin' => false, 'controller' => 'changes', 'action' => 'index', $Node['id'], 'ext' => 'rss'),
+ array('title' => sprintf(__('Change history for %s', true), $Revision['title']))
+ , false);
+?><cake:nocache> <?php
+extract($this->data);
+$menu->add(array(
+ 'section' => 'Feeds',
+ 'title' => sprintf(__('Comments for %s', true), $Revision['title']),
+ 'url' => array('plugin' => false, 'controller' => 'comments', 'action' => 'index', $Node['id'], $Revision['slug'], 'ext' => 'rss'),
+));
+
+$menu->add(array(
+ 'section' => 'Feeds',
+ 'title' => sprintf(__('Change history for %s', true), $Revision['title']),
+ 'url' => array('plugin' => false, 'controller' => 'changes', 'action' => 'index', $Node['id'], 'ext' => 'rss'),
+));
+?></cake:nocache>
\ No newline at end of file
diff --git a/views/nodes/xml/view_all.ctp b/views/nodes/xml/view_all.ctp
new file mode 100644
index 0000000..aa60323
--- /dev/null
+++ b/views/nodes/xml/view_all.ctp
@@ -0,0 +1,16 @@
+<?php /* SVN FILE: $Id: view_all.ctp 607 2008-08-19 15:36:49Z AD7six $ */
+$uuid = String::uuid();
+$key = array_pop(explode('-', $uuid));
+$content = $xml->serialize(array('meta' => array(
+ 'from' => env('HTTP_HOST'),
+ 'uuid_key' => $key,
+ 'on' => date('Y-m-d H:i'),
+ 'lang' => $this->params['lang'])), array('format' => 'tags'));
+foreach ($data as $row) {
+ if ($this->webroot != '/') {
+ $row['Revision']['content'] = preg_replace('@(href|src)=(\'|")' . $this->webroot . '@', '\\1=\\2/', $row['Revision']['content']);
+ }
+ $content .= $xml->serialize($row, array('format' => 'tags'));
+}
+echo $xml->elem('contents', null, $content);
+?>
\ No newline at end of file
diff --git a/views/revisions/admin_change_status.ctp b/views/revisions/admin_change_status.ctp
new file mode 100644
index 0000000..8522321
--- /dev/null
+++ b/views/revisions/admin_change_status.ctp
@@ -0,0 +1,16 @@
+<?php /* SVN FILE: $Id: admin_change_status.ctp 600 2008-08-07 17:55:23Z AD7six $ */ ?>
+<h1><?php echo str_replace('admin_', '', $this->action) ?> Revision</h1>
+<?php
+echo $form->create(null, array('url' => '/' . $this->params['url']['url']));
+$fields = array(
+ 'legend' => false,
+ 'Revision.reason' => array('label' => 'Public Log message')
+);
+if (isset($isSignificant) && $isSignificant) {
+ $fields['is_significant'] = array('type' => 'radio', 'options' => array(1 => 'yes', 0 => 'no'),
+ 'legend' => 'Should translations be marked for review?');
+
+}
+echo $form->inputs($fields);
+echo $form->submit();
+echo $form->end();
\ No newline at end of file
diff --git a/views/revisions/admin_edit.ctp b/views/revisions/admin_edit.ctp
new file mode 100755
index 0000000..297f212
--- /dev/null
+++ b/views/revisions/admin_edit.ctp
@@ -0,0 +1,35 @@
+<?php /* SVN FILE: $Id: admin_edit.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<h1>Revisions - edit Revision </h1>
+<div class="form-container">
+<?php
+$contents = '';
+if (isset($this->data['Revision']['content'])) {
+ $contents = $this->data['Revision']['content'];
+ preg_match_all('@<pre[^>]*>([\\s\\S]*?)</pre>@i', $contents, $result, PREG_PATTERN_ORDER);
+ if (!empty($result['0'])) {
+ $count = count($result['0']);
+ for($i = 0; $i < $count; $i++) {
+ $replaced = str_replace('<', '<', $result['1'][$i]);
+ $replaced = str_replace('>', '>', $replaced);
+ $contents = str_replace($result[1][$i], $replaced, $contents);
+ }
+ }
+}
+$action = in_array($this->action, array('add', 'admin_add'))?'Add':'Edit';
+$action = Inflector::humanize($action);
+echo $form->create();
+echo $form->inputs(array(
+ 'legend' => false,
+ 'id',
+ 'node_id' => array('empty' => true),
+ 'under_node_id',
+ 'after_node_id',
+ 'status',
+ 'user_id' => array('empty' => true),
+ 'lang',
+ 'title',
+ 'content' => array('value' => $contents),
+));
+echo $form->end('Submit');
+echo $this->element('markitup', array('process' => 'textarea'));
+?></div>
\ No newline at end of file
diff --git a/views/revisions/admin_history.ctp b/views/revisions/admin_history.ctp
new file mode 100755
index 0000000..6f13776
--- /dev/null
+++ b/views/revisions/admin_history.ctp
@@ -0,0 +1,45 @@
+<?php /* SVN FILE: $Id: admin_history.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<h1>History</h1>
+<div class="container">
+<table>
+<?php
+$pass = $this->passedArgs;
+$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', $this->action);
+$paginator->options(array('url' => $pass));
+$th = array(
+ $paginator->sort('id'),
+ $paginator->sort('Title', 'slug'),
+ $paginator->sort('status'),
+ $paginator->sort('lang'),
+ $paginator->sort('User','User.username'),
+ '<span>Actions</span>'
+);
+echo $html->tableHeaders($th);
+
+foreach ($data as $node) {
+ $actions = array();
+ if ($node['Revision']['status'] == 'current') {
+ $actions[] = $html->link('Hide', array('action' => 'hide', $node['Revision']['id']));
+ } elseif ($node['Revision']['status'] == 'pending') {
+ $actions[] = $html->link('Approve', array('action' => 'approve', $node['Revision']['id']));
+ } elseif ($node['Revision']['status'] == 'previous') {
+ $actions[] = $html->link('Revert', array('action' => 'approve', $node['Revision']['id']));
+ }
+
+ $actions[] = $html->link('Edit', array('action' => 'edit', $node['Revision']['id']));
+ $actions[] = $html->link('Delete', array('action' => 'delete', $node['Revision']['id']));
+
+ $tr = array (
+ $node['Revision']['id'],
+ $html->link($node['Revision']['title'],array('action'=>'view',$node['Revision']['id'])),
+ $html->link($node['Revision']['status'], am($pass, array('page' => 1, 'status' => $node['Revision']['status']))),
+ $html->link($node['Revision']['lang'], am($pass, array('page' => 1, 'lang' => $node['Revision']['lang']))),
+ $node['User']?$html->link($node['User']['realname'], array('controller' => 'users', 'action' => 'view', $node['User']['id'])):'',
+ implode($actions, ' - ')
+ );
+ echo $html->tableCells($tr);
+}
+?>
+</table>
+<?php echo $this->element('paging'); ?>
+</div>
\ No newline at end of file
diff --git a/views/revisions/admin_index.ctp b/views/revisions/admin_index.ctp
new file mode 100755
index 0000000..c6e945c
--- /dev/null
+++ b/views/revisions/admin_index.ctp
@@ -0,0 +1,80 @@
+<?php /* SVN FILE: $Id: admin_index.ctp 673 2008-10-06 14:05:17Z AD7six $ */ ?>
+<h1>Revisions</h1>
+<div class="container">
+<?php echo $this->element('filter', array(
+ 'Node.sequence',
+ 'title',
+ 'lang',
+ 'status'
+)); ?>
+<table>
+<?php
+$pass = $this->passedArgs;
+$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', $this->action); // temp
+$paginator->options(array('url' => $pass));
+$th = array(
+ $paginator->sort('id'),
+ 'Book',
+ $paginator->sort('Section', 'Node.sequence'),
+ $paginator->sort('title'),
+ $paginator->sort('lang'),
+ //$paginator->sort('under_node_id'),
+ //$paginator->sort('after_node_id'),
+ $paginator->sort('User', 'User.username'),
+ $paginator->sort('Email', 'User.email'),
+ $paginator->sort('status'),
+ $paginator->sort('created'),
+ //$paginator->sort('modified'),
+ 'actions'
+);
+echo $html->tableHeaders($th);
+foreach ($data as $row) {
+ extract($row);
+ $collection = $book = '-';
+ foreach ($collections as $c) {
+ if ($c['Node']['lft'] <= $Node['lft'] && $c['Node']['rght'] >= $Node['rght']) {
+ $collection = $html->link($c['Revision']['title'], am($pass, array('restrict_to' => $c['Node']['id'])));
+ $collection = $html->link($c['Revision']['title'], array('restrict_to' => $c['Node']['id']));
+ break;
+ }
+ }
+ foreach ($books as $b) {
+ if ($b['Node']['lft'] <= $Node['lft'] && $b['Node']['rght'] >= $Node['rght']) {
+ $book = $html->link($b['Revision']['title'], am($pass, array('restrict_to' => $b['Node']['id'])));
+ break;
+ }
+ }
+ $actions = array();
+ $actions[] = $html->link('V', array('action' => 'view', $Revision['id']), array('title' => 'view'));
+ $actions[] = $html->link('E', array('action' => 'edit', $Revision['id']), array('title' => 'edit'));
+ //$actions[] = $html->link('X', array('action' => 'delete', $Revision['id']), array('title' => 'delete'));
+ /*
+ if ($Revision['status'] == 'current') {
+ $actions[] = $html->link('Hide', array('action' => 'hide', $Revision['id']));
+ } elseif ($Revision['status'] == 'pending') {
+ $actions[] = $html->link('Approve', array('action' => 'approve', $Revision['id']));
+ } elseif ($Revision['status'] == 'previous') {
+ $actions[] = $html->link('Revert', array('action' => 'approve', $Revision['id']));
+ }
+ */
+ $actions = implode(' - ', $actions);
+ $tr = array(
+ $html->link($Revision['id'], array('action' => 'view', $Revision['id'])),
+ $book . ' (' . $collection . ')',
+ $Node?$html->link($Node['sequence'], am($pass, array('page' => 1, 'node_id' => $Revision['node_id']))):'',
+ //$html->link($Revision['under_node_id'], am($pass, array('page' => 1, 'under_node_id' => $Revision['under_node_id']))),
+ //$html->link($Revision['after_node_id'], am($pass, array('page' => 1, 'after_node_id' => $Revision['after_node_id']))),
+ $html->link($Revision['title'], array('action' => 'view', $Revision['id'])),
+ $html->link($Revision['lang'], am($pass, array('page' => 1, 'lang:' . $Revision['lang']))),
+ $User?$html->link($User['username'], am($pass, array('page' => 1, 'user_id' => $Revision['user_id']))):'',
+ $User?'<a href="mailto:' . $User['email'] . '">' . $User['email'] . '</a>':'',
+ $html->link($Revision['status'], am($pass, array('page' => 1, 'status' => $Revision['status']))),
+ $html->link($Revision['created'], am($pass, array('page' => 1, 'created' => $Revision['created']))),
+ //$html->link($Revision['modified'], am($pass, array('page' => 1, 'modified' => $Revision['modified']))),
+ $actions
+ );
+ echo $html->tableCells($tr, array('class' => 'odd'), array('class' => 'even'));
+}
+?>
+</table>
+<?php echo $this->element('paging'); ?></div>
\ No newline at end of file
diff --git a/views/revisions/admin_pending.ctp b/views/revisions/admin_pending.ctp
new file mode 100755
index 0000000..5ba9631
--- /dev/null
+++ b/views/revisions/admin_pending.ctp
@@ -0,0 +1,72 @@
+<?php /* SVN FILE: $Id: admin_pending.ctp 672 2008-10-06 14:03:23Z AD7six $ */ ?>
+<h1>Pending Submissions</h1>
+<div class="container">
+<?php echo
+$this->set('modelClass', 'Revision');
+$this->element('filter', array('filters' => array(
+ 'Node.sequence',
+ 'title',
+ 'lang',
+ 'status'
+))); ?>
+<table>
+<?php
+$pass = $this->passedArgs;
+$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', $this->action);
+$paginator->options(array('url' => $pass));
+$th = array(
+ $paginator->sort('id'),
+ 'Book',
+ $paginator->sort('Section', 'Node.sequence'),
+ $paginator->sort('Title', 'slug'),
+ $paginator->sort('lang'),
+ $paginator->sort('User','User.username'),
+ $paginator->sort('Email', 'User.email'),
+ $paginator->sort('created'),
+ 'actions'
+);
+echo $html->tableHeaders($th);
+
+foreach ($data as $row) {
+ extract($row);
+ $collection = $book = '-';
+ foreach ($collections as $c) {
+ if ($c['Node']['lft'] <= $Node['lft'] && $c['Node']['rght'] >= $Node['rght']) {
+ $collection = $html->link($c['Revision']['title'], am($pass, array('restrict_to' => $c['Node']['id'])));
+ $collection = $html->link($c['Revision']['title'], array('restrict_to' => $c['Node']['id']));
+ break;
+ }
+ }
+ foreach ($books as $b) {
+ if ($b['Node']['lft'] <= $Node['lft'] && $b['Node']['rght'] >= $Node['rght']) {
+ $book = $html->link($b['Revision']['title'], am($pass, array('restrict_to' => $b['Node']['id'])));
+ break;
+ }
+ }
+ $actions = array();
+ $actions[] = $html->link('Approve', array('action' => 'approve', $Revision['id']));
+ $actions[] = $html->link('Reject', array('action' => 'reject', $Revision['id']));
+ $actions[] = $html->link('Edit', array('action' => 'edit', $Revision['id']));
+ //$actions[] = $html->link('Delete', array('action' => 'delete', $Revision['id']));
+ if(empty($Revision['node_id']) && !empty($UnderNode['sequence']) ){
+ $sequence = $html->link('{'.$UnderNode['sequence'].'}', am($pass, array('page' => 1, 'node_id' => $UnderNode['id'])), array('title' => 'New Section: under - '.$UnderNode['sequence'].' after: '.$AfterNode['sequence'] ));
+ } else {
+ $sequence = $html->link($Node['sequence'], am($pass, array('page' => 1, 'node_id' => $Node['id'])));
+ }
+ $tr = array (
+ $html->link($Revision['id'], array('action' => 'view', $Revision['id'])),
+ $book . ' (' . $collection . ')',
+ $sequence,
+ $html->link($Revision['title'],array('action'=>'view',$Revision['id'])),
+ $html->link($Revision['lang'], am($pass, array('page' => 1, 'lang' => $Revision['lang']))),
+ $User?$html->link($User['username'], am($pass, array('page' => 1, 'user_id' => $Revision['user_id']))):'',
+ $User?'<a href="mailto:' . $User['email'] . '">' . $User['email'] . '</a>':'',
+ $html->link($Revision['created'], am($pass, array('page' => 1, 'created' => $Revision['created']))),
+ implode($actions, ' - ')
+ );
+ echo $html->tableCells($tr);
+}
+?>
+</table>
+<?php echo $this->element('paging'); ?>
+</div>
\ No newline at end of file
diff --git a/views/revisions/admin_view.ctp b/views/revisions/admin_view.ctp
new file mode 100755
index 0000000..e0ae43c
--- /dev/null
+++ b/views/revisions/admin_view.ctp
@@ -0,0 +1,75 @@
+<?php /* SVN FILE: $Id: admin_view.ctp 698 2008-11-19 08:53:30Z AD7six $ */ ?>
+<div class="revisions view">
+<?php
+echo '<h2>Revision Details</h2>';
+if(empty($data['Revision']['node_id']) && !empty($data['Revision']['under_node_id'])) {
+ echo '<p>A new section under '.$data['UnderNode']['sequence'].' after '.$data['AfterNode']['sequence']. '</p>';
+}
+echo '<p>' . up($data['Revision']['lang']) . ' Submission by ' . $html->link($user['username'], 'mailto:' . $user['email']) . ' ' . $time->timeAgoInWords($data['Revision']['created']) . '</p>';
+if(!empty($data['Revision']['reason'])) {
+ echo '<p>Reason: ' . htmlspecialchars($data['Revision']['reason']) . '</p>';
+}
+echo "<h2>" . $data['Revision']['title'] . "</h2>";
+echo $this->element('node_options', array (
+ 'nodeData' => $data['Node'],
+ 'menuItems' => array (
+ 'toc' => array (
+ 'action' => 'toc',
+ '#' => $data['Revision']['slug']
+ )
+ )
+));
+echo $data['Revision']['content'];
+$revisionContent = htmlspecialchars_decode('<title>' . $data['Revision']['title'] . "</title>\r\n" . $data['Revision']['content']);
+if ($data['Revision']['node_id'] && isset($current) && $data['Revision']['id'] != $current['Revision']['id']) {
+ echo '<hr />';
+ echo '<h2>{Current} ' . $current['Revision']['title'] . '</h2>';
+ echo $current['Revision']['content'];
+ $currentContent = htmlspecialchars_decode('<title>' . $current['Revision']['title'] . "</title>\r\n" . $current['Revision']['content']);
+ echo '<hr />';
+ echo '<h2>Changes</h2>';
+ echo $diff->compare(htmlspecialchars($currentContent),htmlspecialchars($revisionContent));
+}
+
+echo $this->element('node_navigation');
+$menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'See History',
+ 'url' => array('action' => 'history', $data['Node']['id'])
+));
+$menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Edit',
+ 'url' => array('action' => 'edit', $data['Revision']['id'])
+));
+if ($data['Revision']['status'] == 'current') {
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Unapprove',
+ 'url' => array('action' => 'hide', $data['Revision']['id'])
+ ));
+} elseif ($data['Revision']['status'] == 'pending') {
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Approve',
+ 'url' => array('action' => 'approve', $data['Revision']['id'])
+ ));
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Reject',
+ 'url' => array('action' => 'reject', $data['Revision']['id'])
+ ));
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Ignore',
+ 'url' => array('action' => 'ignore', $data['Revision']['id'])
+ ));
+} elseif ($data['Revision']['status'] == 'previous') {
+ $menu->add(array(
+ 'section' => 'Options',
+ 'title' => 'Revert',
+ 'url' => array('action' => 'approve', $data['Revision']['id'])
+ ));
+}
+?>
+</div>
\ No newline at end of file
diff --git a/views/revisions/results.ctp b/views/revisions/results.ctp
new file mode 100644
index 0000000..f4e55fe
--- /dev/null
+++ b/views/revisions/results.ctp
@@ -0,0 +1,35 @@
+<?php /* SVN FILE: $Id: results.ctp 673 2008-10-06 14:05:17Z AD7six $ */
+//echo $this->element('search_form');
+echo '<div class="searchresults">';
+if(!empty($results)){
+$pass = $this->passedArgs;
+$pass['action'] = str_replace(Configure::read('Routing.admin') . '_', '', $this->action);
+$paginator->options(array('url' => $pass));
+echo '<h2>' . __('Search Results', true) . ' ('.$paginator->counter(array('format' => '%start% - %end% of %count%')).')</h2>';
+ echo '<ol id="results">';
+ foreach($results as $result){
+ switch($result['Result']['cake_model']){
+ case 'Revision':
+ echo '<li><h3>';
+ $url = array('controller' => 'nodes',
+ 'action' => 'view', $result['Result']['node_id'], $result['Result']['slug']);
+ break;
+ case 'Comment':
+ echo '<li>';
+ $url = array('controller' => 'comments',
+ 'action' => 'view', $result['Result']['cake_id']);
+ break;
+ }
+ echo $html->link($search->highlight($terms, $result['Result']['title'], false),
+ $url, null, null, false );
+ echo '</h3><p>';
+ echo $search->highlight($terms, $result['Result']['content']);
+ echo '</p></li>';
+ }
+ echo '</ol>';
+ echo $this->element('paging');
+} else {
+ echo '<h2>' . __('No results', true) . '</h2>';
+}
+?>
+</div>
\ No newline at end of file
diff --git a/views/revisions/search.ctp b/views/revisions/search.ctp
new file mode 100644
index 0000000..221ab84
--- /dev/null
+++ b/views/revisions/search.ctp
@@ -0,0 +1,2 @@
+<h2><?php __('Search book.cakephp.org'); ?></h2>
+<?php echo $this->element('search_form'); ?>
\ No newline at end of file
diff --git a/views/revisions/view.ctp b/views/revisions/view.ctp
new file mode 100644
index 0000000..6784421
--- /dev/null
+++ b/views/revisions/view.ctp
@@ -0,0 +1,16 @@
+<div class="nodes view">
+<?php
+$compare = array();
+foreach ($data as $key => $row) {
+ extract($row);
+ echo '<h2>{' . up($Revision['id']) . '} - ' . $Node['sequence'] . ' ' . htmlspecialchars($Revision['title']) . '</h2>';
+ //echo $html->clean($currentNode['Revision']['content']);
+ echo '<div class="summary">' . $Revision['content'] . '</div>';
+ $compare[] = '<title>' . $Revision['title'] . "</title>\r\n" . $Revision['content'];
+}
+if (count($compare) > 1) {
+ echo '<h2>' . __('Differences', true) . '</h2>';
+ echo $diff->compare(htmlspecialchars($compare[0]), htmlspecialchars($compare[1]));
+}
+?>
+</div>
\ No newline at end of file
diff --git a/webroot/.htaccess b/webroot/.htaccess
new file mode 100644
index 0000000..8ca27c0
--- /dev/null
+++ b/webroot/.htaccess
@@ -0,0 +1,6 @@
+<IfModule mod_rewrite.c>
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
+</IfModule>
\ No newline at end of file
diff --git a/webroot/css.php b/webroot/css.php
new file mode 100644
index 0000000..bc99078
--- /dev/null
+++ b/webroot/css.php
@@ -0,0 +1,99 @@
+<?php
+/* SVN FILE: $Id: css.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.webroot
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Enter description here...
+ */
+ require(CONFIGS . 'paths.php');
+ require(CAKE . 'basics.php');
+ require(LIBS . 'folder.php');
+ require(LIBS . 'file.php');
+/**
+ * Enter description here...
+ *
+ * @param unknown_type $path
+ * @param unknown_type $name
+ * @return unknown
+ */
+ function make_clean_css($path, $name) {
+ require(VENDORS . 'csspp' . DS . 'csspp.php');
+ $data =file_get_contents($path);
+ $csspp =new csspp();
+ $output=$csspp->compress($data);
+ $ratio =100 - (round(strlen($output) / strlen($data), 3) * 100);
+ $output=" /* file: $name, ratio: $ratio% */ " . $output;
+ return $output;
+ }
+/**
+ * Enter description here...
+ *
+ * @param unknown_type $path
+ * @param unknown_type $content
+ * @return unknown
+ */
+ function write_css_cache($path, $content) {
+ if (!is_dir(dirname($path))) {
+ mkdir(dirname($path));
+ }
+ $cache=new File($path);
+ return $cache->write($content);
+ }
+
+ if (preg_match('|\.\.|', $url) || !preg_match('|^ccss/(.+)$|i', $url, $regs)) {
+ die('Wrong file name.');
+ }
+
+ $filename = 'css/' . $regs[1];
+ $filepath = CSS . $regs[1];
+ $cachepath = CACHE . 'css' . DS . str_replace(array('/','\\'), '-', $regs[1]);
+
+ if (!file_exists($filepath)) {
+ die('Wrong file name.');
+ }
+
+ if (file_exists($cachepath)) {
+ $templateModified=filemtime($filepath);
+ $cacheModified =filemtime($cachepath);
+
+ if ($templateModified > $cacheModified) {
+ $output=make_clean_css($filepath, $filename);
+ write_css_cache($cachepath, $output);
+ } else {
+ $output = file_get_contents($cachepath);
+ }
+ } else {
+ $output=make_clean_css($filepath, $filename);
+ write_css_cache($cachepath, $output);
+ }
+ header("Date: " . date("D, j M Y G:i:s ", $templateModified) . 'GMT');
+ header("Content-Type: text/css");
+ header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT");
+ header("Cache-Control: cache"); // HTTP/1.1
+ header("Pragma: cache"); // HTTP/1.0
+ print $output;
+?>
\ No newline at end of file
diff --git a/webroot/css/cake.cookbook.css b/webroot/css/cake.cookbook.css
new file mode 100644
index 0000000..9286d05
--- /dev/null
+++ b/webroot/css/cake.cookbook.css
@@ -0,0 +1,403 @@
+span.search_term_0 {background-color: #66ffff; color: black;}
+span.search_term_1 {background-color: #ff66ff; color: black;}
+span.search_term_2 {background-color: #ffff66; color: black;}
+span.search_term_3 {background-color: #ff8888; color: black;}
+span.search_term_4 {background-color: #88ff88; color: black;}
+span.search_term_5 {background-color: #8888ff; color: black;}
+span.search_term_6 {background-color: #88dddd; color: black;}
+span.search_term_7 {background-color: #dd88dd; color: black;}
+span.search_term_8 {background-color: #dddd88; color: black;}
+span.search_term_9 {background-color: #aaddff; color: black;}
+span.search_term_10 {background-color: #aaffdd; color: black;}
+span.search_term_11 {background-color: #ddaaff; color: black;}
+#results {
+margin-right:3em;
+}
+#results dt {
+margin:1.5em 0pt 0pt;
+}
+#results dt a {
+color:#3333CC;
+}
+#results dd {
+font-size:80%;
+margin:0pt;
+padding:0pt;
+}
+#results .author, #results .date, #results .keywords {
+color:#009900;
+}
+
+.admin {
+ color: #993;
+}
+#content {
+ padding: 0px 0px;
+ background: #F4F4F4;
+ overflow: hidden;
+}
+#side {
+ margin: 0 0 0em 70%;
+ border-left: 2px #e4e4e4 solid;
+ background: #F4F4F4;
+ min-width: 200px;
+ min-height: 200px;
+}
+#side h4 {
+ padding-top: 20px;
+}
+#body {
+ float: left;
+ width: 70%;
+ text-align: justify;
+ background: #fff;
+ padding: 0 0px 20px 0px;
+ margin: 0 2em 0 0;
+ border-right: 2px #e4e4e4 solid;
+ min-height: 200px;
+}
+div.form {
+ padding: 0 24px;
+}
+div.view {
+ padding: 0 24px;
+}
+div.view ul {
+ margin-left: 2em;
+}
+div.view li {
+ margin-bottom: .5em;
+}
+
+div.logo {
+ margin: 5px;
+ padding: 5px;
+ width: 225px;
+ display: block;
+ margin-left: auto;
+}
+
+div.introduction {
+ margin: 5px;
+ padding: 5px;
+ width: 240px;
+ text-align: justify;
+ margin-left: auto;
+}
+
+div.rss-icon {
+ margin: 5px;
+ padding: 5px;
+ width: 246px;
+ margin-left: auto;
+}
+div.rss-icon img {
+ float: left;
+ padding-left: 10px;
+}
+div.rss-icon h4 {
+ display: block;
+ padding-top: 22px;
+ margin-bottom: 0px;
+}
+div.login {
+ padding: 0 40px;
+}
+div.login form {
+ width: 400px;
+}
+span.date {
+ display: block;
+}
+p {
+ font-size: 12px;
+ line-height: 18px;
+ margin-bottom: 10px;
+}
+p.caption {
+ width: 100%;
+ text-align: center;
+ font-style: italic;
+}
+div.comments {
+ width: 100%;
+}
+div.comment {
+ background: #F4F4F4;
+ margin: 1em;
+ padding: 1em;
+}
+p.commenttitle {
+ color:#333333;
+ font-size:120%;
+ margin-bottom:0.6em;
+}
+p.commentmeta {
+ float: right;
+ padding: 0;
+ font-style: italic;
+ margin: 0.5em 0 0.5em 0;
+}
+ol, ol li {
+ margin-left: 2em;
+}
+ul li ul {
+ padding-left: 1em;
+}
+div.crumbs {
+ padding: 10px 20px 0 18px;
+ background: #fff;
+}
+div.crumbs a {
+ padding-right: 6px;
+ padding-left: 6px;
+}
+div.context-menu {
+ margin: 1em 0 0 1em;
+}
+div.context-menu ul {
+ margin-left: 20px;
+}
+div.context-menu li {
+ margin: .5em 0;
+ font-size: 14px;
+}
+div.toc h3 {
+ margin-bottom: 1em;
+}
+div.toc div.tree {
+ margin-left: 20px;
+}
+div.toc ul {
+ margin: 0em 0 0em 1em;
+}
+div.toc li {
+ font-size: 14px;
+ margin: .5em 0 0 0;
+}
+div.toc ul > li.active a{
+ font-size: +1;
+}
+div.toc li.active a {
+}
+div.view h2 {
+ margin-bottom: 0;
+ font-size: 240%;
+ clear:left;
+}
+div.view h3 {
+ clear: left;
+ margin-top: 20px;
+ margin-bottom: 0;
+ font-size: 210%;
+}
+div.view h4 {
+ font-size: 180%;
+}
+div.view h5 {
+ font-size: 150%;
+}
+div.view h6 {
+ font-size: 120%;
+}
+div.view div.summary {
+ padding: 0 2px;
+}
+div.view div.sections div.body {
+ padding: 0 2px;
+}
+div.view img {
+ margin: 1em auto;
+}
+div.options {
+ margin-bottom: 10px;
+}
+div.options ul.node-options {
+ margin: 0;
+}
+
+div.options ul.node-options li {
+ margin-right: 10px;
+ display: inline;
+}
+div.options ul.node-options a, a.treeOptionsToggle {
+ color: #993;
+}
+ul.tree-options {
+ margin: 0;
+ padding-left: none;
+ display: inline;
+}
+ul.tree-options li {
+ margin-right: 10px;
+ display: inline;
+}
+ul.tree-options a {
+ color: #993;
+ text-decoration: none;
+}
+div.userstats ul {
+ width: 30%;
+ float: left;
+}
+div.node-nav {
+ clear: left;
+ padding: 40px 20px 20px 24px;
+}
+table {
+ clear: none;
+}
+pre {
+ overflow:auto;
+}
+pre.code {
+ display: none;
+ background: #F0F0F0;
+ padding: 1em 30px;
+ line-height: 1.1em;
+}
+pre.diff {
+ width: 100em;
+}
+pre.shell {
+ background: black;
+ color: white;
+ font: monospace;
+}
+
+ol.code {
+ margin: 0 0 1em;
+ padding: 0;
+ font-size: 0.75em;
+ line-height: 1.8em;
+ overflow: auto;
+ color: #939399;
+ text-align: left;
+ list-style-position: inside;
+ border: 1px solid #d3d3d0;
+}
+ol.code li {
+ float: left;
+ clear: both;
+ width: 99%;
+ white-space: nowrap;
+ margin: 0;
+ padding: 0 0 0 1%;
+}
+ol.code li.even { background: #f3f3f0; }
+ol.code li code {
+ font: 1.2em courier, monospace;
+ color: #c30;
+ white-space: pre;
+ padding-left: 0.5em;
+}
+.code .comment { color: #939399; }
+.code .default { color: #44c; }
+.code .keyword { color: #373; }
+.code .string { color: #c30; }
+
+
+#main_nav ul.navigation{
+ margin-right: 280px;
+}
+#search {
+ height:40px;
+ line-height:40px;
+ min-width:100px;
+ padding-right:15px;
+ right:0pt;
+ top:42px;
+}
+#search_submit_btn {
+ display:inline;
+ font-size:100%;
+ padding:0px;
+ text-align:right;
+ position:absolute;
+ right:10px;
+ text-align:right;
+ top:12px;
+}
+form#search input.query {
+ width:200px;
+ display:inline;
+ margin-top: 2px;
+ position:absolute;
+ right:70px;
+ top:10px;
+ width:180px;
+}
+h1.pending, h2.pending, h3.pending, h4.pending, h5.pending, h6.pending {
+ background:transparent url(images/pending.png) no-repeat scroll 0% 50%;
+ padding-left: 20px;
+ margin-left: -20px;
+}
+div.flags {
+ height: 50px;
+ padding: 0;
+ margin: 0;
+ float: right;
+}
+
+div.flags img {
+ padding: 0;
+ margin: 0;
+}
+
+/* DIFF */
+table.diff td.diffdeleted { border-color: #F21515;}
+table.diff td.diffadded { border-color: #7AB317;}
+table.diff td.diffdeleted span.diffchar { background-color: #FFD3D2;}
+table.diff td.diffadded span.diffchar { background-color: #CAFFCF;}
+
+dl#results dt.content a { color:#EE3322; font-size:29px; margin:0.5em 0pt; }
+
+/* SNOOK STYLES! */
+dl#results dt,
+div.nodes h2,
+div.nodes h3,
+div.nodes h4,
+div.nodes h5 {background-color:#f1f1f1;padding:7px 20px;border-top:1px solid #036;font-weight:normal;margin-top:0;}
+
+.nodes .body h3,
+.nodes .summary h3,
+.nodes .body h4,
+.nodes .summary h4,
+.nodes .body h5,
+.nodes .summary h5 {background-color:transparent;padding:0;border:0;}
+
+.summary, .body {margin-bottom:1em;}
+div.crumbs {padding:10px 20px;}
+
+div.view div.comments {padding:10px 0 10px 20px;width:auto;}
+div.view div.comments h2 {background-color:transparent;border-top:1px solid #900;font-size:160%;padding:5px;}
+div.view div.comments h2 a {color:#900;}
+
+div.view div.comment {padding:0;margin:0;background-color:#F9F9F9;}
+div.comment div.commentbody {padding:10px;margin-bottom:5px;}
+div.comment p.commenttitle {background-color:#F1F1F1;font-size:12px;padding:5px 10px;font-weight:bold;}
+div.comment p.commentmeta {padding-right:10px;font-size:11px;}
+
+div#body {text-align:left;}
+.options {background-color:#f6f6ff;padding:5px 20px;font-size:11px;}
+a.codeToggle {font-size:11px; background: #f3f3f0; display:block; padding: 0.1em 1em}
+div.flags {position:relative;top:25px;right:10px;height:auto;float:none;text-align:right;}
+
+.summary a {text-decoration:underline;}
+.summary a:hover {color:#C00;}
+
+div.view {padding-left:0;}
+div#body div.revisions,
+div.view div.summary,
+div.view div.body {padding-left:20px;}
+
+table {border-collapse:collapse;}
+table tr th, table tr td {padding:5px; border:1px solid #DDD;}
+
+.warning {margin:1em 0;padding:10px 10px 10px 35px;background:#FEE url(images/24-message-warn.png) no-repeat 7px 7px;border:1px solid #900;}
+.note {margin:1em 0;padding:10px 10px 10px 35px;background:#FFB url(images/24-message-info.png) no-repeat 7px 7px;border:1px solid #CC6;}
+.method {margin:1em 0;font-weight:bold;font-size:14px;margin-left:20px}
+a.selected {
+ background: #ddd;
+ padding:0.2em;
+}
\ No newline at end of file
diff --git a/webroot/css/cake.generic.css b/webroot/css/cake.generic.css
new file mode 100644
index 0000000..6492222
--- /dev/null
+++ b/webroot/css/cake.generic.css
@@ -0,0 +1,470 @@
+*{
+margin:0;
+padding:0;
+}
+
+body{
+font-family:"lucida grande",verdana,helvetica,arial,sans-serif;
+font-size:12px;
+color:#333;
+background:#003d4c url(images/body_bg.gif) repeat-x;
+/*background:url(images/grid_bg.gif) repeat-y center;*/
+margin: 0;
+}
+
+.clear {
+display: block;
+clear: both;
+height: 0;
+line-height: 0;
+margin: 0;
+padding: 0;
+}
+
+/*
+* General Style Info
+*/
+
+a{
+color:#003d4c;
+text-decoration:none;
+outline:none;
+}
+a:hover{
+color:#003d4c;
+text-decoration:underline;
+}
+
+a img{
+border:none;
+}
+
+h2,h3{
+font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif;
+font-weight:normal;
+}
+
+em {
+ font-size: 12px;
+}
+
+h2 {
+font-size:29px;
+margin:0.5em 0;
+color:#e32;
+}
+h3 {
+font-size:28px;
+margin:0.2em 0;
+}
+h4 {
+font-size: 120%;
+}
+ul li {
+ list-style-image: url("images/arrow.gif");
+}
+
+li.no-list-style-image {
+ list-style-image: none;
+}
+div.orphan {
+ width: 10em;
+ height: 1em;
+ background: url("images/no-arrow.gif" no-repeat 10px 10px);
+}
+#container{
+ position:relative;
+ background: url(../img/cake.icon.gif) no-repeat 10px 10px;
+ padding-top:44px;
+}
+
+#header {
+ position:absolute;
+ top:44px;
+ left: 26px;
+ height:40px;
+ line-height:40px;
+}
+#header h1 {
+ font-size: 24px;
+}
+#header h1 a {
+ color:#fff;
+}
+
+#content{
+ min-width: 700px;
+ position: relative;
+ padding: 15px 40px;
+ padding-bottom: 20px;
+ background:#fff;
+}
+
+#footer {
+ text-align: right;
+ color: #fff;
+ font-size: 10px;
+ padding: 4px;
+}
+#footer a, #footer a:visited {
+ color: #fff;
+}
+/*
+ * navigations
+ */
+
+.navigation{
+ height:30px;
+ line-height:30px;
+ padding:0 15px;
+}
+
+.navigation li{
+ display:block;
+ float:left;
+}
+
+.navigation a{
+ display:block;
+ float:left;
+ padding:0 12px;
+ text-decoration:none;
+}
+.navigation a:hover {
+ text-decoration: underline;
+}
+#main_nav{
+ font-size:13px;
+ height:30px;
+ line-height:30px;
+ background:#cc6;
+ padding-top: 9px;
+ width:100%;
+}
+#main_nav .navigation {
+ float: right;
+ margin-left: 330px;
+ font-weight: bold;
+}
+#main_nav .navigation a:hover, .navigation a.active {
+ font-weight: bold;
+ background: #f4f4f4;
+ text-decoration: none;
+ white-space: nowrap;
+}
+#main_nav .navigation li.activeMenuAction {
+ padding: 0 6px;
+}
+
+#secondary_nav{
+ font-size:10px;
+ position:absolute;
+ top:5px;
+ right:0;
+ z-index:5;
+ background:transparent;
+}
+
+#secondary_nav a{
+ color:#c0c25b;
+}
+
+
+#sites_nav{
+ position:absolute;
+ top:5px;
+ left:0px;
+ width:100%;
+ height:40px;
+}
+#sites_nav .navigation {
+ margin-left: 10px;
+}
+#sites_nav a {
+ color:#e77844;
+}
+
+#sites_nav li.current a{
+ color:#fff;
+
+}
+
+#search {
+ position:absolute;
+ top:42px;
+ height:40px;
+ line-height:40px;
+ right:0;
+ padding-right:15px;
+ min-width: 100px;
+}
+
+#search input {
+ display: inline;
+ font-size: 100%;
+ padding: 0;
+}
+
+/* Paging */
+div.paging {
+ margin-top: 20px;
+ color: #ccc;
+ margin-bottom: 2em;
+}
+div.paging div.disabled {
+ color: #ddd;
+ display: inline;
+}
+
+/* Tables */
+table {
+ background-color: #fff;
+ border-top: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ color: #333;
+ margin-bottom: 10px;
+ clear: both;
+ width: 100%;
+}
+table.left {
+ clear: none;
+ width: 74%;
+}
+th {
+ background-color: #f2f2f2;
+ border-top: 1px solid #fff;
+ border-left: 1px solid #fff;
+ border-right: 1px solid #bbb;
+ border-bottom: 1px solid #bbb;
+ text-align: left;
+}
+
+th a {
+ display: block;
+ padding: 2px 4px;
+ text-decoration: none;
+}
+th span {
+ display: block;
+ padding: 2px 4px;
+ text-decoration: none;
+}
+
+th a:hover {
+ background-color: #ccc;
+ color: #333;
+ text-decoration: none;
+}
+
+table tr td {
+ background: #fff;
+ border-right: 1px solid #ccc;
+ padding: 4px 6px;
+ text-align: left;
+ vertical-align: top;
+}
+.altrow {
+ background: #f4f4f4;
+}
+td.actions {
+ text-align: center;
+ white-space: nowrap;
+}
+td.actions a {
+ display: inline;
+ margin: 0px 6px;
+}
+.cakeSqlLog table {
+ background: #f4f4f4;
+}
+.cakeSqlLog td {
+ padding: 4px 8px;
+ text-align: left;
+}
+/* Scaffold View */
+dl {
+ line-height: 2em;
+ margin: 0em 0em;
+}
+dl .altrow {
+ background: #f4f4f4;
+}
+dt {
+ font-weight: bold;
+ padding-left: 4px;
+ vertical-align: top;
+}
+dd {
+ margin-left: 12em;
+ margin-top: -2em;
+ vertical-align: top;
+}
+
+/* Forms */
+form {
+ clear: none;
+}
+fieldset {
+ margin-top: 20px;
+ border: 1px solid #ccc;
+ padding: 16px 20px;
+}
+fieldset legend {
+ color: #003d4c;
+ font-size: 160%;
+ font-weight: bold;
+}
+fieldset fieldset legend {
+ font-size: 120%;
+ font-weight: normal;
+}
+form div {
+ clear: left;
+ margin-bottom: 1em;
+ padding: .5em;
+ vertical-align: text-top;
+}
+form div.required {
+ color: #333;
+ font-weight: bold;
+}
+form div.optional, form div.input {
+ color: #444;
+}
+form div.submit {
+ border: 0;
+ clear: left;
+ margin-top: 10px;
+ margin-left: 140px;
+}
+label {
+ display: block;
+ font-size: 110%;
+ padding-right: 20px;
+}
+label em {
+ font-size: 82%;
+ font-weight: normal;
+}
+input, textarea {
+ clear: left;
+ display: block;
+ font-size: 140%;
+ font-family: "frutiger linotype", "lucida grande", "verdana", sans-serif;
+ padding: 2px;
+ width: 100%;
+}
+select {
+ clear: both;
+ font-size: 120%;
+ vertical-align: text-bottom;
+}
+select[multiple=multiple] {
+ width: 100%;
+}
+option {
+ font-size: 120%;
+ padding: 0 3px;
+}
+input[type=checkbox] {
+ clear: left;
+ float: left;
+ margin: 0px 6px 7px 2px;
+ width: auto;
+}
+input[type=submit] {
+ display: inline;
+ padding: 2px 5px;
+ width: auto;
+ font-size: 110%;
+ vertical-align: bottom;
+}
+/* Notices and Errors */
+div.message, p.error, div.error-message {
+ color: #900;
+ font-size: 16px;
+ font-weight: bold;
+ margin: 8px 0px;
+ padding: 0 24px;
+}
+div.error-message {
+ clear: both;
+}
+div.error em {
+ font-size: 20px;
+ color: #003d4c;
+}
+span.notice {
+ background-color: #c6c65b;
+ color: #fff;
+ display: block;
+ font-size: 16px;
+ padding: 0.5em;
+ margin: 1em 0;
+}
+div.hidden {
+ display: none;
+ visibility: hidden;
+ height: 0;
+ padding: 0;
+ margin: 0;
+}
+/* Paging */
+div.paging {
+ color: #ccc;
+ margin-bottom: 2em;
+}
+div.paging div.disabled {
+ color: #ddd;
+ display: inline;
+}
+
+/* Debugging */
+pre {
+ color: #000;
+ background: #f0f0f0;
+ padding: 1em;
+}
+
+pre.cake_debug {
+ background: #ffcc00;
+ font-size: 120%;
+ line-height: 18px;
+ margin: 4px 2px;
+ overflow: auto;
+ position: relative;
+}
+div.cake-stack-trace {
+ background: #fff;
+ color: #333;
+ margin: 4px 2px;
+ padding: 4px;
+ font-size: 120%;
+ line-height: 18px;
+ overflow: auto;
+ position: relative;
+}
+div.cake-code-dump pre {
+ position: relative;
+ overflow: auto;
+}
+div.cake-stack-trace pre, div.cake-code-dump pre {
+ color: #000000;
+ background-color: #F0F0F0;
+ border: 1px dotted #606060;
+ margin: 4px 2px;
+ padding: 4px;
+ overflow: auto;
+}
+div.cake-code-dump pre, div.cake-code-dump pre code {
+ clear: both;
+ font-size: 12px;
+ line-height: 15px;
+ margin: 4px 2px;
+ padding: 4px;
+ overflow: auto;
+}
+div.cake-code-dump span.code-highlight {
+ background-color: #FFFF00;
+}
diff --git a/webroot/css/development.css b/webroot/css/development.css
new file mode 100644
index 0000000..1da0396
--- /dev/null
+++ b/webroot/css/development.css
@@ -0,0 +1,34 @@
+.xdebug-error {
+ color:#000;}
+pre.cake-debug {
+ background:#000;color:#fff;text-align:left;}
+.cake-sql-log {
+ position:fixed;bottom:0;left:85%;z-index:100;width:100%;background-color:#000;color:#FFF;border-collapse:collapse;margin:0;font-size:10px;}
+.cake-sql-log caption {
+ background-color:#900;color:#FFF;padding:2px 10px;text-align:left;}
+.cake-sql-log thead tr th {
+ line-height:10px;padding:0;margin:0;background:#000;color:#FFF;}
+.cake-sql-log tbody tr td {
+ padding:0 1px;border:1px solid #999;background-color:#EEE;color:#000;text-align:left;}
+.cake-sql-log thead, .cake-sql-log tbody {
+ display:none;}
+.cake-sql-log:hover, .cake-sql-log.hover, .sqlWrap:hover .cake-sql-log {
+ left:0 !important;}
+.cake-sql-log:hover caption, .cake-sql-log.hover caption, .sqlWrap:hover .cake-sql-log caption {
+ text-align:center !important;font-size:1em !important;}
+.cake-sql-log:hover thead, .cake-sql-log.hover thead, .sqlWrap:hover .cake-sql-log thead {
+ display:table-header-group !important;}
+.cake-sql-log:hover tbody, .cake-sql-log.hover tbody, .sqlWrap:hover cake-sql-log tbody {
+ display:table-row-group !important;}
+.cakeDebug {
+ position:fixed;top:0;left:90%;z-index:10;width:100%;margin:0;padding:0 1%;background-color:#000;color:#FFF;text-align:left;}
+.cakeDebug * {
+ background-color:#000;color:#FFF;display:none;}
+.cakeDebug:hover, .sqlWrap:hover {
+ left:0;width:98%;max-height:92%;overflow:auto;padding:2% 1%;font-size:12px;}
+.sqlWrap:hover {
+ left:0 !important;position:fixed;bottom:0;height:auto;max-height:100%;padding:0;width:100%;max-width:100%;}
+.sqlWrap:hover .cake-sql-log:hover {
+ position:static;}
+.cakeDebug:hover pre {
+ display:block;}
\ No newline at end of file
diff --git a/webroot/css/images/24-message-info.png b/webroot/css/images/24-message-info.png
new file mode 100644
index 0000000..78cee30
Binary files /dev/null and b/webroot/css/images/24-message-info.png differ
diff --git a/webroot/css/images/24-message-warn.png b/webroot/css/images/24-message-warn.png
new file mode 100644
index 0000000..7034b11
Binary files /dev/null and b/webroot/css/images/24-message-warn.png differ
diff --git a/webroot/css/images/arrow.gif b/webroot/css/images/arrow.gif
new file mode 100644
index 0000000..965b0aa
Binary files /dev/null and b/webroot/css/images/arrow.gif differ
diff --git a/webroot/css/images/arrow_down.gif b/webroot/css/images/arrow_down.gif
new file mode 100644
index 0000000..05e2957
Binary files /dev/null and b/webroot/css/images/arrow_down.gif differ
diff --git a/webroot/css/images/body_bg.gif b/webroot/css/images/body_bg.gif
new file mode 100644
index 0000000..ff5417c
Binary files /dev/null and b/webroot/css/images/body_bg.gif differ
diff --git a/webroot/css/live.css b/webroot/css/live.css
new file mode 100644
index 0000000..d72d99a
--- /dev/null
+++ b/webroot/css/live.css
@@ -0,0 +1,492 @@
+*{
+margin:0;
+padding:0;
+}
+
+body{
+font-family:"lucida grande",verdana,helvetica,arial,sans-serif;
+font-size:12px;
+color:#333;
+background:#003d4c url(images/body_bg.gif) repeat-x;
+/*background:url(images/grid_bg.gif) repeat-y center;*/
+margin: 0;
+}
+
+.clear {
+display: block;
+clear: both;
+height: 0;
+line-height: 0;
+margin: 0;
+padding: 0;
+}
+
+/*
+* General Style Info
+*/
+
+a{
+color:#003d4c;
+text-decoration:none;
+outline:none;
+}
+a:hover{
+color:#003d4c;
+text-decoration:underline;
+}
+
+a img{
+border:none;
+}
+
+h2,h3{
+font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif;
+font-weight:normal;
+}
+
+em {
+ font-size: 12px;
+}
+
+h2, #flashMessage{
+font-size:29px;
+margin:0.5em 0;
+color:#e32;
+}
+h3 {
+font-size:28px;
+margin:0.2em 0;
+}
+h4 {
+font-size: 120%;
+}
+ul li {
+list-style-image: url("images/arrow.gif");
+}
+
+#container{
+ position:relative;
+ background: url(../img/cake.icon.gif) no-repeat 10px 10px;
+ padding-top:44px;
+}
+
+#header {
+ position:absolute;
+ top:44px;
+ left: 12px;
+ height:40px;
+ line-height:40px;
+}
+
+#header h1 a {
+ color:#fff;
+}
+
+#content{
+ min-width: 700px;
+ position: relative;
+ padding: 15px 40px;
+ padding-bottom: 40px;
+ background:#fff;
+}
+
+#footer {
+ text-align: right;
+ color: #fff;
+ font-size: 10px;
+ padding: 4px;
+}
+
+/*
+ * navigations
+ */
+
+.navigation{
+ height:30px;
+ line-height:30px;
+ padding:0 15px;
+}
+
+.navigation li{
+ display:block;
+ float:left;
+}
+
+.navigation a{
+ display:block;
+ float:left;
+ padding:0 12px;
+ text-decoration:none;
+}
+.navigation a:hover {
+ text-decoration: underline;
+}
+#main_nav{
+ font-size:120%;
+ height:30px;
+ line-height:30px;
+ background:#cc6;
+ padding-top: 9px;
+ width:100%;
+}
+#main_nav .navigation {
+ float: right;
+ margin-left: 130px;
+ font-weight: bold;
+}
+#main_nav .navigation a:hover, #main_nav .navigation a.on, #main_nav .navigation li.activeMenuAction {
+ font-weight: bold;
+ background: #fff;
+ text-decoration: none;
+}
+#main_nav .navigation li.activeMenuAction {
+ padding: 0 1em;
+}
+
+#secondary_nav{
+ font-size:90%;
+ position:absolute;
+ top:5px;
+ right:0;
+ z-index:5;
+ background:transparent;
+}
+
+#secondary_nav a{
+ color:#c0c25b;
+}
+
+
+#sites_nav{
+ position:absolute;
+ top:5px;
+ left:0px;
+ width:100%;
+ height:40px;
+}
+#sites_nav .navigation {
+ margin-left: 10px;
+}
+#sites_nav a {
+ color:#e77844;
+}
+
+#sites_nav li.current a{
+ color:#fff;
+
+}
+
+#search {
+ position:absolute;
+ top:42px;
+ height:40px;
+ line-height:40px;
+ right:0;
+ padding-right:15px;
+ min-width: 100px;
+}
+
+#search input {
+ display: inline;
+ font-size: 100%;
+ padding: 0;
+}
+
+/* Paging */
+div.paging {
+ margin-top: 20px;
+ color: #ccc;
+ margin-bottom: 2em;
+}
+div.paging div.disabled {
+ color: #ddd;
+ display: inline;
+}
+
+/* Tables */
+table {
+ background-color: #fff;
+ border-top: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ clear: both;
+ color: #333;
+ margin-bottom: 10px;
+ width: 100%;
+}
+th {
+ background-color: #f2f2f2;
+ border-top: 1px solid #fff;
+ border-left: 1px solid #fff;
+ border-right: 1px solid #bbb;
+ border-bottom: 1px solid #bbb;
+ text-align: center;
+}
+
+th a {
+ display: block;
+ padding: 2px 4px;
+ text-decoration: none;
+}
+
+th a:hover {
+ background-color: #ccc;
+ color: #333;
+ text-decoration: none;
+}
+
+table tr td {
+ background: #fff;
+ border-right: 1px solid #ccc;
+ padding: 4px;
+ text-align: center;
+ vertical-align: top;
+}
+.altrow {
+ background: #f4f4f4;
+}
+td.actions {
+ text-align: center;
+ white-space: nowrap;
+}
+td.actions a {
+ display: inline;
+ margin: 0px 6px;
+}
+.cakeSqlLog table {
+ background: #f4f4f4;
+}
+.cakeSqlLog td {
+ padding: 4px 8px;
+ text-align: left;
+}
+/* Scaffold View */
+dl {
+ line-height: 2em;
+ margin: 0em 0em;
+ width: 50%;
+}
+dl .altrow {
+ background: #f4f4f4;
+}
+dt {
+ font-weight: bold;
+ padding-left: 4px;
+ vertical-align: top;
+}
+dd {
+ margin-left: 10em;
+ margin-top: -2em;
+ vertical-align: top;
+}
+
+/* Forms */
+form {
+ clear: both;
+ margin-right: 20px;
+ padding: 0;
+ width: 60%;
+}
+fieldset {
+ border: 1px solid #ccc;
+ margin-top: 30px;
+ padding: 16px 20px;
+}
+fieldset legend {
+ color: #003d4c;
+ font-size: 160%;
+ font-weight: bold;
+}
+fieldset fieldset legend {
+ font-size: 120%;
+ font-weight: normal;
+}
+form div {
+ clear: both;
+ margin-bottom: 1em;
+ padding: .5em;
+ vertical-align: text-top;
+}
+form div.required {
+ color: #333;
+ font-weight: bold;
+}
+form div.optional, form div.input {
+ color: #444;
+}
+form div.submit {
+ border: 0;
+ clear: both;
+ margin-top: 10px;
+ margin-left: 140px;
+}
+label {
+ display: block;
+ font-size: 110%;
+ padding-right: 20px;
+}
+label em {
+ font-size: 82%;
+ font-weight: normal;
+}
+input, textarea {
+ clear: both;
+ display: block;
+ font-size: 140%;
+ font-family: "frutiger linotype", "lucida grande", "verdana", sans-serif;
+ padding: 2px;
+ width: 100%;
+}
+select {
+ clear: both;
+ font-size: 120%;
+ vertical-align: text-bottom;
+}
+select[multiple=multiple] {
+ width: 100%;
+}
+option {
+ font-size: 120%;
+ padding: 0 3px;
+}
+input[type=checkbox] {
+ clear: left;
+ float: left;
+ margin: 0px 6px 7px 2px;
+ width: auto;
+}
+input[type=submit] {
+ display: inline;
+ padding: 2px 5px;
+ width: auto;
+ font-size: 110%;
+ vertical-align: bottom;
+}
+
+/* Notices and Errors */
+div.message, p.error, div.error-message {
+ color: #900;
+ font-size: 140%;
+ font-weight: bold;
+ margin: 8px 0px;
+}
+div.error-message {
+ clear: both;
+}
+div.error em {
+ font-size: 140%;
+ color: #003d4c;
+}
+span.notice {
+ background-color: #c6c65b;
+ color: #fff;
+ display: block;
+ font-size: 140%;
+ padding: 0.5em;
+ margin: 1em 0;
+}
+div.hidden {
+ dispaly: none;
+ visibility: hidden;
+ height: 0;
+ padding: 0;
+ margin: 0;
+}
+
+/* custom show stuff */
+div.side {
+ float: right;
+ width: 20%;
+ min-width: 200px;
+ padding: 2em;
+}
+div.body {
+ float: left;
+ width: 60%;
+ min-width: 400px;
+}
+
+div.logo {
+ margin: 5px;
+ padding: 5px;
+ width: 225px;
+ display: block;
+ margin-left: auto;
+}
+
+div.introduction {
+ margin: 5px;
+ padding: 5px;
+ width: 240px;
+ text-align: justify;
+ margin-left: auto;
+}
+
+div.rss-icon {
+ margin: 5px;
+ padding: 5px;
+ width: 246px;
+ margin-left: auto;
+}
+div.rss-icon img {
+ float: left;
+ padding-left: 10px;
+}
+div.rss-icon h4 {
+ display: block;
+ padding-top: 22px;
+ margin-bottom: 0px;
+}
+div.login form {
+ width: 400px;
+}
+div.shows ul {
+ margin: 0;
+ padding: 0;
+}
+div.shows ul li {
+ margin: 0;
+ display: block;
+}
+span.date {
+ dispaly: block;
+}
+p {
+ margin-bottom: 10px;
+}
+div.comments {
+ width: 420px;
+}
+div.comments h3 {
+ margin-top: 40px;
+}
+div.comments div {
+ padding: 10px;
+}
+div.comments form {
+ width: 400px;
+ font-size: 90%;
+}
+div.comments form div {
+ padding: 0;
+}
+
+/* Pagination */
+#paging {
+ text-align: center;
+ margin: auto;
+}
+
+#prev {
+ float: left;
+ text-align: right;
+ width: 49%;
+}
+
+#next {
+ float: left;
+}
+
+ul li ul {
+ padding-left: 1em;
+}
diff --git a/webroot/css/print.css b/webroot/css/print.css
new file mode 100644
index 0000000..dc3eeed
--- /dev/null
+++ b/webroot/css/print.css
@@ -0,0 +1,73 @@
+div#header h1, div#main_nav, div#secondary_nav, div#sites_nav, form#search, div#side, div.options, div#footer span, pre.code, div.flags {
+ display:none;
+}
+body{
+ font-family:"lucida grande",verdana,helvetica,arial,sans-serif;
+ font-size:12px;
+ color:#333;
+ background:#003d4c url(images/body_bg.gif) repeat-x;
+ /*background:url(images/grid_bg.gif) repeat-y center;*/
+ margin: 0;
+}
+#container {
+ position:relative;
+ background: url(../img/cake.icon.gif) no-repeat 10px 10px;
+ padding-top:44px;
+}
+div#body {
+ display:block;
+ width: auto;
+ margin: 0;
+ padding: 0 10px;
+ border: 0;
+ color: black;
+ background:#fff;
+ border-top: 1px solid #333;
+ border-bottom: 1px solid #333;
+}
+
+#container a:link, #container a:visited {
+ color: blue;
+ background: transparent;
+ font-weight: bold;
+ text-decoration: underline;
+}
+
+#container a:link:after, #container a:visited:after {
+ content: " (" attr(href) ") ";
+ font-size: 90%;
+}
+h3 {
+ /* page-break-before: always; */
+}
+ol.code {
+ margin: 1em 0;
+ padding: 0;
+ font-size: 0.75em;
+ line-height: 1.8em;
+ overflow: hidden;
+ color: #939399;
+ text-align: left;
+ list-style-position: inside;
+ border: 1px solid #d3d3d0;
+}
+ol.code li {
+ float: left;
+ clear: both;
+ width: 99%;
+ white-space: nowrap;
+ margin: 0;
+ padding: 0 0 0 1%;
+ background: #f3f3f0;
+}
+ol.code li.even { background: #fff; }
+ol.code li code {
+ font: 1.2em courier, monospace;
+ color: #c30;
+ white-space: pre;
+ padding-left: 0.5em;
+}
+.code .comment { color: #939399; }
+.code .default { color: #44c; }
+.code .keyword { color: #373; }
+.code .string { color: #c30; }
diff --git a/webroot/default.pot b/webroot/default.pot
new file mode 120000
index 0000000..51aae1e
--- /dev/null
+++ b/webroot/default.pot
@@ -0,0 +1 @@
+../locale/default.pot
\ No newline at end of file
diff --git a/webroot/favicon.ico b/webroot/favicon.ico
new file mode 100644
index 0000000..8c5c557
Binary files /dev/null and b/webroot/favicon.ico differ
diff --git a/webroot/img/arrow.gif b/webroot/img/arrow.gif
new file mode 100644
index 0000000..965b0aa
Binary files /dev/null and b/webroot/img/arrow.gif differ
diff --git a/webroot/img/basic_mvc.png b/webroot/img/basic_mvc.png
new file mode 100644
index 0000000..099d134
Binary files /dev/null and b/webroot/img/basic_mvc.png differ
diff --git a/webroot/img/cake.icon.gif b/webroot/img/cake.icon.gif
new file mode 100644
index 0000000..3421445
Binary files /dev/null and b/webroot/img/cake.icon.gif differ
diff --git a/webroot/img/cake.power.gif b/webroot/img/cake.power.gif
new file mode 100644
index 0000000..c9a6052
Binary files /dev/null and b/webroot/img/cake.power.gif differ
diff --git a/webroot/img/license.png b/webroot/img/license.png
new file mode 100644
index 0000000..072f8cd
Binary files /dev/null and b/webroot/img/license.png differ
diff --git a/webroot/img/pending.gif b/webroot/img/pending.gif
new file mode 100644
index 0000000..429a5d9
Binary files /dev/null and b/webroot/img/pending.gif differ
diff --git a/webroot/img/typical-cake-request.gif b/webroot/img/typical-cake-request.gif
new file mode 100644
index 0000000..159044c
Binary files /dev/null and b/webroot/img/typical-cake-request.gif differ
diff --git a/webroot/index.php b/webroot/index.php
new file mode 100644
index 0000000..0067c39
--- /dev/null
+++ b/webroot/index.php
@@ -0,0 +1,87 @@
+<?php
+/* SVN FILE: $Id: index.php 689 2008-11-05 10:30:07Z AD7six $ */
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.webroot
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 689 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Do not change
+ */
+ if (!defined('DS')) {
+ define('DS', DIRECTORY_SEPARATOR);
+ }
+/**
+ * These defines should only be edited if you have cake installed in
+ * a directory layout other than the way it is distributed.
+ * Each define has a commented line of code that explains what you would change.
+ *
+ */
+ if (!defined('ROOT')) {
+ //define('ROOT', 'FULL PATH TO DIRECTORY WHERE APP DIRECTORY IS LOCATED DO NOT ADD A TRAILING DIRECTORY SEPARATOR';
+ //You should also use the DS define to seperate your directories
+ define('ROOT', dirname(dirname(dirname(__FILE__))));
+ }
+ if (!defined('APP_DIR')) {
+ //define('APP_DIR', 'DIRECTORY NAME OF APPLICATION';
+ define('APP_DIR', basename(dirname(dirname(__FILE__))));
+ }
+/**
+ * This only needs to be changed if the cake installed libs are located
+ * outside of the distributed directory structure.
+ */
+ if (!defined('CAKE_CORE_INCLUDE_PATH')) {
+ //define ('CAKE_CORE_INCLUDE_PATH', FULL PATH TO DIRECTORY WHERE CAKE CORE IS INSTALLED DO NOT ADD A TRAILING DIRECTORY SEPARATOR';
+ //You should also use the DS define to seperate your directories
+ define('CAKE_CORE_INCLUDE_PATH', ROOT);
+ }
+///////////////////////////////
+//DO NOT EDIT BELOW THIS LINE//
+///////////////////////////////
+ if (!defined('WEBROOT_DIR')) {
+ define('WEBROOT_DIR', basename(dirname(__FILE__)));
+ }
+ if (!defined('WWW_ROOT')) {
+ define('WWW_ROOT', dirname(__FILE__) . DS);
+ }
+ if (!defined('CORE_PATH')) {
+ if (function_exists('ini_set')) {
+ ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS);
+ define('APP_PATH', null);
+ define('CORE_PATH', null);
+ } else {
+ define('APP_PATH', ROOT . DS . APP_DIR . DS);
+ define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS);
+ }
+ }
+ require CORE_PATH . 'cake' . DS . 'bootstrap.php';
+ if (isset($_GET['url']) && $_GET['url'] === 'favicon.ico') {
+ } else {
+ $Dispatcher=new Dispatcher();
+ $Dispatcher->dispatch($url);
+ }
+ if (Configure::read()) {
+ echo "<!-- " . round(getMicrotime() - $TIME_START, 4) . "s -->";
+ }
+?>
\ No newline at end of file
diff --git a/webroot/js/development.js b/webroot/js/development.js
new file mode 100644
index 0000000..42ea2fb
--- /dev/null
+++ b/webroot/js/development.js
@@ -0,0 +1,59 @@
+$(function() {
+ /* Hide XDebug errors */
+ $('table.xdebug-error').hide().before('<a href="#" class="debugToggle">Error Message! Show</a>');
+ $('a.debugToggle').click(function(){
+ if ($('a.debugToggle').next().toggle().is(':visible')) {
+ $(this).text('Hide Error');
+ } else {
+ $(this).text('Show Error');
+ }
+ return false;
+ });
+ positionSqlLogs('outOfSight');
+ $('.cake-sql-log').hover(function() {
+ $(this).addClass("hover");
+ positionSqlLogs('visible', this);
+ },function(){
+ $(this).removeClass("hover");
+ positionSqlLogs('outOfSight');
+ });
+});
+function positionSqlLogs(where, what) {
+ $('.cake-sql-log:first').ajaxStop(function() {
+ positionSqlLogs('outOfSight');
+ });
+ if (where == 'outOfSight') {
+ var zIndex = 100;
+ var count = 0;
+ $('.cake-sql-log').each(function() {
+ if (!$(this).parent().hasClass('sqlWrap')) {
+ $(this).wrap('<div class="sqlWrap">');
+ }
+ $(this).css('left', '85%');
+ $(this).css('z-index', zIndex);
+ if (count) {
+ $(this).css('bottom', count * 15 + 'px');
+ }
+ count++;
+ zIndex--;
+ });
+ } else if (what) {
+ $(what).css('left', 0);
+ $(what).css('bottom', 0);
+ $(what).css('z-index', 1000);
+ }
+ /* Toggle Code */
+ $('pre.code').before('<a class="codeToggle" href="#">Plain Text View</a>');
+ $('a.codeToggle').click().toggle(function() {
+ $(this).next().show();
+ $(this).next().next().hide();
+ $(this).text('Code View');
+ return false;
+ }, function() {
+ $(this).next().hide();
+ $(this).next().next().show();
+ $(this).text('Plain Text View');
+ return false;
+ });
+
+}
diff --git a/webroot/js/jquery/jquery.js b/webroot/js/jquery/jquery.js
new file mode 100644
index 0000000..0f6d0ce
--- /dev/null
+++ b/webroot/js/jquery/jquery.js
@@ -0,0 +1,3366 @@
+(function(){
+/*
+ * jQuery 1.2.2b2 - New Wave Javascript
+ *
+ * Copyright (c) 2007 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-01-17 12:47:29 +0100 (Thu, 17 Jan 2008) $
+ * $Rev: 176 $
+ */
+
+// Map over jQuery in case of overwrite
+if ( window.jQuery )
+ var _jQuery = window.jQuery;
+
+var jQuery = window.jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.prototype.init( selector, context );
+};
+
+// Map over the $ in case of overwrite
+if ( window.$ )
+ var _$ = window.$;
+
+// Map the jQuery namespace to the '$' one
+window.$ = jQuery;
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;
+
+// Is it a simple selector
+var isSimple = /^.[^:#\[\.]*$/;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ // Make sure that a selection was provided
+ selector = selector || document;
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // Handle HTML strings
+ } else if ( typeof selector == "string" ) {
+ // Are we dealing with HTML string or an ID?
+ var match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] )
+ selector = jQuery.clean( [ match[1] ], context );
+
+ // HANDLE: $("#id")
+ else {
+ var elem = document.getElementById( match[3] );
+
+ // Make sure an element was located
+ if ( elem )
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id != match[3] )
+ return jQuery().find( selector );
+
+ // Otherwise, we inject the element directly into the jQuery object
+ else {
+ this[0] = elem;
+ this.length = 1;
+ return this;
+ }
+
+ else
+ selector = [];
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return new jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+ return this.setArray(
+ // HANDLE: $(array)
+ selector.constructor == Array && selector ||
+
+ // HANDLE: $(arraylike)
+ // Watch for when an array-like object, contains DOM nodes, is passed in as the selector
+ (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) ||
+
+ // HANDLE: $(*)
+ [ selector ] );
+ },
+
+ // The current version of jQuery being used
+ jquery: "1.2.2b2",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // The number of elements contained in the matched element set
+ length: 0,
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == undefined ?
+
+ // Return a 'clean' array
+ jQuery.makeArray( this ) :
+
+ // Return just the object
+ this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery( elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Force the current matched set of elements to become
+ // the specified array of elements (destroying the stack in the process)
+ // You should use pushStack() in order to do this, but maintain the stack
+ setArray: function( elems ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ this.length = 0;
+ Array.prototype.push.apply( this, elems );
+
+ return this;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ var ret = -1;
+
+ // Locate the position of the desired element
+ this.each(function(i){
+ if ( this == elem )
+ ret = i;
+ });
+
+ return ret;
+ },
+
+ attr: function( name, value, type ) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if ( name.constructor == String )
+ if ( value == undefined )
+ return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined;
+
+ else {
+ options = {};
+ options[ name ] = value;
+ }
+
+ // Check to see if we're setting style values
+ return this.each(function(i){
+ // Set all the styles
+ for ( name in options )
+ jQuery.attr(
+ type ?
+ this.style :
+ this,
+ name, jQuery.prop( this, options[ name ], type, i, name )
+ );
+ });
+ },
+
+ css: function( key, value ) {
+ // ignore negative width and height values
+ if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+ value = undefined;
+ return this.attr( key, value, "curCSS" );
+ },
+
+ text: function( text ) {
+ if ( typeof text != "object" && text != null )
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+ var ret = "";
+
+ jQuery.each( text || this, function(){
+ jQuery.each( this.childNodes, function(){
+ if ( this.nodeType != 8 )
+ ret += this.nodeType != 1 ?
+ this.nodeValue :
+ jQuery.fn.text( [ this ] );
+ });
+ });
+
+ return ret;
+ },
+
+ wrapAll: function( html ) {
+ if ( this[0] )
+ // The elements to wrap the target around
+ jQuery( html, this[0].ownerDocument )
+ .clone()
+ .insertBefore( this[0] )
+ .map(function(){
+ var elem = this;
+
+ while ( elem.firstChild )
+ elem = elem.firstChild;
+
+ return elem;
+ })
+ .append(this);
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ return this.each(function(){
+ jQuery( this ).contents().wrapAll( html );
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function(){
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, false, function(elem){
+ if (this.nodeType == 1)
+ this.appendChild( elem );
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, true, function(elem){
+ if (this.nodeType == 1)
+ this.insertBefore( elem, this.firstChild );
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, false, function(elem){
+ this.parentNode.insertBefore( elem, this );
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, true, function(elem){
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery( [] );
+ },
+
+ find: function( selector ) {
+ var elems = jQuery.map(this, function(elem){
+ return jQuery.find( selector, elem );
+ });
+
+ return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+ jQuery.unique( elems ) :
+ elems );
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function(){
+ if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var clone = this.cloneNode(true),
+ container = document.createElement("div"),
+ container2 = document.createElement("div");
+ container.appendChild(clone);
+ container2.innerHTML = container.innerHTML;
+ return container2.firstChild;
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Need to set the expando to null on the cloned set if it exists
+ // removeData doesn't work here, IE removes it from the original as well
+ // this is primarily for IE but the data expando shouldn't be copied over in any browser
+ var clone = ret.find("*").andSelf().each(function(){
+ if ( this[ expando ] != undefined )
+ this[ expando ] = null;
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true )
+ this.find("*").andSelf().each(function(i){
+ if (this.nodeType == 3)
+ return;
+ var events = jQuery.data( this, "events" );
+
+ for ( var type in events )
+ for ( var handler in events[ type ] )
+ jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+ });
+
+ // Return the cloned set
+ return ret;
+ },
+
+ filter: function( selector ) {
+ return this.pushStack(
+ jQuery.isFunction( selector ) &&
+ jQuery.grep(this, function(elem, i){
+ return selector.call( elem, i );
+ }) ||
+
+ jQuery.multiFilter( selector, this ) );
+ },
+
+ not: function( selector ) {
+ if ( selector.constructor == String )
+ // test special case where just one selector is passed in
+ if ( isSimple.test( selector ) )
+ return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+ else
+ selector = jQuery.multiFilter( selector, this );
+
+ var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+ return this.filter(function() {
+ return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+ });
+ },
+
+ add: function( selector ) {
+ return !selector ? this : this.pushStack( jQuery.merge(
+ this.get(),
+ selector.constructor == String ?
+ jQuery( selector ).get() :
+ selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ?
+ selector : [selector] ) );
+ },
+
+ is: function( selector ) {
+ return selector ?
+ jQuery.multiFilter( selector, this ).length > 0 :
+ false;
+ },
+
+ hasClass: function( selector ) {
+ return this.is( "." + selector );
+ },
+
+ val: function( value ) {
+ if ( value == undefined ) {
+
+ if ( this.length ) {
+ var elem = this[0];
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type == "select-one";
+
+ // Nothing was selected
+ if ( index < 0 )
+ return null;
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+
+ // We don't need an array for one selects
+ if ( one )
+ return value;
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+
+ // Everything else, we just grab the value
+ } else
+ return (this[0].value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ return this.each(function(){
+ if ( this.nodeType != 1 )
+ return;
+
+ if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+ this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+ jQuery.inArray(this.name, value) >= 0);
+
+ else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = value.constructor == Array ?
+ value :
+ [ value ];
+
+ jQuery( "option", this ).each(function(){
+ this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+ jQuery.inArray( this.text, values ) >= 0);
+ });
+
+ if ( !values.length )
+ this.selectedIndex = -1;
+
+ } else
+ this.value = value;
+ });
+ },
+
+ html: function( value ) {
+ return value == undefined ?
+ (this.length ?
+ this[0].innerHTML :
+ null) :
+ this.empty().append( value );
+ },
+
+ replaceWith: function( value ) {
+ return this.after( value ).remove();
+ },
+
+ eq: function( i ) {
+ return this.slice( i, i + 1 );
+ },
+
+ slice: function() {
+ return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function(elem, i){
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ },
+
+ domManip: function( args, table, reverse, callback ) {
+ var clone = this.length > 1, elems;
+
+ return this.each(function(){
+ if ( !elems ) {
+ elems = jQuery.clean( args, this.ownerDocument );
+
+ if ( reverse )
+ elems.reverse();
+ }
+
+ var obj = this;
+
+ if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+ obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+ var scripts = jQuery( [] );
+
+ jQuery.each(elems, function(){
+ var elem = clone ?
+ jQuery( this ).clone( true )[0] :
+ this;
+
+ // execute all scripts after the elements have been injected
+ if ( jQuery.nodeName( elem, "script" ) ) {
+ scripts = scripts.add( elem );
+ } else {
+ // Remove any inner scripts for later evaluation
+ if ( elem.nodeType == 1 )
+ scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+ // Inject the elements into the document
+ callback.call( obj, elem );
+ }
+ });
+
+ scripts.each( evalScript );
+ });
+ }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.prototype.init.prototype = jQuery.prototype;
+
+function evalScript( i, elem ) {
+ if ( elem.src )
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+
+ else
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+ if ( elem.parentNode )
+ elem.parentNode.removeChild( elem );
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if ( target.constructor == Boolean ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target != "object" && typeof target != "function" )
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if ( length == 1 ) {
+ target = this;
+ i = 0;
+ }
+
+ for ( ; i < length; i++ )
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null )
+ // Extend the base object
+ for ( var name in options ) {
+ // Prevent never-ending loop
+ if ( target === options[ name ] )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType )
+ target[ name ] = jQuery.extend( target[ name ], options[ name ] );
+
+ // Don't bring in undefined values
+ else if ( options[ name ] != undefined )
+ target[ name ] = options[ name ];
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
+
+// exclude the following css properties to add px
+var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i;
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep )
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // This may seem like some crazy code, but trust me when I say that this
+ // is the only cross-browser way to do this. --John
+ isFunction: function( fn ) {
+ return !!fn && typeof fn != "string" && !fn.nodeName &&
+ fn.constructor != Array && /function/i.test( fn + "" );
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function( elem ) {
+ return elem.documentElement && !elem.body ||
+ elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ data = jQuery.trim( data );
+
+ if ( data ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+ if ( jQuery.browser.msie )
+ script.text = data;
+ else
+ script.appendChild( document.createTextNode( data ) );
+
+ head.appendChild( script );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ cache: {},
+
+ data: function( elem, name, data ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // Compute a unique ID for the element
+ if ( !id )
+ id = elem[ expando ] = ++uuid;
+
+ // Only generate the data cache if we're
+ // trying to access or manipulate it
+ if ( name && !jQuery.cache[ id ] )
+ jQuery.cache[ id ] = {};
+
+ // Prevent overriding the named cache with undefined values
+ if ( data != undefined )
+ jQuery.cache[ id ][ name ] = data;
+
+ // Return the named cache data, or the ID for the element
+ return name ?
+ jQuery.cache[ id ][ name ] :
+ id;
+ },
+
+ removeData: function( elem, name ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( jQuery.cache[ id ] ) {
+ // Remove the section of cache data
+ delete jQuery.cache[ id ][ name ];
+
+ // If we've removed all the data, remove the element's cache
+ name = "";
+
+ for ( name in jQuery.cache[ id ] )
+ break;
+
+ if ( !name )
+ jQuery.removeData( elem );
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ // Clean up the element expando
+ try {
+ delete elem[ expando ];
+ } catch(e){
+ // IE has trouble directly removing the expando
+ // but it's ok with using removeAttribute
+ if ( elem.removeAttribute )
+ elem.removeAttribute( expando );
+ }
+
+ // Completely remove the data cache
+ delete jQuery.cache[ id ];
+ }
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ if ( args ) {
+ if ( object.length == undefined )
+ for ( var name in object )
+ callback.apply( object[ name ], args );
+ else
+ for ( var i = 0, length = object.length; i < length; i++ )
+ if ( callback.apply( object[ i ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( object.length == undefined )
+ for ( var name in object )
+ callback.call( object[ name ], name, object[ name ] );
+ else
+ for ( var i = 0, length = object.length, value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+ }
+
+ return object;
+ },
+
+ prop: function( elem, value, type, i, name ) {
+ // Handle executable functions
+ if ( jQuery.isFunction( value ) )
+ value = value.call( elem, i );
+
+ // Handle passing in a number to a CSS property
+ return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+ value + "px" :
+ value;
+ },
+
+ className: {
+ // internal only, use addClass("class")
+ add: function( elem, classNames ) {
+ jQuery.each((classNames || "").split(/\s+/), function(i, className){
+ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+ elem.className += (elem.className ? " " : "") + className;
+ });
+ },
+
+ // internal only, use removeClass("class")
+ remove: function( elem, classNames ) {
+ if (elem.nodeType == 1)
+ elem.className = classNames != undefined ?
+ jQuery.grep(elem.className.split(/\s+/), function(className){
+ return !jQuery.className.has( classNames, className );
+ }).join(" ") :
+ "";
+ },
+
+ // internal only, use is(".class")
+ has: function( elem, className ) {
+ return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options )
+ elem.style[ name ] = old[ name ];
+ },
+
+ css: function( elem, name, force ) {
+ if ( name == "width" || name == "height" ) {
+ var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+ function getWH() {
+ val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+ var padding = 0, border = 0;
+ jQuery.each( which, function() {
+ padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ });
+ val -= Math.round(padding + border);
+ }
+
+ if ( jQuery(elem).is(":visible") )
+ getWH();
+ else
+ jQuery.swap( elem, props, getWH );
+
+ return Math.max(0, val);
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret;
+
+ // A helper method for determining if an element's values are broken
+ function color( elem ) {
+ if ( !jQuery.browser.safari )
+ return false;
+
+ var ret = document.defaultView.getComputedStyle( elem, null );
+ return !ret || ret.getPropertyValue("color") == "";
+ }
+
+ // We need to handle opacity special in IE
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ ret = jQuery.attr( elem.style, "opacity" );
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+ // Opera sometimes will give the wrong display answer, this fixes it, see #2037
+ if ( jQuery.browser.opera && name == "display" ) {
+ var save = elem.style.display;
+ elem.style.display = "block";
+ elem.style.display = save;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( name.match( /float/i ) )
+ name = styleFloat;
+
+ if ( !force && elem.style[ name ] )
+ ret = elem.style[ name ];
+
+ else if ( document.defaultView && document.defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var getComputedStyle = document.defaultView.getComputedStyle( elem, null );
+
+ if ( getComputedStyle && !color( elem ) )
+ ret = getComputedStyle.getPropertyValue( name );
+
+ // If the element isn't reporting its values properly in Safari
+ // then some display: none elements are involved
+ else {
+ var swap = [], stack = [];
+
+ // Locate all of the parent display: none elements
+ for ( var a = elem; a && color(a); a = a.parentNode )
+ stack.unshift(a);
+
+ // Go through and make them visible, but in reverse
+ // (It would be better if we knew the exact display type that they had)
+ for ( var i = 0; i < stack.length; i++ )
+ if ( color( stack[ i ] ) ) {
+ swap[ i ] = stack[ i ].style.display;
+ stack[ i ].style.display = "block";
+ }
+
+ // Since we flip the display style, we have to handle that
+ // one special, otherwise get the value
+ ret = name == "display" && swap[ stack.length - 1 ] != null ?
+ "none" :
+ ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || "";
+
+ // Finally, revert the display styles back
+ for ( var i = 0; i < swap.length; i++ )
+ if ( swap[ i ] != null )
+ stack[ i ].style.display = swap[ i ];
+ }
+
+ // We should always get a number back from opacity
+ if ( name == "opacity" && ret == "" )
+ ret = "1";
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+ // Remember the original values
+ var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ elem.style.left = ret || 0;
+ ret = elem.style.pixelLeft + "px";
+
+ // Revert the changed values
+ elem.style.left = style;
+ elem.runtimeStyle.left = runtimeStyle;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function( elems, context ) {
+ var ret = [];
+ context = context || document;
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if (typeof context.createElement == 'undefined')
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+ jQuery.each(elems, function(i, elem){
+ if ( !elem )
+ return;
+
+ if ( elem.constructor == Number )
+ elem = elem.toString();
+
+ // Convert html string into DOM nodes
+ if ( typeof elem == "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+ all :
+ front + "></" + tag + ">";
+ });
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+ var wrap =
+ // option or optgroup
+ !tags.indexOf("<opt") &&
+ [ 1, "<select multiple='multiple'>", "</select>" ] ||
+
+ !tags.indexOf("<leg") &&
+ [ 1, "<fieldset>", "</fieldset>" ] ||
+
+ tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+ [ 1, "<table>", "</table>" ] ||
+
+ !tags.indexOf("<tr") &&
+ [ 2, "<table><tbody>", "</tbody></table>" ] ||
+
+ // <thead> matched above
+ (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+ [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+
+ !tags.indexOf("<col") &&
+ [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+ // IE can't serialize <link> and <script> tags normally
+ jQuery.browser.msie &&
+ [ 1, "div<div>", "</div>" ] ||
+
+ [ 0, "", "" ];
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( wrap[0]-- )
+ div = div.lastChild;
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( jQuery.browser.msie ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+ div.childNodes :
+ [];
+
+ for ( var j = tbody.length - 1; j >= 0 ; --j )
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( /^\s/.test( elem ) )
+ div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+
+ }
+
+ elem = jQuery.makeArray( div.childNodes );
+ }
+
+ if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+ return;
+
+ if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+ ret.push( elem );
+
+ else
+ ret = jQuery.merge( ret, elem );
+
+ });
+
+ return ret;
+ },
+
+ attr: function( elem, name, value ) {
+ // don't set attributes on text and comment nodes
+ if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+ return undefined;
+
+ var fix = jQuery.isXMLDoc( elem ) ?
+ {} :
+ jQuery.props;
+
+ // Safari mis-reports the default selected property of a hidden option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name == "selected" && jQuery.browser.safari )
+ elem.parentNode.selectedIndex;
+
+ // Certain attributes only work when accessed via the old DOM 0 way
+ if ( fix[ name ] ) {
+ if ( value != undefined )
+ elem[ fix[ name ] ] = value;
+
+ return elem[ fix[ name ] ];
+
+ } else if ( jQuery.browser.msie && name == "style" )
+ return jQuery.attr( elem.style, "cssText", value );
+
+ else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName( elem, "form" ) && (name == "action" || name == "method") )
+ return elem.getAttributeNode( name ).nodeValue;
+
+ // IE elem.getAttribute passes even for style
+ else if ( elem.tagName ) {
+
+ if ( value != undefined ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+ throw "type property can't be changed";
+
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+ }
+
+ if ( jQuery.browser.msie && /href|src/.test( name ) && !jQuery.isXMLDoc( elem ) )
+ return elem.getAttribute( name, 2 );
+
+ return elem.getAttribute( name );
+
+ // elem is actually elem.style ... set the style
+ } else {
+ // IE actually uses filters for opacity
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ if ( value != undefined ) {
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ elem.zoom = 1;
+
+ // Set the alpha filter to set the opacity
+ elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+ (parseFloat( value ).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+ }
+
+ return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() :
+ "";
+ }
+
+ name = name.replace(/-([a-z])/ig, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ if ( value != undefined )
+ elem[ name ] = value;
+
+ return elem[ name ];
+ }
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( /^\s+|\s+$/g, "" );
+ },
+
+ makeArray: function( array ) {
+ var ret = [];
+
+ // Need to use typeof to fight Safari childNodes crashes
+ if ( typeof array != "array" )
+ for ( var i = 0, length = array.length; i < length; i++ )
+ ret.push( array[ i ] );
+ else
+ ret = array.slice( 0 );
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ for ( var i = 0, length = array.length; i < length; i++ )
+ if ( array[ i ] == elem )
+ return i;
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ // We have to loop this way because IE & Opera overwrite the length
+ // expando of getElementsByTagName
+
+ // Also, we need to make sure that the correct elements are being returned
+ // (IE returns comment nodes in a '*' query)
+ if ( jQuery.browser.msie ) {
+ for ( var i = 0; second[ i ]; i++ )
+ if ( second[ i ].nodeType != 8 )
+ first.push( second[ i ] );
+
+ } else
+ for ( var i = 0; second[ i ]; i++ )
+ first.push( second[ i ] );
+
+ return first;
+ },
+
+ unique: function( array ) {
+ var ret = [], done = {};
+
+ try {
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ var id = jQuery.data( array[ i ] );
+
+ if ( !done[ id ] ) {
+ done[ id ] = true;
+ ret.push( array[ i ] );
+ }
+ }
+
+ } catch( e ) {
+ ret = array;
+ }
+
+ return ret;
+ },
+
+ grep: function( elems, callback, inv ) {
+ // If a string is passed in for the function, make a function
+ // for it (a handy shortcut)
+ if ( typeof callback == "string" )
+ callback = eval("false||function(a,i){return " + callback + "}");
+
+ var ret = [];
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ )
+ if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
+ ret.push( elems[ i ] );
+
+ return ret;
+ },
+
+ map: function( elems, callback ) {
+ var ret = [];
+
+ // Go through the array, translating each of the items to their
+ // new value (or values).
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ var value = callback( elems[ i ], i );
+
+ if ( value !== null && value != undefined ) {
+ if ( value.constructor != Array )
+ value = [ value ];
+
+ ret = ret.concat( value );
+ }
+ }
+
+ return ret;
+ }
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+ version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+ safari: /webkit/.test( userAgent ),
+ opera: /opera/.test( userAgent ),
+ msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+ mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+ "styleFloat" :
+ "cssFloat";
+
+jQuery.extend({
+ // Check to see if the W3C box model is being used
+ boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+
+ props: {
+ "for": "htmlFor",
+ "class": "className",
+ "float": styleFloat,
+ cssFloat: styleFloat,
+ styleFloat: styleFloat,
+ innerHTML: "innerHTML",
+ className: "className",
+ value: "value",
+ disabled: "disabled",
+ checked: "checked",
+ readonly: "readOnly",
+ selected: "selected",
+ maxlength: "maxLength",
+ selectedIndex: "selectedIndex",
+ defaultValue: "defaultValue",
+ tagName: "tagName",
+ nodeName: "nodeName"
+ }
+});
+
+jQuery.each({
+ parent: "elem.parentNode",
+ parents: "jQuery.dir(elem,'parentNode')",
+ next: "jQuery.nth(elem,2,'nextSibling')",
+ prev: "jQuery.nth(elem,2,'previousSibling')",
+ nextAll: "jQuery.dir(elem,'nextSibling')",
+ prevAll: "jQuery.dir(elem,'previousSibling')",
+ siblings: "jQuery.sibling(elem.parentNode.firstChild,elem)",
+ children: "jQuery.sibling(elem.firstChild)",
+ contents: "jQuery.nodeName(elem,'iframe')?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes)"
+}, function(name, fn){
+ fn = eval("false||function(elem){return " + fn + "}");
+
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = jQuery.map( this, fn );
+
+ if ( selector && typeof selector == "string" )
+ ret = jQuery.multiFilter( selector, ret );
+
+ return this.pushStack( jQuery.unique( ret ) );
+ };
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function(name, original){
+ jQuery.fn[ name ] = function() {
+ var args = arguments;
+
+ return this.each(function(){
+ for ( var i = 0, length = args.length; i < length; i++ )
+ jQuery( args[ i ] )[ original ]( this );
+ });
+ };
+});
+
+jQuery.each({
+ removeAttr: function( name ) {
+ jQuery.attr( this, name, "" );
+ if (this.nodeType == 1)
+ this.removeAttribute( name );
+ },
+
+ addClass: function( classNames ) {
+ jQuery.className.add( this, classNames );
+ },
+
+ removeClass: function( classNames ) {
+ jQuery.className.remove( this, classNames );
+ },
+
+ toggleClass: function( classNames ) {
+ jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+ },
+
+ remove: function( selector ) {
+ if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+ // Prevent memory leaks
+ jQuery( "*", this ).add(this).each(function(){
+ jQuery.event.remove(this);
+ jQuery.removeData(this);
+ });
+ if (this.parentNode)
+ this.parentNode.removeChild( this );
+ }
+ },
+
+ empty: function() {
+ // Remove element nodes and prevent memory leaks
+ jQuery( ">*", this ).remove();
+
+ // Remove any remaining nodes
+ while ( this.firstChild )
+ this.removeChild( this.firstChild );
+ }
+}, function(name, fn){
+ jQuery.fn[ name ] = function(){
+ return this.each( fn, arguments );
+ };
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+ var type = name.toLowerCase();
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ return this[0] == window ?
+ // Opera reports document.body.client[Width/Height] properly in both quirks and standards
+ jQuery.browser.opera && document.body[ "client" + name ] ||
+
+ // Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+ jQuery.browser.safari && window[ "inner" + name ] ||
+
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+
+ // Get document width or height
+ this[0] == document ?
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ Math.max(
+ Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]),
+ Math.max(document.body["offset" + name], document.documentElement["offset" + name])
+ ) :
+
+ // Get or set width or height on the element
+ size == undefined ?
+ // Get width or height on the element
+ (this.length ? jQuery.css( this[0], type ) : null) :
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ this.css( type, size.constructor == String ? size : size + "px" );
+ };
+});
+
+var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+ "(?:[\\w*_-]|\\\\.)" :
+ "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+ quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+ quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+ quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+ expr: {
+ "": "m[2]=='*'||jQuery.nodeName(a,m[2])",
+ "#": "a.getAttribute('id')==m[2]",
+ ":": {
+ // Position Checks
+ lt: "i<m[3]-0",
+ gt: "i>m[3]-0",
+ nth: "m[3]-0==i",
+ eq: "m[3]-0==i",
+ first: "i==0",
+ last: "i==r.length-1",
+ even: "i%2==0",
+ odd: "i%2",
+
+ // Child Checks
+ "first-child": "a.parentNode.getElementsByTagName('*')[0]==a",
+ "last-child": "jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a",
+ "only-child": "!jQuery.nth(a.parentNode.lastChild,2,'previousSibling')",
+
+ // Parent Checks
+ parent: "a.firstChild",
+ empty: "!a.firstChild",
+
+ // Text Check
+ contains: "(a.textContent||a.innerText||jQuery(a).text()||'').indexOf(m[3])>=0",
+
+ // Visibility
+ visible: '"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden"',
+ hidden: '"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden"',
+
+ // Form attributes
+ enabled: "!a.disabled",
+ disabled: "a.disabled",
+ checked: "a.checked",
+ selected: "a.selected||jQuery.attr(a,'selected')",
+
+ // Form elements
+ text: "'text'==a.type",
+ radio: "'radio'==a.type",
+ checkbox: "'checkbox'==a.type",
+ file: "'file'==a.type",
+ password: "'password'==a.type",
+ submit: "'submit'==a.type",
+ image: "'image'==a.type",
+ reset: "'reset'==a.type",
+ button: '"button"==a.type||jQuery.nodeName(a,"button")',
+ input: "/input|select|textarea|button/i.test(a.nodeName)",
+
+ // :has()
+ has: "jQuery.find(m[3],a).length",
+
+ // :header
+ header: "/h\\d/i.test(a.nodeName)",
+
+ // :animated
+ animated: "jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length"
+ }
+ },
+
+ // The regular expressions that power the parsing engine
+ parse: [
+ // Match: [@value='test'], [@foo]
+ /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+ // Match: :contains('foo')
+ /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+ // Match: :even, :last-chlid, #id, .class
+ new RegExp("^([:.#]*)(" + chars + "+)")
+ ],
+
+ multiFilter: function( expr, elems, not ) {
+ var old, cur = [];
+
+ while ( expr && expr != old ) {
+ old = expr;
+ var f = jQuery.filter( expr, elems, not );
+ expr = f.t.replace(/^\s*,\s*/, "" );
+ cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+ }
+
+ return cur;
+ },
+
+ find: function( t, context ) {
+ // Quickly handle non-string expressions
+ if ( typeof t != "string" )
+ return [ t ];
+
+ // check to make sure context is a DOM element or a document
+ if ( context && context.nodeType != 1 && context.nodeType != 9)
+ return [ ];
+
+ // Set the correct context (if none is provided)
+ context = context || document;
+
+ // Initialize the search
+ var ret = [context], done = [], last, nodeName;
+
+ // Continue while a selector expression exists, and while
+ // we're no longer looping upon ourselves
+ while ( t && last != t ) {
+ var r = [];
+ last = t;
+
+ t = jQuery.trim(t);
+
+ var foundToken = false;
+
+ // An attempt at speeding up child selectors that
+ // point to a specific element tag
+ var re = quickChild;
+ var m = re.exec(t);
+
+ if ( m ) {
+ nodeName = m[1].toUpperCase();
+
+ // Perform our own iteration and filter
+ for ( var i = 0; ret[i]; i++ )
+ for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+ if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+ r.push( c );
+
+ ret = r;
+ t = t.replace( re, "" );
+ if ( t.indexOf(" ") == 0 ) continue;
+ foundToken = true;
+ } else {
+ re = /^([>+~])\s*(\w*)/i;
+
+ if ( (m = re.exec(t)) != null ) {
+ r = [];
+
+ var merge = {};
+ nodeName = m[2].toUpperCase();
+ m = m[1];
+
+ for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+ var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+ for ( ; n; n = n.nextSibling )
+ if ( n.nodeType == 1 ) {
+ var id = jQuery.data(n);
+
+ if ( m == "~" && merge[id] ) break;
+
+ if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+ if ( m == "~" ) merge[id] = true;
+ r.push( n );
+ }
+
+ if ( m == "+" ) break;
+ }
+ }
+
+ ret = r;
+
+ // And remove the token
+ t = jQuery.trim( t.replace( re, "" ) );
+ foundToken = true;
+ }
+ }
+
+ // See if there's still an expression, and that we haven't already
+ // matched a token
+ if ( t && !foundToken ) {
+ // Handle multiple expressions
+ if ( !t.indexOf(",") ) {
+ // Clean the result set
+ if ( context == ret[0] ) ret.shift();
+
+ // Merge the result sets
+ done = jQuery.merge( done, ret );
+
+ // Reset the context
+ r = ret = [context];
+
+ // Touch up the selector string
+ t = " " + t.substr(1,t.length);
+
+ } else {
+ // Optimize for the case nodeName#idName
+ var re2 = quickID;
+ var m = re2.exec(t);
+
+ // Re-organize the results, so that they're consistent
+ if ( m ) {
+ m = [ 0, m[2], m[3], m[1] ];
+
+ } else {
+ // Otherwise, do a traditional filter check for
+ // ID, class, and element selectors
+ re2 = quickClass;
+ m = re2.exec(t);
+ }
+
+ m[2] = m[2].replace(/\\/g, "");
+
+ var elem = ret[ret.length-1];
+
+ // Try to do a global search by ID, where we can
+ if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+ // Optimization for HTML document case
+ var oid = elem.getElementById(m[2]);
+
+ // Do a quick check for the existence of the actual ID attribute
+ // to avoid selecting by the name attribute in IE
+ // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+ if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+ oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+ // Do a quick check for node name (where applicable) so
+ // that div#foo searches will be really fast
+ ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+ } else {
+ // We need to find all descendant elements
+ for ( var i = 0; ret[i]; i++ ) {
+ // Grab the tag name being searched for
+ var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+ // Handle IE7 being really dumb about <object>s
+ if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+ tag = "param";
+
+ r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+ }
+
+ // It's faster to filter by class and be done with it
+ if ( m[1] == "." )
+ r = jQuery.classFilter( r, m[2] );
+
+ // Same with ID filtering
+ if ( m[1] == "#" ) {
+ var tmp = [];
+
+ // Try to find the element with the ID
+ for ( var i = 0; r[i]; i++ )
+ if ( r[i].getAttribute("id") == m[2] ) {
+ tmp = [ r[i] ];
+ break;
+ }
+
+ r = tmp;
+ }
+
+ ret = r;
+ }
+
+ t = t.replace( re2, "" );
+ }
+
+ }
+
+ // If a selector string still exists
+ if ( t ) {
+ // Attempt to filter it
+ var val = jQuery.filter(t,r);
+ ret = r = val.r;
+ t = jQuery.trim(val.t);
+ }
+ }
+
+ // An error occurred with the selector;
+ // just return an empty set instead
+ if ( t )
+ ret = [];
+
+ // Remove the root context
+ if ( ret && context == ret[0] )
+ ret.shift();
+
+ // And combine the results
+ done = jQuery.merge( done, ret );
+
+ return done;
+ },
+
+ classFilter: function(r,m,not){
+ m = " " + m + " ";
+ var tmp = [];
+ for ( var i = 0; r[i]; i++ ) {
+ var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+ if ( !not && pass || not && !pass )
+ tmp.push( r[i] );
+ }
+ return tmp;
+ },
+
+ filter: function(t,r,not) {
+ var last;
+
+ // Look for common filter expressions
+ while ( t && t != last ) {
+ last = t;
+
+ var p = jQuery.parse, m;
+
+ for ( var i = 0; p[i]; i++ ) {
+ m = p[i].exec( t );
+
+ if ( m ) {
+ // Remove what we just matched
+ t = t.substring( m[0].length );
+
+ m[2] = m[2].replace(/\\/g, "");
+ break;
+ }
+ }
+
+ if ( !m )
+ break;
+
+ // :not() is a special case that can be optimized by
+ // keeping it out of the expression list
+ if ( m[1] == ":" && m[2] == "not" )
+ // optimize if only one selector found (most common case)
+ r = isSimple.test( m[3] ) ?
+ jQuery.filter(m[3], r, true).r :
+ jQuery( r ).not( m[3] );
+
+ // We can get a big speed boost by filtering by class here
+ else if ( m[1] == "." )
+ r = jQuery.classFilter(r, m[2], not);
+
+ else if ( m[1] == "[" ) {
+ var tmp = [], type = m[3];
+
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+
+ if ( z == null || /href|src|selected/.test(m[2]) )
+ z = jQuery.attr(a,m[2]) || '';
+
+ if ( (type == "" && !!z ||
+ type == "=" && z == m[5] ||
+ type == "!=" && z != m[5] ||
+ type == "^=" && z && !z.indexOf(m[5]) ||
+ type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+ (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+ tmp.push( a );
+ }
+
+ r = tmp;
+
+ // We can get a speed boost by handling nth-child here
+ } else if ( m[1] == ":" && m[2] == "nth-child" ) {
+ var merge = {}, tmp = [],
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+ !/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+ // calculate the numbers (first)n+(last) including if they are negative
+ first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+
+ // loop through all the elements left in the jQuery object
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+ if ( !merge[id] ) {
+ var c = 1;
+
+ for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+ if ( n.nodeType == 1 )
+ n.nodeIndex = c++;
+
+ merge[id] = true;
+ }
+
+ var add = false;
+
+ if ( first == 0 ) {
+ if ( node.nodeIndex == last )
+ add = true;
+ } else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+ add = true;
+
+ if ( add ^ not )
+ tmp.push( node );
+ }
+
+ r = tmp;
+
+ // Otherwise, find the expression to execute
+ } else {
+ var f = jQuery.expr[m[1]];
+ if ( typeof f != "string" )
+ f = jQuery.expr[m[1]][m[2]];
+
+ // Build a custom macro to enclose it
+ f = eval("false||function(a,i){return " + f + "}");
+
+ // Execute it against the current filter
+ r = jQuery.grep( r, f, not );
+ }
+ }
+
+ // Return an array of filtered elements (r)
+ // and the modified expression string (t)
+ return { r: r, t: t };
+ },
+
+ dir: function( elem, dir ){
+ var matched = [];
+ var cur = elem[dir];
+ while ( cur && cur != document ) {
+ if ( cur.nodeType == 1 )
+ matched.push( cur );
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function(cur,result,dir,elem){
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] )
+ if ( cur.nodeType == 1 && ++num == result )
+ break;
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType == 1 && (!elem || n != elem) )
+ r.push( n );
+ }
+
+ return r;
+ }
+});
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function(elem, types, handler, data) {
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ // For whatever reason, IE has trouble passing the window object
+ // around, causing it to be cloned in the process
+ if ( jQuery.browser.msie && elem.setInterval != undefined )
+ elem = window;
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid )
+ handler.guid = this.guid++;
+
+ // if data is passed, bind to handler
+ if( data != undefined ) {
+ // Create temporary function pointer to original handler
+ var fn = handler;
+
+ // Create unique handler function, wrapped around original handler
+ handler = function() {
+ // Pass arguments and context to original handler
+ return fn.apply(this, arguments);
+ };
+
+ // Store data in unique handler
+ handler.data = data;
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ handler.guid = fn.guid;
+ }
+
+ // Init the element's event structure
+ var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+ handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+ // returned undefined or false
+ var val;
+
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ if ( typeof jQuery == "undefined" || jQuery.event.triggered )
+ return val;
+
+ val = jQuery.event.handle.apply(arguments.callee.elem, arguments);
+
+ return val;
+ });
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native
+ // event in IE.
+ handle.elem = elem;
+
+ // Handle multiple events seperated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type) {
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+ handler.type = parts[1];
+
+ // Get the current list of functions bound to this event
+ var handlers = events[type];
+
+ // Init the event handler queue
+ if (!handlers) {
+ handlers = events[type] = {};
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+ // Bind the global event handler to the element
+ if (elem.addEventListener)
+ elem.addEventListener(type, handle, false);
+ else if (elem.attachEvent)
+ elem.attachEvent("on" + type, handle);
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers[handler.guid] = handler;
+
+ // Keep track of which events have been used, for global triggering
+ jQuery.event.global[type] = true;
+ });
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ guid: 1,
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function(elem, types, handler) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ var events = jQuery.data(elem, "events"), ret, index;
+
+ if ( events ) {
+ // Unbind all events for the element
+ if ( types == undefined )
+ for ( var type in events )
+ this.remove( elem, type );
+ else {
+ // types is actually an event object here
+ if ( types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Handle multiple events seperated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type){
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+
+ if ( events[type] ) {
+ // remove the given handler for the given type
+ if ( handler )
+ delete events[type][handler.guid];
+
+ // remove all handlers for the given type
+ else
+ for ( handler in events[type] )
+ // Handle the removal of namespaced events
+ if ( !parts[1] || events[type][handler].type == parts[1] )
+ delete events[type][handler];
+
+ // remove generic event handler if no more handlers exist
+ for ( ret in events[type] ) break;
+ if ( !ret ) {
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+ if (elem.removeEventListener)
+ elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+ else if (elem.detachEvent)
+ elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+ }
+ ret = null;
+ delete events[type];
+ }
+ }
+ });
+ }
+
+ // Remove the expando if it's no longer used
+ for ( ret in events ) break;
+ if ( !ret ) {
+ var handle = jQuery.data( elem, "handle" );
+ if ( handle ) handle.elem = null;
+ jQuery.removeData( elem, "events" );
+ jQuery.removeData( elem, "handle" );
+ }
+ }
+ },
+
+ trigger: function(type, data, elem, donative, extra) {
+ // Clone the incoming data, if any
+ data = jQuery.makeArray(data || []);
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Only trigger if we've ever bound an event for it
+ if ( this.global[type] )
+ jQuery("*").add([window, document]).trigger(type, data);
+
+ // Handle triggering a single element
+ } else {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return undefined;
+
+ var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+ // Check to see if we need to provide a fake event, or not
+ event = !data[0] || !data[0].preventDefault;
+
+ // Pass along a fake event
+ if ( event )
+ data.unshift( this.fix({ type: type, target: elem }) );
+
+ // Enforce the right trigger type
+ data[0].type = type;
+
+ // Trigger the event
+ if ( jQuery.isFunction( jQuery.data(elem, "handle") ) )
+ val = jQuery.data(elem, "handle").apply( elem, data );
+
+ // Handle triggering native .onfoo handlers
+ if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+ val = false;
+
+ // Extra functions don't get the custom event object
+ if ( event )
+ data.shift();
+
+ // Handle triggering of extra function
+ if ( extra && jQuery.isFunction( extra ) ) {
+ // call the extra function and tack the current return value on the end for possible inspection
+ ret = extra.apply( elem, data.concat( val ) );
+ // if anything is returned, give it precedence and have it overwrite the previous value
+ if (ret !== undefined)
+ val = ret;
+ }
+
+ // Trigger the native events (except for clicks on links)
+ if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+ this.triggered = true;
+ try {
+ elem[ type ]();
+ // prevent IE from throwing an error for some hidden elements
+ } catch (e) {}
+ }
+
+ this.triggered = false;
+ }
+
+ return val;
+ },
+
+ handle: function(event) {
+ // returned undefined or false
+ var val;
+
+ // Empty object is for triggered events with no data
+ event = jQuery.event.fix( event || window.event || {} );
+
+ // Namespaced event handlers
+ var parts = event.type.split(".");
+ event.type = parts[0];
+
+ var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 );
+ args.unshift( event );
+
+ for ( var j in handlers ) {
+ var handler = handlers[j];
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ args[0].handler = handler;
+ args[0].data = handler.data;
+
+ // Filter the functions by class
+ if ( !parts[1] || handler.type == parts[1] ) {
+ var ret = handler.apply( this, args );
+
+ if ( val !== false )
+ val = ret;
+
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+
+ // Clean up added properties in IE to prevent memory leak
+ if (jQuery.browser.msie)
+ event.target = event.preventDefault = event.stopPropagation =
+ event.handler = event.data = null;
+
+ return val;
+ },
+
+ fix: function(event) {
+ // store a copy of the original event object
+ // and clone to set read-only properties
+ var originalEvent = event;
+ event = jQuery.extend({}, originalEvent);
+
+ // add preventDefault and stopPropagation since
+ // they will not work on the clone
+ event.preventDefault = function() {
+ // if preventDefault exists run it on the original event
+ if (originalEvent.preventDefault)
+ originalEvent.preventDefault();
+ // otherwise set the returnValue property of the original event to false (IE)
+ originalEvent.returnValue = false;
+ };
+ event.stopPropagation = function() {
+ // if stopPropagation exists run it on the original event
+ if (originalEvent.stopPropagation)
+ originalEvent.stopPropagation();
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ originalEvent.cancelBubble = true;
+ };
+
+ // Fix target property, if necessary
+ if ( !event.target )
+ event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType == 3 )
+ event.target = originalEvent.target.parentNode;
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement )
+ event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var doc = document.documentElement, body = document.body;
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+ event.which = event.charCode || event.keyCode;
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey )
+ event.metaKey = event.ctrlKey;
+
+ // Add which for click: 1 == left; 2 == middle; 3 == right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button )
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+ return event;
+ },
+
+ special: {
+ ready: {
+ setup: function() {
+ // Make sure the ready event is setup
+ bindReady();
+ return;
+ },
+
+ teardown: function() { return; }
+ },
+
+ mouseenter: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseenter
+ arguments[0].type = "mouseenter";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ },
+
+ mouseleave: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseleave
+ arguments[0].type = "mouseleave";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ }
+ }
+};
+
+jQuery.fn.extend({
+ bind: function( type, data, fn ) {
+ return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+ jQuery.event.add( this, type, fn || data, fn && data );
+ });
+ },
+
+ one: function( type, data, fn ) {
+ return this.each(function(){
+ jQuery.event.add( this, type, function(event) {
+ jQuery(this).unbind(event);
+ return (fn || data).apply( this, arguments);
+ }, fn && data);
+ });
+ },
+
+ unbind: function( type, fn ) {
+ return this.each(function(){
+ jQuery.event.remove( this, type, fn );
+ });
+ },
+
+ trigger: function( type, data, fn ) {
+ return this.each(function(){
+ jQuery.event.trigger( type, data, this, true, fn );
+ });
+ },
+
+ triggerHandler: function( type, data, fn ) {
+ if ( this[0] )
+ return jQuery.event.trigger( type, data, this[0], false, fn );
+ return undefined;
+ },
+
+ toggle: function() {
+ // Save reference to arguments for access in closure
+ var args = arguments;
+
+ return this.click(function(event) {
+ // Figure out which function to execute
+ this.lastToggle = 0 == this.lastToggle ? 1 : 0;
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[this.lastToggle].apply( this, arguments ) || false;
+ });
+ },
+
+ hover: function(fnOver, fnOut) {
+ return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+ },
+
+ ready: function(fn) {
+ // Attach the listeners
+ bindReady();
+
+ // If the DOM is already ready
+ if ( jQuery.isReady )
+ // Execute the function immediately
+ fn.call( document, jQuery );
+
+ // Otherwise, remember the function for later
+ else
+ // Add the function to the wait list
+ jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+
+ return this;
+ }
+});
+
+jQuery.extend({
+ isReady: false,
+ readyList: [],
+ // Handle when the DOM is ready
+ ready: function() {
+ // Make sure that the DOM is not already loaded
+ if ( !jQuery.isReady ) {
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If there are functions bound, to execute
+ if ( jQuery.readyList ) {
+ // Execute all of them
+ jQuery.each( jQuery.readyList, function(){
+ this.apply( document );
+ });
+
+ // Reset the list of functions
+ jQuery.readyList = null;
+ }
+
+ // Trigger any bound ready events
+ jQuery(document).triggerHandler("ready");
+ }
+ }
+});
+
+var readyBound = false;
+
+function bindReady(){
+ if ( readyBound ) return;
+ readyBound = true;
+
+ // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
+ if ( document.addEventListener && !jQuery.browser.opera)
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+
+ // If IE is used and is not in a frame
+ // Continually check to see if the document is ready
+ if ( jQuery.browser.msie && window == top ) (function(){
+ if (jQuery.isReady) return;
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch( error ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+
+ if ( jQuery.browser.opera )
+ document.addEventListener( "DOMContentLoaded", function () {
+ if (jQuery.isReady) return;
+ for (var i = 0; i < document.styleSheets.length; i++)
+ if (document.styleSheets[i].disabled) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ }, false);
+
+ if ( jQuery.browser.safari ) {
+ var numStyles;
+ (function(){
+ if (jQuery.isReady) return;
+ if ( document.readyState != "loaded" && document.readyState != "complete" ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ if ( numStyles === undefined )
+ numStyles = jQuery("style, link[rel=stylesheet]").length;
+ if ( document.styleSheets.length != numStyles ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+ }
+
+ // A fallback to window.onload, that will always work
+ jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+ "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
+ "submit,keydown,keypress,keyup,error").split(","), function(i, name){
+
+ // Handle event binding
+ jQuery.fn[name] = function(fn){
+ return fn ? this.bind(name, fn) : this.trigger(name);
+ };
+});
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event, elem) {
+ // Check if mouse(over|out) are still within the same parent element
+ var parent = event.relatedTarget;
+ // Traverse up the tree
+ while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
+ // Return true if we actually just moused on to a sub-element
+ return parent == elem;
+};
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery(window).bind("unload", function() {
+ jQuery("*").add(document).unbind();
+});
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( jQuery.isFunction( url ) )
+ return this.bind("load", url);
+
+ var off = url.indexOf(" ");
+ if ( off >= 0 ) {
+ var selector = url.slice(off, url.length);
+ url = url.slice(0, off);
+ }
+
+ callback = callback || function(){};
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params )
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = null;
+
+ // Otherwise, build a param string
+ } else {
+ params = jQuery.param( params );
+ type = "POST";
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function(res, status){
+ // If successful, inject the HTML into all the matched elements
+ if ( status == "success" || status == "notmodified" )
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div/>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ res.responseText );
+
+ self.each( callback, [res.responseText, status, res] );
+ }
+ });
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param(this.serializeArray());
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return jQuery.nodeName(this, "form") ?
+ jQuery.makeArray(this.elements) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ (this.checked || /select|textarea/i.test(this.nodeName) ||
+ /text|hidden|password/i.test(this.type));
+ })
+ .map(function(i, elem){
+ var val = jQuery(this).val();
+ return val == null ? null :
+ val.constructor == Array ?
+ jQuery.map( val, function(val, i){
+ return {name: elem.name, value: val};
+ }) :
+ {name: elem.name, value: val};
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+ jQuery.fn[o] = function(f){
+ return this.bind(o, f);
+ };
+});
+
+var jsc = (new Date).getTime();
+
+jQuery.extend({
+ get: function( url, data, callback, type ) {
+ // shift arguments if data argument was ommited
+ if ( jQuery.isFunction( data ) ) {
+ callback = data;
+ data = null;
+ }
+
+ return jQuery.ajax({
+ type: "GET",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get(url, null, callback, "script");
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get(url, data, callback, "json");
+ },
+
+ post: function( url, data, callback, type ) {
+ if ( jQuery.isFunction( data ) ) {
+ callback = data;
+ data = {};
+ }
+
+ return jQuery.ajax({
+ type: "POST",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ ajaxSetup: function( settings ) {
+ jQuery.extend( jQuery.ajaxSettings, settings );
+ },
+
+ ajaxSettings: {
+ global: true,
+ type: "GET",
+ timeout: 0,
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ data: null,
+ username: null,
+ password: null
+ },
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+
+ ajax: function( s ) {
+ var jsonp, jsre = /=\?(&|$)/g, status, data;
+
+ // Extend the settings, but re-extend 's' so that it can be
+ // checked again later (in the test suite, specifically)
+ s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+ // convert data if not already a string
+ if ( s.data && s.processData && typeof s.data != "string" )
+ s.data = jQuery.param(s.data);
+
+ // Handle JSONP Parameter Callbacks
+ if ( s.dataType == "jsonp" ) {
+ if ( s.type.toLowerCase() == "get" ) {
+ if ( !s.url.match(jsre) )
+ s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+ } else if ( !s.data || !s.data.match(jsre) )
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+ s.dataType = "json";
+ }
+
+ // Build temporary JSONP function
+ if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+ jsonp = "jsonp" + jsc++;
+
+ // Replace the =? sequence both in the query string and the data
+ if ( s.data )
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+ // We need to make sure
+ // that a JSONP style response is executed properly
+ s.dataType = "script";
+
+ // Handle JSONP-style loading
+ window[ jsonp ] = function(tmp){
+ data = tmp;
+ success();
+ complete();
+ // Garbage collect
+ window[ jsonp ] = undefined;
+ try{ delete window[ jsonp ]; } catch(e){}
+ if ( head )
+ head.removeChild( script );
+ };
+ }
+
+ if ( s.dataType == "script" && s.cache == null )
+ s.cache = false;
+
+ if ( s.cache === false && s.type.toLowerCase() == "get" ) {
+ var ts = (new Date()).getTime();
+ // try replacing _= if it is there
+ var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+ }
+
+ // If data is available, append data to url for get requests
+ if ( s.data && s.type.toLowerCase() == "get" ) {
+ s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+ // IE likes to send both get and post data, prevent this
+ s.data = null;
+ }
+
+ // Watch for a new set of requests
+ if ( s.global && ! jQuery.active++ )
+ jQuery.event.trigger( "ajaxStart" );
+
+ // If we're requesting a remote document
+ // and trying to load JSON or Script with a GET
+ if ( (!s.url.indexOf("http") || !s.url.indexOf("//")) && ( s.dataType == "script" || s.dataType =="json" ) && s.type.toLowerCase() == "get" ) {
+ var head = document.getElementsByTagName("head")[0];
+ var script = document.createElement("script");
+ script.src = s.url;
+ if (s.scriptCharset)
+ script.charset = s.scriptCharset;
+
+ // Handle Script loading
+ if ( !jsonp ) {
+ var done = false;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function(){
+ if ( !done && (!this.readyState ||
+ this.readyState == "loaded" || this.readyState == "complete") ) {
+ done = true;
+ success();
+ complete();
+ head.removeChild( script );
+ }
+ };
+ }
+
+ head.appendChild(script);
+
+ // We handle everything using the script element injection
+ return undefined;
+ }
+
+ var requestDone = false;
+
+ // Create the request object; Microsoft failed to properly
+ // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+ var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+ // Open the socket
+ xml.open(s.type, s.url, s.async, s.username, s.password);
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ // Set the correct header, if data is being sent
+ if ( s.data )
+ xml.setRequestHeader("Content-Type", s.contentType);
+
+ // Set the If-Modified-Since header, if ifModified mode.
+ if ( s.ifModified )
+ xml.setRequestHeader("If-Modified-Since",
+ jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+ // Set header so the called script knows that it's an XMLHttpRequest
+ xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ } catch(e){}
+
+ // Allow custom headers/mimetypes
+ if ( s.beforeSend )
+ s.beforeSend(xml);
+
+ if ( s.global )
+ jQuery.event.trigger("ajaxSend", [xml, s]);
+
+ // Wait for a response to come back
+ var onreadystatechange = function(isTimeout){
+ // The transfer is complete and the data is available, or the request timed out
+ if ( !requestDone && xml && (xml.readyState == 4 || isTimeout == "timeout") ) {
+ requestDone = true;
+
+ // clear poll interval
+ if (ival) {
+ clearInterval(ival);
+ ival = null;
+ }
+
+ status = isTimeout == "timeout" && "timeout" ||
+ !jQuery.httpSuccess( xml ) && "error" ||
+ s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" ||
+ "success";
+
+ if ( status == "success" ) {
+ // Watch for, and catch, XML document parse errors
+ try {
+ // process the data (runs the xml through httpData regardless of callback)
+ data = jQuery.httpData( xml, s.dataType );
+ } catch(e) {
+ status = "parsererror";
+ }
+ }
+
+ // Make sure that the request was successful or notmodified
+ if ( status == "success" ) {
+ // Cache Last-Modified header, if ifModified mode.
+ var modRes;
+ try {
+ modRes = xml.getResponseHeader("Last-Modified");
+ } catch(e) {} // swallow exception thrown by FF if header is not available
+
+ if ( s.ifModified && modRes )
+ jQuery.lastModified[s.url] = modRes;
+
+ // JSONP handles its own success callback
+ if ( !jsonp )
+ success();
+ } else
+ jQuery.handleError(s, xml, status);
+
+ // Fire the complete handlers
+ complete();
+
+ // Stop memory leaks
+ if ( s.async )
+ xml = null;
+ }
+ };
+
+ if ( s.async ) {
+ // don't attach the handler to the request, just poll it instead
+ var ival = setInterval(onreadystatechange, 13);
+
+ // Timeout checker
+ if ( s.timeout > 0 )
+ setTimeout(function(){
+ // Check to see if the request is still happening
+ if ( xml ) {
+ // Cancel the request
+ xml.abort();
+
+ if( !requestDone )
+ onreadystatechange( "timeout" );
+ }
+ }, s.timeout);
+ }
+
+ // Send the data
+ try {
+ xml.send(s.data);
+ } catch(e) {
+ jQuery.handleError(s, xml, null, e);
+ }
+
+ // firefox 1.5 doesn't fire statechange for sync requests
+ if ( !s.async )
+ onreadystatechange();
+
+ function success(){
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success )
+ s.success( data, status );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+ }
+
+ function complete(){
+ // Process result
+ if ( s.complete )
+ s.complete(xml, status);
+
+ // The request was completed
+ if ( s.global )
+ jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+ }
+
+ // return XMLHttpRequest to allow aborting the request etc.
+ return xml;
+ },
+
+ handleError: function( s, xml, status, e ) {
+ // If a local callback was specified, fire it
+ if ( s.error ) s.error( xml, status, e );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxError", [xml, s, e] );
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Determines if an XMLHttpRequest was successful or not
+ httpSuccess: function( r ) {
+ try {
+ // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+ return !r.status && location.protocol == "file:" ||
+ ( r.status >= 200 && r.status < 300 ) || r.status == 304 || r.status == 1223 ||
+ jQuery.browser.safari && r.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ // Determines if an XMLHttpRequest returns NotModified
+ httpNotModified: function( xml, url ) {
+ try {
+ var xmlRes = xml.getResponseHeader("Last-Modified");
+
+ // Firefox always returns 200. check Last-Modified date
+ return xml.status == 304 || xmlRes == jQuery.lastModified[url] ||
+ jQuery.browser.safari && xml.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ httpData: function( r, type ) {
+ var ct = r.getResponseHeader("content-type");
+ var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0;
+ var data = xml ? r.responseXML : r.responseText;
+
+ if ( xml && data.documentElement.tagName == "parsererror" )
+ throw "parsererror";
+
+ // If the type is "script", eval it in global context
+ if ( type == "script" )
+ jQuery.globalEval( data );
+
+ // Get the JavaScript object, if JSON is used.
+ if ( type == "json" )
+ data = eval("(" + data + ")");
+
+ return data;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a ) {
+ var s = [];
+
+ // If an array was passed in, assume that it is an array
+ // of form elements
+ if ( a.constructor == Array || a.jquery )
+ // Serialize the form elements
+ jQuery.each( a, function(){
+ s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
+ });
+
+ // Otherwise, assume that it's an object of key/value pairs
+ else
+ // Serialize the key/values
+ for ( var j in a )
+ // If the value is an array then the key names need to be repeated
+ if ( a[j] && a[j].constructor == Array )
+ jQuery.each( a[j], function(){
+ s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) );
+ });
+ else
+ s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) );
+
+ // Return the resulting serialization
+ return s.join("&").replace(/%20/g, "+");
+ }
+
+});
+jQuery.fn.extend({
+ show: function(speed,callback){
+ return speed ?
+ this.animate({
+ height: "show", width: "show", opacity: "show"
+ }, speed, callback) :
+
+ this.filter(":hidden").each(function(){
+ this.style.display = this.oldblock || "";
+ if ( jQuery.css(this,"display") == "none" ) {
+ var elem = jQuery("<" + this.tagName + " />").appendTo("body");
+ this.style.display = elem.css("display");
+ elem.remove();
+ }
+ }).end();
+ },
+
+ hide: function(speed,callback){
+ return speed ?
+ this.animate({
+ height: "hide", width: "hide", opacity: "hide"
+ }, speed, callback) :
+
+ this.filter(":visible").each(function(){
+ this.oldblock = this.oldblock || jQuery.css(this,"display");
+ this.style.display = "none";
+ }).end();
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2 ){
+ return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+ this._toggle( fn, fn2 ) :
+ fn ?
+ this.animate({
+ height: "toggle", width: "toggle", opacity: "toggle"
+ }, fn, fn2) :
+ this.each(function(){
+ jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+ });
+ },
+
+ slideDown: function(speed,callback){
+ return this.animate({height: "show"}, speed, callback);
+ },
+
+ slideUp: function(speed,callback){
+ return this.animate({height: "hide"}, speed, callback);
+ },
+
+ slideToggle: function(speed, callback){
+ return this.animate({height: "toggle"}, speed, callback);
+ },
+
+ fadeIn: function(speed, callback){
+ return this.animate({opacity: "show"}, speed, callback);
+ },
+
+ fadeOut: function(speed, callback){
+ return this.animate({opacity: "hide"}, speed, callback);
+ },
+
+ fadeTo: function(speed,to,callback){
+ return this.animate({opacity: to}, speed, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed(speed, easing, callback);
+
+ return this[ optall.queue === false ? "each" : "queue" ](function(){
+ if ( this.nodeType != 1)
+ return false;
+
+ var opt = jQuery.extend({}, optall);
+ var hidden = jQuery(this).is(":hidden"), self = this;
+
+ for ( var p in prop ) {
+ if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+ return jQuery.isFunction(opt.complete) && opt.complete.apply(this);
+
+ if ( p == "height" || p == "width" ) {
+ // Store display property
+ opt.display = jQuery.css(this, "display");
+
+ // Make sure that nothing sneaks out
+ opt.overflow = this.style.overflow;
+ }
+ }
+
+ if ( opt.overflow != null )
+ this.style.overflow = "hidden";
+
+ opt.curAnim = jQuery.extend({}, prop);
+
+ jQuery.each( prop, function(name, val){
+ var e = new jQuery.fx( self, opt, name );
+
+ if ( /toggle|show|hide/.test(val) )
+ e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+ else {
+ var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+ start = e.cur(true) || 0;
+
+ if ( parts ) {
+ var end = parseFloat(parts[2]),
+ unit = parts[3] || "px";
+
+ // We need to compute starting value
+ if ( unit != "px" ) {
+ self.style[ name ] = (end || 1) + unit;
+ start = ((end || 1) / e.cur(true)) * start;
+ self.style[ name ] = start + unit;
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] )
+ end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+ e.custom( start, end, unit );
+ } else
+ e.custom( start, val, "" );
+ }
+ });
+
+ // For JS strict compliance
+ return true;
+ });
+ },
+
+ queue: function(type, fn){
+ if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) {
+ fn = type;
+ type = "fx";
+ }
+
+ if ( !type || (typeof type == "string" && !fn) )
+ return queue( this[0], type );
+
+ return this.each(function(){
+ if ( fn.constructor == Array )
+ queue(this, type, fn);
+ else {
+ queue(this, type).push( fn );
+
+ if ( queue(this, type).length == 1 )
+ fn.apply(this);
+ }
+ });
+ },
+
+ stop: function(clearQueue, gotoEnd){
+ var timers = jQuery.timers;
+
+ if (clearQueue)
+ this.queue([]);
+
+ this.each(function(){
+ // go in reverse order so anything added to the queue during the loop is ignored
+ for ( var i = timers.length - 1; i >= 0; i-- )
+ if ( timers[i].elem == this ) {
+ if (gotoEnd)
+ // force the next step to be the last
+ timers[i](true);
+ timers.splice(i, 1);
+ }
+ });
+
+ // start the next in the queue if the last step wasn't forced
+ if (!gotoEnd)
+ this.dequeue();
+
+ return this;
+ }
+
+});
+
+var queue = function( elem, type, array ) {
+ if ( !elem )
+ return undefined;
+
+ type = type || "fx";
+
+ var q = jQuery.data( elem, type + "queue" );
+
+ if ( !q || array )
+ q = jQuery.data( elem, type + "queue",
+ array ? jQuery.makeArray(array) : [] );
+
+ return q;
+};
+
+jQuery.fn.dequeue = function(type){
+ type = type || "fx";
+
+ return this.each(function(){
+ var q = queue(this, type);
+
+ q.shift();
+
+ if ( q.length )
+ q[0].apply( this );
+ });
+};
+
+jQuery.extend({
+
+ speed: function(speed, easing, fn) {
+ var opt = speed && speed.constructor == Object ? speed : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && easing.constructor != Function && easing
+ };
+
+ opt.duration = (opt.duration && opt.duration.constructor == Number ?
+ opt.duration :
+ { slow: 600, fast: 200 }[opt.duration]) || 400;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function(){
+ if ( opt.queue !== false )
+ jQuery(this).dequeue();
+ if ( jQuery.isFunction( opt.old ) )
+ opt.old.apply( this );
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+ timerId: null,
+
+ fx: function( elem, options, prop ){
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ if ( !options.orig )
+ options.orig = {};
+ }
+
+});
+
+jQuery.fx.prototype = {
+
+ // Simple function for setting a style value
+ update: function(){
+ if ( this.options.step )
+ this.options.step.apply( this.elem, [ this.now, this ] );
+
+ (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+ // Set display property to block for height/width animations
+ if ( this.prop == "height" || this.prop == "width" )
+ this.elem.style.display = "block";
+ },
+
+ // Get the current size
+ cur: function(force){
+ if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null )
+ return this.elem[ this.prop ];
+
+ var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+ return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+ },
+
+ // Start an animation from one number to another
+ custom: function(from, to, unit){
+ this.startTime = (new Date()).getTime();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || "px";
+ this.now = this.start;
+ this.pos = this.state = 0;
+ this.update();
+
+ var self = this;
+ function t(gotoEnd){
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ jQuery.timers.push(t);
+
+ if ( jQuery.timerId == null ) {
+ jQuery.timerId = setInterval(function(){
+ var timers = jQuery.timers;
+
+ for ( var i = 0; i < timers.length; i++ )
+ if ( !timers[i]() )
+ timers.splice(i--, 1);
+
+ if ( !timers.length ) {
+ clearInterval( jQuery.timerId );
+ jQuery.timerId = null;
+ }
+ }, 13);
+ }
+ },
+
+ // Simple 'show' function
+ show: function(){
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ this.custom(0, this.cur());
+
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ if ( this.prop == "width" || this.prop == "height" )
+ this.elem.style[this.prop] = "1px";
+
+ // Start by showing the element
+ jQuery(this.elem).show();
+ },
+
+ // Simple 'hide' function
+ hide: function(){
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom(this.cur(), 0);
+ },
+
+ // Each step of an animation
+ step: function(gotoEnd){
+ var t = (new Date()).getTime();
+
+ if ( gotoEnd || t > this.options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ this.options.curAnim[ this.prop ] = true;
+
+ var done = true;
+ for ( var i in this.options.curAnim )
+ if ( this.options.curAnim[i] !== true )
+ done = false;
+
+ if ( done ) {
+ if ( this.options.display != null ) {
+ // Reset the overflow
+ this.elem.style.overflow = this.options.overflow;
+
+ // Reset the display
+ this.elem.style.display = this.options.display;
+ if ( jQuery.css(this.elem, "display") == "none" )
+ this.elem.style.display = "block";
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( this.options.hide )
+ this.elem.style.display = "none";
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( this.options.hide || this.options.show )
+ for ( var p in this.options.curAnim )
+ jQuery.attr(this.elem.style, p, this.options.orig[p]);
+ }
+
+ // If a callback was provided, execute it
+ if ( done && jQuery.isFunction( this.options.complete ) )
+ // Execute the complete function
+ this.options.complete.apply( this.elem );
+
+ return false;
+ } else {
+ var n = t - this.startTime;
+ this.state = n / this.options.duration;
+
+ // Perform the easing function, defaults to swing
+ this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+
+};
+
+jQuery.fx.step = {
+ scrollLeft: function(fx){
+ fx.elem.scrollLeft = fx.now;
+ },
+
+ scrollTop: function(fx){
+ fx.elem.scrollTop = fx.now;
+ },
+
+ opacity: function(fx){
+ jQuery.attr(fx.elem.style, "opacity", fx.now);
+ },
+
+ _default: function(fx){
+ fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+ }
+};
+// The Offset Method
+// Originally By Brandon Aaron, part of the Dimension Plugin
+// http://jquery.com/plugins/project/dimensions
+jQuery.fn.offset = function() {
+ var left = 0, top = 0, elem = this[0], results;
+
+ if ( elem ) with ( jQuery.browser ) {
+ var parent = elem.parentNode,
+ offsetChild = elem,
+ offsetParent = elem.offsetParent,
+ doc = elem.ownerDocument,
+ safari2 = safari && parseInt(version) < 522,
+ fixed = jQuery.css(elem, "position") == "fixed";
+
+ // Use getBoundingClientRect if available
+ if ( elem.getBoundingClientRect ) {
+ var box = elem.getBoundingClientRect();
+
+ // Add the document scroll offsets
+ add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+ box.top + Math.max(doc.documentElement.scrollTop, doc.body.scrollTop));
+
+ // IE adds the HTML element's border, by default it is medium which is 2px
+ // IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
+ // IE 7 standards mode, the border is always 2px
+ // This border/offset is typically represented by the clientLeft and clientTop properties
+ // However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
+ // Therefore this method will be off by 2px in IE while in quirksmode
+ add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );
+
+ // Otherwise loop through the offsetParents and parentNodes
+ } else {
+
+ // Initial element offsets
+ add( elem.offsetLeft, elem.offsetTop );
+
+ // Get parent offsets
+ while ( offsetParent ) {
+ // Add offsetParent offsets
+ add( offsetParent.offsetLeft, offsetParent.offsetTop );
+
+ // Mozilla and Safari > 2 does not include the border on offset parents
+ // However Mozilla adds the border for table or table cells
+ if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
+ border( offsetParent );
+
+ // Add the document scroll offsets if position is fixed on any offsetParent
+ if ( !fixed && jQuery.css(offsetParent, "position") == "fixed" )
+ fixed = true;
+
+ // Set offsetChild to previous offsetParent unless it is the body element
+ offsetChild = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
+ // Get next offsetParent
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ // Get parent scroll offsets
+ while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
+ // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
+ if ( !/^inline|table.*$/i.test(jQuery.css(parent, "display")) )
+ // Subtract parent scroll offsets
+ add( -parent.scrollLeft, -parent.scrollTop );
+
+ // Mozilla does not add the border for a parent that has overflow != visible
+ if ( mozilla && jQuery.css(parent, "overflow") != "visible" )
+ border( parent );
+
+ // Get next parent
+ parent = parent.parentNode;
+ }
+
+ // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
+ // Mozilla doubles body offsets with a non-absolutely positioned offsetChild
+ if ( (safari2 && (fixed || jQuery.css(offsetChild, "position") == "absolute")) ||
+ (mozilla && jQuery.css(offsetChild, "position") != "absolute") )
+ add( -doc.body.offsetLeft, -doc.body.offsetTop );
+
+ // Add the document scroll offsets if position is fixed
+ if ( fixed )
+ add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+ Math.max(doc.documentElement.scrollTop, doc.body.scrollTop));
+ }
+
+ // Return an object with top and left properties
+ results = { top: top, left: left };
+ }
+
+ function border(elem) {
+ add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );
+ }
+
+ function add(l, t) {
+ left += parseInt(l) || 0;
+ top += parseInt(t) || 0;
+ }
+
+ return results;
+};
+})();
diff --git a/webroot/js/jquery/jquery.lite.js b/webroot/js/jquery/jquery.lite.js
new file mode 100644
index 0000000..0f6d0ce
--- /dev/null
+++ b/webroot/js/jquery/jquery.lite.js
@@ -0,0 +1,3366 @@
+(function(){
+/*
+ * jQuery 1.2.2b2 - New Wave Javascript
+ *
+ * Copyright (c) 2007 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-01-17 12:47:29 +0100 (Thu, 17 Jan 2008) $
+ * $Rev: 176 $
+ */
+
+// Map over jQuery in case of overwrite
+if ( window.jQuery )
+ var _jQuery = window.jQuery;
+
+var jQuery = window.jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.prototype.init( selector, context );
+};
+
+// Map over the $ in case of overwrite
+if ( window.$ )
+ var _$ = window.$;
+
+// Map the jQuery namespace to the '$' one
+window.$ = jQuery;
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;
+
+// Is it a simple selector
+var isSimple = /^.[^:#\[\.]*$/;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ // Make sure that a selection was provided
+ selector = selector || document;
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // Handle HTML strings
+ } else if ( typeof selector == "string" ) {
+ // Are we dealing with HTML string or an ID?
+ var match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] )
+ selector = jQuery.clean( [ match[1] ], context );
+
+ // HANDLE: $("#id")
+ else {
+ var elem = document.getElementById( match[3] );
+
+ // Make sure an element was located
+ if ( elem )
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id != match[3] )
+ return jQuery().find( selector );
+
+ // Otherwise, we inject the element directly into the jQuery object
+ else {
+ this[0] = elem;
+ this.length = 1;
+ return this;
+ }
+
+ else
+ selector = [];
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return new jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+ return this.setArray(
+ // HANDLE: $(array)
+ selector.constructor == Array && selector ||
+
+ // HANDLE: $(arraylike)
+ // Watch for when an array-like object, contains DOM nodes, is passed in as the selector
+ (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) ||
+
+ // HANDLE: $(*)
+ [ selector ] );
+ },
+
+ // The current version of jQuery being used
+ jquery: "1.2.2b2",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // The number of elements contained in the matched element set
+ length: 0,
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == undefined ?
+
+ // Return a 'clean' array
+ jQuery.makeArray( this ) :
+
+ // Return just the object
+ this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery( elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Force the current matched set of elements to become
+ // the specified array of elements (destroying the stack in the process)
+ // You should use pushStack() in order to do this, but maintain the stack
+ setArray: function( elems ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ this.length = 0;
+ Array.prototype.push.apply( this, elems );
+
+ return this;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ var ret = -1;
+
+ // Locate the position of the desired element
+ this.each(function(i){
+ if ( this == elem )
+ ret = i;
+ });
+
+ return ret;
+ },
+
+ attr: function( name, value, type ) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if ( name.constructor == String )
+ if ( value == undefined )
+ return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined;
+
+ else {
+ options = {};
+ options[ name ] = value;
+ }
+
+ // Check to see if we're setting style values
+ return this.each(function(i){
+ // Set all the styles
+ for ( name in options )
+ jQuery.attr(
+ type ?
+ this.style :
+ this,
+ name, jQuery.prop( this, options[ name ], type, i, name )
+ );
+ });
+ },
+
+ css: function( key, value ) {
+ // ignore negative width and height values
+ if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+ value = undefined;
+ return this.attr( key, value, "curCSS" );
+ },
+
+ text: function( text ) {
+ if ( typeof text != "object" && text != null )
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+ var ret = "";
+
+ jQuery.each( text || this, function(){
+ jQuery.each( this.childNodes, function(){
+ if ( this.nodeType != 8 )
+ ret += this.nodeType != 1 ?
+ this.nodeValue :
+ jQuery.fn.text( [ this ] );
+ });
+ });
+
+ return ret;
+ },
+
+ wrapAll: function( html ) {
+ if ( this[0] )
+ // The elements to wrap the target around
+ jQuery( html, this[0].ownerDocument )
+ .clone()
+ .insertBefore( this[0] )
+ .map(function(){
+ var elem = this;
+
+ while ( elem.firstChild )
+ elem = elem.firstChild;
+
+ return elem;
+ })
+ .append(this);
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ return this.each(function(){
+ jQuery( this ).contents().wrapAll( html );
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function(){
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, false, function(elem){
+ if (this.nodeType == 1)
+ this.appendChild( elem );
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, true, function(elem){
+ if (this.nodeType == 1)
+ this.insertBefore( elem, this.firstChild );
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, false, function(elem){
+ this.parentNode.insertBefore( elem, this );
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, true, function(elem){
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery( [] );
+ },
+
+ find: function( selector ) {
+ var elems = jQuery.map(this, function(elem){
+ return jQuery.find( selector, elem );
+ });
+
+ return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+ jQuery.unique( elems ) :
+ elems );
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function(){
+ if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var clone = this.cloneNode(true),
+ container = document.createElement("div"),
+ container2 = document.createElement("div");
+ container.appendChild(clone);
+ container2.innerHTML = container.innerHTML;
+ return container2.firstChild;
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Need to set the expando to null on the cloned set if it exists
+ // removeData doesn't work here, IE removes it from the original as well
+ // this is primarily for IE but the data expando shouldn't be copied over in any browser
+ var clone = ret.find("*").andSelf().each(function(){
+ if ( this[ expando ] != undefined )
+ this[ expando ] = null;
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true )
+ this.find("*").andSelf().each(function(i){
+ if (this.nodeType == 3)
+ return;
+ var events = jQuery.data( this, "events" );
+
+ for ( var type in events )
+ for ( var handler in events[ type ] )
+ jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+ });
+
+ // Return the cloned set
+ return ret;
+ },
+
+ filter: function( selector ) {
+ return this.pushStack(
+ jQuery.isFunction( selector ) &&
+ jQuery.grep(this, function(elem, i){
+ return selector.call( elem, i );
+ }) ||
+
+ jQuery.multiFilter( selector, this ) );
+ },
+
+ not: function( selector ) {
+ if ( selector.constructor == String )
+ // test special case where just one selector is passed in
+ if ( isSimple.test( selector ) )
+ return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+ else
+ selector = jQuery.multiFilter( selector, this );
+
+ var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+ return this.filter(function() {
+ return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+ });
+ },
+
+ add: function( selector ) {
+ return !selector ? this : this.pushStack( jQuery.merge(
+ this.get(),
+ selector.constructor == String ?
+ jQuery( selector ).get() :
+ selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ?
+ selector : [selector] ) );
+ },
+
+ is: function( selector ) {
+ return selector ?
+ jQuery.multiFilter( selector, this ).length > 0 :
+ false;
+ },
+
+ hasClass: function( selector ) {
+ return this.is( "." + selector );
+ },
+
+ val: function( value ) {
+ if ( value == undefined ) {
+
+ if ( this.length ) {
+ var elem = this[0];
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type == "select-one";
+
+ // Nothing was selected
+ if ( index < 0 )
+ return null;
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+
+ // We don't need an array for one selects
+ if ( one )
+ return value;
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+
+ // Everything else, we just grab the value
+ } else
+ return (this[0].value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ return this.each(function(){
+ if ( this.nodeType != 1 )
+ return;
+
+ if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+ this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+ jQuery.inArray(this.name, value) >= 0);
+
+ else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = value.constructor == Array ?
+ value :
+ [ value ];
+
+ jQuery( "option", this ).each(function(){
+ this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+ jQuery.inArray( this.text, values ) >= 0);
+ });
+
+ if ( !values.length )
+ this.selectedIndex = -1;
+
+ } else
+ this.value = value;
+ });
+ },
+
+ html: function( value ) {
+ return value == undefined ?
+ (this.length ?
+ this[0].innerHTML :
+ null) :
+ this.empty().append( value );
+ },
+
+ replaceWith: function( value ) {
+ return this.after( value ).remove();
+ },
+
+ eq: function( i ) {
+ return this.slice( i, i + 1 );
+ },
+
+ slice: function() {
+ return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function(elem, i){
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ },
+
+ domManip: function( args, table, reverse, callback ) {
+ var clone = this.length > 1, elems;
+
+ return this.each(function(){
+ if ( !elems ) {
+ elems = jQuery.clean( args, this.ownerDocument );
+
+ if ( reverse )
+ elems.reverse();
+ }
+
+ var obj = this;
+
+ if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+ obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+ var scripts = jQuery( [] );
+
+ jQuery.each(elems, function(){
+ var elem = clone ?
+ jQuery( this ).clone( true )[0] :
+ this;
+
+ // execute all scripts after the elements have been injected
+ if ( jQuery.nodeName( elem, "script" ) ) {
+ scripts = scripts.add( elem );
+ } else {
+ // Remove any inner scripts for later evaluation
+ if ( elem.nodeType == 1 )
+ scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+ // Inject the elements into the document
+ callback.call( obj, elem );
+ }
+ });
+
+ scripts.each( evalScript );
+ });
+ }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.prototype.init.prototype = jQuery.prototype;
+
+function evalScript( i, elem ) {
+ if ( elem.src )
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+
+ else
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+ if ( elem.parentNode )
+ elem.parentNode.removeChild( elem );
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if ( target.constructor == Boolean ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target != "object" && typeof target != "function" )
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if ( length == 1 ) {
+ target = this;
+ i = 0;
+ }
+
+ for ( ; i < length; i++ )
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null )
+ // Extend the base object
+ for ( var name in options ) {
+ // Prevent never-ending loop
+ if ( target === options[ name ] )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType )
+ target[ name ] = jQuery.extend( target[ name ], options[ name ] );
+
+ // Don't bring in undefined values
+ else if ( options[ name ] != undefined )
+ target[ name ] = options[ name ];
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
+
+// exclude the following css properties to add px
+var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i;
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep )
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // This may seem like some crazy code, but trust me when I say that this
+ // is the only cross-browser way to do this. --John
+ isFunction: function( fn ) {
+ return !!fn && typeof fn != "string" && !fn.nodeName &&
+ fn.constructor != Array && /function/i.test( fn + "" );
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function( elem ) {
+ return elem.documentElement && !elem.body ||
+ elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ data = jQuery.trim( data );
+
+ if ( data ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+ if ( jQuery.browser.msie )
+ script.text = data;
+ else
+ script.appendChild( document.createTextNode( data ) );
+
+ head.appendChild( script );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ cache: {},
+
+ data: function( elem, name, data ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // Compute a unique ID for the element
+ if ( !id )
+ id = elem[ expando ] = ++uuid;
+
+ // Only generate the data cache if we're
+ // trying to access or manipulate it
+ if ( name && !jQuery.cache[ id ] )
+ jQuery.cache[ id ] = {};
+
+ // Prevent overriding the named cache with undefined values
+ if ( data != undefined )
+ jQuery.cache[ id ][ name ] = data;
+
+ // Return the named cache data, or the ID for the element
+ return name ?
+ jQuery.cache[ id ][ name ] :
+ id;
+ },
+
+ removeData: function( elem, name ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( jQuery.cache[ id ] ) {
+ // Remove the section of cache data
+ delete jQuery.cache[ id ][ name ];
+
+ // If we've removed all the data, remove the element's cache
+ name = "";
+
+ for ( name in jQuery.cache[ id ] )
+ break;
+
+ if ( !name )
+ jQuery.removeData( elem );
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ // Clean up the element expando
+ try {
+ delete elem[ expando ];
+ } catch(e){
+ // IE has trouble directly removing the expando
+ // but it's ok with using removeAttribute
+ if ( elem.removeAttribute )
+ elem.removeAttribute( expando );
+ }
+
+ // Completely remove the data cache
+ delete jQuery.cache[ id ];
+ }
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ if ( args ) {
+ if ( object.length == undefined )
+ for ( var name in object )
+ callback.apply( object[ name ], args );
+ else
+ for ( var i = 0, length = object.length; i < length; i++ )
+ if ( callback.apply( object[ i ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( object.length == undefined )
+ for ( var name in object )
+ callback.call( object[ name ], name, object[ name ] );
+ else
+ for ( var i = 0, length = object.length, value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+ }
+
+ return object;
+ },
+
+ prop: function( elem, value, type, i, name ) {
+ // Handle executable functions
+ if ( jQuery.isFunction( value ) )
+ value = value.call( elem, i );
+
+ // Handle passing in a number to a CSS property
+ return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+ value + "px" :
+ value;
+ },
+
+ className: {
+ // internal only, use addClass("class")
+ add: function( elem, classNames ) {
+ jQuery.each((classNames || "").split(/\s+/), function(i, className){
+ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+ elem.className += (elem.className ? " " : "") + className;
+ });
+ },
+
+ // internal only, use removeClass("class")
+ remove: function( elem, classNames ) {
+ if (elem.nodeType == 1)
+ elem.className = classNames != undefined ?
+ jQuery.grep(elem.className.split(/\s+/), function(className){
+ return !jQuery.className.has( classNames, className );
+ }).join(" ") :
+ "";
+ },
+
+ // internal only, use is(".class")
+ has: function( elem, className ) {
+ return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options )
+ elem.style[ name ] = old[ name ];
+ },
+
+ css: function( elem, name, force ) {
+ if ( name == "width" || name == "height" ) {
+ var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+ function getWH() {
+ val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+ var padding = 0, border = 0;
+ jQuery.each( which, function() {
+ padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ });
+ val -= Math.round(padding + border);
+ }
+
+ if ( jQuery(elem).is(":visible") )
+ getWH();
+ else
+ jQuery.swap( elem, props, getWH );
+
+ return Math.max(0, val);
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret;
+
+ // A helper method for determining if an element's values are broken
+ function color( elem ) {
+ if ( !jQuery.browser.safari )
+ return false;
+
+ var ret = document.defaultView.getComputedStyle( elem, null );
+ return !ret || ret.getPropertyValue("color") == "";
+ }
+
+ // We need to handle opacity special in IE
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ ret = jQuery.attr( elem.style, "opacity" );
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+ // Opera sometimes will give the wrong display answer, this fixes it, see #2037
+ if ( jQuery.browser.opera && name == "display" ) {
+ var save = elem.style.display;
+ elem.style.display = "block";
+ elem.style.display = save;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( name.match( /float/i ) )
+ name = styleFloat;
+
+ if ( !force && elem.style[ name ] )
+ ret = elem.style[ name ];
+
+ else if ( document.defaultView && document.defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var getComputedStyle = document.defaultView.getComputedStyle( elem, null );
+
+ if ( getComputedStyle && !color( elem ) )
+ ret = getComputedStyle.getPropertyValue( name );
+
+ // If the element isn't reporting its values properly in Safari
+ // then some display: none elements are involved
+ else {
+ var swap = [], stack = [];
+
+ // Locate all of the parent display: none elements
+ for ( var a = elem; a && color(a); a = a.parentNode )
+ stack.unshift(a);
+
+ // Go through and make them visible, but in reverse
+ // (It would be better if we knew the exact display type that they had)
+ for ( var i = 0; i < stack.length; i++ )
+ if ( color( stack[ i ] ) ) {
+ swap[ i ] = stack[ i ].style.display;
+ stack[ i ].style.display = "block";
+ }
+
+ // Since we flip the display style, we have to handle that
+ // one special, otherwise get the value
+ ret = name == "display" && swap[ stack.length - 1 ] != null ?
+ "none" :
+ ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || "";
+
+ // Finally, revert the display styles back
+ for ( var i = 0; i < swap.length; i++ )
+ if ( swap[ i ] != null )
+ stack[ i ].style.display = swap[ i ];
+ }
+
+ // We should always get a number back from opacity
+ if ( name == "opacity" && ret == "" )
+ ret = "1";
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+ // Remember the original values
+ var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ elem.style.left = ret || 0;
+ ret = elem.style.pixelLeft + "px";
+
+ // Revert the changed values
+ elem.style.left = style;
+ elem.runtimeStyle.left = runtimeStyle;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function( elems, context ) {
+ var ret = [];
+ context = context || document;
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if (typeof context.createElement == 'undefined')
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+ jQuery.each(elems, function(i, elem){
+ if ( !elem )
+ return;
+
+ if ( elem.constructor == Number )
+ elem = elem.toString();
+
+ // Convert html string into DOM nodes
+ if ( typeof elem == "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+ all :
+ front + "></" + tag + ">";
+ });
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+ var wrap =
+ // option or optgroup
+ !tags.indexOf("<opt") &&
+ [ 1, "<select multiple='multiple'>", "</select>" ] ||
+
+ !tags.indexOf("<leg") &&
+ [ 1, "<fieldset>", "</fieldset>" ] ||
+
+ tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+ [ 1, "<table>", "</table>" ] ||
+
+ !tags.indexOf("<tr") &&
+ [ 2, "<table><tbody>", "</tbody></table>" ] ||
+
+ // <thead> matched above
+ (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+ [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+
+ !tags.indexOf("<col") &&
+ [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+ // IE can't serialize <link> and <script> tags normally
+ jQuery.browser.msie &&
+ [ 1, "div<div>", "</div>" ] ||
+
+ [ 0, "", "" ];
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( wrap[0]-- )
+ div = div.lastChild;
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( jQuery.browser.msie ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+ div.childNodes :
+ [];
+
+ for ( var j = tbody.length - 1; j >= 0 ; --j )
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( /^\s/.test( elem ) )
+ div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+
+ }
+
+ elem = jQuery.makeArray( div.childNodes );
+ }
+
+ if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+ return;
+
+ if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+ ret.push( elem );
+
+ else
+ ret = jQuery.merge( ret, elem );
+
+ });
+
+ return ret;
+ },
+
+ attr: function( elem, name, value ) {
+ // don't set attributes on text and comment nodes
+ if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+ return undefined;
+
+ var fix = jQuery.isXMLDoc( elem ) ?
+ {} :
+ jQuery.props;
+
+ // Safari mis-reports the default selected property of a hidden option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name == "selected" && jQuery.browser.safari )
+ elem.parentNode.selectedIndex;
+
+ // Certain attributes only work when accessed via the old DOM 0 way
+ if ( fix[ name ] ) {
+ if ( value != undefined )
+ elem[ fix[ name ] ] = value;
+
+ return elem[ fix[ name ] ];
+
+ } else if ( jQuery.browser.msie && name == "style" )
+ return jQuery.attr( elem.style, "cssText", value );
+
+ else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName( elem, "form" ) && (name == "action" || name == "method") )
+ return elem.getAttributeNode( name ).nodeValue;
+
+ // IE elem.getAttribute passes even for style
+ else if ( elem.tagName ) {
+
+ if ( value != undefined ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+ throw "type property can't be changed";
+
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+ }
+
+ if ( jQuery.browser.msie && /href|src/.test( name ) && !jQuery.isXMLDoc( elem ) )
+ return elem.getAttribute( name, 2 );
+
+ return elem.getAttribute( name );
+
+ // elem is actually elem.style ... set the style
+ } else {
+ // IE actually uses filters for opacity
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ if ( value != undefined ) {
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ elem.zoom = 1;
+
+ // Set the alpha filter to set the opacity
+ elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+ (parseFloat( value ).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+ }
+
+ return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() :
+ "";
+ }
+
+ name = name.replace(/-([a-z])/ig, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ if ( value != undefined )
+ elem[ name ] = value;
+
+ return elem[ name ];
+ }
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( /^\s+|\s+$/g, "" );
+ },
+
+ makeArray: function( array ) {
+ var ret = [];
+
+ // Need to use typeof to fight Safari childNodes crashes
+ if ( typeof array != "array" )
+ for ( var i = 0, length = array.length; i < length; i++ )
+ ret.push( array[ i ] );
+ else
+ ret = array.slice( 0 );
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ for ( var i = 0, length = array.length; i < length; i++ )
+ if ( array[ i ] == elem )
+ return i;
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ // We have to loop this way because IE & Opera overwrite the length
+ // expando of getElementsByTagName
+
+ // Also, we need to make sure that the correct elements are being returned
+ // (IE returns comment nodes in a '*' query)
+ if ( jQuery.browser.msie ) {
+ for ( var i = 0; second[ i ]; i++ )
+ if ( second[ i ].nodeType != 8 )
+ first.push( second[ i ] );
+
+ } else
+ for ( var i = 0; second[ i ]; i++ )
+ first.push( second[ i ] );
+
+ return first;
+ },
+
+ unique: function( array ) {
+ var ret = [], done = {};
+
+ try {
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ var id = jQuery.data( array[ i ] );
+
+ if ( !done[ id ] ) {
+ done[ id ] = true;
+ ret.push( array[ i ] );
+ }
+ }
+
+ } catch( e ) {
+ ret = array;
+ }
+
+ return ret;
+ },
+
+ grep: function( elems, callback, inv ) {
+ // If a string is passed in for the function, make a function
+ // for it (a handy shortcut)
+ if ( typeof callback == "string" )
+ callback = eval("false||function(a,i){return " + callback + "}");
+
+ var ret = [];
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ )
+ if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
+ ret.push( elems[ i ] );
+
+ return ret;
+ },
+
+ map: function( elems, callback ) {
+ var ret = [];
+
+ // Go through the array, translating each of the items to their
+ // new value (or values).
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ var value = callback( elems[ i ], i );
+
+ if ( value !== null && value != undefined ) {
+ if ( value.constructor != Array )
+ value = [ value ];
+
+ ret = ret.concat( value );
+ }
+ }
+
+ return ret;
+ }
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+ version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+ safari: /webkit/.test( userAgent ),
+ opera: /opera/.test( userAgent ),
+ msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+ mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+ "styleFloat" :
+ "cssFloat";
+
+jQuery.extend({
+ // Check to see if the W3C box model is being used
+ boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+
+ props: {
+ "for": "htmlFor",
+ "class": "className",
+ "float": styleFloat,
+ cssFloat: styleFloat,
+ styleFloat: styleFloat,
+ innerHTML: "innerHTML",
+ className: "className",
+ value: "value",
+ disabled: "disabled",
+ checked: "checked",
+ readonly: "readOnly",
+ selected: "selected",
+ maxlength: "maxLength",
+ selectedIndex: "selectedIndex",
+ defaultValue: "defaultValue",
+ tagName: "tagName",
+ nodeName: "nodeName"
+ }
+});
+
+jQuery.each({
+ parent: "elem.parentNode",
+ parents: "jQuery.dir(elem,'parentNode')",
+ next: "jQuery.nth(elem,2,'nextSibling')",
+ prev: "jQuery.nth(elem,2,'previousSibling')",
+ nextAll: "jQuery.dir(elem,'nextSibling')",
+ prevAll: "jQuery.dir(elem,'previousSibling')",
+ siblings: "jQuery.sibling(elem.parentNode.firstChild,elem)",
+ children: "jQuery.sibling(elem.firstChild)",
+ contents: "jQuery.nodeName(elem,'iframe')?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes)"
+}, function(name, fn){
+ fn = eval("false||function(elem){return " + fn + "}");
+
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = jQuery.map( this, fn );
+
+ if ( selector && typeof selector == "string" )
+ ret = jQuery.multiFilter( selector, ret );
+
+ return this.pushStack( jQuery.unique( ret ) );
+ };
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function(name, original){
+ jQuery.fn[ name ] = function() {
+ var args = arguments;
+
+ return this.each(function(){
+ for ( var i = 0, length = args.length; i < length; i++ )
+ jQuery( args[ i ] )[ original ]( this );
+ });
+ };
+});
+
+jQuery.each({
+ removeAttr: function( name ) {
+ jQuery.attr( this, name, "" );
+ if (this.nodeType == 1)
+ this.removeAttribute( name );
+ },
+
+ addClass: function( classNames ) {
+ jQuery.className.add( this, classNames );
+ },
+
+ removeClass: function( classNames ) {
+ jQuery.className.remove( this, classNames );
+ },
+
+ toggleClass: function( classNames ) {
+ jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+ },
+
+ remove: function( selector ) {
+ if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+ // Prevent memory leaks
+ jQuery( "*", this ).add(this).each(function(){
+ jQuery.event.remove(this);
+ jQuery.removeData(this);
+ });
+ if (this.parentNode)
+ this.parentNode.removeChild( this );
+ }
+ },
+
+ empty: function() {
+ // Remove element nodes and prevent memory leaks
+ jQuery( ">*", this ).remove();
+
+ // Remove any remaining nodes
+ while ( this.firstChild )
+ this.removeChild( this.firstChild );
+ }
+}, function(name, fn){
+ jQuery.fn[ name ] = function(){
+ return this.each( fn, arguments );
+ };
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+ var type = name.toLowerCase();
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ return this[0] == window ?
+ // Opera reports document.body.client[Width/Height] properly in both quirks and standards
+ jQuery.browser.opera && document.body[ "client" + name ] ||
+
+ // Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+ jQuery.browser.safari && window[ "inner" + name ] ||
+
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+
+ // Get document width or height
+ this[0] == document ?
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ Math.max(
+ Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]),
+ Math.max(document.body["offset" + name], document.documentElement["offset" + name])
+ ) :
+
+ // Get or set width or height on the element
+ size == undefined ?
+ // Get width or height on the element
+ (this.length ? jQuery.css( this[0], type ) : null) :
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ this.css( type, size.constructor == String ? size : size + "px" );
+ };
+});
+
+var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+ "(?:[\\w*_-]|\\\\.)" :
+ "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+ quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+ quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+ quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+ expr: {
+ "": "m[2]=='*'||jQuery.nodeName(a,m[2])",
+ "#": "a.getAttribute('id')==m[2]",
+ ":": {
+ // Position Checks
+ lt: "i<m[3]-0",
+ gt: "i>m[3]-0",
+ nth: "m[3]-0==i",
+ eq: "m[3]-0==i",
+ first: "i==0",
+ last: "i==r.length-1",
+ even: "i%2==0",
+ odd: "i%2",
+
+ // Child Checks
+ "first-child": "a.parentNode.getElementsByTagName('*')[0]==a",
+ "last-child": "jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a",
+ "only-child": "!jQuery.nth(a.parentNode.lastChild,2,'previousSibling')",
+
+ // Parent Checks
+ parent: "a.firstChild",
+ empty: "!a.firstChild",
+
+ // Text Check
+ contains: "(a.textContent||a.innerText||jQuery(a).text()||'').indexOf(m[3])>=0",
+
+ // Visibility
+ visible: '"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden"',
+ hidden: '"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden"',
+
+ // Form attributes
+ enabled: "!a.disabled",
+ disabled: "a.disabled",
+ checked: "a.checked",
+ selected: "a.selected||jQuery.attr(a,'selected')",
+
+ // Form elements
+ text: "'text'==a.type",
+ radio: "'radio'==a.type",
+ checkbox: "'checkbox'==a.type",
+ file: "'file'==a.type",
+ password: "'password'==a.type",
+ submit: "'submit'==a.type",
+ image: "'image'==a.type",
+ reset: "'reset'==a.type",
+ button: '"button"==a.type||jQuery.nodeName(a,"button")',
+ input: "/input|select|textarea|button/i.test(a.nodeName)",
+
+ // :has()
+ has: "jQuery.find(m[3],a).length",
+
+ // :header
+ header: "/h\\d/i.test(a.nodeName)",
+
+ // :animated
+ animated: "jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length"
+ }
+ },
+
+ // The regular expressions that power the parsing engine
+ parse: [
+ // Match: [@value='test'], [@foo]
+ /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+ // Match: :contains('foo')
+ /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+ // Match: :even, :last-chlid, #id, .class
+ new RegExp("^([:.#]*)(" + chars + "+)")
+ ],
+
+ multiFilter: function( expr, elems, not ) {
+ var old, cur = [];
+
+ while ( expr && expr != old ) {
+ old = expr;
+ var f = jQuery.filter( expr, elems, not );
+ expr = f.t.replace(/^\s*,\s*/, "" );
+ cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+ }
+
+ return cur;
+ },
+
+ find: function( t, context ) {
+ // Quickly handle non-string expressions
+ if ( typeof t != "string" )
+ return [ t ];
+
+ // check to make sure context is a DOM element or a document
+ if ( context && context.nodeType != 1 && context.nodeType != 9)
+ return [ ];
+
+ // Set the correct context (if none is provided)
+ context = context || document;
+
+ // Initialize the search
+ var ret = [context], done = [], last, nodeName;
+
+ // Continue while a selector expression exists, and while
+ // we're no longer looping upon ourselves
+ while ( t && last != t ) {
+ var r = [];
+ last = t;
+
+ t = jQuery.trim(t);
+
+ var foundToken = false;
+
+ // An attempt at speeding up child selectors that
+ // point to a specific element tag
+ var re = quickChild;
+ var m = re.exec(t);
+
+ if ( m ) {
+ nodeName = m[1].toUpperCase();
+
+ // Perform our own iteration and filter
+ for ( var i = 0; ret[i]; i++ )
+ for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+ if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+ r.push( c );
+
+ ret = r;
+ t = t.replace( re, "" );
+ if ( t.indexOf(" ") == 0 ) continue;
+ foundToken = true;
+ } else {
+ re = /^([>+~])\s*(\w*)/i;
+
+ if ( (m = re.exec(t)) != null ) {
+ r = [];
+
+ var merge = {};
+ nodeName = m[2].toUpperCase();
+ m = m[1];
+
+ for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+ var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+ for ( ; n; n = n.nextSibling )
+ if ( n.nodeType == 1 ) {
+ var id = jQuery.data(n);
+
+ if ( m == "~" && merge[id] ) break;
+
+ if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+ if ( m == "~" ) merge[id] = true;
+ r.push( n );
+ }
+
+ if ( m == "+" ) break;
+ }
+ }
+
+ ret = r;
+
+ // And remove the token
+ t = jQuery.trim( t.replace( re, "" ) );
+ foundToken = true;
+ }
+ }
+
+ // See if there's still an expression, and that we haven't already
+ // matched a token
+ if ( t && !foundToken ) {
+ // Handle multiple expressions
+ if ( !t.indexOf(",") ) {
+ // Clean the result set
+ if ( context == ret[0] ) ret.shift();
+
+ // Merge the result sets
+ done = jQuery.merge( done, ret );
+
+ // Reset the context
+ r = ret = [context];
+
+ // Touch up the selector string
+ t = " " + t.substr(1,t.length);
+
+ } else {
+ // Optimize for the case nodeName#idName
+ var re2 = quickID;
+ var m = re2.exec(t);
+
+ // Re-organize the results, so that they're consistent
+ if ( m ) {
+ m = [ 0, m[2], m[3], m[1] ];
+
+ } else {
+ // Otherwise, do a traditional filter check for
+ // ID, class, and element selectors
+ re2 = quickClass;
+ m = re2.exec(t);
+ }
+
+ m[2] = m[2].replace(/\\/g, "");
+
+ var elem = ret[ret.length-1];
+
+ // Try to do a global search by ID, where we can
+ if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+ // Optimization for HTML document case
+ var oid = elem.getElementById(m[2]);
+
+ // Do a quick check for the existence of the actual ID attribute
+ // to avoid selecting by the name attribute in IE
+ // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+ if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+ oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+ // Do a quick check for node name (where applicable) so
+ // that div#foo searches will be really fast
+ ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+ } else {
+ // We need to find all descendant elements
+ for ( var i = 0; ret[i]; i++ ) {
+ // Grab the tag name being searched for
+ var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+ // Handle IE7 being really dumb about <object>s
+ if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+ tag = "param";
+
+ r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+ }
+
+ // It's faster to filter by class and be done with it
+ if ( m[1] == "." )
+ r = jQuery.classFilter( r, m[2] );
+
+ // Same with ID filtering
+ if ( m[1] == "#" ) {
+ var tmp = [];
+
+ // Try to find the element with the ID
+ for ( var i = 0; r[i]; i++ )
+ if ( r[i].getAttribute("id") == m[2] ) {
+ tmp = [ r[i] ];
+ break;
+ }
+
+ r = tmp;
+ }
+
+ ret = r;
+ }
+
+ t = t.replace( re2, "" );
+ }
+
+ }
+
+ // If a selector string still exists
+ if ( t ) {
+ // Attempt to filter it
+ var val = jQuery.filter(t,r);
+ ret = r = val.r;
+ t = jQuery.trim(val.t);
+ }
+ }
+
+ // An error occurred with the selector;
+ // just return an empty set instead
+ if ( t )
+ ret = [];
+
+ // Remove the root context
+ if ( ret && context == ret[0] )
+ ret.shift();
+
+ // And combine the results
+ done = jQuery.merge( done, ret );
+
+ return done;
+ },
+
+ classFilter: function(r,m,not){
+ m = " " + m + " ";
+ var tmp = [];
+ for ( var i = 0; r[i]; i++ ) {
+ var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+ if ( !not && pass || not && !pass )
+ tmp.push( r[i] );
+ }
+ return tmp;
+ },
+
+ filter: function(t,r,not) {
+ var last;
+
+ // Look for common filter expressions
+ while ( t && t != last ) {
+ last = t;
+
+ var p = jQuery.parse, m;
+
+ for ( var i = 0; p[i]; i++ ) {
+ m = p[i].exec( t );
+
+ if ( m ) {
+ // Remove what we just matched
+ t = t.substring( m[0].length );
+
+ m[2] = m[2].replace(/\\/g, "");
+ break;
+ }
+ }
+
+ if ( !m )
+ break;
+
+ // :not() is a special case that can be optimized by
+ // keeping it out of the expression list
+ if ( m[1] == ":" && m[2] == "not" )
+ // optimize if only one selector found (most common case)
+ r = isSimple.test( m[3] ) ?
+ jQuery.filter(m[3], r, true).r :
+ jQuery( r ).not( m[3] );
+
+ // We can get a big speed boost by filtering by class here
+ else if ( m[1] == "." )
+ r = jQuery.classFilter(r, m[2], not);
+
+ else if ( m[1] == "[" ) {
+ var tmp = [], type = m[3];
+
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+
+ if ( z == null || /href|src|selected/.test(m[2]) )
+ z = jQuery.attr(a,m[2]) || '';
+
+ if ( (type == "" && !!z ||
+ type == "=" && z == m[5] ||
+ type == "!=" && z != m[5] ||
+ type == "^=" && z && !z.indexOf(m[5]) ||
+ type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+ (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+ tmp.push( a );
+ }
+
+ r = tmp;
+
+ // We can get a speed boost by handling nth-child here
+ } else if ( m[1] == ":" && m[2] == "nth-child" ) {
+ var merge = {}, tmp = [],
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+ !/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+ // calculate the numbers (first)n+(last) including if they are negative
+ first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+
+ // loop through all the elements left in the jQuery object
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+ if ( !merge[id] ) {
+ var c = 1;
+
+ for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+ if ( n.nodeType == 1 )
+ n.nodeIndex = c++;
+
+ merge[id] = true;
+ }
+
+ var add = false;
+
+ if ( first == 0 ) {
+ if ( node.nodeIndex == last )
+ add = true;
+ } else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+ add = true;
+
+ if ( add ^ not )
+ tmp.push( node );
+ }
+
+ r = tmp;
+
+ // Otherwise, find the expression to execute
+ } else {
+ var f = jQuery.expr[m[1]];
+ if ( typeof f != "string" )
+ f = jQuery.expr[m[1]][m[2]];
+
+ // Build a custom macro to enclose it
+ f = eval("false||function(a,i){return " + f + "}");
+
+ // Execute it against the current filter
+ r = jQuery.grep( r, f, not );
+ }
+ }
+
+ // Return an array of filtered elements (r)
+ // and the modified expression string (t)
+ return { r: r, t: t };
+ },
+
+ dir: function( elem, dir ){
+ var matched = [];
+ var cur = elem[dir];
+ while ( cur && cur != document ) {
+ if ( cur.nodeType == 1 )
+ matched.push( cur );
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function(cur,result,dir,elem){
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] )
+ if ( cur.nodeType == 1 && ++num == result )
+ break;
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType == 1 && (!elem || n != elem) )
+ r.push( n );
+ }
+
+ return r;
+ }
+});
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function(elem, types, handler, data) {
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ // For whatever reason, IE has trouble passing the window object
+ // around, causing it to be cloned in the process
+ if ( jQuery.browser.msie && elem.setInterval != undefined )
+ elem = window;
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid )
+ handler.guid = this.guid++;
+
+ // if data is passed, bind to handler
+ if( data != undefined ) {
+ // Create temporary function pointer to original handler
+ var fn = handler;
+
+ // Create unique handler function, wrapped around original handler
+ handler = function() {
+ // Pass arguments and context to original handler
+ return fn.apply(this, arguments);
+ };
+
+ // Store data in unique handler
+ handler.data = data;
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ handler.guid = fn.guid;
+ }
+
+ // Init the element's event structure
+ var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+ handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+ // returned undefined or false
+ var val;
+
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ if ( typeof jQuery == "undefined" || jQuery.event.triggered )
+ return val;
+
+ val = jQuery.event.handle.apply(arguments.callee.elem, arguments);
+
+ return val;
+ });
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native
+ // event in IE.
+ handle.elem = elem;
+
+ // Handle multiple events seperated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type) {
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+ handler.type = parts[1];
+
+ // Get the current list of functions bound to this event
+ var handlers = events[type];
+
+ // Init the event handler queue
+ if (!handlers) {
+ handlers = events[type] = {};
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+ // Bind the global event handler to the element
+ if (elem.addEventListener)
+ elem.addEventListener(type, handle, false);
+ else if (elem.attachEvent)
+ elem.attachEvent("on" + type, handle);
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers[handler.guid] = handler;
+
+ // Keep track of which events have been used, for global triggering
+ jQuery.event.global[type] = true;
+ });
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ guid: 1,
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function(elem, types, handler) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ var events = jQuery.data(elem, "events"), ret, index;
+
+ if ( events ) {
+ // Unbind all events for the element
+ if ( types == undefined )
+ for ( var type in events )
+ this.remove( elem, type );
+ else {
+ // types is actually an event object here
+ if ( types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Handle multiple events seperated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type){
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+
+ if ( events[type] ) {
+ // remove the given handler for the given type
+ if ( handler )
+ delete events[type][handler.guid];
+
+ // remove all handlers for the given type
+ else
+ for ( handler in events[type] )
+ // Handle the removal of namespaced events
+ if ( !parts[1] || events[type][handler].type == parts[1] )
+ delete events[type][handler];
+
+ // remove generic event handler if no more handlers exist
+ for ( ret in events[type] ) break;
+ if ( !ret ) {
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+ if (elem.removeEventListener)
+ elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+ else if (elem.detachEvent)
+ elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+ }
+ ret = null;
+ delete events[type];
+ }
+ }
+ });
+ }
+
+ // Remove the expando if it's no longer used
+ for ( ret in events ) break;
+ if ( !ret ) {
+ var handle = jQuery.data( elem, "handle" );
+ if ( handle ) handle.elem = null;
+ jQuery.removeData( elem, "events" );
+ jQuery.removeData( elem, "handle" );
+ }
+ }
+ },
+
+ trigger: function(type, data, elem, donative, extra) {
+ // Clone the incoming data, if any
+ data = jQuery.makeArray(data || []);
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Only trigger if we've ever bound an event for it
+ if ( this.global[type] )
+ jQuery("*").add([window, document]).trigger(type, data);
+
+ // Handle triggering a single element
+ } else {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return undefined;
+
+ var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+ // Check to see if we need to provide a fake event, or not
+ event = !data[0] || !data[0].preventDefault;
+
+ // Pass along a fake event
+ if ( event )
+ data.unshift( this.fix({ type: type, target: elem }) );
+
+ // Enforce the right trigger type
+ data[0].type = type;
+
+ // Trigger the event
+ if ( jQuery.isFunction( jQuery.data(elem, "handle") ) )
+ val = jQuery.data(elem, "handle").apply( elem, data );
+
+ // Handle triggering native .onfoo handlers
+ if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+ val = false;
+
+ // Extra functions don't get the custom event object
+ if ( event )
+ data.shift();
+
+ // Handle triggering of extra function
+ if ( extra && jQuery.isFunction( extra ) ) {
+ // call the extra function and tack the current return value on the end for possible inspection
+ ret = extra.apply( elem, data.concat( val ) );
+ // if anything is returned, give it precedence and have it overwrite the previous value
+ if (ret !== undefined)
+ val = ret;
+ }
+
+ // Trigger the native events (except for clicks on links)
+ if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+ this.triggered = true;
+ try {
+ elem[ type ]();
+ // prevent IE from throwing an error for some hidden elements
+ } catch (e) {}
+ }
+
+ this.triggered = false;
+ }
+
+ return val;
+ },
+
+ handle: function(event) {
+ // returned undefined or false
+ var val;
+
+ // Empty object is for triggered events with no data
+ event = jQuery.event.fix( event || window.event || {} );
+
+ // Namespaced event handlers
+ var parts = event.type.split(".");
+ event.type = parts[0];
+
+ var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 );
+ args.unshift( event );
+
+ for ( var j in handlers ) {
+ var handler = handlers[j];
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ args[0].handler = handler;
+ args[0].data = handler.data;
+
+ // Filter the functions by class
+ if ( !parts[1] || handler.type == parts[1] ) {
+ var ret = handler.apply( this, args );
+
+ if ( val !== false )
+ val = ret;
+
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+
+ // Clean up added properties in IE to prevent memory leak
+ if (jQuery.browser.msie)
+ event.target = event.preventDefault = event.stopPropagation =
+ event.handler = event.data = null;
+
+ return val;
+ },
+
+ fix: function(event) {
+ // store a copy of the original event object
+ // and clone to set read-only properties
+ var originalEvent = event;
+ event = jQuery.extend({}, originalEvent);
+
+ // add preventDefault and stopPropagation since
+ // they will not work on the clone
+ event.preventDefault = function() {
+ // if preventDefault exists run it on the original event
+ if (originalEvent.preventDefault)
+ originalEvent.preventDefault();
+ // otherwise set the returnValue property of the original event to false (IE)
+ originalEvent.returnValue = false;
+ };
+ event.stopPropagation = function() {
+ // if stopPropagation exists run it on the original event
+ if (originalEvent.stopPropagation)
+ originalEvent.stopPropagation();
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ originalEvent.cancelBubble = true;
+ };
+
+ // Fix target property, if necessary
+ if ( !event.target )
+ event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType == 3 )
+ event.target = originalEvent.target.parentNode;
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement )
+ event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var doc = document.documentElement, body = document.body;
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+ event.which = event.charCode || event.keyCode;
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey )
+ event.metaKey = event.ctrlKey;
+
+ // Add which for click: 1 == left; 2 == middle; 3 == right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button )
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+ return event;
+ },
+
+ special: {
+ ready: {
+ setup: function() {
+ // Make sure the ready event is setup
+ bindReady();
+ return;
+ },
+
+ teardown: function() { return; }
+ },
+
+ mouseenter: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseenter
+ arguments[0].type = "mouseenter";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ },
+
+ mouseleave: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseleave
+ arguments[0].type = "mouseleave";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ }
+ }
+};
+
+jQuery.fn.extend({
+ bind: function( type, data, fn ) {
+ return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+ jQuery.event.add( this, type, fn || data, fn && data );
+ });
+ },
+
+ one: function( type, data, fn ) {
+ return this.each(function(){
+ jQuery.event.add( this, type, function(event) {
+ jQuery(this).unbind(event);
+ return (fn || data).apply( this, arguments);
+ }, fn && data);
+ });
+ },
+
+ unbind: function( type, fn ) {
+ return this.each(function(){
+ jQuery.event.remove( this, type, fn );
+ });
+ },
+
+ trigger: function( type, data, fn ) {
+ return this.each(function(){
+ jQuery.event.trigger( type, data, this, true, fn );
+ });
+ },
+
+ triggerHandler: function( type, data, fn ) {
+ if ( this[0] )
+ return jQuery.event.trigger( type, data, this[0], false, fn );
+ return undefined;
+ },
+
+ toggle: function() {
+ // Save reference to arguments for access in closure
+ var args = arguments;
+
+ return this.click(function(event) {
+ // Figure out which function to execute
+ this.lastToggle = 0 == this.lastToggle ? 1 : 0;
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[this.lastToggle].apply( this, arguments ) || false;
+ });
+ },
+
+ hover: function(fnOver, fnOut) {
+ return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+ },
+
+ ready: function(fn) {
+ // Attach the listeners
+ bindReady();
+
+ // If the DOM is already ready
+ if ( jQuery.isReady )
+ // Execute the function immediately
+ fn.call( document, jQuery );
+
+ // Otherwise, remember the function for later
+ else
+ // Add the function to the wait list
+ jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+
+ return this;
+ }
+});
+
+jQuery.extend({
+ isReady: false,
+ readyList: [],
+ // Handle when the DOM is ready
+ ready: function() {
+ // Make sure that the DOM is not already loaded
+ if ( !jQuery.isReady ) {
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If there are functions bound, to execute
+ if ( jQuery.readyList ) {
+ // Execute all of them
+ jQuery.each( jQuery.readyList, function(){
+ this.apply( document );
+ });
+
+ // Reset the list of functions
+ jQuery.readyList = null;
+ }
+
+ // Trigger any bound ready events
+ jQuery(document).triggerHandler("ready");
+ }
+ }
+});
+
+var readyBound = false;
+
+function bindReady(){
+ if ( readyBound ) return;
+ readyBound = true;
+
+ // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
+ if ( document.addEventListener && !jQuery.browser.opera)
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+
+ // If IE is used and is not in a frame
+ // Continually check to see if the document is ready
+ if ( jQuery.browser.msie && window == top ) (function(){
+ if (jQuery.isReady) return;
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch( error ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+
+ if ( jQuery.browser.opera )
+ document.addEventListener( "DOMContentLoaded", function () {
+ if (jQuery.isReady) return;
+ for (var i = 0; i < document.styleSheets.length; i++)
+ if (document.styleSheets[i].disabled) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ }, false);
+
+ if ( jQuery.browser.safari ) {
+ var numStyles;
+ (function(){
+ if (jQuery.isReady) return;
+ if ( document.readyState != "loaded" && document.readyState != "complete" ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ if ( numStyles === undefined )
+ numStyles = jQuery("style, link[rel=stylesheet]").length;
+ if ( document.styleSheets.length != numStyles ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+ }
+
+ // A fallback to window.onload, that will always work
+ jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+ "mousedown,mouseup,mousemove,mouseover,mouseout,cha