Fixing issues with the scaffold generator (custom actions did not work).
[akelos.git] / lib / AkRequest.php
blobdaade528f859744241b78064fcafb868c6c675b2
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
11 if(!class_exists('AkResponse')){
13 /**
14 * @package ActionController
15 * @subpackage Request
16 * @author Bermi Ferrer <bermi a.t akelos c.om>
17 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
18 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
21 if(!defined('AK_DEFAULT_CONTROLLER')){
22 define('AK_DEFAULT_CONTROLLER', 'page');
24 if(!defined('AK_DEFAULT_ACTION')){
25 define('AK_DEFAULT_ACTION', 'index');
28 defined('AK_HIGH_LOAD_MODE') ? null : define('AK_HIGH_LOAD_MODE', false);
29 defined('AK_AUTOMATIC_DB_CONNECTION') ? null : define('AK_AUTOMATIC_DB_CONNECTION', !AK_HIGH_LOAD_MODE);
30 defined('AK_AUTOMATIC_SESSION_START') ? null : define('AK_AUTOMATIC_SESSION_START', !AK_HIGH_LOAD_MODE);
32 // IIS does not provide a valid REQUEST_URI so we need to guess it from the script name + query string
33 $_SERVER['REQUEST_URI'] = (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['SCRIPT_NAME'].(( isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')));
36 /**
37 * Class that handles incoming request.
39 * The Request Object handles user request (CLI, GET, POST, session or
40 * cookie requests), transforms it and sets it up for the
41 * ApplicationController class, who takes control of the data
42 * flow.
44 * @author Bermi Ferrer <bermi@akelos.com>
45 * @copyright Copyright (c) 2002-2005, Akelos Media, S.L. http://www.akelos.org
46 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
48 class AkRequest extends AkObject
50 // {{{ properties
53 // --- Private properties --- //
55 /**
56 * Array containing the request parameters.
58 * This property stores the parameters parsed from the
59 * parseRequest() method. This array is used by addParams()
60 * method.
62 * @access private
63 * @var array $_request
65 var $_request = array();
67 var $_init_check = false;
68 var $__internationalization_support_enabled = false;
70 var $action = AK_DEFAULT_ACTION;
71 var $controller = AK_DEFAULT_CONTROLLER;
72 var $view;
74 /**
75 * Holds information about current environment. Initially a reference to $_SERVER
77 * @var array
79 var $env = array();
80 // }}}
83 // ------ CLASS METHODS ------ //
88 // ---- Public methods ---- //
91 // {{{ _parseAkRequestString()
93 /**
94 * String parse method.
96 * This method gets a petition as parameter, using the "Ruby
97 * on Rails" request format (see prettyURL in RoR documentation). The format is:
98 * file.php?ak=/controller/action/id&paramN=valueN
100 * This method requires for a previous execution of the _mergeRequest() method,
101 * in order to merge all the request all i one array.
103 * This method expands dynamically the class Request, adding a public property for
104 * every parameter sent in the request.
107 * @access public
108 * @return array
110 function _parseAkRequestString($ak_request_string, $pattern = '/')
112 $result = array();
113 $ak_request = trim($ak_request_string,$pattern);
114 if(strstr($ak_request,$pattern)){
115 $result = explode($pattern,$ak_request);
117 return $result;
120 // }}}
123 function __construct ()
125 $this->init();
129 // ---- Private methods ---- //
132 // {{{ init()
135 * Initialization method.
137 * Initialization method. Use this via the class constructor.
139 * @access public
140 * @uses parseRequest
141 * @return void
143 function init()
145 if(!$this->_init_check){
147 $this->_fixGpcMagic();
148 $this->_urlDecode();
150 $this->_mergeRequest();
152 if(is_array($this->_request)){
153 foreach ($this->_request as $k=>$v){
154 $this->_addParam($k, $v);
158 $this->_init_check = true;
161 $this->env =& $_SERVER;
163 if(defined('AK_LOG_EVENTS') && AK_LOG_EVENTS){
164 $Logger =& Ak::getLogger();
165 $Logger->message($Logger->formatText('Request','green').' from '.$this->getRemoteIp(), $this->getParams());
169 // }}}
171 function get($var_name)
173 return $this->_request[$var_name];
176 function getParams()
178 return array_merge(array('controller'=>$this->controller,'action'=>$this->action),$this->_request);
181 function getAction()
183 return $this->action;
186 function getController()
188 return $this->controller;
191 function reset()
193 $this->_request = array();
194 $this->_init_check = false;
197 function set($variable, $value)
199 $this->_addParam($variable, $value);
203 function checkForRoutedRequests(&$Router)
205 $ak_request = isset($this->_request['ak']) ? '/'.trim($this->_request['ak'],'/').'/' : '/';
207 if($found = $Router->toParams($ak_request)){
208 if(!isset($found['controller'])){
209 trigger_error(Ak::t('No controller was specified.'), E_USER_WARNING);
211 if(!isset($found['action'])){
212 trigger_error(Ak::t('No action was specified.'), E_USER_WARNING);
215 if(isset($found['controller'])){
216 if($this->_addParam('controller',$found['controller'])){
217 $this->controller = $this->_request['controller'] = $found['controller'];
220 if(isset($found['action'])){
221 if($this->_addParam('action',$found['action'])){
222 $this->action = $this->_request['action'] = $found['action'];
225 if(isset($found['module'])){
226 if($this->_addParam('module',$found['module'])){
227 $this->module = $this->_request['module'] = $found['module'];
231 foreach ($found as $k=>$v){
232 if($this->_addParam($k,$v)){
233 $this->_request[$k] = $v;
240 function isValidControllerName($controller_name)
242 return $this->_validateTechName($controller_name);
245 function isValidActionName($action_name)
247 return $this->_validateTechName($action_name);
250 function isValidModuleName($module_name)
252 return preg_match('/^[A-Za-z]{1,}[A-Za-z0-9_\/]*$/', $module_name);
258 * Returns both GET and POST parameters in a single array.
260 function getParameters()
262 if(empty($this->parameters)){
263 $this->parameters = $this->getParams();
265 return $this->parameters;
268 function setPathParameters($parameters)
270 $this->_path_parameters = $parameters;
273 function getPathParameters()
275 return empty($this->_path_parameters) ? array() : $this->_path_parameters;
278 function getUrlParams()
280 return $_GET;
284 * Must be implemented in the concrete request
286 function getQueryParameters ()
289 function getRequestParameters ()
294 * Returns the path minus the web server relative installation directory. This method returns null unless the web server is apache.
296 function getRelativeUrlRoot()
298 return str_replace('/index.php','', @$this->env['PHP_SELF']);
302 * Returns the locale identifier of current URL
304 function getLocaleFromUrl()
306 $locale = Ak::get_url_locale();
307 if(strstr(AK_CURRENT_URL,AK_SITE_URL.'/'.$locale)){
308 return $locale;
310 return '';
314 * Returns the HTTP request method as a lowercase symbol ('get, for example)
316 function getMethod()
318 return strtolower($this->env['REQUEST_METHOD']);
322 * Is this a GET request? Equivalent to $Request->getMethod() == 'get'
324 function isGet()
326 return $this->getMethod() == 'get';
330 * Is this a POST request? Equivalent to $Request->getMethod() == 'post'
332 function isPost()
334 return $this->getMethod() == 'post';
338 * Is this a PUT request? Equivalent to $Request->getMethod() == 'put'
340 function isPut()
342 return $this->getMethod() == 'put';
346 * Is this a DELETE request? Equivalent to $Request->getMethod() == 'delete'
348 function isDelete()
350 return $this->getMethod() == 'delete';
354 * Is this a HEAD request? Equivalent to $Request->getMethod() == 'head'
356 function isHead()
358 return $this->getMethod() == 'head';
364 * Determine originating IP address. REMOTE_ADDR is the standard
365 * but will fail if( the user is behind a proxy. HTTP_CLIENT_IP and/or
366 * HTTP_X_FORWARDED_FOR are set by proxies so check for these before
367 * falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
368 * delimited list in the case of multiple chained proxies; the first is
369 * the originating IP.
371 function getRemoteIp()
373 if(!empty($this->env['HTTP_CLIENT_IP'])){
374 return $this->env['HTTP_CLIENT_IP'];
376 if(!empty($this->env['HTTP_X_FORWARDED_FOR'])){
377 foreach ((strstr($this->env['HTTP_X_FORWARDED_FOR'],',') ? split(',',$this->env['HTTP_X_FORWARDED_FOR']) : array($this->env['HTTP_X_FORWARDED_FOR'])) as $remote_ip){
378 if($remote_ip == 'unknown' ||
379 preg_match('/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/', $remote_ip) ||
380 preg_match('/^([0-9a-fA-F]{4}|0)(\:([0-9a-fA-F]{4}|0)){7}$/', $remote_ip)
382 return $remote_ip;
386 return empty($this->env['REMOTE_ADDR']) ? '' : $this->env['REMOTE_ADDR'];
391 * Returns the domain part of a host, such as akelos.com in 'www.akelos.com'. You can specify
392 * a different <tt>tld_length</tt>, such as 2 to catch akelos.co.uk in 'www.akelos.co.uk'.
394 function getDomain($tld_length = 1)
396 return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$this->getHost()) ?
397 null :
398 join('.',array_slice(explode('.',$this->getHost()),(1 + $tld_length)*-1));
402 * Returns all the subdomains as an array, so ['dev', 'www'] would be returned for 'dev.www.akelos.com'.
403 * You can specify a different <tt>tld_length</tt>, such as 2 to catch ['www'] instead of ['www', 'akelos']
404 * in 'www.akelos.co.uk'.
406 function getSubdomains($tld_length = 1)
408 return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$this->getHost()) ||
409 !strstr($this->getHost(),'.') ? array() : (array)array_slice(explode('.',$this->getHost()),0,(1 + $tld_length)*-1);
414 * Returns the request URI correctly
416 function getRequestUri()
418 return $this->getProtocol().$this->getHostWithPort();
422 * Return 'https://' if( this is an SSL request and 'http://' otherwise.
424 function getProtocol()
426 return $this->isSsl() ? 'https://' : 'http://';
430 * Is this an SSL request?
432 function isSsl()
434 return isset($this->env['HTTPS']) && ($this->env['HTTPS'] === true || $this->env['HTTPS'] == 'on');
438 * Returns the interpreted path to requested resource
440 function getPath()
442 return strstr($this->env['REQUEST_URI'],'?') ? substr($this->env['REQUEST_URI'],0,strpos($this->env['REQUEST_URI'],'?')) : $this->env['REQUEST_URI'];
446 * Returns the port number of this request as an integer.
448 function getPort()
450 $this->port_as_int = AK_WEB_REQUEST ? AK_SERVER_PORT : 80;
451 return $this->port_as_int;
455 * Returns the standard port number for this request's protocol
457 function getStandardPort()
459 return $this->isSsl() ? 443 : 80;
463 * Returns a port suffix like ':8080' if( the port number of this request
464 * is not the default HTTP port 80 or HTTPS port 443.
466 function getPortString()
468 $port = $this->getPort();
469 return $port == $this->getStandardPort() ? '' : ($port ? ':'.$this->getPort() : '');
473 * Returns a host:port string for this request, such as example.com or
474 * example.com:8080.
476 function getHostWithPort()
478 return $this->getHost() . $this->getPortString();
482 function getHost()
484 if(!empty($this->_host)){
485 return $this->_host;
487 return AK_WEB_REQUEST ? $this->env['SERVER_NAME'] : 'localhost';
490 function &getSession()
492 return $_SESSION;
495 function resetSession()
497 $_SESSION = array();
500 function &getCookies()
502 return $_COOKIE;
506 function &getEnv()
508 return $this->env;
512 function getServerSoftware()
514 if(!empty($this->env['SERVER_SOFTWARE'])){
515 if(preg_match('/^([a-zA-Z]+)/', $this->env['SERVER_SOFTWARE'],$match)){
516 return strtolower($match[0]);
519 return '';
524 * Returns true if the request's 'X-Requested-With' header contains
525 * 'XMLHttpRequest'. (The Prototype Javascript library sends this header with
526 * every Ajax request.)
528 function isXmlHttpRequest()
530 return !empty($this->env['HTTP_X_REQUESTED_WITH']) && strstr(strtolower($this->env['HTTP_X_REQUESTED_WITH']),'xmlhttprequest');
532 function xhr()
534 return $this->isXmlHttpRequest();
537 function isAjax()
539 return $this->isXmlHttpRequest();
544 * Receive the raw post data.
545 * This is useful for services such as REST, XMLRPC and SOAP
546 * which communicate over HTTP POST but don't use the traditional parameter format.
548 function getRawPost()
550 return empty($_ENV['RAW_POST_DATA']) ? '' : $_ENV['RAW_POST_DATA'];
554 function _validateTechName($name)
556 return preg_match('/^[A-Za-z]{1,}[A-Za-z0-9_]*$/',$name);
561 // {{{ _mergeRequest()
564 * Populates $this->_request attribute with incoming request in the following precedence:
566 * $_SESSION['request'] <- This will override options provided by previous methods
567 * $_COOKIE
568 * $_POST
569 * $_GET
570 * Command line params
572 * @access public
573 * @return void Void returned. Modifies the private property "
575 function _mergeRequest()
577 $this->_request = array();
579 $session_params = isset($_SESSION['request']) ? $_SESSION['request'] : null;
580 $command_line_params = !empty($_REQUEST) ? $_REQUEST : null;
582 $requests = array($command_line_params, $_GET, array_merge_recursive($_POST, $this->_getNormalizedFilesArray()), $_COOKIE, $session_params);
584 foreach ($requests as $request){
585 $this->_request = (!is_null($request) && is_array($request)) ?
586 array_merge($this->_request,$request) : $this->_request;
590 // }}}
592 function _getNormalizedFilesArray($params = null, $first_call = true)
594 $params = $first_call ? $_FILES : $params;
595 $result = array();
597 $params = array_diff($params,array(''));
598 if(!empty($params) && is_array($params)){
599 foreach ($params as $name=>$details){
601 if(is_array($details) && !empty($details['name']) && !empty($details['tmp_name']) && !empty($details['size'])){
602 if(is_array($details['tmp_name'])){
603 foreach ($details['tmp_name'] as $item=>$item_details){
604 if(is_array($item_details)){
605 foreach (array_keys($item_details) as $k){
606 if(UPLOAD_ERR_NO_FILE != $details['error'][$item][$k]){
607 $result[$name][$item][$k] = array(
608 'name'=>$details['name'][$item][$k],
609 'tmp_name'=>$details['tmp_name'][$item][$k],
610 'size'=>$details['size'][$item][$k],
611 'type'=>$details['type'][$item][$k],
612 'error'=>$details['error'][$item][$k],
616 }else{
617 if(UPLOAD_ERR_NO_FILE != $details['error'][$item]){
618 $result[$name][$item] = array(
619 'name'=>$details['name'][$item],
620 'tmp_name'=>$details['tmp_name'][$item],
621 'size'=>$details['size'][$item],
622 'type'=>$details['type'][$item],
623 'error'=>$details['error'][$item],
628 }elseif ($first_call){
629 $result[$name] = $details;
630 }else{
631 $result[$name][] = $details;
633 }elseif(is_array($details)){
634 $_nested = $this->_getNormalizedFilesArray($details, false);
636 if(!empty($_nested)){
637 $result = array_merge(array($name=>$_nested), $result);
643 return $result;
646 // {{{ _addParams()
649 * Builds (i.e., "expands") the Request class for accessing
650 * the request parameters as public properties.
651 * For example, when the requests is "ak=/controller/action/id&parameter=value",
652 * once parsed, you can access the parameters of the request just like
653 * an object, e.g.:
655 * $value_to_get = $request->parameter
657 * @access private
658 * @return void
660 function _addParam($variable, $value)
662 if($variable[0] != '_'){
663 if( ( $variable == 'action' && !$this->isValidActionName($value)) ||
664 ( $variable == 'controller' && !$this->isValidControllerName($value)) ||
665 ( $variable == 'module' && !$this->isValidModuleName($value))
667 return false;
669 $this->$variable = $value;
670 return true;
672 return false;
675 // }}}
679 * Correct double-escaping problems caused by "magic quotes" in some PHP
680 * installations.
682 function _fixGpcMagic()
684 if(!defined('AK_GPC_MAGIC_FIXED')){
685 if (get_magic_quotes_gpc()) {
686 array_walk($_GET, array('AkRequest', '_fixGpc'));
687 array_walk($_POST, array('AkRequest', '_fixGpc'));
688 array_walk($_COOKIE, array('AkRequest', '_fixGpc'));
690 define('AK_GPC_MAGIC_FIXED',true);
694 function _fixGpc(&$item)
696 if (is_array($item)) {
697 array_walk($item, array('AkRequest', '_fixGpc'));
698 }else {
699 $item = stripslashes($item);
704 function _urlDecode()
706 if(!defined('AK_URL_DECODED')){
707 array_walk($_GET, array('AkRequest', '_performUrlDecode'));
708 define('AK_URL_DECODED',true);
712 function _performUrlDecode(&$item)
714 if (is_array($item)) {
715 array_walk($item, array('AkRequest', '_performUrlDecode'));
716 }else {
717 $item = urldecode($item);
722 // {{{ recognize()
725 * Recognizes a Request and returns the responsible controller instance
727 * @return AkActionController
729 function &recognize($Map = null)
731 AK_ENVIRONMENT != 'setup' ? $this->_connectToDatabase() : null;
732 $this->_startSession();
733 $this->_enableInternationalizationSupport();
734 $this->_mapRoutes($Map);
736 $params = $this->getParams();
738 $module_path = $module_class_peffix = '';
739 if(!empty($params['module'])){
740 $module_path = trim(str_replace(array('/','\\'), DS, Ak::sanitize_include($params['module'], 'high')), DS).DS;
741 $module_shared_model = AK_CONTROLLERS_DIR.DS.trim($module_path,DS).'_controller.php';
742 $module_class_peffix = str_replace(' ','_',AkInflector::titleize(str_replace(DS,' ', trim($module_path, DS)))).'_';
745 $controller_file_name = AkInflector::underscore($params['controller']).'_controller.php';
746 $controller_class_name = $module_class_peffix.AkInflector::camelize($params['controller']).'Controller';
747 $controller_path = AK_CONTROLLERS_DIR.DS.$module_path.$controller_file_name;
748 include_once(AK_APP_DIR.DS.'application_controller.php');
750 if(!empty($module_path) && file_exists($module_shared_model)){
751 include_once($module_shared_model);
754 if(!is_file($controller_path) || !include_once($controller_path)){
755 if(AK_ENVIRONMENT == 'development'){
756 trigger_error(Ak::t('Could not find the file /app/controllers/<i>%controller_file_name</i> for '.
757 'the controller %controller_class_name',
758 array('%controller_file_name'=> $controller_file_name,
759 '%controller_class_name' => $controller_class_name)), E_USER_ERROR);
760 }elseif(@include(AK_PUBLIC_DIR.DS.'404.php')){
761 exit;
762 }else{
763 die('404 Page not found');
766 if(!class_exists($controller_class_name)){
768 if(AK_ENVIRONMENT == 'development'){
769 trigger_error(Ak::t('Controller <i>%controller_name</i> does not exist',
770 array('%controller_name' => $controller_class_name)), E_USER_ERROR);
771 }elseif(@include(AK_PUBLIC_DIR.DS.'404.php')){
772 exit;
773 }else{
774 die('404 Page not found');
777 $Controller =& new $controller_class_name(array('controller'=>true));
778 $Controller->_module_path = $module_path;
779 isset($_SESSION) ? $Controller->session =& $_SESSION : null;
780 return $Controller;
784 // }}}
786 function _enableInternationalizationSupport()
788 if(AK_AVAILABLE_LOCALES != 'en'){
789 require_once(AK_LIB_DIR.DS.'AkLocaleManager.php');
791 $LocaleManager = new AkLocaleManager();
792 $LocaleManager->init();
793 $LocaleManager->initApplicationInternationalization($this);
794 $this->__internationalization_support_enabled = true;
798 function _mapRoutes($Map = null)
800 require_once(AK_LIB_DIR.DS.'AkRouter.php');
801 if(is_file(AK_ROUTES_MAPPING_FILE)){
802 if(empty($Map)){
803 $Map =& AkRouter();
805 include(AK_ROUTES_MAPPING_FILE);
806 // Set this routes for being used via Ak::toUrl
807 Ak::toUrl($Map,true);
808 $this->checkForRoutedRequests($Map);
813 function _connectToDatabase()
815 if(AK_AUTOMATIC_DB_CONNECTION){
816 Ak::db(AK_DEFAULT_DATABASE_PROFILE);
820 function _startSession()
822 if(AK_AUTOMATIC_SESSION_START){
823 if(!isset($_SESSION)){
824 if(AK_SESSION_HANDLER == 1 && defined('AK_DATABASE_CONNECTION_AVAILABLE') && AK_DATABASE_CONNECTION_AVAILABLE){
825 require_once(AK_LIB_DIR.DS.'AkDbSession.php');
827 $AkDbSession = new AkDbSession();
828 $AkDbSession->session_life = AK_SESSION_EXPIRE;
829 session_set_save_handler (
830 array(&$AkDbSession, '_open'),
831 array(&$AkDbSession, '_close'),
832 array(&$AkDbSession, '_read'),
833 array(&$AkDbSession, '_write'),
834 array(&$AkDbSession, '_destroy'),
835 array(&$AkDbSession, '_gc')
838 @session_start();
845 function &AkRequest()
847 $null = null;
848 $AkRequest =& Ak::singleton('AkRequest', $null);
849 return $AkRequest;