Merge branch '2.0-pagination' into 2.0

This commit is contained in:
mark_story 2010-12-31 12:37:53 -05:00
commit a9a1994a7e
18 changed files with 1623 additions and 737 deletions

View file

@ -18,21 +18,71 @@
*/
/**
* PaginatorComponent
* This component is used to handle automatic model data pagination. The primary way to use this
* component is to call the paginate() method. There is a convience wrapper on Controller as well.
*
* This component is used to handle automatic model data pagination
* ### Configuring pagination
*
* You configure pagination using the PaginatorComponent::$settings. This allows you to configure
* the default pagination behavior in general or for a specific model. General settings are used when there
* are no specific model configuration, or the model you are paginating does not have specific settings.
*
* {{{
* $this->Paginator->settings = array(
* 'limit' => 20,
* 'maxLimit' => 100
* );
* }}}
*
* The above settings will be used to paginate any model. You can configure model specific settings by
* keying the settings with the model name.
*
* {{{
* $this->Paginator->settings = array(
* 'Post' => array(
* 'limit' => 20,
* 'maxLimit' => 100
* ),
* 'Comment' => array( ... )
* );
* }}}
*
* This would allow you to have different pagination settings for `Comment` and `Post` models.
*
* @package cake.libs.controller.components
*
*/
class PaginatorComponent extends Component {
/**
* Pagination settings
* Pagination settings. These settings control pagination at a general level.
* You can also define sub arrays for pagination settings for specific models.
*
* - `maxLimit` The maximum limit users can choose to view. Defaults to 100
* - `limit` The initial number of items per page. Defaults to 20.
* - `page` The starting page, defaults to 1.
* - `paramType` What type of parameters you want pagination to use?
* - `named` Use named parameters / routed parameters.
* - `querystring` Use query string parameters.
*
* @var array
*/
public $settings = array();
public $settings = array(
'page' => 1,
'limit' => 20,
'maxLimit' => 100,
'paramType' => 'named'
);
/**
* A list of parameters users are allowed to set using request parameters. Modifying
* this list will allow users to have more influence over pagination,
* be careful with what you permit.
*
* @var array
*/
public $whitelist = array(
'limit', 'sort', 'page', 'direction'
);
/**
* Constructor
@ -41,7 +91,7 @@ class PaginatorComponent extends Component {
* @param array $settings Array of configuration settings.
*/
public function __construct(ComponentCollection $collection, $settings = array()) {
$settings = array_merge(array('page' => 1, 'limit' => 20), (array)$settings);
$settings = array_merge($this->settings, (array)$settings);
$this->Controller = $collection->getController();
parent::__construct($collection, $settings);
}
@ -50,8 +100,9 @@ class PaginatorComponent extends Component {
* Handles automatic pagination of model records.
*
* @param mixed $object Model to paginate (e.g: model instance, or 'Model', or 'Model.InnerModel')
* @param mixed $scope Conditions to use while paginating
* @param array $whitelist List of allowed options for paging
* @param mixed $scope Additional find conditions to use while paginating
* @param array $whitelist List of allowed fields for ordering. This allows you to prevent ordering
* on non-indexed, or undesirable columns.
* @return array Model query results
*/
public function paginate($object = null, $scope = array(), $whitelist = array()) {
@ -60,8 +111,116 @@ class PaginatorComponent extends Component {
$scope = $object;
$object = null;
}
$assoc = null;
$object = $this->_getObject($object);
if (!is_object($object)) {
throw new MissingModelException($object);
}
$options = $this->mergeOptions($object->alias);
$options = $this->validateSort($object, $options, $whitelist);
$options = $this->checkLimit($options);
$conditions = $fields = $order = $limit = $page = $recursive = null;
if (!isset($options['conditions'])) {
$options['conditions'] = array();
}
$type = 'all';
if (isset($options[0])) {
$type = $options[0];
unset($options[0]);
}
extract($options);
if (is_array($scope) && !empty($scope)) {
$conditions = array_merge($conditions, $scope);
} elseif (is_string($scope)) {
$conditions = array($conditions, $scope);
}
if ($recursive === null) {
$recursive = $object->recursive;
}
$extra = array_diff_key($options, compact(
'conditions', 'fields', 'order', 'limit', 'page', 'recursive'
));
if ($type !== 'all') {
$extra['type'] = $type;
}
if ($object->hasMethod('paginateCount')) {
$count = $object->paginateCount($conditions, $recursive, $extra);
} else {
$parameters = compact('conditions');
if ($recursive != $object->recursive) {
$parameters['recursive'] = $recursive;
}
$count = $object->find('count', array_merge($parameters, $extra));
}
$pageCount = intval(ceil($count / $limit));
if ($page === 'last' || $page >= $pageCount) {
$options['page'] = $page = $pageCount;
} elseif (intval($page) < 1) {
$options['page'] = $page = 1;
}
$page = $options['page'] = (int)$page;
if ($object->hasMethod('paginate')) {
$results = $object->paginate(
$conditions, $fields, $order, $limit, $page, $recursive, $extra
);
} else {
$parameters = compact('conditions', 'fields', 'order', 'limit', 'page');
if ($recursive != $object->recursive) {
$parameters['recursive'] = $recursive;
}
$results = $object->find($type, array_merge($parameters, $extra));
}
$defaults = $this->getDefaults($object->alias);
unset($defaults[0]);
$paging = array(
'page' => $page,
'current' => count($results),
'count' => $count,
'prevPage' => ($page > 1),
'nextPage' => ($count > ($page * $limit)),
'pageCount' => $pageCount,
'order' => $order,
'limit' => $limit,
'options' => Set::diff($options, $defaults),
'paramType' => $options['paramType']
);
if (!isset($this->Controller->request['paging'])) {
$this->Controller->request['paging'] = array();
}
$this->Controller->request['paging'] = array_merge(
(array)$this->Controller->request['paging'],
array($object->alias => $paging)
);
if (
!in_array('Paginator', $this->Controller->helpers) &&
!array_key_exists('Paginator', $this->Controller->helpers)
) {
$this->Controller->helpers[] = 'Paginator';
}
return $results;
}
/**
* Get the object pagination will occur on.
*
* @param mixed $object The object you are looking for.
* @return mixed The model object to paginate on.
*/
protected function _getObject($object) {
if (is_string($object)) {
$assoc = null;
if (strpos($object, '.') !== false) {
@ -98,26 +257,71 @@ class PaginatorComponent extends Component {
}
}
}
return $object;
}
if (!is_object($object)) {
throw new MissingModelException($object);
/**
* Merges the various options that Pagination uses.
* Pulls settings together from the following places:
*
* - General pagination settings
* - Model specific settings.
* - Request parameters
*
* The result of this method is the aggregate of all the option sets combined together. You can change
* PaginatorComponent::$whitelist to modify which options/values can be set using request parameters.
*
* @param string $alias Model alias being paginated, if the general settings has a key with this value
* that key's settings will be used for pagination instead of the general ones.
* @return array Array of merged options.
*/
public function mergeOptions($alias) {
$defaults = $this->getDefaults($alias);
switch ($defaults['paramType']) {
case 'named':
$request = $this->Controller->request->params['named'];
break;
case 'querystring':
$request = $this->Controller->request->query;
break;
}
$options = array_merge(
$this->Controller->request->params,
$this->Controller->request->query,
$this->Controller->passedArgs
);
$request = array_intersect_key($request, array_flip($this->whitelist));
return array_merge($defaults, $request);
}
if (isset($this->settings[$object->alias])) {
$defaults = $this->settings[$object->alias];
/**
* Get the default settings for a $model. If there are no settings for a specific model, the general settings
* will be used.
*
* @param string $alias Model name to get default settings for.
* @return array An array of pagination defaults for a model, or the general settings.
*/
public function getDefaults($alias) {
if (isset($this->settings[$alias])) {
$defaults = $this->settings[$alias];
} else {
$defaults = $this->settings;
}
if (isset($options['show'])) {
$options['limit'] = $options['show'];
}
return array_merge(
array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named'),
$defaults
);
}
/**
* Validate that the desired sorting can be performed on the $object. Only fields or
* virtualFields can be sorted on. The direction param will also be sanitized. Lastly
* sort + direction keys will be converted into the model friendly order key.
*
* You can use the whitelist parameter to control which columns/fields are available for sorting.
* This helps prevent users from ordering large result sets on un-indexed values.
*
* @param Model $object The model being paginated.
* @param array $options The pagination options being used for this request.
* @param array $whitelist The list of columns that can be used for sorting. If empty all keys are allowed.
* @return array An array of options with sort + direction removed and replaced with order if possible.
*/
public function validateSort($object, $options, $whitelist = array()) {
if (isset($options['sort'])) {
$direction = null;
if (isset($options['direction'])) {
@ -128,7 +332,14 @@ class PaginatorComponent extends Component {
}
$options['order'] = array($options['sort'] => $direction);
}
if (!empty($whitelist)) {
$field = key($options['order']);
if (!in_array($field, $whitelist)) {
$options['order'] = null;
}
}
if (!empty($options['order']) && is_array($options['order'])) {
$alias = $object->alias ;
$key = $field = key($options['order']);
@ -147,107 +358,22 @@ class PaginatorComponent extends Component {
$options['order'][$alias . '.' . $field] = $value;
}
}
$vars = array('fields', 'order', 'limit', 'page', 'recursive');
$keys = array_keys($options);
$count = count($keys);
return $options;
}
for ($i = 0; $i < $count; $i++) {
if (!in_array($keys[$i], $vars, true)) {
unset($options[$keys[$i]]);
}
if (empty($whitelist) && ($keys[$i] === 'fields' || $keys[$i] === 'recursive')) {
unset($options[$keys[$i]]);
} elseif (!empty($whitelist) && !in_array($keys[$i], $whitelist)) {
unset($options[$keys[$i]]);
}
}
$conditions = $fields = $order = $limit = $page = $recursive = null;
if (!isset($defaults['conditions'])) {
$defaults['conditions'] = array();
}
$type = 'all';
if (isset($defaults[0])) {
$type = $defaults[0];
unset($defaults[0]);
}
$options = array_merge(array('page' => 1, 'limit' => 20), $defaults, $options);
/**
* Check the limit parameter and ensure its within the maxLimit bounds.
*
* @param array $options An array of options with a limit key to be checked.
* @return array An array of options for pagination
*/
public function checkLimit($options) {
$options['limit'] = (int) $options['limit'];
if (empty($options['limit']) || $options['limit'] < 1) {
$options['limit'] = 1;
}
extract($options);
if (is_array($scope) && !empty($scope)) {
$conditions = array_merge($conditions, $scope);
} elseif (is_string($scope)) {
$conditions = array($conditions, $scope);
}
if ($recursive === null) {
$recursive = $object->recursive;
}
$extra = array_diff_key($defaults, compact(
'conditions', 'fields', 'order', 'limit', 'page', 'recursive'
));
if ($type !== 'all') {
$extra['type'] = $type;
}
if (method_exists($object, 'paginateCount')) {
$count = $object->paginateCount($conditions, $recursive, $extra);
} else {
$parameters = compact('conditions');
if ($recursive != $object->recursive) {
$parameters['recursive'] = $recursive;
}
$count = $object->find('count', array_merge($parameters, $extra));
}
$pageCount = intval(ceil($count / $limit));
if ($page === 'last' || $page >= $pageCount) {
$options['page'] = $page = $pageCount;
} elseif (intval($page) < 1) {
$options['page'] = $page = 1;
}
$page = $options['page'] = (integer)$page;
if (method_exists($object, 'paginate')) {
$results = $object->paginate(
$conditions, $fields, $order, $limit, $page, $recursive, $extra
);
} else {
$parameters = compact('conditions', 'fields', 'order', 'limit', 'page');
if ($recursive != $object->recursive) {
$parameters['recursive'] = $recursive;
}
$results = $object->find($type, array_merge($parameters, $extra));
}
$paging = array(
'page' => $page,
'current' => count($results),
'count' => $count,
'prevPage' => ($page > 1),
'nextPage' => ($count > ($page * $limit)),
'pageCount' => $pageCount,
'defaults' => array_merge(array('limit' => 20, 'step' => 1), $defaults),
'options' => $options
);
if (!isset($this->Controller->request['paging'])) {
$this->Controller->request['paging'] = array();
}
$this->Controller->request['paging'] = array_merge(
(array)$this->Controller->request['paging'],
array($object->alias => $paging)
);
if (!in_array('Paginator', $this->Controller->helpers) && !array_key_exists('Paginator', $this->Controller->helpers)) {
$this->Controller->helpers[] = 'Paginator';
}
return $results;
$options['limit'] = min((int)$options['limit'], $options['maxLimit']);
return $options;
}
}

View file

@ -537,7 +537,7 @@ class HttpSocket extends CakeSocket {
if (!method_exists($authClass, 'authentication')) {
throw new SocketException(sprintf(__('The %s do not support authentication.'), $authClass));
}
call_user_func("$authClass::authentication", $this, $this->_auth[$method]);
call_user_func_array("$authClass::authentication", array($this, &$this->_auth[$method]));
}
/**
@ -563,7 +563,7 @@ class HttpSocket extends CakeSocket {
if (!method_exists($authClass, 'proxyAuthentication')) {
throw new SocketException(sprintf(__('The %s do not support proxy authentication.'), $authClass));
}
call_user_func("$authClass::proxyAuthentication", $this, $this->_proxy);
call_user_func_array("$authClass::proxyAuthentication", array($this, &$this->_proxy));
}
/**

View file

@ -42,14 +42,14 @@ class BehaviorCollection extends ObjectCollection {
*
* @var array
*/
private $__methods = array();
protected $_methods = array();
/**
* Keeps a list of all methods which have been mapped with regular expressions
*
* @var array
*/
private $__mappedMethods = array();
protected $_mappedMethods = array();
/**
* Attaches a model object and loads a list of behaviors
@ -128,7 +128,7 @@ class BehaviorCollection extends ObjectCollection {
$this->_loaded[$name]->setup(ClassRegistry::getObject($this->modelName), $config);
foreach ($this->_loaded[$name]->mapMethods as $method => $alias) {
$this->__mappedMethods[$method] = array($alias, $name);
$this->_mappedMethods[$method] = array($name, $alias);
}
$methods = get_class_methods($this->_loaded[$name]);
$parentMethods = array_flip(get_class_methods('ModelBehavior'));
@ -140,11 +140,11 @@ class BehaviorCollection extends ObjectCollection {
foreach ($methods as $m) {
if (!isset($parentMethods[$m])) {
$methodAllowed = (
$m[0] != '_' && !array_key_exists($m, $this->__methods) &&
$m[0] != '_' && !array_key_exists($m, $this->_methods) &&
!in_array($m, $callbacks)
);
if ($methodAllowed) {
$this->__methods[$m] = array($m, $name);
$this->_methods[$m] = array($name, $m);
}
}
}
@ -170,9 +170,9 @@ class BehaviorCollection extends ObjectCollection {
$this->_loaded[$name]->cleanup(ClassRegistry::getObject($this->modelName));
unset($this->_loaded[$name]);
}
foreach ($this->__methods as $m => $callback) {
if (is_array($callback) && $callback[1] == $name) {
unset($this->__methods[$m]);
foreach ($this->_methods as $m => $callback) {
if (is_array($callback) && $callback[0] == $name) {
unset($this->_methods[$m]);
}
}
$this->_enabled = array_values(array_diff($this->_enabled, (array)$name));
@ -190,51 +190,70 @@ class BehaviorCollection extends ObjectCollection {
}
/**
* Dispatches a behavior method
* Dispatches a behavior method. Will call either normal methods or mapped methods.
*
* If a method is not handeled by the BehaviorCollection, and $strict is false, a
* special return of `array('unhandled')` will be returned to signal the method was not found.
*
* @param Model $model The model the method was originally called on.
* @param string $method The method called.
* @param array $params Parameters for the called method.
* @param boolean $strict If methods are not found, trigger an error.
* @return array All methods for all behaviors attached to this object
*/
public function dispatchMethod(&$model, $method, $params = array(), $strict = false) {
$methods = array_keys($this->__methods);
$check = array_flip($methods);
$found = isset($check[$method]);
$call = null;
if ($strict && !$found) {
public function dispatchMethod($model, $method, $params = array(), $strict = false) {
$method = $this->hasMethod($method, true);
if ($strict && empty($method)) {
trigger_error(__("BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior", $method), E_USER_WARNING);
return null;
} elseif ($found) {
$methods = array_combine($methods, array_values($this->__methods));
$call = $methods[$method];
} else {
$count = count($this->__mappedMethods);
$mapped = array_keys($this->__mappedMethods);
for ($i = 0; $i < $count; $i++) {
if (preg_match($mapped[$i] . 'i', $method)) {
$call = $this->__mappedMethods[$mapped[$i]];
array_unshift($params, $method);
break;
}
}
}
if (!empty($call)) {
return call_user_func_array(
array(&$this->_loaded[$call[1]], $call[0]),
array_merge(array(&$model), $params)
);
if (empty($method)) {
return array('unhandled');
}
return array('unhandled');
if (count($method) === 3) {
array_unshift($params, $method[2]);
unset($method[2]);
}
return call_user_func_array(
array($this->_loaded[$method[0]], $method[1]),
array_merge(array(&$model), $params)
);
}
/**
* Gets the method list for attached behaviors, i.e. all public, non-callback methods
* Gets the method list for attached behaviors, i.e. all public, non-callback methods.
* This does not include mappedMethods.
*
* @return array All public methods for all behaviors attached to this collection
*/
public function methods() {
return $this->__methods;
return $this->_methods;
}
/**
* Check to see if a behavior in this collection implements the provided method. Will
* also check mappedMethods.
*
* @param string $method The method to find.
* @param boolean $callback Return the callback for the method.
* @return mixed If $callback is false, a boolean will be returnned, if its true, an array
* containing callback information will be returnned. For mapped methods the array will have 3 elements.
*/
public function hasMethod($method, $callback = false) {
if (isset($this->_methods[$method])) {
return $callback ? $this->_methods[$method] : true;
}
foreach ($this->_mappedMethods as $pattern => $target) {
if (preg_match($pattern . 'i', $method)) {
if ($callback) {
$target[] = $method;
return $target;
}
return true;
}
}
return false;
}
}

View file

@ -1066,6 +1066,23 @@ class Model extends Object {
return false;
}
/**
* Check that a method is callable on a model. This will check both the model's own methods, its
* inherited methods and methods that could be callable through behaviors.
*
* @param string $method The method to be called.
* @return boolean True on method being callable.
*/
public function hasMethod($method) {
if (method_exists($this, $method)) {
return true;
}
if ($this->Behaviors->hasMethod($method)) {
return true;
}
return false;
}
/**
* Returns true if the supplied field is a model Virtual Field
*

View file

@ -39,6 +39,25 @@
*
* Would be called like `$this->Model->doSomething($arg1, $arg2);`.
*
* ### Mapped methods
*
* Behaviors can also define mapped methods. Mapped methods use pattern matching for method invocation. This
* allows you to create methods similar to Model::findAllByXXX methods on your behaviors. Mapped methods need to
* be declared in your behaviors `$mapMethods` array. The method signature for a mapped method is slightly different
* than a normal behavior mixin method.
*
* {{{
* var $mapMethods = array('/do(\w+)/' => 'doSomething');
*
* function doSomething($model, $method, $arg1, $arg2) {
* //do something
* }
* }}}
*
* The above will map every doXXX() method call to the behavior. As you can see, the model is
* still the first parameter, but the called method name will be the 2nd parameter. This allows
* you to munge the method name for additional information, much like Model::findAllByXX.
*
* @package cake.libs.model
* @see Model::$actsAs
* @see BehaviorCollection::load()

View file

@ -164,6 +164,11 @@ class CakeRoute {
$parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed);
$this->_compiledRoute = '#^' . $parsed . '[/]*$#';
$this->keys = $names;
//remove defaults that are also keys. They can cause match failures
foreach ($this->keys as $key) {
unset($this->defaults[$key]);
}
}
/**
@ -259,53 +264,62 @@ class CakeRoute {
//check that all the key names are in the url
$keyNames = array_flip($this->keys);
if (array_intersect_key($keyNames, $url) != $keyNames) {
if (array_intersect_key($keyNames, $url) !== $keyNames) {
return false;
}
$diffUnfiltered = Set::diff($url, $defaults);
$diff = array();
// Missing defaults is a fail.
if (array_diff_key($defaults, $url) !== array()) {
return false;
}
foreach ($diffUnfiltered as $key => $var) {
if ($var === 0 || $var === '0' || !empty($var)) {
$diff[$key] = $var;
$greedyNamed = Router::$named['greedy'];
$allowedNamedParams = Router::$named['rules'];
$named = $pass = $_query = array();
foreach ($url as $key => $value) {
// keys that exist in the defaults and have different values is a match failure.
$defaultExists = array_key_exists($key, $defaults);
if ($defaultExists && $defaults[$key] != $value) {
return false;
} elseif ($defaultExists) {
continue;
}
// If the key is a routed key, its not different yet.
if (array_key_exists($key, $keyNames)) {
continue;
}
// pull out passed args
$numeric = is_numeric($key);
if ($numeric && isset($defaults[$key]) && $defaults[$key] == $value) {
continue;
} elseif ($numeric) {
$pass[] = $value;
unset($url[$key]);
continue;
}
// pull out named params if named params are greedy or a rule exists.
if (
($greedyNamed || isset($allowedNamedParams[$key])) &&
($value !== false && $value !== null)
) {
$named[$key] = $value;
continue;
}
// keys that don't exist are different.
if (!$defaultExists && !empty($value)) {
return false;
}
}
//if a not a greedy route, no extra params are allowed.
if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) {
return false;
}
//remove defaults that are also keys. They can cause match failures
foreach ($this->keys as $key) {
unset($defaults[$key]);
}
$filteredDefaults = array_filter($defaults);
//if the difference between the url diff and defaults contains keys from defaults its not a match
if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) {
return false;
}
$passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames);
list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']);
//remove any pass params, they have numeric indexes, skip any params that are in the defaults
$pass = array();
$i = 0;
while (isset($url[$i])) {
if (!isset($diff[$i])) {
$i++;
continue;
}
$pass[] = $url[$i];
unset($url[$i], $params[$i]);
$i++;
}
//still some left over parameters that weren't named or passed args, bail.
if (!empty($params)) {
if (!$this->_greedy && (!empty($pass) || !empty($named))) {
return false;
}
@ -317,7 +331,7 @@ class CakeRoute {
}
}
}
return $this->_writeUrl(array_merge($url, compact('pass', 'named')));
return $this->_writeUrl(array_merge($url, compact('pass', 'named', '_query')));
}
/**
@ -373,4 +387,21 @@ class CakeRoute {
$out = str_replace('//', '/', $out);
return $out;
}
/**
* Generates a well-formed querystring from $q
*
* Will compose an array or nested array into a proper querystring.
*
* @param mixed $q An array of parameters to compose into a query string.
* @param bool $escape Whether or not to use escaped &
* @return string
*/
public function queryString($q, $escape = false) {
$join = '&';
if ($escape === true) {
$join = '&amp;';
}
return '?' . http_build_query($q, null, $join);
}
}

View file

@ -2,7 +2,9 @@
App::import('Core', 'CakeResponse');
App::import('Core', 'route/CakeRoute');
/**
* Redirect route will perform an immediate redirect
* Redirect route will perform an immediate redirect. Redirect routes
* are useful when you want to have Routing layer redirects occur in your
* application, for when URLs move.
*
* PHP5
*
@ -27,6 +29,25 @@ class RedirectRoute extends CakeRoute {
*/
public $response = null;
/**
* The location to redirect to. Either a string or a cake array url.
*
* @var mixed
*/
public $redirect;
/**
* Constructor
*
* @param string $template Template string with parameter placeholders
* @param array $defaults Array of defaults for the route.
* @param string $params Array of parameters and additional options for the Route
*/
public function __construct($template, $defaults = array(), $options = array()) {
parent::__construct($template, $defaults, $options);
$this->redirect = (array)$defaults;
}
/**
* Parses a string url into an array. Parsed urls will result in an automatic
* redirection
@ -42,22 +63,21 @@ class RedirectRoute extends CakeRoute {
if (!$this->response) {
$this->response = new CakeResponse();
}
$redirect = $this->defaults;
if (count($this->defaults) == 1 && !isset($this->defaults['controller'])) {
$redirect = $this->defaults[0];
$redirect = $this->redirect;
if (count($this->redirect) == 1 && !isset($this->redirect['controller'])) {
$redirect = $this->redirect[0];
}
if (isset($this->options['persist']) && is_array($redirect)) {
$argOptions['context'] = array('action' => $redirect['action'], 'controller' => $redirect['controller']);
$args = Router::getArgs($params['_args_'], $argOptions);
$redirect += $args['pass'];
$redirect += $args['named'];
}
$redirect += Router::getArgs($params['_args_'], $argOptions) + array('url' => array());
$redirect = Router::reverse($redirect);
}
$status = 301;
if (isset($this->options['status']) && ($this->options['status'] >= 300 && $this->options['status'] < 400)) {
$status = $this->options['status'];
}
$this->response->header(array('Location' => Router::url($redirect, true)));
$this->response->statusCode($status);
$this->response->statusCode($status);
$this->response->send();
}

View file

@ -281,7 +281,8 @@ class Router {
*
* `Router::redirect('/home/*', array('controller' => 'posts', 'action' => 'view', array('persist' => true));`
*
* Redirects /home/* to /posts/view and passes the parameters to /posts/view
* Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the
* redirect destination allows you to use other routes to define where a url string should be redirected ot.
*
* `Router::redirect('/posts/*', 'http://google.com', array('status' => 302));`
*
@ -290,7 +291,8 @@ class Router {
* ### Options:
*
* - `status` Sets the HTTP status (default 301)
* - `persist` Passes the params to the redirected route, if it can
* - `persist` Passes the params to the redirected route, if it can. This is useful with greedy routes,
* routes that end in `*` are greedy. As you can remap urls and not loose any passed/named args.
*
* @param string $route A string describing the template of the route
* @param array $url A url to redirect to. Can be a string or a Cake array-based url
@ -310,7 +312,9 @@ class Router {
}
/**
* Specifies what named parameters CakePHP should be parsing. The most common setups are:
* Specifies what named parameters CakePHP should be parsing out of incoming urls. By default
* CakePHP will parse every named parameter out of incoming URLs. However, if you want to take more
* control over how named parameters are parsed you can use one of the following setups:
*
* Do not parse any named parameters:
*
@ -324,7 +328,7 @@ class Router {
*
* {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}}
*
* Parse only the page parameter no mater what.
* Parse only the page parameter no matter what.
*
* {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}}
*
@ -346,15 +350,23 @@ class Router {
* );
* }}}
*
* ### Options
*
* - `greedy` Setting this to true will make Router parse all named params. Setting it to false will
* parse only the connected named params.
* - `default` Set this to true to merge in the default set of named parameters.
* - `reset` Set to true to clear existing rules and start fresh.
* - `separator` Change the string used to separate the key & value in a named parameter. Defaults to `:`
*
* @param array $named A list of named parameters. Key value pairs are accepted where values are
* either regex strings to match, or arrays as seen above.
* @param array $options Allows to control all settings: separator, greedy, reset, default
* @return array
*/
public static function connectNamed($named, $options = array()) {
if (isset($options['argSeparator'])) {
self::$named['separator'] = $options['argSeparator'];
unset($options['argSeparator']);
if (isset($options['separator'])) {
self::$named['separator'] = $options['separator'];
unset($options['separator']);
}
if ($named === true || $named === false) {
@ -901,7 +913,7 @@ class Router {
* @see Router::url()
*/
protected static function _handleNoRoute($url) {
$named = $args = array();
$named = $args = $query = array();
$skip = array_merge(
array('bare', 'action', 'controller', 'plugin', 'prefix'),
self::$_prefixes
@ -912,10 +924,11 @@ class Router {
// Remove this once parsed URL parameters can be inserted into 'pass'
for ($i = 0; $i < $count; $i++) {
$key = $keys[$i];
if (is_numeric($keys[$i])) {
$args[] = $url[$keys[$i]];
$args[] = $url[$key];
} else {
$named[$keys[$i]] = $url[$keys[$i]];
$named[$key] = $url[$key];
}
}
@ -927,7 +940,7 @@ class Router {
}
}
if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) {
if (empty($named) && empty($args) && empty($query) && (!isset($url['action']) || $url['action'] === 'index')) {
$url['action'] = null;
}
@ -950,7 +963,7 @@ class Router {
}
if (!empty($named)) {
foreach ($named as $name => $value) {
foreach ($named as $name => $value) {
if (is_array($value)) {
$flattend = Set::flatten($value, '][');
foreach ($flattend as $namedKey => $namedValue) {
@ -961,6 +974,9 @@ class Router {
}
}
}
if (!empty($query)) {
$output .= Router::queryString($query);
}
return $output;
}
@ -979,7 +995,7 @@ class Router {
if (isset(self::$named['rules'][$param])) {
$rule = self::$named['rules'][$param];
if (Router::matchNamed($param, $val, $rule, compact('controller', 'action'))) {
$named[$param] = $val;
$named[substr($param, 1)] = $val;
unset($params[$param]);
}
}

View file

@ -137,7 +137,6 @@ class Helper extends Object {
*/
public function __construct(View $View, $settings = array()) {
$this->_View = $View;
$this->params = $View->params;
$this->request = $View->request;
if (!empty($this->helpers)) {
$this->_helperMap = ObjectCollection::normalizeObjectArray($this->helpers);

View file

@ -42,7 +42,8 @@ class PaginatorHelper extends AppHelper {
private $__defaultModel = null;
/**
* The class used for 'Ajax' pagination links.
* The class used for 'Ajax' pagination links. Defaults to JsHelper. You should make sure
* that JsHelper is defined as a helper before PaginatorHelper, if you want to customize the JsHelper.
*
* @var string
*/
@ -53,26 +54,30 @@ class PaginatorHelper extends AppHelper {
*
* The values that may be specified are:
*
* - `$options['format']` Format of the counter. Supported formats are 'range' and 'pages'
* - `format` Format of the counter. Supported formats are 'range' and 'pages'
* and custom (default). In the default mode the supplied string is parsed and constants are replaced
* by their actual values.
* Constants: %page%, %pages%, %current%, %count%, %start%, %end% .
* - `$options['separator']` The separator of the actual page and number of pages (default: ' of ').
* - `$options['url']` Url of the action. See Router::url()
* - `$options['url']['sort']` the key that the recordset is sorted.
* - `$options['url']['direction']` Direction of the sorting (default: 'asc').
* - `$options['url']['page']` Page # to display.
* - `$options['model']` The name of the model.
* - `$options['escape']` Defines if the title field for the link should be escaped (default: true).
* - `$options['update']` DOM id of the element updated with the results of the AJAX call.
* placeholders: %page%, %pages%, %current%, %count%, %start%, %end% .
* - `separator` The separator of the actual page and number of pages (default: ' of ').
* - `url` Url of the action. See Router::url()
* - `url['sort']` the key that the recordset is sorted.
* - `url['direction']` Direction of the sorting (default: 'asc').
* - `url['page']` Page number to use in links.
* - `model` The name of the model.
* - `escape` Defines if the title field for the link should be escaped (default: true).
* - `update` DOM id of the element updated with the results of the AJAX call.
* If this key isn't specified Paginator will use plain HTML links.
* - `$options['indicator']` DOM id of the element that will be shown when doing AJAX requests. **Only supported by
* AjaxHelper**
* - `paging['paramType']` The type of parameters to use when creating links. Valid options are
* 'querystring', 'named', and 'route'. See PaginatorComponent::$settings for more information.
* - `convertKeys` - A list of keys in url arrays that should be converted to querysting params
* if paramType == 'querystring'.
*
* @var array
* @access public
*/
public $options = array();
public $options = array(
'convertKeys' => array('page', 'limit', 'sort', 'direction')
);
/**
* Constructor for the helper. Sets up the helper that is used for creating 'AJAX' links.
@ -110,7 +115,6 @@ class PaginatorHelper extends AppHelper {
*/
public function beforeRender($viewFile) {
$this->options['url'] = array_merge($this->request->params['pass'], $this->request->params['named']);
parent::beforeRender($viewFile);
}
@ -160,6 +164,9 @@ class PaginatorHelper extends AppHelper {
);
unset($options[$model]);
}
if (!empty($options['convertKeys'])) {
$options['convertKeys'] = array_merge($this->options['convertKeys'], $options['convertKeys']);
}
$this->options = array_filter(array_merge($this->options, $options));
}
@ -189,7 +196,7 @@ class PaginatorHelper extends AppHelper {
public function sortKey($model = null, $options = array()) {
if (empty($options)) {
$params = $this->params($model);
$options = array_merge($params['defaults'], $params['options']);
$options = $params['options'];
}
if (isset($options['sort']) && !empty($options['sort'])) {
@ -215,7 +222,7 @@ class PaginatorHelper extends AppHelper {
if (empty($options)) {
$params = $this->params($model);
$options = array_merge($params['defaults'], $params['options']);
$options = $params['options'];
}
if (isset($options['direction'])) {
@ -246,6 +253,10 @@ class PaginatorHelper extends AppHelper {
* @return string A "previous" link or $disabledTitle text if the link is disabled.
*/
public function prev($title = '<< Previous', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
$defaults = array(
'rel' => 'prev'
);
$options = array_merge($defaults, (array)$options);
return $this->__pagingLink('Prev', $title, $options, $disabledTitle, $disabledOptions);
}
@ -265,6 +276,10 @@ class PaginatorHelper extends AppHelper {
* @return string A "next" link or or $disabledTitle text if the link is disabled.
*/
public function next($title = 'Next >>', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
$defaults = array(
'rel' => 'next'
);
$options = array_merge($defaults, (array)$options);
return $this->__pagingLink('Next', $title, $options, $disabledTitle, $disabledOptions);
}
@ -348,11 +363,11 @@ class PaginatorHelper extends AppHelper {
$url = array_merge((array)$options['url'], (array)$url);
unset($options['url']);
}
unset($options['convertKeys']);
$url = $this->url($url, true, $model);
$obj = isset($options['update']) ? $this->_ajaxHelperClass : 'Html';
$url = array_merge(array('page' => $this->current($model)), $url);
$url = array_merge(Set::filter($url, true), array_intersect_key($url, array('plugin' => true)));
return $this->{$obj}->link($title, $url, $options);
}
@ -366,9 +381,7 @@ class PaginatorHelper extends AppHelper {
*/
public function url($options = array(), $asArray = false, $model = null) {
$paging = $this->params($model);
$url = array_merge(array_filter(Set::diff(array_merge(
$paging['defaults'], $paging['options']), $paging['defaults'])), $options
);
$url = array_merge(array_filter($paging['options']), $options);
if (isset($url['order'])) {
$sort = $direction = null;
@ -378,6 +391,7 @@ class PaginatorHelper extends AppHelper {
unset($url['order']);
$url = array_merge($url, compact('sort', 'direction'));
}
$url = $this->_convertUrlKeys($url, $paging['paramType']);
if ($asArray) {
return $url;
@ -385,6 +399,28 @@ class PaginatorHelper extends AppHelper {
return parent::url($url);
}
/**
* Converts the keys being used into the format set by options.paramType
*
* @param array $url Array of url params to convert
* @return converted url params.
*/
protected function _convertUrlKeys($url, $type) {
if ($type == 'named') {
return $url;
}
if (!isset($url['?'])) {
$url['?'] = array();
}
foreach ($this->options['convertKeys'] as $key) {
if (isset($url[$key])) {
$url['?'][$key] = $url[$key];
unset($url[$key]);
}
}
return $url;
}
/**
* Protected method for generating prev/next links
*
@ -419,6 +455,7 @@ class PaginatorHelper extends AppHelper {
if ($this->{$check}($model)) {
return $this->Html->tag($tag, $this->link($title, $url, array_merge($options, compact('escape', 'class'))));
} else {
unset($options['rel']);
return $this->Html->tag($tag, $title, array_merge($options, compact('escape', 'class')));
}
}
@ -526,9 +563,9 @@ class PaginatorHelper extends AppHelper {
}
$start = 0;
if ($paging['count'] >= 1) {
$start = (($paging['page'] - 1) * $paging['options']['limit']) + 1;
$start = (($paging['page'] - 1) * $paging['limit']) + 1;
}
$end = $start + $paging['options']['limit'] - 1;
$end = $start + $paging['limit'] - 1;
if ($paging['count'] < $end) {
$end = $paging['count'];
}
@ -566,7 +603,12 @@ class PaginatorHelper extends AppHelper {
/**
* Returns a set of numbers for the paged result set
* uses a modulus to decide how many numbers to show on each side of the current page (default: 8)
* uses a modulus to decide how many numbers to show on each side of the current page (default: 8).
*
* `$this->Paginator->numbers(array('first' => 2, 'last' => 2));`
*
* Using the first and last options you can create links to the beginning and end of the page set.
*
*
* ### Options
*
@ -577,9 +619,9 @@ class PaginatorHelper extends AppHelper {
* - `separator` Separator content defaults to ' | '
* - `tag` The tag to wrap links in, defaults to 'span'
* - `first` Whether you want first links generated, set to an integer to define the number of 'first'
* links to generate
* links to generate.
* - `last` Whether you want last links generated, set to an integer to define the number of 'last'
* links to generate
* links to generate.
* - `ellipsis` Ellipsis content, defaults to '...'
*
* @param mixed $options Options for the numbers, (before, after, model, modulus, separator)
@ -689,32 +731,42 @@ class PaginatorHelper extends AppHelper {
}
/**
* Returns a first or set of numbers for the first pages
* Returns a first or set of numbers for the first pages.
*
* `echo $this->Paginator->first('< first');`
*
* Creates a single link for the first page. Will output nothing if you are on the first page.
*
* `echo $this->Paginator->first(3);`
*
* Will create links for the first 3 pages, once you get to the third or greater page. Prior to that
* nothing will be output.
*
* ### Options:
*
* - `tag` The tag wrapping tag you want to use, defaults to 'span'
* - `before` Content to insert before the link/tag
* - `after` Content to insert after the link/tag
* - `model` The model to use defaults to PaginatorHelper::defaultModel()
* - `separator` Content between the generated links, defaults to ' | '
* - `ellipsis` Content for ellipsis, defaults to '...'
*
* @param mixed $first if string use as label for the link, if numeric print page numbers
* @param mixed $options
* @param mixed $first if string use as label for the link. If numeric, the number of page links
* you want at the beginning of the range.
* @param mixed $options An array of options.
* @return string numbers string.
*/
public function first($first = '<< first', $options = array()) {
$options = array_merge(
array(
'tag' => 'span',
'after'=> null,
'after' => null,
'model' => $this->defaultModel(),
'separator' => ' | ',
'ellipsis' => '...',
'ellipsis' => '...'
),
(array)$options);
$params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
$params = array_merge(array('page' => 1), (array)$this->params($options['model']));
unset($options['model']);
if ($params['pageCount'] <= 1) {
@ -725,7 +777,7 @@ class PaginatorHelper extends AppHelper {
$out = '';
if (is_int($first) && $params['page'] > $first) {
if (is_int($first) && $params['page'] >= $first) {
if ($after === null) {
$after = $ellipsis;
}
@ -736,7 +788,8 @@ class PaginatorHelper extends AppHelper {
}
}
$out .= $after;
} elseif ($params['page'] > 1) {
} elseif ($params['page'] > 1 && is_string($first)) {
$options += array('rel' => 'first');
$out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options))
. $after;
}
@ -744,7 +797,15 @@ class PaginatorHelper extends AppHelper {
}
/**
* Returns a last or set of numbers for the last pages
* Returns a last or set of numbers for the last pages.
*
* `echo $this->Paginator->last('last >');`
*
* Creates a single link for the last page. Will output nothing if you are on the last page.
*
* `echo $this->Paginator->last(3);`
*
* Will create links for the last 3 pages. Once you enter the page range, no output will be created.
*
* ### Options:
*
@ -782,7 +843,7 @@ class PaginatorHelper extends AppHelper {
$out = '';
$lower = $params['pageCount'] - $last + 1;
if (is_int($last) && $params['page'] < $lower) {
if (is_int($last) && $params['page'] <= $lower) {
if ($before === null) {
$before = $ellipsis;
}
@ -793,7 +854,8 @@ class PaginatorHelper extends AppHelper {
}
}
$out = $before . $out;
} elseif ($params['page'] < $params['pageCount']) {
} elseif ($params['page'] < $params['pageCount'] && is_string($last)) {
$options += array('rel' => 'last');
$out = $before . $this->Html->tag(
$tag, $this->link($last, array('page' => $params['pageCount']), $options
));

View file

@ -19,6 +19,7 @@
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::import('Controller', 'Controller', false);
App::import('Component', 'Paginator');
App::import('Core', array('CakeRequest', 'CakeResponse'));
/**
@ -205,6 +206,22 @@ class PaginatorTest extends CakeTestCase {
*/
public $fixtures = array('core.post', 'core.comment');
/**
* setup
*
* @return void
*/
function setUp() {
parent::setUp();
$this->request = new CakeRequest('controller_posts/index');
$this->request->params['pass'] = $this->request->params['named'] = array();
$this->Controller = new Controller($this->request);
$this->Paginator = new PaginatorComponent($this->getMock('ComponentCollection'), array());
$this->Paginator->Controller = $this->Controller;
$this->Controller->Post = $this->getMock('Model');
$this->Controller->Post->alias = 'Post';
}
/**
* testPaginate method
*
@ -212,13 +229,10 @@ class PaginatorTest extends CakeTestCase {
* @return void
*/
function testPaginate() {
$request = new CakeRequest('controller_posts/index');
$request->params['pass'] = $request->params['named'] = array();
$Controller = new PaginatorTestController($request);
$Controller = new PaginatorTestController($this->request);
$Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment');
$Controller->passedArgs[] = '1';
$Controller->params['url'] = array();
$Controller->request->params['pass'] = array('1');
$Controller->request->query = array();
$Controller->constructClasses();
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
@ -233,67 +247,95 @@ class PaginatorTest extends CakeTestCase {
$results = Set::extract($Controller->Paginator->paginate(), '{n}.PaginatorControllerPost.id');
$this->assertEqual($results, array(1, 2, 3));
$Controller->passedArgs = array('page' => '-1');
$Controller->request->params['named'] = array('page' => '-1');
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertEqual($results, array(1, 2, 3));
$Controller->passedArgs = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'asc');
$Controller->request->params['named'] = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'asc');
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertEqual($results, array(1, 2, 3));
$Controller->passedArgs = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'desc');
$Controller->request->params['named'] = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'desc');
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertEqual($results, array(3, 2, 1));
$Controller->passedArgs = array('sort' => 'id', 'direction' => 'desc');
$Controller->request->params['named'] = array('sort' => 'id', 'direction' => 'desc');
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertEqual($results, array(3, 2, 1));
$Controller->passedArgs = array('sort' => 'NotExisting.field', 'direction' => 'desc');
$Controller->request->params['named'] = array('sort' => 'NotExisting.field', 'direction' => 'desc');
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'Invalid field in query %s');
$this->assertEqual($results, array(1, 2, 3));
$Controller->passedArgs = array('sort' => 'PaginatorControllerPost.author_id', 'direction' => 'allYourBase');
$Controller->request->params['named'] = array(
'sort' => 'PaginatorControllerPost.author_id', 'direction' => 'allYourBase'
);
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
$this->assertEqual($Controller->PaginatorControllerPost->lastQuery['order'][0], array('PaginatorControllerPost.author_id' => 'asc'));
$this->assertEqual($results, array(1, 3, 2));
$Controller->passedArgs = array('page' => '1 " onclick="alert(\'xss\');">');
$Controller->Paginator->settings = array('limit' => 1);
$Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'XSS exploit opened %s');
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['options']['page'], 1, 'XSS exploit opened %s');
$Controller->passedArgs = array();
$Controller->Paginator->settings = array('limit' => 0);
$Controller->request->params['named'] = array();
$Controller->Paginator->settings = array('limit' => 0, 'maxLimit' => 10, 'paramType' => 'named');
$Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['prevPage'], false);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true);
$Controller->passedArgs = array();
$Controller->Paginator->settings = array('limit' => 'garbage!');
$Controller->request->params['named'] = array();
$Controller->Paginator->settings = array('limit' => 'garbage!', 'maxLimit' => 10, 'paramType' => 'named');
$Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['prevPage'], false);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true);
$Controller->passedArgs = array();
$Controller->Paginator->settings = array('limit' => '-1');
$Controller->request->params['named'] = array();
$Controller->Paginator->settings = array('limit' => '-1', 'maxLimit' => 10, 'paramType' => 'named');
$Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['limit'], 1);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['prevPage'], false);
$this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true);
}
/**
* Test that non-numeric values are rejected for page, and limit
*
* @return void
*/
function testPageParamCasting() {
$this->Controller->Post->expects($this->at(0))
->method('hasMethod')
->with('paginateCount')
->will($this->returnValue(false));
$this->Controller->Post->expects($this->at(1))
->method('find')
->will($this->returnValue(2));
$this->Controller->Post->expects($this->at(2))
->method('hasMethod')
->with('paginate')
->will($this->returnValue(false));
$this->Controller->Post->expects($this->at(3))
->method('find')
->will($this->returnValue(array('stuff')));
$this->request->params['named'] = array('page' => '1 " onclick="alert(\'xss\');">');
$this->Paginator->settings = array('limit' => 1, 'maxLimit' => 10, 'paramType' => 'named');
$this->Paginator->paginate('Post');
$this->assertSame(1, $this->request->params['paging']['Post']['page'], 'XSS exploit opened');
}
/**
* testPaginateExtraParams method
*
@ -301,120 +343,118 @@ class PaginatorTest extends CakeTestCase {
* @return void
*/
function testPaginateExtraParams() {
$request = new CakeRequest('controller_posts/index');
$request->params['pass'] = $request->params['named'] = array();
$Controller = new PaginatorTestController($request);
$Controller = new PaginatorTestController($this->request);
$Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment');
$Controller->passedArgs[] = '1';
$Controller->request->params['pass'] = array('1');
$Controller->params['url'] = array();
$Controller->constructClasses();
$Controller->passedArgs = array('page' => '-1', 'contain' => array('PaginatorControllerComment'));
$Controller->request->params['named'] = array('page' => '-1', 'contain' => array('PaginatorControllerComment'));
$result = $Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(1, 2, 3));
$this->assertTrue(!isset($Controller->PaginatorControllerPost->lastQuery['contain']));
$Controller->passedArgs = array('page' => '-1');
$Controller->Paginator->settings = array('PaginatorControllerPost' => array('contain' => array('PaginatorControllerComment')));
$Controller->request->params['named'] = array('page' => '-1');
$Controller->Paginator->settings = array(
'PaginatorControllerPost' => array(
'contain' => array('PaginatorControllerComment'),
'maxLimit' => 10,
'paramType' => 'named'
),
);
$result = $Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1);
$this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(1, 2, 3));
$this->assertTrue(isset($Controller->PaginatorControllerPost->lastQuery['contain']));
$Controller->Paginator->settings = array('PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title')));
$Controller->Paginator->settings = array(
'PaginatorControllerPost' => array(
'popular', 'fields' => array('id', 'title'), 'maxLimit' => 10, 'paramType' => 'named'
),
);
$result = $Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3));
$this->assertEqual($Controller->PaginatorControllerPost->lastQuery['conditions'], array('PaginatorControllerPost.id > ' => '1'));
$Controller->passedArgs = array('limit' => 12);
$Controller->Paginator->settings = array('limit' => 30);
$Controller->request->params['named'] = array('limit' => 12);
$Controller->Paginator->settings = array('limit' => 30, 'maxLimit' => 100, 'paramType' => 'named');
$result = $Controller->Paginator->paginate('PaginatorControllerPost');
$paging = $Controller->params['paging']['PaginatorControllerPost'];
$this->assertEqual($Controller->PaginatorControllerPost->lastQuery['limit'], 12);
$this->assertEqual($paging['options']['limit'], 12);
$Controller = new PaginatorTestController($request);
$Controller = new PaginatorTestController($this->request);
$Controller->uses = array('ControllerPaginateModel');
$Controller->params['url'] = array();
$Controller->request->query = array();
$Controller->constructClasses();
$Controller->Paginator->settings = array(
'ControllerPaginateModel' => array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id')
'ControllerPaginateModel' => array(
'contain' => array('ControllerPaginateModel'),
'group' => 'Comment.author_id',
'maxLimit' => 10,
'paramType' => 'named'
)
);
$result = $Controller->Paginator->paginate('ControllerPaginateModel');
$expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id');
$expected = array(
'contain' => array('ControllerPaginateModel'),
'group' => 'Comment.author_id',
'maxLimit' => 10,
'paramType' => 'named'
);
$this->assertEqual($Controller->ControllerPaginateModel->extra, $expected);
$this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected);
$Controller->Paginator->settings = array(
'ControllerPaginateModel' => array('foo', 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id')
'ControllerPaginateModel' => array(
'foo', 'contain' => array('ControllerPaginateModel'),
'group' => 'Comment.author_id',
'maxLimit' => 10,
'paramType' => 'named'
)
);
$Controller->Paginator->paginate('ControllerPaginateModel');
$expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'type' => 'foo');
$expected = array(
'contain' => array('ControllerPaginateModel'),
'group' => 'Comment.author_id',
'type' => 'foo',
'maxLimit' => 10,
'paramType' => 'named'
);
$this->assertEqual($Controller->ControllerPaginateModel->extra, $expected);
$this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected);
}
/**
* testPaginatePassedArgs method
*
* @return void
*/
public function testPaginatePassedArgs() {
$request = new CakeRequest('controller_posts/index');
$request->params['pass'] = $request->params['named'] = array();
$Controller = new PaginatorTestController($request);
$Controller->uses = array('PaginatorControllerPost');
$Controller->passedArgs[] = array('1', '2', '3');
$Controller->params['url'] = array();
$Controller->constructClasses();
$Controller->Paginator->settings = array(
'fields' => array(),
'order' => '',
'limit' => 5,
'page' => 1,
'recursive' => -1
);
$conditions = array();
$Controller->Paginator->paginate('PaginatorControllerPost',$conditions);
$expected = array(
'fields' => array(),
'order' => '',
'limit' => 5,
'page' => 1,
'recursive' => -1,
'conditions' => array()
);
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options'],$expected);
}
/**
* Test that special paginate types are called and that the type param doesn't leak out into defaults or options.
*
* @return void
*/
function testPaginateSpecialType() {
$request = new CakeRequest('controller_posts/index');
$request->params['pass'] = $request->params['named'] = array();
$Controller = new PaginatorTestController($request);
$Controller = new PaginatorTestController($this->request);
$Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment');
$Controller->passedArgs[] = '1';
$Controller->params['url'] = array();
$Controller->constructClasses();
$Controller->Paginator->settings = array('PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title')));
$Controller->Paginator->settings = array(
'PaginatorControllerPost' => array(
'popular',
'fields' => array('id', 'title'),
'maxLimit' => 10,
'paramType' => 'named'
)
);
$result = $Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3));
$this->assertEqual($Controller->PaginatorControllerPost->lastQuery['conditions'], array('PaginatorControllerPost.id > ' => '1'));
$this->assertFalse(isset($Controller->params['paging']['PaginatorControllerPost']['defaults'][0]));
$this->assertEqual(
$Controller->PaginatorControllerPost->lastQuery['conditions'],
array('PaginatorControllerPost.id > ' => '1')
);
$this->assertFalse(isset($Controller->params['paging']['PaginatorControllerPost']['options'][0]));
}
@ -425,17 +465,17 @@ class PaginatorTest extends CakeTestCase {
* @return void
*/
function testDefaultPaginateParams() {
$request = new CakeRequest('controller_posts/index');
$request->params['pass'] = $request->params['named'] = array();
$Controller = new PaginatorTestController($request);
$Controller = new PaginatorTestController($this->request);
$Controller->modelClass = 'PaginatorControllerPost';
$Controller->params['url'] = array();
$Controller->constructClasses();
$Controller->Paginator->settings = array('order' => 'PaginatorControllerPost.id DESC');
$Controller->Paginator->settings = array(
'order' => 'PaginatorControllerPost.id DESC',
'maxLimit' => 10,
'paramType' => 'named'
);
$results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['defaults']['order'], 'PaginatorControllerPost.id DESC');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['order'], 'PaginatorControllerPost.id DESC');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['order'], 'PaginatorControllerPost.id DESC');
$this->assertEqual($results, array(3, 2, 1));
}
@ -445,10 +485,7 @@ class PaginatorTest extends CakeTestCase {
* @return void
*/
function testPaginateOrderVirtualField() {
$request = new CakeRequest('controller_posts/index');
$request->params['pass'] = $request->params['named'] = array();
$Controller = new PaginatorTestController($request);
$Controller = new PaginatorTestController($this->request);
$Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment');
$Controller->params['url'] = array();
$Controller->constructClasses();
@ -458,12 +495,14 @@ class PaginatorTest extends CakeTestCase {
$Controller->Paginator->settings = array(
'fields' => array('id', 'title', 'offset_test'),
'order' => array('offset_test' => 'DESC')
'order' => array('offset_test' => 'DESC'),
'maxLimit' => 10,
'paramType' => 'named'
);
$result = $Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.offset_test'), array(4, 3, 2));
$Controller->passedArgs = array('sort' => 'offset_test', 'direction' => 'asc');
$Controller->request->params['named'] = array('sort' => 'offset_test', 'direction' => 'asc');
$result = $Controller->Paginator->paginate('PaginatorControllerPost');
$this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.offset_test'), array(2, 3, 4));
}
@ -474,11 +513,252 @@ class PaginatorTest extends CakeTestCase {
* @expectedException MissingModelException
*/
function testPaginateMissingModel() {
$request = new CakeRequest('controller_posts/index');
$request->params['pass'] = $request->params['named'] = array();
$Controller = new PaginatorTestController($request);
$Controller = new PaginatorTestController($this->request);
$Controller->constructClasses();
$Controller->Paginator->paginate('MissingModel');
$Controller->Paginator->paginate('MissingModel');
}
/**
* test that option merging prefers specific models
*
* @return void
*/
function testMergeOptionsModelSpecific() {
$this->Paginator->settings = array(
'page' => 1,
'limit' => 20,
'maxLimit' => 100,
'paramType' => 'named',
'Post' => array(
'page' => 1,
'limit' => 10,
'maxLimit' => 50,
'paramType' => 'named',
)
);
$result = $this->Paginator->mergeOptions('Silly');
$this->assertEquals($this->Paginator->settings, $result);
$result = $this->Paginator->mergeOptions('Post');
$expected = array('page' => 1, 'limit' => 10, 'paramType' => 'named', 'maxLimit' => 50);
$this->assertEquals($expected, $result);
}
/**
* test mergeOptions with named params.
*
* @return void
*/
function testMergeOptionsNamedParams() {
$this->request->params['named'] = array(
'page' => 10,
'limit' => 10
);
$this->Paginator->settings = array(
'page' => 1,
'limit' => 20,
'maxLimit' => 100,
'paramType' => 'named',
);
$result = $this->Paginator->mergeOptions('Post');
$expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named');
$this->assertEquals($expected, $result);
}
/**
* test merging options from the querystring.
*
* @return void
*/
function testMergeOptionsQueryString() {
$this->request->params['named'] = array(
'page' => 10,
'limit' => 10
);
$this->request->query = array(
'page' => 99,
'limit' => 75
);
$this->Paginator->settings = array(
'page' => 1,
'limit' => 20,
'maxLimit' => 100,
'paramType' => 'querystring',
);
$result = $this->Paginator->mergeOptions('Post');
$expected = array('page' => 99, 'limit' => 75, 'maxLimit' => 100, 'paramType' => 'querystring');
$this->assertEquals($expected, $result);
}
/**
* test that the default whitelist doesn't let people screw with things they should not be allowed to.
*
* @return void
*/
function testMergeOptionsDefaultWhiteList() {
$this->request->params['named'] = array(
'page' => 10,
'limit' => 10,
'fields' => array('bad.stuff'),
'recursive' => 1000,
'conditions' => array('bad.stuff'),
'contain' => array('bad')
);
$this->Paginator->settings = array(
'page' => 1,
'limit' => 20,
'maxLimit' => 100,
'paramType' => 'named',
);
$result = $this->Paginator->mergeOptions('Post');
$expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named');
$this->assertEquals($expected, $result);
}
/**
* test that modifying the whitelist works.
*
* @return void
*/
function testMergeOptionsExtraWhitelist() {
$this->request->params['named'] = array(
'page' => 10,
'limit' => 10,
'fields' => array('bad.stuff'),
'recursive' => 1000,
'conditions' => array('bad.stuff'),
'contain' => array('bad')
);
$this->Paginator->settings = array(
'page' => 1,
'limit' => 20,
'maxLimit' => 100,
'paramType' => 'named',
);
$this->Paginator->whitelist[] = 'fields';
$result = $this->Paginator->mergeOptions('Post');
$expected = array(
'page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'fields' => array('bad.stuff')
);
$this->assertEquals($expected, $result);
}
/**
* test that invalid directions are ignored.
*
* @return void
*/
function testValidateSortInvalidDirection() {
$model = $this->getMock('Model');
$model->alias = 'model';
$model->expects($this->any())->method('hasField')->will($this->returnValue(true));
$options = array('sort' => 'something', 'direction' => 'boogers');
$result = $this->Paginator->validateSort($model, $options);
$this->assertEquals('asc', $result['order']['model.something']);
}
/**
* test that fields not in whitelist won't be part of order conditions.
*
* @return void
*/
function testValidateSortWhitelistFailure() {
$model = $this->getMock('Model');
$model->alias = 'model';
$model->expects($this->any())->method('hasField')->will($this->returnValue(true));
$options = array('sort' => 'body', 'direction' => 'asc');
$result = $this->Paginator->validateSort($model, $options, array('title', 'id'));
$this->assertNull($result['order']);
}
/**
* test that virtual fields work.
*
* @return void
*/
function testValidateSortVirtualField() {
$model = $this->getMock('Model');
$model->alias = 'model';
$model->expects($this->at(0))
->method('hasField')
->with('something')
->will($this->returnValue(false));
$model->expects($this->at(1))
->method('hasField')
->with('something', true)
->will($this->returnValue(true));
$options = array('sort' => 'something', 'direction' => 'desc');
$result = $this->Paginator->validateSort($model, $options);
$this->assertEquals('desc', $result['order']['something']);
}
/**
* test that maxLimit is respected
*
* @return void
*/
function testCheckLimit() {
$result = $this->Paginator->checkLimit(array('limit' => 1000000, 'maxLimit' => 100));
$this->assertEquals(100, $result['limit']);
$result = $this->Paginator->checkLimit(array('limit' => 'sheep!', 'maxLimit' => 100));
$this->assertEquals(1, $result['limit']);
$result = $this->Paginator->checkLimit(array('limit' => '-1', 'maxLimit' => 100));
$this->assertEquals(1, $result['limit']);
$result = $this->Paginator->checkLimit(array('limit' => null, 'maxLimit' => 100));
$this->assertEquals(1, $result['limit']);
$result = $this->Paginator->checkLimit(array('limit' => 0, 'maxLimit' => 100));
$this->assertEquals(1, $result['limit']);
}
/**
* testPaginateMaxLimit
*
* @return void
* @access public
*/
function testPaginateMaxLimit() {
$Controller = new Controller($this->request);
$Controller->uses = array('PaginatorControllerPost', 'ControllerComment');
$Controller->passedArgs[] = '1';
$Controller->params['url'] = array();
$Controller->constructClasses();
$Controller->request->params['named'] = array(
'contain' => array('ControllerComment'), 'limit' => '1000'
);
$result = $Controller->paginate('PaginatorControllerPost');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100);
$Controller->request->params['named'] = array(
'contain' => array('ControllerComment'), 'limit' => '1000', 'maxLimit' => 1000
);
$result = $Controller->paginate('PaginatorControllerPost');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100);
$Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '10');
$result = $Controller->paginate('PaginatorControllerPost');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 10);
$Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '1000');
$Controller->paginate = array('maxLimit' => 2000, 'paramType' => 'named');
$result = $Controller->paginate('PaginatorControllerPost');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 1000);
$Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '5000');
$result = $Controller->paginate('PaginatorControllerPost');
$this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 2000);
}
}

View file

@ -1255,7 +1255,8 @@ class ControllerTest extends CakeTestCase {
$Controller->passedArgs[] = '1';
$Controller->params['url'] = array();
$Controller->constructClasses();
$this->assertEqual($Controller->paginate, array('page' => 1, 'limit' => 20));
$expected = array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named');
$this->assertEqual($Controller->paginate, $expected);
$results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id');
$this->assertEqual($results, array(1, 2, 3));

View file

@ -340,7 +340,16 @@ class TestBehavior extends ModelBehavior {
*
* @package cake.tests.cases.libs.model
*/
class Test2Behavior extends TestBehavior{
class Test2Behavior extends TestBehavior {
public $mapMethods = array('/mappingRobot(\w+)/' => 'mapped');
function resolveMethod($model, $stuff) {
}
function mapped($model, $method, $query) {
}
}
/**
@ -1026,34 +1035,6 @@ class BehaviorCollectionTest extends CakeTestCase {
$this->assertTrue($Apple->testData('one', 'two', 'three', 'four', 'five', 'six'));
}
/**
* testBehaviorTrigger method
*
* @access public
* @return void
*/
function testBehaviorTrigger() {
$Apple = new Apple();
$Apple->Behaviors->attach('Test');
$Apple->Behaviors->attach('Test2');
$Apple->Behaviors->attach('Test3');
$Apple->beforeTestResult = array();
$Apple->Behaviors->trigger('beforeTest', array(&$Apple));
$expected = array('testbehavior', 'test2behavior', 'test3behavior');
$this->assertIdentical($Apple->beforeTestResult, $expected);
$Apple->beforeTestResult = array();
$Apple->Behaviors->trigger('beforeTest', array(&$Apple), array('break' => true, 'breakOn' => 'test2behavior'));
$expected = array('testbehavior', 'test2behavior');
$this->assertIdentical($Apple->beforeTestResult, $expected);
$Apple->beforeTestResult = array();
$Apple->Behaviors->trigger('beforeTest', array($Apple), array('break' => true, 'breakOn' => array('test2behavior', 'test3behavior')));
$expected = array('testbehavior', 'test2behavior');
$this->assertIdentical($Apple->beforeTestResult, $expected);
}
/**
* undocumented function
*
@ -1116,4 +1097,59 @@ class BehaviorCollectionTest extends CakeTestCase {
$Sample->Behaviors->trigger('beforeTest', array(&$Sample));
}
/**
* test that hasMethod works with basic functions.
*
* @return void
*/
function testHasMethodBasic() {
$Sample = new Sample();
$Collection = new BehaviorCollection();
$Collection->init('Sample', array('Test', 'Test2'));
$this->assertTrue($Collection->hasMethod('testMethod'));
$this->assertTrue($Collection->hasMethod('resolveMethod'));
$this->assertFalse($Collection->hasMethod('No method'));
}
/**
* test that hasMethod works with mapped methods.
*
* @return void
*/
function testHasMethodMappedMethods() {
$Sample = new Sample();
$Collection = new BehaviorCollection();
$Collection->init('Sample', array('Test', 'Test2'));
$this->assertTrue($Collection->hasMethod('look for the remote in the couch'));
$this->assertTrue($Collection->hasMethod('mappingRobotOnTheRoof'));
}
/**
* test hasMethod returrning a 'callback'
*
* @return void
*/
function testHasMethodAsCallback() {
$Sample = new Sample();
$Collection = new BehaviorCollection();
$Collection->init('Sample', array('Test', 'Test2'));
$result = $Collection->hasMethod('testMethod', true);
$expected = array('Test', 'testMethod');
$this->assertEquals($expected, $result);
$result = $Collection->hasMethod('resolveMethod', true);
$expected = array('Test2', 'resolveMethod');
$this->assertEquals($expected, $result);
$result = $Collection->hasMethod('mappingRobotOnTheRoof', true);
$expected = array('Test2', 'mapped', 'mappingRobotOnTheRoof');
$this->assertEquals($expected, $result);
}
}

View file

@ -3189,112 +3189,6 @@ class ContainableBehaviorTest extends CakeTestCase {
$this->assertEqual($result, $expected);
}
/**
* testPaginate method
*
* @access public
* @return void
*/
function testPaginate() {
App::import('Core', 'Controller');
$Controller = new Controller($this->getMock('CakeRequest'));
$Controller->uses = array('Article');
$Controller->passedArgs[] = '1';
$Controller->request->params['url'] = array();
$Controller->constructClasses();
$Controller->paginate = array('Article' => array('fields' => array('title'), 'contain' => array('User(user)')));
$result = $Controller->paginate('Article');
$expected = array(
array('Article' => array('title' => 'First Article'), 'User' => array('user' => 'mariano', 'id' => 1)),
array('Article' => array('title' => 'Second Article'), 'User' => array('user' => 'larry', 'id' => 3)),
array('Article' => array('title' => 'Third Article'), 'User' => array('user' => 'mariano', 'id' => 1)),
);
$this->assertEqual($result, $expected);
$r = $Controller->Article->find('all');
$this->assertTrue(Set::matches('/Article[id=1]', $r));
$this->assertTrue(Set::matches('/User[id=1]', $r));
$this->assertTrue(Set::matches('/Tag[id=1]', $r));
$Controller->paginate = array('Article' => array('contain' => array('Comment(comment)' => 'User(user)'), 'fields' => array('title')));
$result = $Controller->paginate('Article');
$expected = array(
array(
'Article' => array('title' => 'First Article', 'id' => 1),
'Comment' => array(
array(
'comment' => 'First Comment for First Article',
'user_id' => 2,
'article_id' => 1,
'User' => array('user' => 'nate')
),
array(
'comment' => 'Second Comment for First Article',
'user_id' => 4,
'article_id' => 1,
'User' => array('user' => 'garrett')
),
array(
'comment' => 'Third Comment for First Article',
'user_id' => 1,
'article_id' => 1,
'User' => array('user' => 'mariano')
),
array(
'comment' => 'Fourth Comment for First Article',
'user_id' => 1,
'article_id' => 1,
'User' => array('user' => 'mariano')
)
)
),
array(
'Article' => array('title' => 'Second Article', 'id' => 2),
'Comment' => array(
array(
'comment' => 'First Comment for Second Article',
'user_id' => 1,
'article_id' => 2,
'User' => array('user' => 'mariano')
),
array(
'comment' => 'Second Comment for Second Article',
'user_id' => 2,
'article_id' => 2,
'User' => array('user' => 'nate')
)
)
),
array(
'Article' => array('title' => 'Third Article', 'id' => 3),
'Comment' => array()
),
);
$this->assertEqual($result, $expected);
$r = $Controller->Article->find('all');
$this->assertTrue(Set::matches('/Article[id=1]', $r));
$this->assertTrue(Set::matches('/User[id=1]', $r));
$this->assertTrue(Set::matches('/Tag[id=1]', $r));
$Controller->Article->unbindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User'), 'hasAndBelongsToMany' => array('Tag')), false);
$Controller->Article->bindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User')), false);
$Controller->paginate = array('Article' => array('contain' => array('Comment(comment)', 'User(user)'), 'fields' => array('title')));
$r = $Controller->paginate('Article');
$this->assertTrue(Set::matches('/Article[id=1]', $r));
$this->assertTrue(Set::matches('/User[id=1]', $r));
$this->assertTrue(Set::matches('/Comment[article_id=1]', $r));
$this->assertFalse(Set::matches('/Comment[id=1]', $r));
$r = $this->Article->find('all');
$this->assertTrue(Set::matches('/Article[id=1]', $r));
$this->assertTrue(Set::matches('/User[id=1]', $r));
$this->assertTrue(Set::matches('/Comment[article_id=1]', $r));
$this->assertTrue(Set::matches('/Comment[id=1]', $r));
}
/**
* testOriginalAssociations method
*

View file

@ -2008,4 +2008,27 @@ class ModelIntegrationTest extends BaseModelTest {
$expected = $db->name('Domain.DomainHandle');
$this->assertEqual($result, $expected);
}
/**
* test that model->hasMethod checks self and behaviors.
*
* @return void
*/
function testHasMethod() {
$Article = new Article();
$Article->Behaviors = $this->getMock('BehaviorCollection');
$Article->Behaviors->expects($this->at(0))
->method('hasMethod')
->will($this->returnValue(true));
$Article->Behaviors->expects($this->at(1))
->method('hasMethod')
->will($this->returnValue(false));
$this->assertTrue($Article->hasMethod('find'));
$this->assertTrue($Article->hasMethod('pass'));
$this->assertFalse($Article->hasMethod('fail'));
}
}

View file

@ -191,8 +191,7 @@ class CakeRouteTestCase extends CakeTestCase {
$this->assertEqual($route->options, array('extra' => '[a-z1-9_]*', 'slug' => '[a-z1-9_]+', 'action' => 'view'));
$expected = array(
'controller' => 'pages',
'action' => 'view',
'extra' => null,
'action' => 'view'
);
$this->assertEqual($route->defaults, $expected);
@ -289,6 +288,45 @@ class CakeRouteTestCase extends CakeTestCase {
$this->assertEqual($result, $expected);
}
/**
* test that non-greedy routes fail with extra passed args
*
* @return void
*/
function testGreedyRouteFailurePassedArg() {
$route = new CakeRoute('/:controller/:action', array('plugin' => null));
$result = $route->match(array('controller' => 'posts', 'action' => 'view', '0'));
$this->assertFalse($result);
$route = new CakeRoute('/:controller/:action', array('plugin' => null));
$result = $route->match(array('controller' => 'posts', 'action' => 'view', 'test'));
$this->assertFalse($result);
}
/**
* test that non-greedy routes fail with extra passed args
*
* @return void
*/
function testGreedyRouteFailureNamedParam() {
$route = new CakeRoute('/:controller/:action', array('plugin' => null));
$result = $route->match(array('controller' => 'posts', 'action' => 'view', 'page' => 1));
$this->assertFalse($result);
}
/**
* test that falsey values do not interrupt a match.
*
* @return void
*/
function testMatchWithFalseyValues() {
$route = new CakeRoute('/:controller/:action/*', array('plugin' => null));
$result = $route->match(array(
'controller' => 'posts', 'action' => 'index', 'plugin' => null, 'admin' => false
));
$this->assertEqual($result, '/posts/index/');
}
/**
* test match() with greedy routes, named parameters and passed args.
*
@ -304,6 +342,12 @@ class CakeRouteTestCase extends CakeTestCase {
$result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5));
$this->assertEqual($result, '/posts/view/5');
$result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 0));
$this->assertEqual($result, '/posts/view/0');
$result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, '0'));
$this->assertEqual($result, '/posts/view/0');
$result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, 'page' => 1, 'limit' => 20, 'order' => 'title'));
$this->assertEqual($result, '/posts/view/5/page:1/limit:20/order:title');
@ -319,6 +363,17 @@ class CakeRouteTestCase extends CakeTestCase {
$this->assertFalse($result);
}
/**
* test that named params with null/false are excluded
*
* @return void
*/
function testNamedParamsWithNullFalse() {
$route = new CakeRoute('/:controller/:action/*');
$result = $route->match(array('controller' => 'posts', 'action' => 'index', 'page' => null, 'sort' => false));
$this->assertEquals('/posts/index/', $result);
}
/**
* test that match with patterns works.
*
@ -417,4 +472,5 @@ class CakeRouteTestCase extends CakeTestCase {
$result = $route->parse('/blog/foobar');
$this->assertFalse($result);
}
}

View file

@ -648,7 +648,7 @@ class RouterTest extends CakeTestCase {
$result = Router::url(array('page' => 3));
$expected = '/magazine/admin/subscriptions/index/page:3';
$this->assertEqual($result, $expected);
$this->assertEquals($expected, $result);
Router::reload();
Router::connect('/admin/subscriptions/:action/*', array('controller' => 'subscribe', 'admin' => true, 'prefix' => 'admin'));
@ -1333,14 +1333,14 @@ class RouterTest extends CakeTestCase {
Router::reload();
Router::connect('/foo/*', array('controller' => 'bar', 'action' => 'fubar'));
Router::connectNamed(array(), array('argSeparator' => '='));
Router::connectNamed(array(), array('separator' => '='));
$result = Router::parse('/foo/param1=value1/param2=value2');
$expected = array('pass' => array(), 'named' => array('param1' => 'value1', 'param2' => 'value2'), 'controller' => 'bar', 'action' => 'fubar', 'plugin' => null);
$this->assertEqual($result, $expected);
Router::reload();
Router::connect('/controller/action/*', array('controller' => 'controller', 'action' => 'action'), array('named' => array('param1' => 'value[\d]')));
Router::connectNamed(array(), array('greedy' => false, 'argSeparator' => '='));
Router::connectNamed(array(), array('greedy' => false, 'separator' => '='));
$result = Router::parse('/controller/action/param1=value1/param2=value2');
$expected = array('pass' => array('param2=value2'), 'named' => array('param1' => 'value1'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null);
$this->assertEqual($result, $expected);
@ -1348,7 +1348,7 @@ class RouterTest extends CakeTestCase {
Router::reload();
Router::connect('/:controller/:action/*');
Router::connectNamed(array('page'), array('default' => false, 'greedy' => false));
$result = Router::parse('/categories/index?limit=5');
$result = Router::parse('/categories/index/limit=5');
$this->assertTrue(empty($result['named']));
}
@ -1514,7 +1514,7 @@ class RouterTest extends CakeTestCase {
$result = Router::url(array('controller' => 'images', 'action' => 'add'));
$expected = '/images/add';
$this->assertEqual($result, $expected);
$this->assertEquals($expected, $result);
$result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true));
$expected = '/protected/images/add';

File diff suppressed because it is too large Load diff