cake/libs/controller/controller.php

1 <?php
2 /**
3 * Base controller class.
4 *
5 * PHP versions 4 and 5
6 *
7 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8 * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
9 *
10 * Licensed under The MIT License
11 * Redistributions of files must retain the above copyright notice.
12 *
13 * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
14 * @link http://cakephp.org CakePHP(tm) Project
15 * @package cake
16 * @subpackage cake.cake.libs.controller
17 * @since CakePHP(tm) v 0.2.9
18 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
19 */
20  
21 /**
22 * Include files
23 */
24 App::import('Controller', 'Component', false);
25 App::import('View', 'View', false);
26 /**
27 * Controller
28 *
29 * Application controller class for organization of business logic.
30 * Provides basic functionality, such as rendering views inside layouts,
31 * automatic model availability, redirection, callbacks, and more.
32 *
33 * @package cake
34 * @subpackage cake.cake.libs.controller
35 * @link http://book.cakephp.org/view/956/Introduction
36 */
37 class Controller extends Object {
38  
39 /**
40 * The name of this controller. Controller names are plural, named after the model they manipulate.
41 *
42 * @var string
43 * @access public
44 * @link http://book.cakephp.org/view/959/Controller-Attributes
45 */
46 var $name = null;
47  
48 /**
49 * Stores the current URL, relative to the webroot of the application.
50 *
51 * @var string
52 * @access public
53 */
54 var $here = null;
55  
56 /**
57 * The webroot of the application.
58 *
59 * @var string
60 * @access public
61 */
62 var $webroot = null;
63  
64 /**
65 * The name of the currently requested controller action.
66 *
67 * @var string
68 * @access public
69 */
70 var $action = null;
71  
72 /**
73 * An array containing the class names of models this controller uses.
74 *
75 * Example: `var $uses = array('Product', 'Post', 'Comment');`
76 *
77 * Can be set to array() to use no models. Can be set to false to
78 * use no models and prevent the merging of $uses with AppController
79 *
80 * @var mixed A single name as a string or a list of names as an array.
81 * @access protected
82 * @link http://book.cakephp.org/view/961/components-helpers-and-uses
83 */
84 var $uses = false;
85  
86 /**
87 * An array containing the names of helpers this controller uses. The array elements should
88 * not contain the "Helper" part of the classname.
89 *
90 * Example: `var $helpers = array('Html', 'Javascript', 'Time', 'Ajax');`
91 *
92 * @var mixed A single name as a string or a list of names as an array.
93 * @access protected
94 * @link http://book.cakephp.org/view/961/components-helpers-and-uses
95 */
96 var $helpers = array('Session', 'Html', 'Form');
97  
98 /**
99 * Parameters received in the current request: GET and POST data, information
100 * about the request, etc.
101 *
102 * @var array
103 * @access public
104 * @link http://book.cakephp.org/view/963/The-Parameters-Attribute-params
105 */
106 var $params = array();
107  
108 /**
109 * Data POSTed to the controller using the HtmlHelper. Data here is accessible
110 * using the `$this->data['ModelName']['fieldName']` pattern.
111 *
112 * @var array
113 * @access public
114 */
115 var $data = array();
116  
117 /**
118 * Holds pagination defaults for controller actions. The keys that can be included
119 * in this array are: 'conditions', 'fields', 'order', 'limit', 'page', and 'recursive',
120 * similar to the keys in the second parameter of Model::find().
121 *
122 * Pagination defaults can also be supplied in a model-by-model basis by using
123 * the name of the model as a key for a pagination array:
124 *
125 * {{{
126 * var $paginate = array(
127 * 'Post' => array(...),
128 * 'Comment' => array(...)
129 * );
130 * }}}
131 *
132 * @var array
133 * @access public
134 * @link http://book.cakephp.org/view/1231/Pagination
135 */
136 var $paginate = array('limit' => 20, 'page' => 1);
137  
138 /**
139 * The name of the views subfolder containing views for this controller.
140 *
141 * @var string
142 * @access public
143 */
144 var $viewPath = null;
145  
146 /**
147 * The name of the layouts subfolder containing layouts for this controller.
148 *
149 * @var string
150 * @access public
151 */
152 var $layoutPath = null;
153  
154 /**
155 * Contains variables to be handed to the view.
156 *
157 * @var array
158 * @access public
159 */
160 var $viewVars = array();
161  
162 /**
163 * An array containing the class names of the models this controller uses.
164 *
165 * @var array Array of model objects.
166 * @access public
167 */
168 var $modelNames = array();
169  
170 /**
171 * Base URL path.
172 *
173 * @var string
174 * @access public
175 */
176 var $base = null;
177  
178 /**
179 * The name of the layout file to render the view inside of. The name specified
180 * is the filename of the layout in /app/views/layouts without the .ctp
181 * extension.
182 *
183 * @var string
184 * @access public
185 * @link http://book.cakephp.org/view/962/Page-related-Attributes-layout-and-pageTitle
186 */
187 var $layout = 'default';
188  
189 /**
190 * Set to true to automatically render the view
191 * after action logic.
192 *
193 * @var boolean
194 * @access public
195 */
196 var $autoRender = true;
197  
198 /**
199 * Set to true to automatically render the layout around views.
200 *
201 * @var boolean
202 * @access public
203 */
204 var $autoLayout = true;
205  
206 /**
207 * Instance of Component used to handle callbacks.
208 *
209 * @var string
210 * @access public
211 */
212 var $Component = null;
213  
214 /**
215 * Array containing the names of components this controller uses. Component names
216 * should not contain the "Component" portion of the classname.
217 *
218 * Example: `var $components = array('Session', 'RequestHandler', 'Acl');`
219 *
220 * @var array
221 * @access public
222 * @link http://book.cakephp.org/view/961/components-helpers-and-uses
223 */
224 var $components = array('Session');
225  
226 /**
227 * The name of the View class this controller sends output to.
228 *
229 * @var string
230 * @access public
231 */
232 var $view = 'View';
233  
234 /**
235 * File extension for view templates. Defaults to Cake's conventional ".ctp".
236 *
237 * @var string
238 * @access public
239 */
240 var $ext = '.ctp';
241  
242 /**
243 * The output of the requested action. Contains either a variable
244 * returned from the action, or the data of the rendered view;
245 * You can use this var in child controllers' afterFilter() callbacks to alter output.
246 *
247 * @var string
248 * @access public
249 */
250 var $output = null;
251  
252 /**
253 * Automatically set to the name of a plugin.
254 *
255 * @var string
256 * @access public
257 */
258 var $plugin = null;
259  
260 /**
261 * Used to define methods a controller that will be cached. To cache a
262 * single action, the value is set to an array containing keys that match
263 * action names and values that denote cache expiration times (in seconds).
264 *
265 * Example:
266 *
267 * {{{
268 * var $cacheAction = array(
269 * 'view/23/' => 21600,
270 * 'recalled/' => 86400
271 * );
272 * }}}
273 *
274 * $cacheAction can also be set to a strtotime() compatible string. This
275 * marks all the actions in the controller for view caching.
276 *
277 * @var mixed
278 * @access public
279 * @link http://book.cakephp.org/view/1380/Caching-in-the-Controller
280 */
281 var $cacheAction = false;
282  
283 /**
284 * Used to create cached instances of models a controller uses.
285 * When set to true, all models related to the controller will be cached.
286 * This can increase performance in many cases.
287 *
288 * @var boolean
289 * @access public
290 */
291 var $persistModel = false;
292  
293 /**
294 * Holds all params passed and named.
295 *
296 * @var mixed
297 * @access public
298 */
299 var $passedArgs = array();
300  
301 /**
302 * Triggers Scaffolding
303 *
304 * @var mixed
305 * @access public
306 * @link http://book.cakephp.org/view/1103/Scaffolding
307 */
308 var $scaffold = false;
309  
310 /**
311 * Holds current methods of the controller
312 *
313 * @var array
314 * @access public
315 * @link
316 */
317 var $methods = array();
318  
319 /**
320 * This controller's primary model class name, the Inflector::classify()'ed version of
321 * the controller's $name property.
322 *
323 * Example: For a controller named 'Comments', the modelClass would be 'Comment'
324 *
325 * @var string
326 * @access public
327 */
328 var $modelClass = null;
329  
330 /**
331 * This controller's model key name, an underscored version of the controller's $modelClass property.
332 *
333 * Example: For a controller named 'ArticleComments', the modelKey would be 'article_comment'
334 *
335 * @var string
336 * @access public
337 */
338 var $modelKey = null;
339  
340 /**
341 * Holds any validation errors produced by the last call of the validateErrors() method/
342 *
343 * @var array Validation errors, or false if none
344 * @access public
345 */
346 var $validationErrors = null;
347  
348 /**
349 * Contains a list of the HTTP codes that CakePHP recognizes. These may be
350 * queried and/or modified through Controller::httpCodes(), which is also
351 * tasked with their lazy-loading.
352 *
353 * @var array Associative array of HTTP codes and their associated messages.
354 * @access private
355 */
356 var $__httpCodes = null;
357  
358 /**
359 * Constructor.
360 *
361 */
362 function __construct() {
363 if ($this->name === null) {
364 $r = null;
365 if (!preg_match('/(.*)Controller/i', get_class($this), $r)) {
366 __("Controller::__construct() : Can not get or parse my own class name, exiting.");
367 $this->_stop();
368 }
369 $this->name = $r[1];
370 }
371  
372 if ($this->viewPath == null) {
373 $this->viewPath = Inflector::underscore($this->name);
374 }
375 $this->modelClass = Inflector::classify($this->name);
376 $this->modelKey = Inflector::underscore($this->modelClass);
377 $this->Component =& new Component();
378  
379 $childMethods = get_class_methods($this);
380 $parentMethods = get_class_methods('Controller');
381  
382 foreach ($childMethods as $key => $value) {
383 $childMethods[$key] = strtolower($value);
384 }
385  
386 foreach ($parentMethods as $key => $value) {
387 $parentMethods[$key] = strtolower($value);
388 }
389 $this->methods = array_diff($childMethods, $parentMethods);
390 parent::__construct();
391 }
392  
393 /**
394 * Merge components, helpers, and uses vars from AppController and PluginAppController.
395 *
396 * @return void
397 * @access protected
398 */
399 function __mergeVars() {
400 $pluginName = Inflector::camelize($this->plugin);
401 $pluginController = $pluginName . 'AppController';
402  
403 if (is_subclass_of($this, 'AppController') || is_subclass_of($this, $pluginController)) {
404 $appVars = get_class_vars('AppController');
405 $uses = $appVars['uses'];
406 $merge = array('components', 'helpers');
407 $plugin = null;
408  
409 if (!empty($this->plugin)) {
410 $plugin = $pluginName . '.';
411 if (!is_subclass_of($this, $pluginController)) {
412 $pluginController = null;
413 }
414 } else {
415 $pluginController = null;
416 }
417  
418 if ($uses == $this->uses && !empty($this->uses)) {
419 if (!in_array($plugin . $this->modelClass, $this->uses)) {
420 array_unshift($this->uses, $plugin . $this->modelClass);
421 } elseif ($this->uses[0] !== $plugin . $this->modelClass) {
422 $this->uses = array_flip($this->uses);
423 unset($this->uses[$plugin . $this->modelClass]);
424 $this->uses = array_flip($this->uses);
425 array_unshift($this->uses, $plugin . $this->modelClass);
426 }
427 } elseif ($this->uses !== null || $this->uses !== false) {
428 $merge[] = 'uses';
429 }
430  
431 foreach ($merge as $var) {
432 if (!empty($appVars[$var]) && is_array($this->{$var})) {
433 if ($var === 'components') {
434 $normal = Set::normalize($this->{$var});
435 $app = Set::normalize($appVars[$var]);
436 if ($app !== $normal) {
437 $this->{$var} = Set::merge($app, $normal);
438 }
439 } else {
440 $this->{$var} = Set::merge(
441 $this->{$var}, array_diff($appVars[$var], $this->{$var})
442 );
443 }
444 }
445 }
446 }
447  
448 if ($pluginController && $pluginName != null) {
449 $appVars = get_class_vars($pluginController);
450 $uses = $appVars['uses'];
451 $merge = array('components', 'helpers');
452  
453 if ($this->uses !== null || $this->uses !== false) {
454 $merge[] = 'uses';
455 }
456  
457 foreach ($merge as $var) {
458 if (isset($appVars[$var]) && !empty($appVars[$var]) && is_array($this->{$var})) {
459 if ($var === 'components') {
460 $normal = Set::normalize($this->{$var});
461 $app = Set::normalize($appVars[$var]);
462 if ($app !== $normal) {
463 $this->{$var} = Set::merge($app, $normal);
464 }
465 } else {
466 $this->{$var} = Set::merge(
467 $this->{$var}, array_diff($appVars[$var], $this->{$var})
468 );
469 }
470 }
471 }
472 }
473 }
474  
475 /**
476 * Loads Model classes based on the the uses property
477 * see Controller::loadModel(); for more info.
478 * Loads Components and prepares them for initialization.
479 *
480 * @return mixed true if models found and instance created, or cakeError if models not found.
481 * @access public
482 * @see Controller::loadModel()
483 * @link http://book.cakephp.org/view/977/Controller-Methods#constructClasses-986
484 */
485 function constructClasses() {
486 $this->__mergeVars();
487 $this->Component->init($this);
488  
489 if ($this->uses !== null || ($this->uses !== array())) {
490 if (empty($this->passedArgs) || !isset($this->passedArgs['0'])) {
491 $id = false;
492 } else {
493 $id = $this->passedArgs['0'];
494 }
495  
496 if ($this->uses === false) {
497 $this->loadModel($this->modelClass, $id);
498 } elseif ($this->uses) {
499 $uses = is_array($this->uses) ? $this->uses : array($this->uses);
500 $modelClassName = $uses[0];
501 if (strpos($uses[0], '.') !== false) {
502 list($plugin, $modelClassName) = explode('.', $uses[0]);
503 }
504 $this->modelClass = $modelClassName;
505 foreach ($uses as $modelClass) {
506 $this->loadModel($modelClass);
507 }
508 }
509 }
510 return true;
511 }
512  
513 /**
514 * Perform the startup process for this controller.
515 * Fire the Component and Controller callbacks in the correct order.
516 *
517 * - Initializes components, which fires their `initialize` callback
518 * - Calls the controller `beforeFilter`.
519 * - triggers Component `startup` methods.
520 *
521 * @return void
522 * @access public
523 */
524 function startupProcess() {
525 $this->Component->initialize($this);
526 $this->beforeFilter();
527 $this->Component->triggerCallback('startup', $this);
528 }
529  
530 /**
531 * Perform the various shutdown processes for this controller.
532 * Fire the Component and Controller callbacks in the correct order.
533 *
534 * - triggers the component `shutdown` callback.
535 * - calls the Controller's `afterFilter` method.
536 *
537 * @return void
538 * @access public
539 */
540 function shutdownProcess() {
541 $this->Component->triggerCallback('shutdown', $this);
542 $this->afterFilter();
543 }
544  
545 /**
546 * Queries & sets valid HTTP response codes & messages.
547 *
548 * @param mixed $code If $code is an integer, then the corresponding code/message is
549 * returned if it exists, null if it does not exist. If $code is an array,
550 * then the 'code' and 'message' keys of each nested array are added to the default
551 * HTTP codes. Example:
552 *
553 * httpCodes(404); // returns array(404 => 'Not Found')
554 *
555 * httpCodes(array(
556 * 701 => 'Unicorn Moved',
557 * 800 => 'Unexpected Minotaur'
558 * )); // sets these new values, and returns true
559 *
560 * @return mixed Associative array of the HTTP codes as keys, and the message
561 * strings as values, or null of the given $code does not exist.
562 */
563 function httpCodes($code = null) {
564 if (empty($this->__httpCodes)) {
565 $this->__httpCodes = array(
566 100 => 'Continue', 101 => 'Switching Protocols',
567 200 => 'OK', 201 => 'Created', 202 => 'Accepted',
568 203 => 'Non-Authoritative Information', 204 => 'No Content',
569 205 => 'Reset Content', 206 => 'Partial Content',
570 300 => 'Multiple Choices', 301 => 'Moved Permanently',
571 302 => 'Found', 303 => 'See Other',
572 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
573 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required',
574 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed',
575 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required',
576 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone',
577 411 => 'Length Required', 412 => 'Precondition Failed',
578 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large',
579 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable',
580 417 => 'Expectation Failed', 500 => 'Internal Server Error',
581 501 => 'Not Implemented', 502 => 'Bad Gateway',
582 503 => 'Service Unavailable', 504 => 'Gateway Time-out'
583 );
584 }
585  
586 if (empty($code)) {
587 return $this->__httpCodes;
588 }
589  
590 if (is_array($code)) {
591 $this->__httpCodes = $code + $this->__httpCodes;
592 return true;
593 }
594  
595 if (!isset($this->__httpCodes[$code])) {
596 return null;
597 }
598 return array($code => $this->__httpCodes[$code]);
599 }
600  
601 /**
602 * Loads and instantiates models required by this controller.
603 * If Controller::$persistModel; is true, controller will cache model instances on first request,
604 * additional request will used cached models.
605 * If the model is non existent, it will throw a missing database table error, as Cake generates
606 * dynamic models for the time being.
607 *
608 * @param string $modelClass Name of model class to load
609 * @param mixed $id Initial ID the instanced model class should have
610 * @return mixed true when single model found and instance created, error returned if model not found.
611 * @access public
612 */
613 function loadModel($modelClass = null, $id = null) {
614 if ($modelClass === null) {
615 $modelClass = $this->modelClass;
616 }
617 $cached = false;
618 $object = null;
619 $plugin = null;
620 if ($this->uses === false) {
621 if ($this->plugin) {
622 $plugin = $this->plugin . '.';
623 }
624 }
625 list($plugin, $modelClass) = pluginSplit($modelClass, true, $plugin);
626  
627 if ($this->persistModel === true) {
628 $cached = $this->_persist($modelClass, null, $object);
629 }
630  
631 if (($cached === false)) {
632 $this->modelNames[] = $modelClass;
633  
634 if (!PHP5) {
635 $this->{$modelClass} =& ClassRegistry::init(array(
636 'class' => $plugin . $modelClass, 'alias' => $modelClass, 'id' => $id
637 ));
638 } else {
639 $this->{$modelClass} = ClassRegistry::init(array(
640 'class' => $plugin . $modelClass, 'alias' => $modelClass, 'id' => $id
641 ));
642 }
643  
644 if (!$this->{$modelClass}) {
645 return $this->cakeError('missingModel', array(array(
646 'className' => $modelClass, 'webroot' => '', 'base' => $this->base
647 )));
648 }
649  
650 if ($this->persistModel === true) {
651 $this->_persist($modelClass, true, $this->{$modelClass});
652 $registry =& ClassRegistry::getInstance();
653 $this->_persist($modelClass . 'registry', true, $registry->__objects, 'registry');
654 }
655 } else {
656 $this->_persist($modelClass . 'registry', true, $object, 'registry');
657 $this->_persist($modelClass, true, $object);
658 $this->modelNames[] = $modelClass;
659 }
660  
661 return true;
662 }
663  
664 /**
665 * Redirects to given $url, after turning off $this->autoRender.
666 * Script execution is halted after the redirect.
667 *
668 * @param mixed $url A string or array-based URL pointing to another location within the app,
669 * or an absolute URL
670 * @param integer $status Optional HTTP status code (eg: 404)
671 * @param boolean $exit If true, exit() will be called after the redirect
672 * @return mixed void if $exit = false. Terminates script if $exit = true
673 * @access public
674 * @link http://book.cakephp.org/view/982/redirect
675 */
676 function redirect($url, $status = null, $exit = true) {
677 $this->autoRender = false;
678  
679 if (is_array($status)) {
680 extract($status, EXTR_OVERWRITE);
681 }
682 $response = $this->Component->beforeRedirect($this, $url, $status, $exit);
683  
684 if ($response === false) {
685 return;
686 }
687 if (is_array($response)) {
688 foreach ($response as $resp) {
689 if (is_array($resp) && isset($resp['url'])) {
690 extract($resp, EXTR_OVERWRITE);
691 } elseif ($resp !== null) {
692 $url = $resp;
693 }
694 }
695 }
696  
697 if (function_exists('session_write_close')) {
698 session_write_close();
699 }
700  
701 if (!empty($status)) {
702 $codes = $this->httpCodes();
703  
704 if (is_string($status)) {
705 $codes = array_flip($codes);
706 }
707  
708 if (isset($codes[$status])) {
709 $code = $msg = $codes[$status];
710 if (is_numeric($status)) {
711 $code = $status;
712 }
713 if (is_string($status)) {
714 $msg = $status;
715 }
716 $status = "HTTP/1.1 {$code} {$msg}";
717  
718 } else {
719 $status = null;
720 }
721 $this->header($status);
722 }
723  
724 if ($url !== null) {
725 $this->header('Location: ' . Router::url($url, true));
726 }
727  
728 if (!empty($status) && ($status >= 300 && $status < 400)) {
729 $this->header($status);
730 }
731  
732 if ($exit) {
733 $this->_stop();
734 }
735 }
736  
737 /**
738 * Convenience and object wrapper method for header(). Useful when doing tests and
739 * asserting that particular headers have been set.
740 *
741 * @param string $status The header message that is being set.
742 * @return void
743 * @access public
744 */
745 function header($status) {
746 header($status);
747 }
748  
749 /**
750 * Saves a variable for use inside a view template.
751 *
752 * @param mixed $one A string or an array of data.
753 * @param mixed $two Value in case $one is a string (which then works as the key).
754 * Unused if $one is an associative array, otherwise serves as the values to $one's keys.
755 * @return void
756 * @access public
757 * @link http://book.cakephp.org/view/979/set
758 */
759 function set($one, $two = null) {
760 $data = array();
761  
762 if (is_array($one)) {
763 if (is_array($two)) {
764 $data = array_combine($one, $two);
765 } else {
766 $data = $one;
767 }
768 } else {
769 $data = array($one => $two);
770 }
771 $this->viewVars = array_merge($this->viewVars, $data);
772 }
773  
774 /**
775 * Internally redirects one action to another. Does not perform another HTTP request unlike Controller::redirect()
776 *
777 * Examples:
778 *
779 * {{{
780 * setAction('another_action');
781 * setAction('action_with_parameters', $parameter1);
782 * }}}
783 *
784 * @param string $action The new action to be 'redirected' to
785 * @param mixed Any other parameters passed to this method will be passed as
786 * parameters to the new action.
787 * @return mixed Returns the return value of the called action
788 * @access public
789 */
790 function setAction($action) {
791 $this->action = $action;
792 $args = func_get_args();
793 unset($args[0]);
794 return call_user_func_array(array(&$this, $action), $args);
795 }
796  
797 /**
798 * Controller callback to tie into Auth component.
799 * Only called when AuthComponent::$authorize is set to 'controller'.
800 *
801 * @return bool true if authorized, false otherwise
802 * @access public
803 * @link http://book.cakephp.org/view/1275/authorize
804 */
805 function isAuthorized() {
806 trigger_error(sprintf(
807 __('%s::isAuthorized() is not defined.', true), $this->name
808 ), E_USER_WARNING);
809 return false;
810 }
811  
812 /**
813 * Returns number of errors in a submitted FORM.
814 *
815 * @return integer Number of errors
816 * @access public
817 */
818 function validate() {
819 $args = func_get_args();
820 $errors = call_user_func_array(array(&$this, 'validateErrors'), $args);
821  
822 if ($errors === false) {
823 return 0;
824 }
825 return count($errors);
826 }
827  
828 /**
829 * Validates models passed by parameters. Example:
830 *
831 * `$errors = $this->validateErrors($this->Article, $this->User);`
832 *
833 * @param mixed A list of models as a variable argument
834 * @return array Validation errors, or false if none
835 * @access public
836 */
837 function validateErrors() {
838 $objects = func_get_args();
839  
840 if (empty($objects)) {
841 return false;
842 }
843  
844 $errors = array();
845 foreach ($objects as $object) {
846 $this->{$object->alias}->set($object->data);
847 $errors = array_merge($errors, $this->{$object->alias}->invalidFields());
848 }
849  
850 return $this->validationErrors = (!empty($errors) ? $errors : false);
851 }
852  
853 /**
854 * Instantiates the correct view class, hands it its data, and uses it to render the view output.
855 *
856 * @param string $action Action name to render
857 * @param string $layout Layout to use
858 * @param string $file File to use for rendering
859 * @return string Full output string of view contents
860 * @access public
861 * @link http://book.cakephp.org/view/980/render
862 */
863 function render($action = null, $layout = null, $file = null) {
864 $this->beforeRender();
865  
866 $viewClass = $this->view;
867 if ($this->view != 'View') {
868 list($plugin, $viewClass) = pluginSplit($viewClass);
869 $viewClass = $viewClass . 'View';
870 App::import('View', $this->view);
871 }
872  
873 $this->Component->triggerCallback('beforeRender', $this);
874  
875 $this->params['models'] = $this->modelNames;
876  
877 if (Configure::read() > 2) {
878 $this->set('cakeDebug', $this);
879 }
880  
881 $View =& new $viewClass($this);
882  
883 if (!empty($this->modelNames)) {
884 $models = array();
885 foreach ($this->modelNames as $currentModel) {
886 if (isset($this->$currentModel) && is_a($this->$currentModel, 'Model')) {
887 $models[] = Inflector::underscore($currentModel);
888 }
889 $isValidModel = (
890 isset($this->$currentModel) && is_a($this->$currentModel, 'Model') &&
891 !empty($this->$currentModel->validationErrors)
892 );
893 if ($isValidModel) {
894 $View->validationErrors[Inflector::camelize($currentModel)] =&
895 $this->$currentModel->validationErrors;
896 }
897 }
898 $models = array_diff(ClassRegistry::keys(), $models);
899 foreach ($models as $currentModel) {
900 if (ClassRegistry::isKeySet($currentModel)) {
901 $currentObject =& ClassRegistry::getObject($currentModel);
902 if (is_a($currentObject, 'Model') && !empty($currentObject->validationErrors)) {
903 $View->validationErrors[Inflector::camelize($currentModel)] =&
904 $currentObject->validationErrors;
905 }
906 }
907 }
908 }
909  
910 $this->autoRender = false;
911 $this->output .= $View->render($action, $layout, $file);
912  
913 return $this->output;
914 }
915  
916 /**
917 * Returns the referring URL for this request.
918 *
919 * @param string $default Default URL to use if HTTP_REFERER cannot be read from headers
920 * @param boolean $local If true, restrict referring URLs to local server
921 * @return string Referring URL
922 * @access public
923 * @link http://book.cakephp.org/view/987/referer
924 */
925 function referer($default = null, $local = false) {
926 $ref = env('HTTP_REFERER');
927 if (!empty($ref) && defined('FULL_BASE_URL')) {
928 $base = FULL_BASE_URL . $this->webroot;
929 if (strpos($ref, $base) === 0) {
930 $return = substr($ref, strlen($base));
931 if ($return[0] != '/') {
932 $return = '/'.$return;
933 }
934 return $return;
935 } elseif (!$local) {
936 return $ref;
937 }
938 }
939  
940 if ($default != null) {
941 $url = Router::url($default, true);
942 return $url;
943 }
944 return '/';
945 }
946  
947 /**
948 * Forces the user's browser not to cache the results of the current request.
949 *
950 * @return void
951 * @access public
952 * @link http://book.cakephp.org/view/988/disableCache
953 */
954 function disableCache() {
955 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
956 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
957 header("Cache-Control: no-store, no-cache, must-revalidate");
958 header("Cache-Control: post-check=0, pre-check=0", false);
959 header("Pragma: no-cache");
960 }
961  
962 /**
963 * Shows a message to the user for $pause seconds, then redirects to $url.
964 * Uses flash.ctp as the default layout for the message.
965 * Does not work if the current debug level is higher than 0.
966 *
967 * @param string $message Message to display to the user
968 * @param mixed $url Relative string or array-based URL to redirect to after the time expires
969 * @param integer $pause Time to show the message
970 * @param string $layout Layout you want to use, defaults to 'flash'
971 * @return void Renders flash layout
972 * @access public
973 * @link http://book.cakephp.org/view/983/flash
974 */
975 function flash($message, $url, $pause = 1, $layout = 'flash') {
976 $this->autoRender = false;
977 $this->set('url', Router::url($url));
978 $this->set('message', $message);
979 $this->set('pause', $pause);
980 $this->set('page_title', $message);
981 $this->render(false, $layout);
982 }
983  
984 /**
985 * Converts POST'ed form data to a model conditions array, suitable for use in a Model::find() call.
986 *
987 * @param array $data POST'ed data organized by model and field
988 * @param mixed $op A string containing an SQL comparison operator, or an array matching operators
989 * to fields
990 * @param string $bool SQL boolean operator: AND, OR, XOR, etc.
991 * @param boolean $exclusive If true, and $op is an array, fields not included in $op will not be
992 * included in the returned conditions
993 * @return array An array of model conditions
994 * @access public
995 * @link http://book.cakephp.org/view/989/postConditions
996 */
997 function postConditions($data = array(), $op = null, $bool = 'AND', $exclusive = false) {
998 if (!is_array($data) || empty($data)) {
999 if (!empty($this->data)) {
1000 $data = $this->data;
1001 } else {
1002 return null;
1003 }
1004 }
1005 $cond = array();
1006  
1007 if ($op === null) {
1008 $op = '';
1009 }
1010  
1011 $arrayOp = is_array($op);
1012 foreach ($data as $model => $fields) {
1013 foreach ($fields as $field => $value) {
1014 $key = $model.'.'.$field;
1015 $fieldOp = $op;
1016 if ($arrayOp) {
1017 if (array_key_exists($key, $op)) {
1018 $fieldOp = $op[$key];
1019 } elseif (array_key_exists($field, $op)) {
1020 $fieldOp = $op[$field];
1021 } else {
1022 $fieldOp = false;
1023 }
1024 }
1025 if ($exclusive && $fieldOp === false) {
1026 continue;
1027 }
1028 $fieldOp = strtoupper(trim($fieldOp));
1029 if ($fieldOp === 'LIKE') {
1030 $key = $key.' LIKE';
1031 $value = '%'.$value.'%';
1032 } elseif ($fieldOp && $fieldOp != '=') {
1033 $key = $key.' '.$fieldOp;
1034 }
1035 $cond[$key] = $value;
1036 }
1037 }
1038 if ($bool != null && strtoupper($bool) != 'AND') {
1039 $cond = array($bool => $cond);
1040 }
1041 return $cond;
1042 }
1043  
1044 /**
1045 * Handles automatic pagination of model records.
1046 *
1047 * @param mixed $object Model to paginate (e.g: model instance, or 'Model', or 'Model.InnerModel')
1048 * @param mixed $scope Conditions to use while paginating
1049 * @param array $whitelist List of allowed options for paging
1050 * @return array Model query results
1051 * @access public
1052 * @link http://book.cakephp.org/view/1232/Controller-Setup
1053 */
1054 function paginate($object = null, $scope = array(), $whitelist = array()) {
1055 if (is_array($object)) {
1056 $whitelist = $scope;
1057 $scope = $object;
1058 $object = null;
1059 }
1060 $assoc = null;
1061  
1062 if (is_string($object)) {
1063 $assoc = null;
1064 if (strpos($object, '.') !== false) {
1065 list($object, $assoc) = pluginSplit($object);
1066 }
1067  
1068 if ($assoc && isset($this->{$object}->{$assoc})) {
1069 $object =& $this->{$object}->{$assoc};
1070 } elseif (
1071 $assoc && isset($this->{$this->modelClass}) &&
1072 isset($this->{$this->modelClass}->{$assoc}
1073 )) {
1074 $object =& $this->{$this->modelClass}->{$assoc};
1075 } elseif (isset($this->{$object})) {
1076 $object =& $this->{$object};
1077 } elseif (
1078 isset($this->{$this->modelClass}) && isset($this->{$this->modelClass}->{$object}
1079 )) {
1080 $object =& $this->{$this->modelClass}->{$object};
1081 }
1082 } elseif (empty($object) || $object === null) {
1083 if (isset($this->{$this->modelClass})) {
1084 $object =& $this->{$this->modelClass};
1085 } else {
1086 $className = null;
1087 $name = $this->uses[0];
1088 if (strpos($this->uses[0], '.') !== false) {
1089 list($name, $className) = explode('.', $this->uses[0]);
1090 }
1091 if ($className) {
1092 $object =& $this->{$className};
1093 } else {
1094 $object =& $this->{$name};
1095 }
1096 }
1097 }
1098  
1099 if (!is_object($object)) {
1100 trigger_error(sprintf(
1101 __('Controller::paginate() - can\'t find model %1$s in controller %2$sController',
1102 true
1103 ), $object, $this->name
1104 ), E_USER_WARNING);
1105 return array();
1106 }
1107 $options = array_merge($this->params, $this->params['url'], $this->passedArgs);
1108  
1109 if (isset($this->paginate[$object->alias])) {
1110 $defaults = $this->paginate[$object->alias];
1111 } else {
1112 $defaults = $this->paginate;
1113 }
1114  
1115 if (isset($options['show'])) {
1116 $options['limit'] = $options['show'];
1117 }
1118  
1119 if (isset($options['sort'])) {
1120 $direction = null;
1121 if (isset($options['direction'])) {
1122 $direction = strtolower($options['direction']);
1123 }
1124 if ($direction != 'asc' && $direction != 'desc') {
1125 $direction = 'asc';
1126 }
1127 $options['order'] = array($options['sort'] => $direction);
1128 }
1129  
1130 if (!empty($options['order']) && is_array($options['order'])) {
1131 $alias = $object->alias ;
1132 $key = $field = key($options['order']);
1133  
1134 if (strpos($key, '.') !== false) {
1135 list($alias, $field) = explode('.', $key);
1136 }
1137 $value = $options['order'][$key];
1138 unset($options['order'][$key]);
1139  
1140 if ($object->hasField($field)) {
1141 $options['order'][$alias . '.' . $field] = $value;
1142 } elseif ($object->hasField($field, true)) {
1143 $options['order'][$field] = $value;
1144 } elseif (isset($object->{$alias}) && $object->{$alias}->hasField($field)) {
1145 $options['order'][$alias . '.' . $field] = $value;
1146 }
1147 }
1148 $vars = array('fields', 'order', 'limit', 'page', 'recursive');
1149 $keys = array_keys($options);
1150 $count = count($keys);
1151  
1152 for ($i = 0; $i < $count; $i++) {
1153 if (!in_array($keys[$i], $vars, true)) {
1154 unset($options[$keys[$i]]);
1155 }
1156 if (empty($whitelist) && ($keys[$i] === 'fields' || $keys[$i] === 'recursive')) {
1157 unset($options[$keys[$i]]);
1158 } elseif (!empty($whitelist) && !in_array($keys[$i], $whitelist)) {
1159 unset($options[$keys[$i]]);
1160 }
1161 }
1162 $conditions = $fields = $order = $limit = $page = $recursive = null;
1163  
1164 if (!isset($defaults['conditions'])) {
1165 $defaults['conditions'] = array();
1166 }
1167  
1168 $type = 'all';
1169  
1170 if (isset($defaults[0])) {
1171 $type = $defaults[0];
1172 unset($defaults[0]);
1173 }
1174  
1175 $options = array_merge(array('page' => 1, 'limit' => 20), $defaults, $options);
1176 $options['limit'] = (int) $options['limit'];
1177 if (empty($options['limit']) || $options['limit'] < 1) {
1178 $options['limit'] = 1;
1179 }
1180  
1181 extract($options);
1182  
1183 if (is_array($scope) && !empty($scope)) {
1184 $conditions = array_merge($conditions, $scope);
1185 } elseif (is_string($scope)) {
1186 $conditions = array($conditions, $scope);
1187 }
1188 if ($recursive === null) {
1189 $recursive = $object->recursive;
1190 }
1191  
1192 $extra = array_diff_key($defaults, compact(
1193 'conditions', 'fields', 'order', 'limit', 'page', 'recursive'
1194 ));
1195 if ($type !== 'all') {
1196 $extra['type'] = $type;
1197 }
1198  
1199 if (method_exists($object, 'paginateCount')) {
1200 $count = $object->paginateCount($conditions, $recursive, $extra);
1201 } else {
1202 $parameters = compact('conditions');
1203 if ($recursive != $object->recursive) {
1204 $parameters['recursive'] = $recursive;
1205 }
1206 $count = $object->find('count', array_merge($parameters, $extra));
1207 }
1208 $pageCount = intval(ceil($count / $limit));
1209  
1210 if ($page === 'last' || $page >= $pageCount) {
1211 $options['page'] = $page = $pageCount;
1212 } elseif (intval($page) < 1) {
1213 $options['page'] = $page = 1;
1214 }
1215 $page = $options['page'] = (integer)$page;
1216  
1217 if (method_exists($object, 'paginate')) {
1218 $results = $object->paginate(
1219 $conditions, $fields, $order, $limit, $page, $recursive, $extra
1220 );
1221 } else {
1222 $parameters = compact('conditions', 'fields', 'order', 'limit', 'page');
1223 if ($recursive != $object->recursive) {
1224 $parameters['recursive'] = $recursive;
1225 }
1226 $results = $object->find($type, array_merge($parameters, $extra));
1227 }
1228 $paging = array(
1229 'page' => $page,
1230 'current' => count($results),
1231 'count' => $count,
1232 'prevPage' => ($page > 1),
1233 'nextPage' => ($count > ($page * $limit)),
1234 'pageCount' => $pageCount,
1235 'defaults' => array_merge(array('limit' => 20, 'step' => 1), $defaults),
1236 'options' => $options
1237 );
1238 $this->params['paging'][$object->alias] = $paging;
1239  
1240 if (!in_array('Paginator', $this->helpers) && !array_key_exists('Paginator', $this->helpers)) {
1241 $this->helpers[] = 'Paginator';
1242 }
1243 return $results;
1244 }
1245  
1246 /**
1247 * Called before the controller action.
1248 *
1249 * @access public
1250 * @link http://book.cakephp.org/view/984/Callbacks
1251 */
1252 function beforeFilter() {
1253 }
1254  
1255 /**
1256 * Called after the controller action is run, but before the view is rendered.
1257 *
1258 * @access public
1259 * @link http://book.cakephp.org/view/984/Callbacks
1260 */
1261 function beforeRender() {
1262 }
1263  
1264 /**
1265 * Called after the controller action is run and rendered.
1266 *
1267 * @access public
1268 * @link http://book.cakephp.org/view/984/Callbacks
1269 */
1270 function afterFilter() {
1271 }
1272  
1273 /**
1274 * This method should be overridden in child classes.
1275 *
1276 * @param string $method name of method called example index, edit, etc.
1277 * @return boolean Success
1278 * @access protected
1279 * @link http://book.cakephp.org/view/984/Callbacks
1280 */
1281 function _beforeScaffold($method) {
1282 return true;
1283 }
1284  
1285 /**
1286 * This method should be overridden in child classes.
1287 *
1288 * @param string $method name of method called either edit or update.
1289 * @return boolean Success
1290 * @access protected
1291 * @link http://book.cakephp.org/view/984/Callbacks
1292 */
1293 function _afterScaffoldSave($method) {
1294 return true;
1295 }
1296  
1297 /**
1298 * This method should be overridden in child classes.
1299 *
1300 * @param string $method name of method called either edit or update.
1301 * @return boolean Success
1302 * @access protected
1303 * @link http://book.cakephp.org/view/984/Callbacks
1304 */
1305 function _afterScaffoldSaveError($method) {
1306 return true;
1307 }
1308  
1309 /**
1310 * This method should be overridden in child classes.
1311 * If not it will render a scaffold error.
1312 * Method MUST return true in child classes
1313 *
1314 * @param string $method name of method called example index, edit, etc.
1315 * @return boolean Success
1316 * @access protected
1317 * @link http://book.cakephp.org/view/984/Callbacks
1318 */
1319 function _scaffoldError($method) {
1320 return false;
1321 }
1322 }
1323  
1324