c63e599e701b6a94131d32d6aef634a40151424a

Author: AD7six

Date: 2008-11-20 12:43:16 +0100

adding current source. No Vendors added, Requires Text_Diff (in a folder named Text) and Zend Lucene (in Zend/Search/Lucene.php - with associated files)

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[] = '&nbsp;&nbsp;' . 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('<', '&lt;', $contents); + $contents = str_replace('>', '&gt;', $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>&lt;\?php/', '<pre>&lt;?php', $contents); + $contents = preg_replace('/\?&gt;<\/p>/', '?&gt;</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('<', '&lt;', $result['1'][$i]); // ensure escaping + $replaced = str_replace('>', '&gt;', $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('&nbsp;', ' ', $code); + $code= str_replace('&amp;', '&#38;', $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= '&#160;'; + } + + 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 . '&nbsp;x&nbsp;' . $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">&lt;img src="' . $html->url($path) . '" alt="' . $description . '" /&gt;'; + $div .= "<br />&lt;p class='caption'&gt;$description&lt;/p&gt;"; + $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 '&nbsp;|&nbsp;'; +} +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('<', '&lt;', $result['2'][$i]); // ensure escaping + $highlighted = '<pre class="code">' . $result['2'][$i] . '</pre>'; + + $stripStart = false; + if (strpos($result['2'][$i], '&lt;?') === 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">&lt;?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('<?', '&lt;?', $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 (' &middot; ', $languages); + ?></span> + <?php echo ' &nbsp; '; + 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>&copy; <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('&lt;', '<', $result['1'][$i]); + $replaced = str_replace('&gt;', '>', $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('&lt;', '<', $result['1'][$i]); + $replaced = str_replace('&gt;', '>', $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('&nbsp;', '', $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('&nbsp;', '', $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('&lt;', '<', $result['1'][$i]); + $replaced = str_replace('&gt;', '>', $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