1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707
  1. <?php
  2. # ============================================================================ #
  3. /**
  4. * L I M O N A D E
  5. *
  6. * a PHP micro framework.
  7. *
  8. * For more informations: {@link https://github.com/sofadesign/limonade}
  9. *
  10. * @author Fabrice Luraine
  11. * @copyright Copyright (c) 2009 Fabrice Luraine
  12. * @license http://opensource.org/licenses/mit-license.php The MIT License
  13. * @package limonade
  14. */
  15. # ----------------------------------------------------------------------- #
  16. # Copyright (c) 2009 Fabrice Luraine #
  17. # #
  18. # Permission is hereby granted, free of charge, to any person #
  19. # obtaining a copy of this software and associated documentation #
  20. # files (the "Software"), to deal in the Software without #
  21. # restriction, including without limitation the rights to use, #
  22. # copy, modify, merge, publish, distribute, sublicense, and/or sell #
  23. # copies of the Software, and to permit persons to whom the #
  24. # Software is furnished to do so, subject to the following #
  25. # conditions: #
  26. # #
  27. # The above copyright notice and this permission notice shall be #
  28. # included in all copies or substantial portions of the Software. #
  29. # #
  30. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, #
  31. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES #
  32. # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND #
  33. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT #
  34. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, #
  35. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING #
  36. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
  37. # OTHER DEALINGS IN THE SOFTWARE. #
  38. # ============================================================================ #
  39. # ============================================================================ #
  40. # 0. PREPARE #
  41. # ============================================================================ #
  42. ## CONSTANTS __________________________________________________________________
  43. /**
  44. * Limonade version
  45. */
  46. define('LIMONADE', '0.5.0');
  47. define('LIM_NAME', 'Un grand cru qui sait se faire attendre');
  48. define('LIM_START_MICROTIME', microtime(true));
  49. define('LIM_SESSION_NAME', 'LIMONADE'.str_replace('.','x',LIMONADE));
  50. define('LIM_SESSION_FLASH_KEY', '_lim_flash_messages');
  51. if(function_exists('memory_get_usage'))
  52. define('LIM_START_MEMORY', memory_get_usage());
  53. define('E_LIM_HTTP', 32768);
  54. define('E_LIM_PHP', 65536);
  55. define('E_LIM_DEPRECATED', 35000);
  56. define('NOT_FOUND', 404);
  57. define('SERVER_ERROR', 500);
  58. define('ENV_PRODUCTION', 10);
  59. define('ENV_DEVELOPMENT', 100);
  60. define('X-SENDFILE', 10);
  61. define('X-LIGHTTPD-SEND-FILE', 20);
  62. # for PHP 5.3.0 <
  63. if(!defined('E_DEPRECATED')) define('E_DEPRECATED', 8192);
  64. if(!defined('E_USER_DEPRECATED')) define('E_USER_DEPRECATED', 16384);
  65. # for PHP 5.2.0 <
  66. if (!defined('E_RECOVERABLE_ERROR')) define('E_RECOVERABLE_ERROR', 4096);
  67. ## SETTING BASIC SECURITY _____________________________________________________
  68. # A. Unsets all global variables set from a superglobal array
  69. /**
  70. * @access private
  71. * @return void
  72. */
  73. function unregister_globals()
  74. {
  75. $args = func_get_args();
  76. foreach($args as $k => $v)
  77. if(array_key_exists($k, $GLOBALS)) unset($GLOBALS[$k]);
  78. }
  79. if(ini_get('register_globals'))
  80. {
  81. unregister_globals( '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER',
  82. '_ENV', '_FILES');
  83. ini_set('register_globals', 0);
  84. }
  85. # B. removing magic quotes
  86. /**
  87. * @access private
  88. * @param string $array
  89. * @return array
  90. */
  91. function remove_magic_quotes($array)
  92. {
  93. foreach ($array as $k => $v)
  94. $array[$k] = is_array($v) ? remove_magic_quotes($v) : stripslashes($v);
  95. return $array;
  96. }
  97. if (get_magic_quotes_gpc())
  98. {
  99. $_GET = remove_magic_quotes($_GET);
  100. $_POST = remove_magic_quotes($_POST);
  101. $_COOKIE = remove_magic_quotes($_COOKIE);
  102. ini_set('magic_quotes_gpc', 0);
  103. }
  104. if(function_exists('set_magic_quotes_runtime') && get_magic_quotes_runtime()) set_magic_quotes_runtime(false);
  105. # C. Disable error display
  106. # by default, no error reporting; it will be switched on later in run().
  107. # ini_set('display_errors', 1); must be called explicitly in app file
  108. # if you want to show errors before running app
  109. ini_set('display_errors', 0);
  110. ## SETTING INTERNAL ROUTES _____________________________________________________
  111. dispatch(array("/_lim_css/*.css", array('_lim_css_filename')), 'render_limonade_css');
  112. /**
  113. * Internal controller that responds to route /_lim_css/*.css
  114. *
  115. * @access private
  116. * @return string
  117. */
  118. function render_limonade_css()
  119. {
  120. option('views_dir', file_path(option('limonade_public_dir'), 'css'));
  121. $fpath = file_path(params('_lim_css_filename').".css");
  122. return css($fpath, null); // with no layout
  123. }
  124. dispatch(array("/_lim_public/**", array('_lim_public_file')), 'render_limonade_file');
  125. /**
  126. * Internal controller that responds to route /_lim_public/**
  127. *
  128. * @access private
  129. * @return void
  130. */
  131. function render_limonade_file()
  132. {
  133. $fpath = file_path(option('limonade_public_dir'), params('_lim_public_file'));
  134. return render_file($fpath, true);
  135. }
  136. # # #
  137. # ============================================================================ #
  138. # 1. BASE #
  139. # ============================================================================ #
  140. ## ABSTRACTS ___________________________________________________________________
  141. # Abstract methods that might be redefined by user:
  142. #
  143. # - function configure(){}
  144. # - function initialize(){}
  145. # - function autoload_controller($callback){}
  146. # - function before($route){}
  147. # - function after($output, $route){}
  148. # - function not_found($errno, $errstr, $errfile=null, $errline=null){}
  149. # - function server_error($errno, $errstr, $errfile=null, $errline=null){}
  150. # - function route_missing($request_method, $request_uri){}
  151. # - function before_exit(){}
  152. # - function before_render($content_or_func, $layout, $locals, $view_path){}
  153. # - function autorender($route){}
  154. # - function before_sending_header($header){}
  155. #
  156. # See abstract.php for more details.
  157. ## MAIN PUBLIC FUNCTIONS _______________________________________________________
  158. /**
  159. * Set and returns options values
  160. *
  161. * If multiple values are provided, set $name option with an array of those values.
  162. * If there is only one value, set $name option with the provided $values
  163. *
  164. * @param string $name
  165. * @param mixed $values,...
  166. * @return mixed option value for $name if $name argument is provided, else return all options
  167. */
  168. function option($name = null, $values = null)
  169. {
  170. static $options = array();
  171. $args = func_get_args();
  172. $name = array_shift($args);
  173. if(is_null($name)) return $options;
  174. if(!empty($args))
  175. {
  176. $options[$name] = count($args) > 1 ? $args : $args[0];
  177. }
  178. if(array_key_exists($name, $options)) return $options[$name];
  179. return;
  180. }
  181. /**
  182. * Set and returns params
  183. *
  184. * Depending on provided arguments:
  185. *
  186. * * Reset params if first argument is null
  187. *
  188. * * If first argument is an array, merge it with current params
  189. *
  190. * * If there is a second argument $value, set param $name (first argument) with $value
  191. * <code>
  192. * params('name', 'Doe') // set 'name' => 'Doe'
  193. * </code>
  194. * * If there is more than 2 arguments, set param $name (first argument) value with
  195. * an array of next arguments
  196. * <code>
  197. * params('months', 'jan', 'feb', 'mar') // set 'month' => array('months', 'jan', 'feb', 'mar')
  198. * </code>
  199. *
  200. * @param mixed $name_or_array_or_null could be null || array of params || name of a param (optional)
  201. * @param mixed $value,... for the $name param (optional)
  202. * @return mixed all params, or one if a first argument $name is provided
  203. */
  204. function params($name_or_array_or_null = null, $value = null)
  205. {
  206. static $params = array();
  207. $args = func_get_args();
  208. if(func_num_args() > 0)
  209. {
  210. $name = array_shift($args);
  211. if(is_null($name))
  212. {
  213. # Reset params
  214. $params = array();
  215. return $params;
  216. }
  217. if(is_array($name))
  218. {
  219. $params = array_merge($params, $name);
  220. return $params;
  221. }
  222. $nargs = count($args);
  223. if($nargs > 0)
  224. {
  225. $value = $nargs > 1 ? $args : $args[0];
  226. $params[$name] = $value;
  227. }
  228. return array_key_exists($name,$params) ? $params[$name] : null;
  229. }
  230. return $params;
  231. }
  232. /**
  233. * Set and returns template variables
  234. *
  235. * If multiple values are provided, set $name variable with an array of those values.
  236. * If there is only one value, set $name variable with the provided $values
  237. *
  238. * @param string $name
  239. * @param mixed $values,...
  240. * @return mixed variable value for $name if $name argument is provided, else return all variables
  241. */
  242. function set($name = null, $values = null)
  243. {
  244. static $vars = array();
  245. $args = func_get_args();
  246. $name = array_shift($args);
  247. if(is_null($name)) return $vars;
  248. if(!empty($args))
  249. {
  250. $vars[$name] = count($args) > 1 ? $args : $args[0];
  251. }
  252. if(array_key_exists($name, $vars)) return $vars[$name];
  253. return $vars;
  254. }
  255. /**
  256. * Sets a template variable with a value or a default value if value is empty
  257. *
  258. * @param string $name
  259. * @param string $value
  260. * @param string $default
  261. * @return mixed setted value
  262. */
  263. function set_or_default($name, $value, $default)
  264. {
  265. return set($name, value_or_default($value, $default));
  266. }
  267. /**
  268. * Running application
  269. *
  270. * @param string $env
  271. * @return void
  272. */
  273. function run($env = null)
  274. {
  275. if(is_null($env)) $env = env();
  276. # 0. Set default configuration
  277. $root_dir = dirname(app_file());
  278. $lim_dir = dirname(__FILE__);
  279. $base_path = dirname(file_path($env['SERVER']['SCRIPT_NAME']));
  280. $base_file = basename($env['SERVER']['SCRIPT_NAME']);
  281. $base_uri = file_path($base_path, (($base_file == 'index.php') ? '?' : $base_file.'?'));
  282. option('root_dir', $root_dir);
  283. option('limonade_dir', file_path($lim_dir));
  284. option('limonade_views_dir', file_path($lim_dir, 'limonade', 'views'));
  285. option('limonade_public_dir',file_path($lim_dir, 'limonade', 'public'));
  286. option('public_dir', file_path($root_dir, 'public'));
  287. option('views_dir', file_path($root_dir, 'views'));
  288. option('controllers_dir', file_path($root_dir, 'controllers'));
  289. option('lib_dir', file_path($root_dir, 'lib'));
  290. option('error_views_dir', option('limonade_views_dir'));
  291. option('base_path', $base_path);
  292. option('base_uri', $base_uri); // set it manually if you use url_rewriting
  293. option('env', ENV_PRODUCTION);
  294. option('debug', true);
  295. option('session', LIM_SESSION_NAME); // true, false or the name of your session
  296. option('encoding', 'utf-8');
  297. option('signature', LIM_NAME); // X-Limonade header value or false to hide it
  298. option('gzip', false);
  299. option('x-sendfile', 0); // 0: disabled,
  300. // X-SENDFILE: for Apache and Lighttpd v. >= 1.5,
  301. // X-LIGHTTPD-SEND-FILE: for Apache and Lighttpd v. < 1.5
  302. # 1. Set handlers
  303. # 1.1 Set error handling
  304. ini_set('display_errors', 1);
  305. set_error_handler('error_handler_dispatcher', E_ALL ^ E_NOTICE);
  306. # 1.2 Register shutdown function
  307. register_shutdown_function('stop_and_exit');
  308. # 2. Set user configuration
  309. call_if_exists('configure');
  310. # 2.1 Set gzip compression if defined
  311. if(is_bool(option('gzip')) && option('gzip'))
  312. {
  313. ini_set('zlib.output_compression', '1');
  314. }
  315. # 2.2 Set X-Limonade header
  316. if($signature = option('signature')) send_header("X-Limonade: $signature");
  317. # 3. Loading libs
  318. require_once_dir(option('lib_dir'));
  319. fallbacks_for_not_implemented_functions();
  320. # 4. Starting session
  321. if(!defined('SID') && option('session'))
  322. {
  323. if(!is_bool(option('session'))) session_name(option('session'));
  324. if(!session_start()) trigger_error("An error occured while trying to start the session", E_USER_WARNING);
  325. }
  326. # 5. Set some default methods if needed
  327. if(!function_exists('after'))
  328. {
  329. function after($output)
  330. {
  331. return $output;
  332. }
  333. }
  334. if(!function_exists('route_missing'))
  335. {
  336. function route_missing($request_method, $request_uri)
  337. {
  338. halt(NOT_FOUND, "($request_method) $request_uri");
  339. }
  340. }
  341. call_if_exists('initialize');
  342. # 6. Check request
  343. if($rm = request_method($env))
  344. {
  345. if(request_is_head($env)) ob_start(); // then no output
  346. if(!request_method_is_allowed($rm))
  347. halt(HTTP_NOT_IMPLEMENTED, "The requested method <code>'$rm'</code> is not implemented");
  348. # 6.1 Check matching route
  349. if($route = route_find($rm, request_uri($env)))
  350. {
  351. params($route['params']);
  352. # 6.2 Load controllers dir
  353. if(!function_exists('autoload_controller'))
  354. {
  355. function autoload_controller($callback)
  356. {
  357. require_once_dir(option('controllers_dir'));
  358. }
  359. }
  360. autoload_controller($route['callback']);
  361. if(is_callable($route['callback']))
  362. {
  363. # 6.3 Call before function
  364. call_if_exists('before', $route);
  365. # 6.4 Call matching controller function and output result
  366. $output = call_user_func_array($route['callback'], array_values($route['params']));
  367. if(is_null($output)) $output = call_if_exists('autorender', $route);
  368. echo after(error_notices_render() . $output, $route);
  369. }
  370. else halt(SERVER_ERROR, "Routing error: undefined function '{$route['callback']}'", $route);
  371. }
  372. else route_missing($rm, request_uri($env));
  373. }
  374. else halt(HTTP_NOT_IMPLEMENTED, "The requested method <code>'$rm'</code> is not implemented");
  375. }
  376. /**
  377. * Stop and exit limonade application
  378. *
  379. * @access private
  380. * @param boolean exit or not
  381. * @return void
  382. */
  383. function stop_and_exit($exit = true)
  384. {
  385. call_if_exists('before_exit', $exit);
  386. $headers = headers_list();
  387. if(request_is_head())
  388. {
  389. ob_end_clean();
  390. } else {
  391. $flash_sweep = true;
  392. foreach($headers as $header)
  393. {
  394. // If a Content-Type header exists, flash_sweep only if is text/html
  395. // Else if there's no Content-Type header, flash_sweep by default
  396. if(stripos($header, 'Content-Type:') === 0)
  397. {
  398. $flash_sweep = stripos($header, 'Content-Type: text/html') === 0;
  399. break;
  400. }
  401. }
  402. if($flash_sweep) flash_sweep();
  403. }
  404. if(defined('SID')) session_write_close();
  405. if($exit) exit;
  406. }
  407. /**
  408. * Returns limonade environment variables:
  409. *
  410. * 'SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE',
  411. * 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'
  412. *
  413. * If a null argument is passed, reset and rebuild environment
  414. *
  415. * @param null @reset reset and rebuild environment
  416. * @return array
  417. */
  418. function env($reset = null)
  419. {
  420. static $env = array();
  421. if(func_num_args() > 0)
  422. {
  423. $args = func_get_args();
  424. if(is_null($args[0])) $env = array();
  425. }
  426. if(empty($env))
  427. {
  428. if(empty($GLOBALS['_SERVER']))
  429. {
  430. // Fixing empty $GLOBALS['_SERVER'] bug
  431. // http://sofadesign.lighthouseapp.com/projects/29612-limonade/tickets/29-env-is-empty
  432. $GLOBALS['_SERVER'] =& $_SERVER;
  433. $GLOBALS['_FILES'] =& $_FILES;
  434. $GLOBALS['_REQUEST'] =& $_REQUEST;
  435. $GLOBALS['_SESSION'] =& $_SESSION;
  436. $GLOBALS['_ENV'] =& $_ENV;
  437. $GLOBALS['_COOKIE'] =& $_COOKIE;
  438. }
  439. $glo_names = array('SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE');
  440. $vars = array_merge($glo_names, request_methods());
  441. foreach($vars as $var)
  442. {
  443. $varname = "_$var";
  444. if(!array_key_exists($varname, $GLOBALS)) $GLOBALS[$varname] = array();
  445. $env[$var] =& $GLOBALS[$varname];
  446. }
  447. $method = request_method($env);
  448. $varname = "_$method";
  449. if ((isset($_SERVER['CONTENT_TYPE'])) && (strpos($_SERVER['CONTENT_TYPE'], 'application/json') === 0))
  450. {
  451. // handle PUT/POST requests which have JSON in request body
  452. $GLOBALS[$varname] = json_decode(file_get_contents('php://input'), true);
  453. }
  454. elseif($method == 'PUT' || $method == 'DELETE')
  455. {
  456. if(array_key_exists('_method', $_POST) && $_POST['_method'] == $method)
  457. {
  458. foreach($_POST as $k => $v)
  459. {
  460. if($k == "_method") continue;
  461. $GLOBALS[$varname][$k] = $v;
  462. }
  463. }
  464. else
  465. {
  466. parse_str(file_get_contents('php://input'), $GLOBALS[$varname]);
  467. }
  468. }
  469. }
  470. return $env;
  471. }
  472. /**
  473. * Returns application root file path
  474. *
  475. * @return string
  476. */
  477. function app_file()
  478. {
  479. static $file;
  480. if(empty($file))
  481. {
  482. $debug_backtrace = debug_backtrace();
  483. $stacktrace = array_pop($debug_backtrace);
  484. $file = $stacktrace['file'];
  485. }
  486. return file_path($file);
  487. }
  488. # # #
  489. # ============================================================================ #
  490. # 2. ERROR #
  491. # ============================================================================ #
  492. /**
  493. * Associate a function with error code(s) and return all associations
  494. *
  495. * @param string $errno
  496. * @param string $function
  497. * @return array
  498. */
  499. function error($errno = null, $function = null)
  500. {
  501. static $errors = array();
  502. if(func_num_args() > 0)
  503. {
  504. $errors[] = array('errno'=>$errno, 'function'=> $function);
  505. }
  506. return $errors;
  507. }
  508. /**
  509. * Raise an error, passing a given error number and an optional message,
  510. * then exit.
  511. * Error number should be a HTTP status code or a php user error (E_USER...)
  512. * $errno and $msg arguments can be passsed in any order
  513. * If no arguments are passed, default $errno is SERVER_ERROR (500)
  514. *
  515. * @param int,string $errno Error number or message string
  516. * @param string,string $msg Message string or error number
  517. * @param mixed $debug_args extra data provided for debugging
  518. * @return void
  519. */
  520. function halt($errno = SERVER_ERROR, $msg = '', $debug_args = null)
  521. {
  522. $args = func_get_args();
  523. $error = array_shift($args);
  524. # switch $errno and $msg args
  525. # TODO cleanup / refactoring
  526. if(is_string($errno))
  527. {
  528. $msg = $errno;
  529. $oldmsg = array_shift($args);
  530. $errno = empty($oldmsg) ? SERVER_ERROR : $oldmsg;
  531. }
  532. else if(!empty($args)) $msg = array_shift($args);
  533. if(empty($msg) && $errno == NOT_FOUND) $msg = request_uri();
  534. if(empty($msg)) $msg = "";
  535. if(!empty($args)) $debug_args = $args;
  536. set('_lim_err_debug_args', $debug_args);
  537. error_handler_dispatcher($errno, $msg, null, null);
  538. }
  539. /**
  540. * Internal error handler dispatcher
  541. * Find and call matching error handler and exit
  542. * If no match found, call default error handler
  543. *
  544. * @access private
  545. * @param int $errno
  546. * @param string $errstr
  547. * @param string $errfile
  548. * @param string $errline
  549. * @return void
  550. */
  551. function error_handler_dispatcher($errno, $errstr, $errfile, $errline)
  552. {
  553. $back_trace = debug_backtrace();
  554. while($trace = array_shift($back_trace))
  555. {
  556. if($trace['function'] == 'halt')
  557. {
  558. $errfile = $trace['file'];
  559. $errline = $trace['line'];
  560. break;
  561. }
  562. }
  563. # Notices and warning won't halt execution
  564. if(error_wont_halt_app($errno))
  565. {
  566. error_notice($errno, $errstr, $errfile, $errline);
  567. return;
  568. }
  569. else
  570. {
  571. # Other errors will stop application
  572. static $handlers = array();
  573. if(empty($handlers))
  574. {
  575. error(E_LIM_PHP, 'error_default_handler');
  576. $handlers = error();
  577. }
  578. $is_http_err = http_response_status_is_valid($errno);
  579. while($handler = array_shift($handlers))
  580. {
  581. $e = is_array($handler['errno']) ? $handler['errno'] : array($handler['errno']);
  582. while($ee = array_shift($e))
  583. {
  584. if($ee == $errno || $ee == E_LIM_PHP || ($ee == E_LIM_HTTP && $is_http_err))
  585. {
  586. echo call_if_exists($handler['function'], $errno, $errstr, $errfile, $errline);
  587. exit;
  588. }
  589. }
  590. }
  591. }
  592. }
  593. /**
  594. * Default error handler
  595. *
  596. * @param string $errno
  597. * @param string $errstr
  598. * @param string $errfile
  599. * @param string $errline
  600. * @return string error output
  601. */
  602. function error_default_handler($errno, $errstr, $errfile, $errline)
  603. {
  604. $is_http_err = http_response_status_is_valid($errno);
  605. $http_error_code = $is_http_err ? $errno : SERVER_ERROR;
  606. status($http_error_code);
  607. return $http_error_code == NOT_FOUND ?
  608. error_not_found_output($errno, $errstr, $errfile, $errline) :
  609. error_server_error_output($errno, $errstr, $errfile, $errline);
  610. }
  611. /**
  612. * Returns not found error output
  613. *
  614. * @access private
  615. * @param string $msg
  616. * @return string
  617. */
  618. function error_not_found_output($errno, $errstr, $errfile, $errline)
  619. {
  620. if(!function_exists('not_found'))
  621. {
  622. /**
  623. * Default not found error output
  624. *
  625. * @param string $errno
  626. * @param string $errstr
  627. * @param string $errfile
  628. * @param string $errline
  629. * @return string
  630. */
  631. function not_found($errno, $errstr, $errfile=null, $errline=null)
  632. {
  633. option('views_dir', option('error_views_dir'));
  634. $msg = h(rawurldecode($errstr));
  635. return html("<h1>Page not found:</h1><p><code>{$msg}</code></p>", error_layout());
  636. }
  637. }
  638. return not_found($errno, $errstr, $errfile, $errline);
  639. }
  640. /**
  641. * Returns server error output
  642. *
  643. * @access private
  644. * @param int $errno
  645. * @param string $errstr
  646. * @param string $errfile
  647. * @param string $errline
  648. * @return string
  649. */
  650. function error_server_error_output($errno, $errstr, $errfile, $errline)
  651. {
  652. if(!function_exists('server_error'))
  653. {
  654. /**
  655. * Default server error output
  656. *
  657. * @param string $errno
  658. * @param string $errstr
  659. * @param string $errfile
  660. * @param string $errline
  661. * @return string
  662. */
  663. function server_error($errno, $errstr, $errfile=null, $errline=null)
  664. {
  665. $is_http_error = http_response_status_is_valid($errno);
  666. $args = compact('errno', 'errstr', 'errfile', 'errline', 'is_http_error');
  667. option('views_dir', option('limonade_views_dir'));
  668. $html = render('error.html.php', null, $args);
  669. option('views_dir', option('error_views_dir'));
  670. return html($html, error_layout(), $args);
  671. }
  672. }
  673. return server_error($errno, $errstr, $errfile, $errline);
  674. }
  675. /**
  676. * Set and returns error output layout
  677. *
  678. * @param string $layout
  679. * @return string
  680. */
  681. function error_layout($layout = false)
  682. {
  683. static $o_layout = 'default_layout.php';
  684. if($layout !== false)
  685. {
  686. option('error_views_dir', option('views_dir'));
  687. $o_layout = $layout;
  688. }
  689. return $o_layout;
  690. }
  691. /**
  692. * Set a notice if arguments are provided
  693. * Returns all stored notices.
  694. * If $errno argument is null, reset the notices array
  695. *
  696. * @access private
  697. * @param string, null $str
  698. * @return array
  699. */
  700. function error_notice($errno = false, $errstr = null, $errfile = null, $errline = null)
  701. {
  702. static $notices = array();
  703. if($errno) $notices[] = compact('errno', 'errstr', 'errfile', 'errline');
  704. else if(is_null($errno)) $notices = array();
  705. return $notices;
  706. }
  707. /**
  708. * Returns notices output rendering and reset notices
  709. *
  710. * @return string
  711. */
  712. function error_notices_render()
  713. {
  714. if(option('debug') && option('env') > ENV_PRODUCTION)
  715. {
  716. $notices = error_notice();
  717. error_notice(null); // reset notices
  718. $c_view_dir = option('views_dir'); // keep for restore after render
  719. option('views_dir', option('limonade_views_dir'));
  720. $o = render('_notices.html.php', null, array('notices' => $notices));
  721. option('views_dir', $c_view_dir); // restore current views dir
  722. return $o;
  723. }
  724. }
  725. /**
  726. * Checks if an error is will halt application execution.
  727. * Notices and warnings will not.
  728. *
  729. * @access private
  730. * @param string $num error code number
  731. * @return boolean
  732. */
  733. function error_wont_halt_app($num)
  734. {
  735. return $num == E_NOTICE ||
  736. $num == E_WARNING ||
  737. $num == E_CORE_WARNING ||
  738. $num == E_COMPILE_WARNING ||
  739. $num == E_USER_WARNING ||
  740. $num == E_USER_NOTICE ||
  741. $num == E_DEPRECATED ||
  742. $num == E_USER_DEPRECATED ||
  743. $num == E_LIM_DEPRECATED;
  744. }
  745. /**
  746. * return error code name for a given code num, or return all errors names
  747. *
  748. * @param string $num
  749. * @return mixed
  750. */
  751. function error_type($num = null)
  752. {
  753. $types = array (
  754. E_ERROR => 'ERROR',
  755. E_WARNING => 'WARNING',
  756. E_PARSE => 'PARSING ERROR',
  757. E_NOTICE => 'NOTICE',
  758. E_CORE_ERROR => 'CORE ERROR',
  759. E_CORE_WARNING => 'CORE WARNING',
  760. E_COMPILE_ERROR => 'COMPILE ERROR',
  761. E_COMPILE_WARNING => 'COMPILE WARNING',
  762. E_USER_ERROR => 'USER ERROR',
  763. E_USER_WARNING => 'USER WARNING',
  764. E_USER_NOTICE => 'USER NOTICE',
  765. E_STRICT => 'STRICT NOTICE',
  766. E_RECOVERABLE_ERROR => 'RECOVERABLE ERROR',
  767. E_DEPRECATED => 'DEPRECATED WARNING',
  768. E_USER_DEPRECATED => 'USER DEPRECATED WARNING',
  769. E_LIM_DEPRECATED => 'LIMONADE DEPRECATED WARNING'
  770. );
  771. return is_null($num) ? $types : $types[$num];
  772. }
  773. /**
  774. * Returns http response status for a given error number
  775. *
  776. * @param string $errno
  777. * @return int
  778. */
  779. function error_http_status($errno)
  780. {
  781. $code = http_response_status_is_valid($errno) ? $errno : SERVER_ERROR;
  782. return http_response_status($code);
  783. }
  784. # # #
  785. # ============================================================================ #
  786. # 3. REQUEST #
  787. # ============================================================================ #
  788. /**
  789. * Returns current request method for a given environment or current one
  790. *
  791. * @param string $env
  792. * @return string
  793. */
  794. function request_method($env = null)
  795. {
  796. if(is_null($env)) $env = env();
  797. $m = array_key_exists('REQUEST_METHOD', $env['SERVER']) ? $env['SERVER']['REQUEST_METHOD'] : null;
  798. if($m == "POST" && array_key_exists('_method', $env['POST']))
  799. $m = strtoupper($env['POST']['_method']);
  800. if(!in_array(strtoupper($m), request_methods()))
  801. {
  802. trigger_error("'$m' request method is unknown or unavailable.", E_USER_WARNING);
  803. $m = false;
  804. }
  805. return $m;
  806. }
  807. /**
  808. * Checks if a request method or current one is allowed
  809. *
  810. * @param string $m
  811. * @return bool
  812. */
  813. function request_method_is_allowed($m = null)
  814. {
  815. if(is_null($m)) $m = request_method();
  816. return in_array(strtoupper($m), request_methods());
  817. }
  818. /**
  819. * Checks if request method is GET
  820. *
  821. * @param string $env
  822. * @return bool
  823. */
  824. function request_is_get($env = null)
  825. {
  826. return request_method($env) == "GET";
  827. }
  828. /**
  829. * Checks if request method is POST
  830. *
  831. * @param string $env
  832. * @return bool
  833. */
  834. function request_is_post($env = null)
  835. {
  836. return request_method($env) == "POST";
  837. }
  838. /**
  839. * Checks if request method is PUT
  840. *
  841. * @param string $env
  842. * @return bool
  843. */
  844. function request_is_put($env = null)
  845. {
  846. return request_method($env) == "PUT";
  847. }
  848. /**
  849. * Checks if request method is DELETE
  850. *
  851. * @param string $env
  852. * @return bool
  853. */
  854. function request_is_delete($env = null)
  855. {
  856. return request_method($env) == "DELETE";
  857. }
  858. /**
  859. * Checks if request method is HEAD
  860. *
  861. * @param string $env
  862. * @return bool
  863. */
  864. function request_is_head($env = null)
  865. {
  866. return request_method($env) == "HEAD";
  867. }
  868. /**
  869. * Checks if request method is PATCH
  870. *
  871. * @param string $env
  872. * @return bool
  873. */
  874. function request_is_patch($env = null)
  875. {
  876. return request_method($env) == "PATCH";
  877. }
  878. /**
  879. * Returns allowed request methods
  880. *
  881. * @return array
  882. */
  883. function request_methods()
  884. {
  885. return array("GET","POST","PUT","DELETE","HEAD","PATCH");
  886. }
  887. /**
  888. * Returns current request uri (the path that will be compared with routes)
  889. *
  890. * (Inspired from codeigniter URI::_fetch_uri_string method)
  891. *
  892. * @return string
  893. */
  894. function request_uri($env = null)
  895. {
  896. static $uri = null;
  897. if(is_null($env))
  898. {
  899. if(!is_null($uri)) return $uri;
  900. $env = env();
  901. }
  902. if(array_key_exists('uri', $env['GET']))
  903. {
  904. $uri = $env['GET']['uri'];
  905. }
  906. else if(array_key_exists('u', $env['GET']))
  907. {
  908. $uri = $env['GET']['u'];
  909. }
  910. // bug: dot are converted to _... so we can't use it...
  911. // else if (count($env['GET']) == 1 && trim(key($env['GET']), '/') != '')
  912. // {
  913. // $uri = key($env['GET']);
  914. // }
  915. else
  916. {
  917. $app_file = app_file();
  918. $path_info = isset($env['SERVER']['PATH_INFO']) ? $env['SERVER']['PATH_INFO'] : @getenv('PATH_INFO');
  919. $query_string = isset($env['SERVER']['QUERY_STRING']) ? $env['SERVER']['QUERY_STRING'] : @getenv('QUERY_STRING');
  920. // Is there a PATH_INFO variable?
  921. // Note: some servers seem to have trouble with getenv() so we'll test it two ways
  922. if (trim($path_info, '/') != '' && $path_info != "/".$app_file)
  923. {
  924. if(strpos($path_info, '&') !== 0)
  925. {
  926. # exclude GET params
  927. $params = explode('&', $path_info);
  928. $path_info = array_shift($params);
  929. # populate $_GET
  930. foreach($params as $param)
  931. {
  932. if(strpos($param, '=') > 0)
  933. {
  934. list($k, $v) = explode('=', $param);
  935. $env['GET'][$k] = $v;
  936. }
  937. }
  938. }
  939. $uri = $path_info;
  940. }
  941. // No PATH_INFO?... What about QUERY_STRING?
  942. elseif (trim($query_string, '/') != '' && $query_string[0] == '/')
  943. {
  944. $uri = $query_string;
  945. $get = $env['GET'];
  946. if(count($get) > 0)
  947. {
  948. # exclude GET params
  949. $keys = array_keys($get);
  950. $first = array_shift($keys);
  951. if(strpos($query_string, $first) === 0) $uri = $first;
  952. }
  953. }
  954. elseif(array_key_exists('REQUEST_URI', $env['SERVER']) && !empty($env['SERVER']['REQUEST_URI']))
  955. {
  956. $request_uri = rtrim($env['SERVER']['REQUEST_URI'], '?/').'/';
  957. $base_path = $env['SERVER']['SCRIPT_NAME'];
  958. if($request_uri."index.php" == $base_path) $request_uri .= "index.php";
  959. $uri = str_replace($base_path, '', $request_uri);
  960. if(option('base_uri') && strpos($uri, option('base_uri')) === 0) {
  961. $uri = substr($uri, strlen(option('base_uri')));
  962. }
  963. if(strpos($uri, '?') !== false) {
  964. $uri = substr($uri, 0, strpos($uri, '?')) . '/';
  965. }
  966. }
  967. elseif($env['SERVER']['argc'] > 1 && trim($env['SERVER']['argv'][1], '/') != '')
  968. {
  969. $uri = $env['SERVER']['argv'][1];
  970. }
  971. }
  972. $uri = rtrim($uri, "/"); # removes ending /
  973. if(empty($uri))
  974. {
  975. $uri = '/';
  976. }
  977. else if($uri[0] != '/')
  978. {
  979. $uri = '/' . $uri; # add a leading slash
  980. }
  981. return rawurldecode($uri);
  982. }
  983. # # #
  984. # ============================================================================ #
  985. # 4. ROUTER #
  986. # ============================================================================ #
  987. /**
  988. * An alias of {@link dispatch_get()}
  989. *
  990. * @return void
  991. */
  992. function dispatch($path_or_array, $callback, $options = array())
  993. {
  994. dispatch_get($path_or_array, $callback, $options);
  995. }
  996. /**
  997. * Add a GET route. Also automatically defines a HEAD route.
  998. *
  999. * @param string $path_or_array
  1000. * @param string $callback
  1001. * @param array $options (optional). See {@link route()} for available options.
  1002. * @return void
  1003. */
  1004. function dispatch_get($path_or_array, $callback, $options = array())
  1005. {
  1006. route("GET", $path_or_array, $callback, $options);
  1007. route("HEAD", $path_or_array, $callback, $options);
  1008. }
  1009. /**
  1010. * Add a POST route
  1011. *
  1012. * @param string $path_or_array
  1013. * @param string $callback
  1014. * @param array $options (optional). See {@link route()} for available options.
  1015. * @return void
  1016. */
  1017. function dispatch_post($path_or_array, $callback, $options = array())
  1018. {
  1019. route("POST", $path_or_array, $callback, $options);
  1020. }
  1021. /**
  1022. * Add a PUT route
  1023. *
  1024. * @param string $path_or_array
  1025. * @param string $callback
  1026. * @param array $options (optional). See {@link route()} for available options.
  1027. * @return void
  1028. */
  1029. function dispatch_put($path_or_array, $callback, $options = array())
  1030. {
  1031. route("PUT", $path_or_array, $callback, $options);
  1032. }
  1033. /**
  1034. * Add a DELETE route
  1035. *
  1036. * @param string $path_or_array
  1037. * @param string $callback
  1038. * @param array $options (optional). See {@link route()} for available options.
  1039. * @return void
  1040. */
  1041. function dispatch_delete($path_or_array, $callback, $options = array())
  1042. {
  1043. route("DELETE", $path_or_array, $callback, $options);
  1044. }
  1045. /**
  1046. * Add a PATCH route
  1047. *
  1048. * @param string $path_or_array
  1049. * @param string $callback
  1050. * @param array $options (optional). See {@link route()} for available options.
  1051. * @return void
  1052. */
  1053. function dispatch_patch($path_or_array, $callback, $options = array())
  1054. {
  1055. route("PATCH", $path_or_array, $callback, $options);
  1056. }
  1057. /**
  1058. * Add route if required params are provided.
  1059. * Delete all routes if null is passed as a unique argument
  1060. * Return all routes
  1061. *
  1062. * @see route_build()
  1063. * @access private
  1064. * @param string $method
  1065. * @param string|array $path_or_array
  1066. * @param callback $func
  1067. * @param array $options (optional). Available options:
  1068. * - 'params' key with an array of parameters: for parametrized routes.
  1069. * those parameters will be merged with routes parameters.
  1070. * @return array
  1071. */
  1072. function route()
  1073. {
  1074. static $routes = array();
  1075. $nargs = func_num_args();
  1076. if( $nargs > 0)
  1077. {
  1078. $args = func_get_args();
  1079. if($nargs === 1 && is_null($args[0])) $routes = array();
  1080. else if($nargs < 3) trigger_error("Missing arguments for route()", E_USER_ERROR);
  1081. else
  1082. {
  1083. $method = $args[0];
  1084. $path_or_array = $args[1];
  1085. $func = $args[2];
  1086. $options = $nargs > 3 ? $args[3] : array();
  1087. $routes[] = route_build($method, $path_or_array, $func, $options);
  1088. }
  1089. }
  1090. return $routes;
  1091. }
  1092. /**
  1093. * An alias of route(null): reset all routes
  1094. *
  1095. * @access private
  1096. * @return void
  1097. */
  1098. function route_reset()
  1099. {
  1100. route(null);
  1101. }
  1102. /**
  1103. * Build a route and return it
  1104. *
  1105. * @access private
  1106. * @param string $method allowed http method (one of those returned by {@link request_methods()})
  1107. * @param string|array $path_or_array
  1108. * @param callback $callback callback called when route is found. It can be
  1109. * a function, an object method, a static method or a closure.
  1110. * See {@link http://php.net/manual/en/language.pseudo-types.php#language.types.callback php documentation}
  1111. * to learn more about callbacks.
  1112. * @param array $options (optional). Available options:
  1113. * - 'params' key with an array of parameters: for parametrized routes.
  1114. * those parameters will be merged with routes parameters.
  1115. * @return array array with keys "method", "pattern", "names", "callback", "options"
  1116. */
  1117. function route_build($method, $path_or_array, $callback, $options = array())
  1118. {
  1119. $method = strtoupper($method);
  1120. if(!in_array($method, request_methods()))
  1121. trigger_error("'$method' request method is unkown or unavailable.", E_USER_WARNING);
  1122. if(is_array($path_or_array))
  1123. {
  1124. $path = array_shift($path_or_array);
  1125. $names = $path_or_array[0];
  1126. }
  1127. else
  1128. {
  1129. $path = $path_or_array;
  1130. $names = array();
  1131. }
  1132. $single_asterisk_subpattern = "(?:/([^\/]*))?";
  1133. $double_asterisk_subpattern = "(?:/(.*))?";
  1134. $optionnal_slash_subpattern = "(?:/*?)";
  1135. $no_slash_asterisk_subpattern = "(?:([^\/]*))?";
  1136. if($path[0] == "^")
  1137. {
  1138. if($path{strlen($path) - 1} != "$") $path .= "$";
  1139. $pattern = "#".$path."#i";
  1140. }
  1141. else if(empty($path) || $path == "/")
  1142. {
  1143. $pattern = "#^".$optionnal_slash_subpattern."$#";
  1144. }
  1145. else
  1146. {
  1147. $parsed = array();
  1148. $elts = explode('/', $path);
  1149. $parameters_count = 0;
  1150. foreach($elts as $elt)
  1151. {
  1152. if(empty($elt)) continue;
  1153. $name = null;
  1154. # extracting double asterisk **
  1155. if($elt == "**"):
  1156. $parsed[] = $double_asterisk_subpattern;
  1157. $name = $parameters_count;
  1158. # extracting single asterisk *
  1159. elseif($elt == "*"):
  1160. $parsed[] = $single_asterisk_subpattern;
  1161. $name = $parameters_count;
  1162. # extracting named parameters :my_param
  1163. elseif($elt[0] == ":"):
  1164. if(preg_match('/^:([^\:]+)$/', $elt, $matches))
  1165. {
  1166. $parsed[] = $single_asterisk_subpattern;
  1167. $name = $matches[1];
  1168. };
  1169. elseif(strpos($elt, '*') !== false):
  1170. $sub_elts = explode('*', $elt);
  1171. $parsed_sub = array();
  1172. foreach($sub_elts as $sub_elt)
  1173. {
  1174. $parsed_sub[] = preg_quote($sub_elt, "#");
  1175. $name = $parameters_count;
  1176. }
  1177. //
  1178. $parsed[] = "/".implode($no_slash_asterisk_subpattern, $parsed_sub);
  1179. else:
  1180. $parsed[] = "/".preg_quote($elt, "#");
  1181. endif;
  1182. /* set parameters names */
  1183. if(is_null($name)) continue;
  1184. if(!array_key_exists($parameters_count, $names) || is_null($names[$parameters_count]))
  1185. $names[$parameters_count] = $name;
  1186. $parameters_count++;
  1187. }
  1188. $pattern = "#^".implode('', $parsed).$optionnal_slash_subpattern."?$#i";
  1189. }
  1190. return array( "method" => $method,
  1191. "pattern" => $pattern,
  1192. "names" => $names,
  1193. "callback" => $callback,
  1194. "options" => $options );
  1195. }
  1196. /**
  1197. * Find a route and returns it.
  1198. * Parameters values extracted from the path are added and merged
  1199. * with the default 'params' option of the route
  1200. * If not found, returns false.
  1201. * Routes are checked from first added to last added.
  1202. *
  1203. * @access private
  1204. * @param string $method
  1205. * @param string $path
  1206. * @return array,false route array has same keys as route returned by
  1207. * {@link route_build()} ("method", "pattern", "names", "callback", "options")
  1208. * + the processed "params" key
  1209. */
  1210. function route_find($method, $path)
  1211. {
  1212. $routes = route();
  1213. $method = strtoupper($method);
  1214. foreach($routes as $route)
  1215. {
  1216. if($method == $route["method"] && preg_match($route["pattern"], $path, $matches))
  1217. {
  1218. $options = $route["options"];
  1219. $params = array_key_exists('params', $options) ? $options["params"] : array();
  1220. if(count($matches) > 1)
  1221. {
  1222. array_shift($matches);
  1223. $n_matches = count($matches);
  1224. $names = array_values($route["names"]);
  1225. $n_names = count($names);
  1226. if( $n_matches < $n_names )
  1227. {
  1228. $a = array_fill(0, $n_names - $n_matches, null);
  1229. $matches = array_merge($matches, $a);
  1230. }
  1231. else if( $n_matches > $n_names )
  1232. {
  1233. $names = range($n_names, $n_matches - 1);
  1234. }
  1235. $arr_comb = array_combine($names, $matches);
  1236. $params = array_replace($params, $arr_comb);
  1237. }
  1238. $route["params"] = $params;
  1239. return $route;
  1240. }
  1241. }
  1242. return false;
  1243. }
  1244. # ============================================================================ #
  1245. # 5. OUTPUT AND RENDERING #
  1246. # ============================================================================ #
  1247. /**
  1248. * Returns a string to output
  1249. *
  1250. * It might use a template file, a function, or a formatted string (like {@link sprintf()}).
  1251. * It could be embraced by a layout or not.
  1252. * Local vars can be passed in addition to variables made available with the {@link set()}
  1253. * function.
  1254. *
  1255. * @param string $content_or_func
  1256. * @param string $layout
  1257. * @param string $locals
  1258. * @return string
  1259. */
  1260. function render($content_or_func, $layout = '', $locals = array())
  1261. {
  1262. $args = func_get_args();
  1263. $content_or_func = array_shift($args);
  1264. $layout = count($args) > 0 ? array_shift($args) : layout();
  1265. $view_path = file_path(option('views_dir'),$content_or_func);
  1266. if(function_exists('before_render'))
  1267. list($content_or_func, $layout, $locals, $view_path) = before_render($content_or_func, $layout, $locals, $view_path);
  1268. $vars = array_merge(set(), $locals);
  1269. $flash = flash_now();
  1270. if(array_key_exists('flash', $vars)) trigger_error('A $flash variable is already passed to view. Flash messages will only be accessible through flash_now()', E_USER_NOTICE);
  1271. else if(!empty($flash)) $vars['flash'] = $flash;
  1272. $infinite_loop = false;
  1273. # Avoid infinite loop: this function is in the backtrace ?
  1274. if(function_exists($content_or_func))
  1275. {
  1276. $back_trace = debug_backtrace();
  1277. while($trace = array_shift($back_trace))
  1278. {
  1279. if($trace['function'] == strtolower($content_or_func))
  1280. {
  1281. $infinite_loop = true;
  1282. break;
  1283. }
  1284. }
  1285. }
  1286. if(function_exists($content_or_func) && !$infinite_loop)
  1287. {
  1288. ob_start();
  1289. call_user_func($content_or_func, $vars);
  1290. $content = ob_get_clean();
  1291. }
  1292. elseif(file_exists($view_path))
  1293. {
  1294. ob_start();
  1295. extract($vars);
  1296. include $view_path;
  1297. $content = ob_get_clean();
  1298. }
  1299. else
  1300. {
  1301. if(substr_count($content_or_func, '%') !== count($vars)) $content = $content_or_func;
  1302. else $content = vsprintf($content_or_func, $vars);
  1303. }
  1304. if(empty($layout)) return $content;
  1305. return render($layout, null, array('content' => $content));
  1306. }
  1307. /**
  1308. * Returns a string to output
  1309. *
  1310. * Shortcut to render with no layout.
  1311. *
  1312. * @param string $content_or_func
  1313. * @param string $locals
  1314. * @return string
  1315. */
  1316. function partial($content_or_func, $locals = array())
  1317. {
  1318. return render($content_or_func, null, $locals);
  1319. }
  1320. /**
  1321. * Returns html output with proper http headers
  1322. *
  1323. * @param string $content_or_func
  1324. * @param string $layout
  1325. * @param string $locals
  1326. * @return string
  1327. */
  1328. function html($content_or_func, $layout = '', $locals = array())
  1329. {
  1330. send_header('Content-Type: text/html; charset='.strtolower(option('encoding')));
  1331. $args = func_get_args();
  1332. return call_user_func_array('render', $args);
  1333. }
  1334. /**
  1335. * Set and return current layout
  1336. *
  1337. * @param string $function_or_file
  1338. * @return string
  1339. */
  1340. function layout($function_or_file = null)
  1341. {
  1342. static $layout = null;
  1343. if(func_num_args() > 0) $layout = $function_or_file;
  1344. return $layout;
  1345. }
  1346. /**
  1347. * Returns xml output with proper http headers
  1348. *
  1349. * @param string $content_or_func
  1350. * @param string $layout
  1351. * @param string $locals
  1352. * @return string
  1353. */
  1354. function xml($data)
  1355. {
  1356. send_header('Content-Type: text/xml; charset='.strtolower(option('encoding')));
  1357. $args = func_get_args();
  1358. return call_user_func_array('render', $args);
  1359. }
  1360. /**
  1361. * Returns css output with proper http headers
  1362. *
  1363. * @param string $content_or_func
  1364. * @param string $layout
  1365. * @param string $locals
  1366. * @return string
  1367. */
  1368. function css($content_or_func, $layout = '', $locals = array())
  1369. {
  1370. send_header('Content-Type: text/css; charset='.strtolower(option('encoding')));
  1371. $args = func_get_args();
  1372. return call_user_func_array('render', $args);
  1373. }
  1374. /**
  1375. * Returns javacript output with proper http headers
  1376. *
  1377. * @param string $content_or_func
  1378. * @param string $layout
  1379. * @param string $locals
  1380. * @return string
  1381. */
  1382. function js($content_or_func, $layout = '', $locals = array())
  1383. {
  1384. send_header('Content-Type: application/javascript; charset='.strtolower(option('encoding')));
  1385. $args = func_get_args();
  1386. return call_user_func_array('render', $args);
  1387. }
  1388. /**
  1389. * Returns txt output with proper http headers
  1390. *
  1391. * @param string $content_or_func
  1392. * @param string $layout
  1393. * @param string $locals
  1394. * @return string
  1395. */
  1396. function txt($content_or_func, $layout = '', $locals = array())
  1397. {
  1398. send_header('Content-Type: text/plain; charset='.strtolower(option('encoding')));
  1399. $args = func_get_args();
  1400. return call_user_func_array('render', $args);
  1401. }
  1402. /**
  1403. * Returns json representation of data with proper http headers.
  1404. * On PHP 5 < PHP 5.2.0, you must provide your own implementation of the
  1405. * <code>json_encode()</code> function beore using <code>json()</code>.
  1406. *
  1407. * @param string $data
  1408. * @param int $json_option
  1409. * @return string
  1410. */
  1411. function json($data, $json_option = 0)
  1412. {
  1413. send_header('Content-Type: application/json; charset='.strtolower(option('encoding')));
  1414. return version_compare(PHP_VERSION, '5.3.0', '>=') ? json_encode($data, $json_option) : json_encode($data);
  1415. }
  1416. /**
  1417. * undocumented function
  1418. *
  1419. * @param string $filename
  1420. * @param string $return
  1421. * @return mixed number of bytes delivered or file output if $return = true
  1422. */
  1423. function render_file($filename, $return = false)
  1424. {
  1425. # TODO implements X-SENDFILE headers
  1426. // if($x-sendfile = option('x-sendfile'))
  1427. // {
  1428. // // add a X-Sendfile header for apache and Lighttpd >= 1.5
  1429. // if($x-sendfile > X-SENDFILE) // add a X-LIGHTTPD-send-file header
  1430. //
  1431. // }
  1432. // else
  1433. // {
  1434. //
  1435. // }
  1436. $filename = str_replace('../', '', $filename);
  1437. if(file_exists($filename))
  1438. {
  1439. $content_type = mime_type(file_extension($filename));
  1440. $header = 'Content-type: '.$content_type;
  1441. if(file_is_text($filename)) $header .= '; charset='.strtolower(option('encoding'));
  1442. send_header($header);
  1443. return file_read($filename, $return);
  1444. }
  1445. else halt(NOT_FOUND, "unknown filename $filename");
  1446. }
  1447. /**
  1448. * Call before_sending_header() if it exists, then send headers
  1449. *
  1450. * @param string $header
  1451. * @return void
  1452. */
  1453. function send_header($header = null, $replace = true, $code = false)
  1454. {
  1455. if(!headers_sent())
  1456. {
  1457. call_if_exists('before_sending_header', $header);
  1458. header($header, $replace, $code);
  1459. }
  1460. }
  1461. # # #
  1462. # ============================================================================ #
  1463. # 6. HELPERS #
  1464. # ============================================================================ #
  1465. /**
  1466. * Returns an url composed of params joined with /
  1467. * A param can be a string or an array.
  1468. * If param is an array, its members will be added at the end of the return url
  1469. * as GET parameters "&key=value".
  1470. *
  1471. * @param string or array $param1, $param2 ...
  1472. * @return string
  1473. */
  1474. function url_for($params = null)
  1475. {
  1476. $paths = array();
  1477. $params = func_get_args();
  1478. $GET_params = array();
  1479. foreach($params as $param)
  1480. {
  1481. if(is_array($param))
  1482. {
  1483. $GET_params = array_merge($GET_params, $param);
  1484. continue;
  1485. }
  1486. if(filter_var_url($param))
  1487. {
  1488. $paths[] = $param;
  1489. continue;
  1490. }
  1491. $p = explode('/',$param);
  1492. foreach($p as $v)
  1493. {
  1494. if($v != "") $paths[] = str_replace('%23', '#', rawurlencode($v));
  1495. }
  1496. }
  1497. $path = rtrim(implode('/', $paths), '/');
  1498. if(!filter_var_url($path))
  1499. {
  1500. # it's a relative URL or an URL without a schema
  1501. $base_uri = option('base_uri');
  1502. $path = file_path($base_uri, $path);
  1503. }
  1504. if(!empty($GET_params))
  1505. {
  1506. $is_first_qs_param = true;
  1507. $path_as_no_question_mark = strpos($path, '?') === false;
  1508. foreach($GET_params as $k => $v)
  1509. {
  1510. $qs_separator = $is_first_qs_param && $path_as_no_question_mark ?
  1511. '?' : '&amp;';
  1512. $path .= $qs_separator . rawurlencode($k) . '=' . rawurlencode($v);
  1513. $is_first_qs_param = false;
  1514. }
  1515. }
  1516. if(DIRECTORY_SEPARATOR != '/') $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
  1517. return $path;
  1518. }
  1519. /**
  1520. * An alias of {@link htmlspecialchars()}.
  1521. * If no $charset is provided, uses option('encoding') value
  1522. *
  1523. * @param string $str
  1524. * @param string $quote_style
  1525. * @param string $charset
  1526. * @return void
  1527. */
  1528. function h($str, $quote_style = ENT_NOQUOTES, $charset = null)
  1529. {
  1530. if(is_null($charset)) $charset = strtoupper(option('encoding'));
  1531. return htmlspecialchars($str, $quote_style, $charset);
  1532. }
  1533. /**
  1534. * Set and returns flash messages that will be available in the next action
  1535. * via the {@link flash_now()} function or the view variable <code>$flash</code>.
  1536. *
  1537. * If multiple values are provided, set <code>$name</code> variable with an array of those values.
  1538. * If there is only one value, set <code>$name</code> variable with the provided $values
  1539. * or if it's <code>$name</code> is an array, merge it with current messages.
  1540. *
  1541. * @param string, array $name
  1542. * @param mixed $values,...
  1543. * @return mixed variable value for $name if $name argument is provided, else return all variables
  1544. */
  1545. function flash($name = null, $value = null)
  1546. {
  1547. if(!defined('SID')) trigger_error("Flash messages can't be used because session isn't enabled", E_USER_WARNING);
  1548. static $messages = array();
  1549. $args = func_get_args();
  1550. $name = array_shift($args);
  1551. if(is_null($name)) return $messages;
  1552. if(is_array($name)) return $messages = array_merge($messages, $name);
  1553. if(!empty($args))
  1554. {
  1555. $messages[$name] = count($args) > 1 ? $args : $args[0];
  1556. }
  1557. if(!array_key_exists($name, $messages)) return null;
  1558. else return $messages[$name];
  1559. return $messages;
  1560. }
  1561. /**
  1562. * Set and returns flash messages available for the current action, included those
  1563. * defined in the previous action with {@link flash()}
  1564. * Those messages will also be passed to the views and made available in the
  1565. * <code>$flash</code> variable.
  1566. *
  1567. * If multiple values are provided, set <code>$name</code> variable with an array of those values.
  1568. * If there is only one value, set <code>$name</code> variable with the provided $values
  1569. * or if it's <code>$name</code> is an array, merge it with current messages.
  1570. *
  1571. * @param string, array $name
  1572. * @param mixed $values,...
  1573. * @return mixed variable value for $name if $name argument is provided, else return all variables
  1574. */
  1575. function flash_now($name = null, $value = null)
  1576. {
  1577. static $messages = null;
  1578. if(is_null($messages))
  1579. {
  1580. $fkey = LIM_SESSION_FLASH_KEY;
  1581. $messages = array();
  1582. if(defined('SID') && array_key_exists($fkey, $_SESSION)) $messages = $_SESSION[$fkey];
  1583. }
  1584. $args = func_get_args();
  1585. $name = array_shift($args);
  1586. if(is_null($name)) return $messages;
  1587. if(is_array($name)) return $messages = array_merge($messages, $name);
  1588. if(!empty($args))
  1589. {
  1590. $messages[$name] = count($args) > 1 ? $args : $args[0];
  1591. }
  1592. if(!array_key_exists($name, $messages)) return null;
  1593. else return $messages[$name];
  1594. return $messages;
  1595. }
  1596. /**
  1597. * Delete current flash messages in session, and set new ones stored with
  1598. * flash function.
  1599. * Called before application exit.
  1600. *
  1601. * @access private
  1602. * @return void
  1603. */
  1604. function flash_sweep()
  1605. {
  1606. if(defined('SID'))
  1607. {
  1608. $fkey = LIM_SESSION_FLASH_KEY;
  1609. $_SESSION[$fkey] = flash();
  1610. }
  1611. }
  1612. /**
  1613. * Starts capturing block of text
  1614. *
  1615. * Calling without params stops capturing (same as end_content_for()).
  1616. * After capturing the captured block is put into a variable
  1617. * named $name for later use in layouts. If second parameter
  1618. * is supplied, its content will be used instead of capturing
  1619. * a block of text.
  1620. *
  1621. * @param string $name
  1622. * @param string $content
  1623. * @return void
  1624. */
  1625. function content_for($name = null, $content = null)
  1626. {
  1627. static $_name = null;
  1628. if(is_null($name) && !is_null($_name))
  1629. {
  1630. set($_name, ob_get_clean());
  1631. $_name = null;
  1632. }
  1633. elseif(!is_null($name) && !isset($content))
  1634. {
  1635. $_name = $name;
  1636. ob_start();
  1637. }
  1638. elseif(isset($name, $content))
  1639. {
  1640. set($name, $content);
  1641. }
  1642. }
  1643. /**
  1644. * Stops capturing block of text
  1645. *
  1646. * @return void
  1647. */
  1648. function end_content_for()
  1649. {
  1650. content_for();
  1651. }
  1652. /**
  1653. * Shows current memory and execution time of the application.
  1654. * Returns only execution time if <code>memory_get_usage()</code>
  1655. * isn't available.
  1656. * ( That's the case before PHP5.2.1 if PHP isn't compiled with option
  1657. * <code>--enable-memory-limit</code>. )
  1658. *
  1659. * @access public
  1660. * @return array
  1661. */
  1662. function benchmark()
  1663. {
  1664. $res = array( 'execution_time' => (microtime(true) - LIM_START_MICROTIME) );
  1665. if(defined('LIM_START_MEMORY'))
  1666. {
  1667. $current_mem_usage = memory_get_usage();
  1668. $res['current_memory'] = $current_mem_usage;
  1669. $res['start_memory'] = LIM_START_MEMORY;
  1670. $res['average_memory'] = (LIM_START_MEMORY + $current_mem_usage) / 2;
  1671. }
  1672. return $res;
  1673. }
  1674. # # #
  1675. # ============================================================================ #
  1676. # 7. UTILS #
  1677. # ============================================================================ #
  1678. /**
  1679. * Calls a function if exists
  1680. *
  1681. * @param callback $callback a function stored in a string variable,
  1682. * or an object and the name of a method within the object
  1683. * See {@link http://php.net/manual/en/language.pseudo-types.php#language.types.callback php documentation}
  1684. * to learn more about callbacks.
  1685. * @param mixed $arg,.. (optional)
  1686. * @return mixed
  1687. */
  1688. function call_if_exists($callback)
  1689. {
  1690. $args = func_get_args();
  1691. $callback = array_shift($args);
  1692. if(is_callable($callback)) return call_user_func_array($callback, $args);
  1693. return;
  1694. }
  1695. /**
  1696. * Define a constant unless it already exists
  1697. *
  1698. * @param string $name
  1699. * @param string $value
  1700. * @return void
  1701. */
  1702. function define_unless_exists($name, $value)
  1703. {
  1704. if(!defined($name)) define($name, $value);
  1705. }
  1706. /**
  1707. * Return a default value if provided value is empty
  1708. *
  1709. * @param mixed $value
  1710. * @param mixed $default default value returned if $value is empty
  1711. * @return mixed
  1712. */
  1713. function value_or_default($value, $default)
  1714. {
  1715. return empty($value) ? $default : $value;
  1716. }
  1717. /**
  1718. * An alias of {@link value_or_default()}
  1719. *
  1720. *
  1721. * @param mixed $value
  1722. * @param mixed $default
  1723. * @return mixed
  1724. */
  1725. function v($value, $default)
  1726. {
  1727. return value_or_default($value, $default);
  1728. }
  1729. /**
  1730. * Load php files with require_once in a given dir
  1731. *
  1732. * @param string $path Path in which are the file to load
  1733. * @param string $pattern a regexp pattern that filter files to load
  1734. * @param bool $prevents_output security option that prevents output
  1735. * @return array paths of loaded files
  1736. */
  1737. function require_once_dir($path, $pattern = "*.php", $prevents_output = true)
  1738. {
  1739. if($path[strlen($path) - 1] != "/") $path .= "/";
  1740. $filenames = glob($path.$pattern);
  1741. if(!is_array($filenames)) $filenames = array();
  1742. if($prevents_output) ob_start();
  1743. foreach($filenames as $filename) require_once $filename;
  1744. if($prevents_output) ob_end_clean();
  1745. return $filenames;
  1746. }
  1747. /**
  1748. * Dumps a variable into inspectable format
  1749. *
  1750. * @param anything $var the variable to debug
  1751. * @param bool $output_as_html sets whether to wrap output in <pre> tags. default: true
  1752. * @return string the variable with output
  1753. */
  1754. function debug($var, $output_as_html = true)
  1755. {
  1756. if ( is_null($var) ) { return '<span class="null-value">[NULL]</span>'; };
  1757. $out = '';
  1758. switch ($var)
  1759. {
  1760. case empty($var):
  1761. $out = '[empty value]';
  1762. break;
  1763. case is_array($var):
  1764. $out = var_export($var, true);
  1765. break;
  1766. case is_object($var):
  1767. $out = var_export($var, true);
  1768. break;
  1769. case is_string($var):
  1770. $out = $var;
  1771. break;
  1772. default:
  1773. $out = var_export($var, true);
  1774. break;
  1775. }
  1776. if ($output_as_html) { $out = "<pre>\n" . h($out) ."</pre>"; }
  1777. return $out;
  1778. }
  1779. ## HTTP utils _________________________________________________________________
  1780. ### Constants: HTTP status codes
  1781. define( 'HTTP_CONTINUE', 100 );
  1782. define( 'HTTP_SWITCHING_PROTOCOLS', 101 );
  1783. define( 'HTTP_PROCESSING', 102 );
  1784. define( 'HTTP_OK', 200 );
  1785. define( 'HTTP_CREATED', 201 );
  1786. define( 'HTTP_ACCEPTED', 202 );
  1787. define( 'HTTP_NON_AUTHORITATIVE', 203 );
  1788. define( 'HTTP_NO_CONTENT', 204 );
  1789. define( 'HTTP_RESET_CONTENT', 205 );
  1790. define( 'HTTP_PARTIAL_CONTENT', 206 );
  1791. define( 'HTTP_MULTI_STATUS', 207 );
  1792. define( 'HTTP_MULTIPLE_CHOICES', 300 );
  1793. define( 'HTTP_MOVED_PERMANENTLY', 301 );
  1794. define( 'HTTP_MOVED_TEMPORARILY', 302 );
  1795. define( 'HTTP_SEE_OTHER', 303 );
  1796. define( 'HTTP_NOT_MODIFIED', 304 );
  1797. define( 'HTTP_USE_PROXY', 305 );
  1798. define( 'HTTP_TEMPORARY_REDIRECT', 307 );
  1799. define( 'HTTP_BAD_REQUEST', 400 );
  1800. define( 'HTTP_UNAUTHORIZED', 401 );
  1801. define( 'HTTP_PAYMENT_REQUIRED', 402 );
  1802. define( 'HTTP_FORBIDDEN', 403 );
  1803. define( 'HTTP_NOT_FOUND', 404 );
  1804. define( 'HTTP_METHOD_NOT_ALLOWED', 405 );
  1805. define( 'HTTP_NOT_ACCEPTABLE', 406 );
  1806. define( 'HTTP_PROXY_AUTHENTICATION_REQUIRED', 407 );
  1807. define( 'HTTP_REQUEST_TIME_OUT', 408 );
  1808. define( 'HTTP_CONFLICT', 409 );
  1809. define( 'HTTP_GONE', 410 );
  1810. define( 'HTTP_LENGTH_REQUIRED', 411 );
  1811. define( 'HTTP_PRECONDITION_FAILED', 412 );
  1812. define( 'HTTP_REQUEST_ENTITY_TOO_LARGE', 413 );
  1813. define( 'HTTP_REQUEST_URI_TOO_LARGE', 414 );
  1814. define( 'HTTP_UNSUPPORTED_MEDIA_TYPE', 415 );
  1815. define( 'HTTP_RANGE_NOT_SATISFIABLE', 416 );
  1816. define( 'HTTP_EXPECTATION_FAILED', 417 );
  1817. define( 'HTTP_UNPROCESSABLE_ENTITY', 422 );
  1818. define( 'HTTP_LOCKED', 423 );
  1819. define( 'HTTP_FAILED_DEPENDENCY', 424 );
  1820. define( 'HTTP_UPGRADE_REQUIRED', 426 );
  1821. define( 'HTTP_INTERNAL_SERVER_ERROR', 500 );
  1822. define( 'HTTP_NOT_IMPLEMENTED', 501 );
  1823. define( 'HTTP_BAD_GATEWAY', 502 );
  1824. define( 'HTTP_SERVICE_UNAVAILABLE', 503 );
  1825. define( 'HTTP_GATEWAY_TIME_OUT', 504 );
  1826. define( 'HTTP_VERSION_NOT_SUPPORTED', 505 );
  1827. define( 'HTTP_VARIANT_ALSO_VARIES', 506 );
  1828. define( 'HTTP_INSUFFICIENT_STORAGE', 507 );
  1829. define( 'HTTP_NOT_EXTENDED', 510 );
  1830. /**
  1831. * Output proper HTTP header for a given HTTP code
  1832. *
  1833. * @param string $code
  1834. * @return void
  1835. */
  1836. function status($code = 500)
  1837. {
  1838. if(!headers_sent())
  1839. {
  1840. $str = http_response_status_code($code);
  1841. send_header($str);
  1842. }
  1843. }
  1844. /**
  1845. * Http redirection
  1846. *
  1847. * Same use as {@link url_for()}
  1848. * By default HTTP status code is 302, but a different code can be specified
  1849. * with a status key in array parameter.
  1850. *
  1851. * <code>
  1852. * redirecto('new','url'); # 302 HTTP_MOVED_TEMPORARILY by default
  1853. * redirecto('new','url', array('status' => HTTP_MOVED_PERMANENTLY));
  1854. * </code>
  1855. *
  1856. * @param string or array $param1, $param2...
  1857. * @return void
  1858. */
  1859. function redirect_to($params)
  1860. {
  1861. # [NOTE]: (from php.net) HTTP/1.1 requires an absolute URI as argument to » Location:
  1862. # including the scheme, hostname and absolute path, but some clients accept
  1863. # relative URIs. You can usually use $_SERVER['HTTP_HOST'],
  1864. # $_SERVER['PHP_SELF'] and dirname() to make an absolute URI from a relative
  1865. # one yourself.
  1866. # TODO make absolute uri
  1867. if(!headers_sent())
  1868. {
  1869. $status = HTTP_MOVED_TEMPORARILY; # default for a redirection in PHP
  1870. $params = func_get_args();
  1871. $n_params = array();
  1872. # extract status param if exists
  1873. foreach($params as $param)
  1874. {
  1875. if(is_array($param))
  1876. {
  1877. if(array_key_exists('status', $param))
  1878. {
  1879. $status = $param['status'];
  1880. unset($param['status']);
  1881. }
  1882. }
  1883. $n_params[] = $param;
  1884. }
  1885. $uri = call_user_func_array('url_for', $n_params);
  1886. $uri = htmlspecialchars_decode($uri, ENT_NOQUOTES);
  1887. stop_and_exit(false);
  1888. send_header('Location: '.$uri, true, $status);
  1889. exit;
  1890. }
  1891. }
  1892. /**
  1893. * Http redirection
  1894. *
  1895. * @deprecated deprecated since version 0.4. Please use {@link redirect_to()} instead.
  1896. * @param string $url
  1897. * @return void
  1898. */
  1899. function redirect($uri)
  1900. {
  1901. # halt('redirect() is deprecated. Please use redirect_to() instead.', E_LIM_DEPRECATED);
  1902. # halt not necesary... it won't be visible because of http redirection...
  1903. redirect_to($uri);
  1904. }
  1905. /**
  1906. * Returns HTTP response status for a given code.
  1907. * If no code provided, return an array of all status
  1908. *
  1909. * @param string $num
  1910. * @return string,array
  1911. */
  1912. function http_response_status($num = null)
  1913. {
  1914. $status = array(
  1915. 100 => 'Continue',
  1916. 101 => 'Switching Protocols',
  1917. 102 => 'Processing',
  1918. 200 => 'OK',
  1919. 201 => 'Created',
  1920. 202 => 'Accepted',
  1921. 203 => 'Non-Authoritative Information',
  1922. 204 => 'No Content',
  1923. 205 => 'Reset Content',
  1924. 206 => 'Partial Content',
  1925. 207 => 'Multi-Status',
  1926. 226 => 'IM Used',
  1927. 300 => 'Multiple Choices',
  1928. 301 => 'Moved Permanently',
  1929. 302 => 'Found',
  1930. 303 => 'See Other',
  1931. 304 => 'Not Modified',
  1932. 305 => 'Use Proxy',
  1933. 306 => 'Reserved',
  1934. 307 => 'Temporary Redirect',
  1935. 400 => 'Bad Request',
  1936. 401 => 'Unauthorized',
  1937. 402 => 'Payment Required',
  1938. 403 => 'Forbidden',
  1939. 404 => 'Not Found',
  1940. 405 => 'Method Not Allowed',
  1941. 406 => 'Not Acceptable',
  1942. 407 => 'Proxy Authentication Required',
  1943. 408 => 'Request Timeout',
  1944. 409 => 'Conflict',
  1945. 410 => 'Gone',
  1946. 411 => 'Length Required',
  1947. 412 => 'Precondition Failed',
  1948. 413 => 'Request Entity Too Large',
  1949. 414 => 'Request-URI Too Long',
  1950. 415 => 'Unsupported Media Type',
  1951. 416 => 'Requested Range Not Satisfiable',
  1952. 417 => 'Expectation Failed',
  1953. 422 => 'Unprocessable Entity',
  1954. 423 => 'Locked',
  1955. 424 => 'Failed Dependency',
  1956. 426 => 'Upgrade Required',
  1957. 500 => 'Internal Server Error',
  1958. 501 => 'Not Implemented',
  1959. 502 => 'Bad Gateway',
  1960. 503 => 'Service Unavailable',
  1961. 504 => 'Gateway Timeout',
  1962. 505 => 'HTTP Version Not Supported',
  1963. 506 => 'Variant Also Negotiates',
  1964. 507 => 'Insufficient Storage',
  1965. 510 => 'Not Extended'
  1966. );
  1967. if(is_null($num)) return $status;
  1968. return array_key_exists($num, $status) ? $status[$num] : '';
  1969. }
  1970. /**
  1971. * Checks if an HTTP response code is valid
  1972. *
  1973. * @param string $num
  1974. * @return bool
  1975. */
  1976. function http_response_status_is_valid($num)
  1977. {
  1978. $r = http_response_status($num);
  1979. return !empty($r);
  1980. }
  1981. /**
  1982. * Returns an HTTP response status string for a given code
  1983. *
  1984. * @param string $num
  1985. * @return string
  1986. */
  1987. function http_response_status_code($num)
  1988. {
  1989. $protocole = empty($_SERVER["SERVER_PROTOCOL"]) ? "HTTP/1.1" : $_SERVER["SERVER_PROTOCOL"];
  1990. if($str = http_response_status($num)) return "$protocole $num $str";
  1991. }
  1992. /**
  1993. * Check if the _Accept_ header is present, and includes the given `type`.
  1994. *
  1995. * When the _Accept_ header is not present `true` is returned. Otherwise
  1996. * the given `type` is matched by an exact match, and then subtypes. You
  1997. * may pass the subtype such as "html" which is then converted internally
  1998. * to "text/html" using the mime lookup table.
  1999. *
  2000. * @param string $type
  2001. * @param string $env
  2002. * @return bool
  2003. */
  2004. function http_ua_accepts($type, $env = null)
  2005. {
  2006. if(is_null($env)) $env = env();
  2007. $accept = array_key_exists('HTTP_ACCEPT', $env['SERVER']) ? $env['SERVER']['HTTP_ACCEPT'] : null;
  2008. if(!$accept || $accept === '*/*') return true;
  2009. if($type)
  2010. {
  2011. // Allow "html" vs "text/html" etc
  2012. if(!strpos($type, '/')) $type = mime_type($type);
  2013. // Check if we have a direct match
  2014. if(strpos($accept, $type) > -1) return true;
  2015. // Check if we have type/*
  2016. $type_parts = explode('/', $type);
  2017. $type = $type_parts[0].'/*';
  2018. return (strpos($accept, $type) > -1);
  2019. }
  2020. return false;
  2021. }
  2022. ## FILE utils _________________________________________________________________
  2023. /**
  2024. * Returns mime type for a given extension or if no extension is provided,
  2025. * all mime types in an associative array, with extensions as keys.
  2026. * If extension is unknown, returns null.
  2027. * (extracted from Orbit source http://orbit.luaforge.net/).
  2028. *
  2029. *
  2030. * @param string $ext
  2031. * @return string, array, null
  2032. */
  2033. function mime_type($ext = null)
  2034. {
  2035. $types = array(
  2036. 'ai' => 'application/postscript',
  2037. 'aif' => 'audio/x-aiff',
  2038. 'aifc' => 'audio/x-aiff',
  2039. 'aiff' => 'audio/x-aiff',
  2040. 'asc' => 'text/plain',
  2041. 'atom' => 'application/atom+xml',
  2042. 'atom' => 'application/atom+xml',
  2043. 'au' => 'audio/basic',
  2044. 'avi' => 'video/x-msvideo',
  2045. 'bcpio' => 'application/x-bcpio',
  2046. 'bin' => 'application/octet-stream',
  2047. 'bmp' => 'image/bmp',
  2048. 'cdf' => 'application/x-netcdf',
  2049. 'cgm' => 'image/cgm',
  2050. 'class' => 'application/octet-stream',
  2051. 'cpio' => 'application/x-cpio',
  2052. 'cpt' => 'application/mac-compactpro',
  2053. 'csh' => 'application/x-csh',
  2054. 'css' => 'text/css',
  2055. 'csv' => 'text/csv',
  2056. 'dcr' => 'application/x-director',
  2057. 'dir' => 'application/x-director',
  2058. 'djv' => 'image/vnd.djvu',
  2059. 'djvu' => 'image/vnd.djvu',
  2060. 'dll' => 'application/octet-stream',
  2061. 'dmg' => 'application/octet-stream',
  2062. 'dms' => 'application/octet-stream',
  2063. 'doc' => 'application/msword',
  2064. 'dtd' => 'application/xml-dtd',
  2065. 'dvi' => 'application/x-dvi',
  2066. 'dxr' => 'application/x-director',
  2067. 'eps' => 'application/postscript',
  2068. 'etx' => 'text/x-setext',
  2069. 'exe' => 'application/octet-stream',
  2070. 'ez' => 'application/andrew-inset',
  2071. 'gif' => 'image/gif',
  2072. 'gram' => 'application/srgs',
  2073. 'grxml' => 'application/srgs+xml',
  2074. 'gtar' => 'application/x-gtar',
  2075. 'hdf' => 'application/x-hdf',
  2076. 'hqx' => 'application/mac-binhex40',
  2077. 'htm' => 'text/html',
  2078. 'html' => 'text/html',
  2079. 'ice' => 'x-conference/x-cooltalk',
  2080. 'ico' => 'image/x-icon',
  2081. 'ics' => 'text/calendar',
  2082. 'ief' => 'image/ief',
  2083. 'ifb' => 'text/calendar',
  2084. 'iges' => 'model/iges',
  2085. 'igs' => 'model/iges',
  2086. 'jpe' => 'image/jpeg',
  2087. 'jpeg' => 'image/jpeg',
  2088. 'jpg' => 'image/jpeg',
  2089. 'js' => 'application/x-javascript',
  2090. 'json' => 'application/json',
  2091. 'kar' => 'audio/midi',
  2092. 'latex' => 'application/x-latex',
  2093. 'lha' => 'application/octet-stream',
  2094. 'lzh' => 'application/octet-stream',
  2095. 'm3u' => 'audio/x-mpegurl',
  2096. 'man' => 'application/x-troff-man',
  2097. 'mathml' => 'application/mathml+xml',
  2098. 'me' => 'application/x-troff-me',
  2099. 'mesh' => 'model/mesh',
  2100. 'mid' => 'audio/midi',
  2101. 'midi' => 'audio/midi',
  2102. 'mif' => 'application/vnd.mif',
  2103. 'mov' => 'video/quicktime',
  2104. 'movie' => 'video/x-sgi-movie',
  2105. 'mp2' => 'audio/mpeg',
  2106. 'mp3' => 'audio/mpeg',
  2107. 'mpe' => 'video/mpeg',
  2108. 'mpeg' => 'video/mpeg',
  2109. 'mpg' => 'video/mpeg',
  2110. 'mpga' => 'audio/mpeg',
  2111. 'ms' => 'application/x-troff-ms',
  2112. 'msh' => 'model/mesh',
  2113. 'mxu' => 'video/vnd.mpegurl',
  2114. 'nc' => 'application/x-netcdf',
  2115. 'oda' => 'application/oda',
  2116. 'ogg' => 'application/ogg',
  2117. 'pbm' => 'image/x-portable-bitmap',
  2118. 'pdb' => 'chemical/x-pdb',
  2119. 'pdf' => 'application/pdf',
  2120. 'pgm' => 'image/x-portable-graymap',
  2121. 'pgn' => 'application/x-chess-pgn',
  2122. 'png' => 'image/png',
  2123. 'pnm' => 'image/x-portable-anymap',
  2124. 'ppm' => 'image/x-portable-pixmap',
  2125. 'ppt' => 'application/vnd.ms-powerpoint',
  2126. 'ps' => 'application/postscript',
  2127. 'qt' => 'video/quicktime',
  2128. 'ra' => 'audio/x-pn-realaudio',
  2129. 'ram' => 'audio/x-pn-realaudio',
  2130. 'ras' => 'image/x-cmu-raster',
  2131. 'rdf' => 'application/rdf+xml',
  2132. 'rgb' => 'image/x-rgb',
  2133. 'rm' => 'application/vnd.rn-realmedia',
  2134. 'roff' => 'application/x-troff',
  2135. 'rss' => 'application/rss+xml',
  2136. 'rtf' => 'text/rtf',
  2137. 'rtx' => 'text/richtext',
  2138. 'sgm' => 'text/sgml',
  2139. 'sgml' => 'text/sgml',
  2140. 'sh' => 'application/x-sh',
  2141. 'shar' => 'application/x-shar',
  2142. 'silo' => 'model/mesh',
  2143. 'sit' => 'application/x-stuffit',
  2144. 'skd' => 'application/x-koan',
  2145. 'skm' => 'application/x-koan',
  2146. 'skp' => 'application/x-koan',
  2147. 'skt' => 'application/x-koan',
  2148. 'smi' => 'application/smil',
  2149. 'smil' => 'application/smil',
  2150. 'snd' => 'audio/basic',
  2151. 'so' => 'application/octet-stream',
  2152. 'spl' => 'application/x-futuresplash',
  2153. 'src' => 'application/x-wais-source',
  2154. 'sv4cpio' => 'application/x-sv4cpio',
  2155. 'sv4crc' => 'application/x-sv4crc',
  2156. 'svg' => 'image/svg+xml',
  2157. 'svgz' => 'image/svg+xml',
  2158. 'swf' => 'application/x-shockwave-flash',
  2159. 't' => 'application/x-troff',
  2160. 'tar' => 'application/x-tar',
  2161. 'tcl' => 'application/x-tcl',
  2162. 'tex' => 'application/x-tex',
  2163. 'texi' => 'application/x-texinfo',
  2164. 'texinfo' => 'application/x-texinfo',
  2165. 'tif' => 'image/tiff',
  2166. 'tiff' => 'image/tiff',
  2167. 'tr' => 'application/x-troff',
  2168. 'tsv' => 'text/tab-separated-values',
  2169. 'txt' => 'text/plain',
  2170. 'ustar' => 'application/x-ustar',
  2171. 'vcd' => 'application/x-cdlink',
  2172. 'vrml' => 'model/vrml',
  2173. 'vxml' => 'application/voicexml+xml',
  2174. 'wav' => 'audio/x-wav',
  2175. 'wbmp' => 'image/vnd.wap.wbmp',
  2176. 'wbxml' => 'application/vnd.wap.wbxml',
  2177. 'wml' => 'text/vnd.wap.wml',
  2178. 'wmlc' => 'application/vnd.wap.wmlc',
  2179. 'wmls' => 'text/vnd.wap.wmlscript',
  2180. 'wmlsc' => 'application/vnd.wap.wmlscriptc',
  2181. 'wrl' => 'model/vrml',
  2182. 'xbm' => 'image/x-xbitmap',
  2183. 'xht' => 'application/xhtml+xml',
  2184. 'xhtml' => 'application/xhtml+xml',
  2185. 'xls' => 'application/vnd.ms-excel',
  2186. 'xml' => 'application/xml',
  2187. 'xpm' => 'image/x-xpixmap',
  2188. 'xsl' => 'application/xml',
  2189. 'xslt' => 'application/xslt+xml',
  2190. 'xul' => 'application/vnd.mozilla.xul+xml',
  2191. 'xwd' => 'image/x-xwindowdump',
  2192. 'xyz' => 'chemical/x-xyz',
  2193. 'zip' => 'application/zip'
  2194. );
  2195. if (is_null($ext)) return $types;
  2196. $lower_ext = strtolower($ext);
  2197. return isset($types[$lower_ext]) ? $types[$lower_ext] : null;
  2198. }
  2199. /**
  2200. * Detect MIME Content-type for a file
  2201. *
  2202. * @param string $filename Path to the tested file.
  2203. * @return string
  2204. */
  2205. function file_mime_content_type($filename)
  2206. {
  2207. $ext = file_extension($filename); /* strtolower isn't necessary */
  2208. if($mime = mime_type($ext)) return $mime;
  2209. elseif (function_exists('finfo_open'))
  2210. {
  2211. if($finfo = finfo_open(FILEINFO_MIME))
  2212. {
  2213. if($mime = finfo_file($finfo, $filename))
  2214. {
  2215. finfo_close($finfo);
  2216. return $mime;
  2217. }
  2218. }
  2219. }
  2220. return 'application/octet-stream';
  2221. }
  2222. /**
  2223. * Read and output file content and return filesize in bytes or status after
  2224. * closing file.
  2225. * This function is very efficient for outputing large files without timeout
  2226. * nor too expensive memory use
  2227. *
  2228. * @param string $filename
  2229. * @param string $retbytes
  2230. * @return bool, int
  2231. */
  2232. function file_read_chunked($filename, $retbytes = true)
  2233. {
  2234. $chunksize = 1*(1024*1024); // how many bytes per chunk
  2235. $buffer = '';
  2236. $cnt = 0;
  2237. $handle = fopen($filename, 'rb');
  2238. if ($handle === false) return false;
  2239. ob_start();
  2240. while (!feof($handle)) {
  2241. $buffer = fread($handle, $chunksize);
  2242. echo $buffer;
  2243. ob_flush();
  2244. flush();
  2245. if ($retbytes) $cnt += strlen($buffer);
  2246. set_time_limit(0);
  2247. }
  2248. ob_end_flush();
  2249. $status = fclose($handle);
  2250. if ($retbytes && $status) return $cnt; // return num. bytes delivered like readfile() does.
  2251. return $status;
  2252. }
  2253. /**
  2254. * Create a file path by concatenation of given arguments.
  2255. * Windows paths with backslash directory separators are normalized in *nix paths.
  2256. *
  2257. * @param string $path, ...
  2258. * @return string normalized path
  2259. */
  2260. function file_path($path)
  2261. {
  2262. $args = func_get_args();
  2263. $ds = '/';
  2264. $win_ds = '\\';
  2265. $n_path = count($args) > 1 ? implode($ds, $args) : $path;
  2266. if(strpos($n_path, $win_ds) !== false) $n_path = str_replace( $win_ds, $ds, $n_path );
  2267. $n_path = preg_replace( "#$ds+#", $ds, $n_path);
  2268. return $n_path;
  2269. }
  2270. /**
  2271. * Returns file extension or false if none
  2272. *
  2273. * @param string $filename
  2274. * @return string, false
  2275. */
  2276. function file_extension($filename)
  2277. {
  2278. $pos = strrpos($filename, '.');
  2279. if($pos !== false) return substr($filename, $pos + 1);
  2280. return false;
  2281. }
  2282. /**
  2283. * Checks if $filename is a text file
  2284. *
  2285. * @param string $filename
  2286. * @return bool
  2287. */
  2288. function file_is_text($filename)
  2289. {
  2290. if($mime = file_mime_content_type($filename)) return substr($mime,0,5) == "text/";
  2291. return null;
  2292. }
  2293. /**
  2294. * Checks if $filename is a binary file
  2295. *
  2296. * @param string $filename
  2297. * @return void
  2298. */
  2299. function file_is_binary($filename)
  2300. {
  2301. $is_text = file_is_text($filename);
  2302. return is_null($is_text) ? null : !$is_text;
  2303. }
  2304. /**
  2305. * Return or output file content
  2306. *
  2307. * @return string, int
  2308. *
  2309. **/
  2310. function file_read($filename, $return = false)
  2311. {
  2312. if(!file_exists($filename)) trigger_error("$filename doesn't exists", E_USER_ERROR);
  2313. if($return) return file_get_contents($filename);
  2314. return file_read_chunked($filename);
  2315. }
  2316. /**
  2317. * Returns an array of files contained in a directory
  2318. *
  2319. * @param string $dir
  2320. * @return array
  2321. */
  2322. function file_list_dir($dir)
  2323. {
  2324. $files = array();
  2325. if ($handle = opendir($dir))
  2326. {
  2327. while (false !== ($file = readdir($handle)))
  2328. {
  2329. if ($file[0] != "." && $file != "..") $files[] = $file;
  2330. }
  2331. closedir($handle);
  2332. }
  2333. return $files;
  2334. }
  2335. ## Extra utils ________________________________________________________________
  2336. if(!function_exists('array_replace'))
  2337. {
  2338. /**
  2339. * For PHP 5 < 5.3.0 (backward compatibility)
  2340. * (from {@link http://www.php.net/manual/fr/function.array-replace.php#92549 this php doc. note})
  2341. *
  2342. * @see array_replace()
  2343. * @param string $array
  2344. * @param string $array1
  2345. * @return $array
  2346. */
  2347. function array_replace( array &$array, array &$array1 )
  2348. {
  2349. $args = func_get_args();
  2350. $count = func_num_args();
  2351. for ($i = 0; $i < $count; ++$i)
  2352. {
  2353. if(is_array($args[$i]))
  2354. {
  2355. foreach ($args[$i] as $key => $val) $array[$key] = $val;
  2356. }
  2357. else
  2358. {
  2359. trigger_error(
  2360. __FUNCTION__ . '(): Argument #' . ($i+1) . ' is not an array',
  2361. E_USER_WARNING
  2362. );
  2363. return null;
  2364. }
  2365. }
  2366. return $array;
  2367. }
  2368. }
  2369. /**
  2370. * Check if a string is an url
  2371. *
  2372. * This implementation no longer requires
  2373. * {@link http://www.php.net/manual/en/book.filter.php the filter extenstion},
  2374. * so it will improve compatibility with older PHP versions.
  2375. *
  2376. * @param string $str
  2377. * @return false, str the string if true, false instead
  2378. */
  2379. function filter_var_url($str)
  2380. {
  2381. $regexp = '@^https?://([-[:alnum:]]+\.)+[a-zA-Z]{2,6}(:[0-9]+)?(.*)?$@';
  2382. $options = array( "options" => array("regexp" => $regexp ));
  2383. return preg_match($regexp, $str) ? $str : false;
  2384. }
  2385. /**
  2386. * For PHP 5 < 5.1.0 (backward compatibility)
  2387. * (from {@link http://www.php.net/manual/en/function.htmlspecialchars-decode.php#82133})
  2388. *
  2389. * @param string $string
  2390. * @param string $quote_style, one of: ENT_COMPAT, ENT_QUOTES, ENT_NOQUOTES
  2391. * @return the decoded string
  2392. */
  2393. function limonade_htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
  2394. {
  2395. $table = array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style));
  2396. if($quote_style === ENT_QUOTES)
  2397. $table['&#039;'] = $table['&#39;'] = '\'';
  2398. return strtr($string, $table);
  2399. }
  2400. if(!function_exists('htmlspecialchars_decode'))
  2401. {
  2402. function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
  2403. {
  2404. return limonade_htmlspecialchars_decode($string, $quote_style);
  2405. }
  2406. }
  2407. /**
  2408. * Called just after loading libs, it provides fallback for some
  2409. * functions if they don't exists.
  2410. *
  2411. */
  2412. function fallbacks_for_not_implemented_functions()
  2413. {
  2414. if(!function_exists('json_encode'))
  2415. {
  2416. /**
  2417. * for PHP 5 < PHP 5.2.0
  2418. *
  2419. */
  2420. function json_encode()
  2421. {
  2422. trigger_error(
  2423. __FUNCTION__ . '(): no JSON functions available. Please provide your own implementation of ' . __FUNCTION__ . '() in order to use it.', E_USER_WARNING
  2424. );
  2425. }
  2426. }
  2427. }
  2428. # ================================= END ================================== #