-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapp.php
More file actions
430 lines (380 loc) · 14.8 KB
/
app.php
File metadata and controls
430 lines (380 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
<?php
/**
* @package CORE PHP Framework
* @copyright Copyright (C) 2012 Sebastian Mayer, Andreas Sicking, Andre Jährling
* @license GNU/GPL, see license.txt
* This file is part of CORE PHP Framework.
*
* CORE PHP Framework is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* CORE PHP Framework is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CORE PHP Framework. If not, see <http://www.gnu.org/licenses/>.
*/
// TODO replace all dirname(__FILE__) with __DIR__ with PHP 5.3
require_once 'core/cache/cache.php'; // can't be autoloaded since the cache (see below) uses this class
require_once 'core/cache/global/session.php'; // can't be autoloaded since the autoloader uses this class
/**
* This class represents the single point of entrance for the web application.
* It is mainly responsible for setting up basic settings and managing the main
* modules.
*
* Magic methods:
* @method Module getMODULENAMEModule()
*
* Required defines:
* CORE_PATH - path on the server to CORE's main folder (defined in config/constants.php)
* PROJECT_NAME - unique name of the project (defined in config/constants.php)
* PROJECT_VERSION - current project version, should be increased with each build (defined in config/constants.php)
* DB_CONNECTION - defines the main db connection, see class DB_Connection (defined in config/local.php)
*
* Available defines:
* PROJECT_PATH - path to the projects main folder
* PROJECT_ROOTURI - root uri of the project (might be wrong in CLI mode, can be overwritten in config)
* DS - shortcut for DIRECTORY_SEPARATOR
*
* Optional defines:
* CORE_MAILSENDER - standard sender for CORE's mail functions if no sender is explicitly given
* CORE_ENABLE_LOGGING - can be set to "false" to disable logging
* CORE_ENABLE_URLREWRITE - can be set to "false" to disable rewriting urls even if mod_rewrite is available
* CORE_TEMPORARY_DIRECTORY - can be used to define a different temporary folder than the one of the OS, see System::getTemporaryDirectory()
* CORE_LOG_SLOW_QUERIES - can be set to an amount of milliseconds to log all queries that take more time than this
* CORE_DEBUG_SHOW_QUERIES - can be set to "true" to dump all executed database queries
* PROJECT_ENVIRONMENT - overrides the environment that is automatically detected by Environment::getCurrentEnvironment()
*
* Callback defines:
* CALLBACK_ERROR - executed as soon as an error occurs. If not defined the error message and a backtrace will be printed
* CALLBACK_ONAFTERRESET - executed after the project has been reset
* CALLBACK_MAINTENANCE - if the application is in maintenance mode this callback will be executed if defined
*/
class App {
private static $instance = null;
private $modules = array();
private $maintenanceModeLockfilePath = '';
// CONSTRUCTION ------------------------------------------------------------
private function __construct() {
// singleton
}
// CUSTOM METHODS ----------------------------------------------------------
/**
* Sets up everything neccessary, e.g. error/exception-handlers.
* Needs to be called before anything else can be done.
*/
public static function boot() {
ob_start();
if (PHP_SAPI == 'cli') {
$_SERVER['REQUEST_URI'] = 'http://localhost/'.$_SERVER['argv'][1];
$_SERVER['SERVER_NAME'] = '';
// TODO fill in the actual ip address with PHP 5.3 (-> gethostbyname(gethostname()))
$_SERVER['SERVER_ADDR'] = '127.0.0.1';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['HTTP_ACCEPT_ENCODING'] = '';
$_SERVER['HTTP_USER_AGENT'] = 'CLI';
}
error_reporting(E_ALL|E_STRICT);
ini_set('default_charset', 'utf-8');
header('Content-type: text/html; charset=utf-8');
date_default_timezone_set('Europe/Berlin');
define('DS', DIRECTORY_SEPARATOR);
$backtrace = debug_backtrace();
define('PROJECT_PATH', realpath(dirname($backtrace[0]['file']).'/..'));
// setup autoloading (before session_start() or deserialization won't work)
spl_autoload_register(array('App_Autoloader', 'autoload'));
// overwrite $_SESSION to isolate data of different projects
session_start();
if (!isset($_SESSION[PROJECT_PATH]))
$_SESSION[PROJECT_PATH] = array();
$GLOBALS['_SESSION'] = &$_SESSION[PROJECT_PATH];
$GLOBALS['cache'] = new Cache_Global_Session();
App_Autoloader::loadClassesByURL();
// register error handlers
set_error_handler(array('Core_ErrorHandler', 'handleError'));
set_exception_handler(array('Core_ExceptionHandler', 'handleException'));
register_shutdown_function(array('Core_ErrorHandler', 'onShutdown'));
$app = self::get();
$app->maintenanceModeLockfilePath = PROJECT_PATH.'/config/maintenance.lock';
// first boot
if (!$GLOBALS['cache']->get('CORE_booted')) {
self::systemCheck();
}
if ($app->isMaintenanceModeEnabled()) {
if (defined('CALLBACK_MAINTENANCE'))
call_user_func(CALLBACK_MAINTENANCE);
else
exit;
}
// load configuration files
if (Environment::getCurrentEnvironment() == Environment::DEVELOPMENT) {
require_once PROJECT_PATH.'/config/environments/config.development.php';
}
else {
ini_set('display_errors', 0);
require_once PROJECT_PATH.'/config/environments/config.live.php';
}
// add project migration folder
Core_MigrationsLoader::addMigrationFolder(PROJECT_PATH.'/migrations');
// get project modules
require_once PROJECT_PATH.'/config/modules.php';
// initialize language scriptlet
Language_Scriptlet::get()->init();
// initialize I18N
I18N::get();
// initialize router
Router::get()->init();
if (Environment::getCurrentEnvironment() == Environment::DEVELOPMENT) {
// always check for changed migrations on development (except when resetting)
if (!(Router::get()->getCurrentModule() instanceof CoreRoutes_Reset))
Core_MigrationsLoader::load();
}
Router::get()->runCurrentModule();
if (Environment::getCurrentEnvironment() == Environment::DEVELOPMENT) {
if (Router::get()->getCurrentModule() instanceof Module) {
HTMLTidy::tidy();
}
}
else {
/*
* TODO there is an issue with using dump()/dump_flat() when using
* gzip. ATM not so important since gzip is only used on LIVE (where
* dumps shouldn't be needed anyway)
*/
// output gzip'ed content
if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && function_exists('gzencode')
&& (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false
|| strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== false)
) {
header('Content-Encoding: gzip');
header('Vary: Accept-Encoding');
echo gzencode(ob_get_clean());
}
}
if (!$GLOBALS['cache']->get('CORE_booted')) {
$GLOBALS['cache']->set('CORE_booted', true);
}
}
/**
* Registers a module.
* @param $module_ the module
* @throws Core_Exception if a module with the same name already exists
*/
public function addModule(Scriptlet $module) {
if (!in_array($module->getName(), $this->modules)) {
$this->modules[$module->getName()] = $module;
Router::get()->addScriptletRoute($module->getRouteName(), $module);
}
else
throw new Core_Exception('A module with this name has already been added: '.$name);
}
/**
* Returns a registered module
* @param $name_ the name of the module
* @return Module the module or null if it doesn't exist
*/
public function getModule($name) {
if (!isset($this->modules[$name]))
return null;
else
return $this->modules[$name];
}
/**
* Returns all available modules.
* @return array of Modules
*/
public function getModules() {
return $this->modules;
}
/**
* Magic function such as get{name of module}Module
* @return depends on type of magic method; in this case: the module
*/
public function __call($name, $params) {
if (preg_match('/^get(.*)Module$/', $name, $matches)) {
$module = $this->getModule(Text::camelCaseToUnderscore($matches[1]));
return $module;
}
else
throw new Core_Exception('Call to a non existent function or magic method: '.$name);
}
/**
* @param $enableMaintenanceMode boolean true if maintenance mode should be
* enabled (default), false if maintenance mode should be disabled
*/
public function enableMaintenanceMode($enableMaintenanceMode = true) {
$lockfile = new IO_File($this->maintenanceModeLockfilePath);
if ($enableMaintenanceMode) {
$lockfile->create();
}
else {
$lockfile->delete();
}
}
/**
* @return boolean true if maintenance mode is enabled, false otherwise
*/
public function isMaintenanceModeEnabled() {
$lockfile = new IO_File($this->maintenanceModeLockfilePath);
return $lockfile->exists();
}
/**
* @return App
*/
public static function get() {
return (self::$instance) ? self::$instance : self::$instance = new self();
}
/**
* Checks that basic server configurations are set as needed
* @throws Core_Exception if something is wrong with the server configuration
*/
private static function systemCheck() {
$extensions = array('mbstring', 'gd', 'mysql');
if (Environment::getCurrentEnvironment() == Environment::DEVELOPMENT)
$extensions[] = 'tidy';
foreach ($extensions as $extension)
if (!extension_loaded($extension))
throw new Core_Exception('Please verify your PHP configuration: extension "'.$extension.'" should be loaded.');
foreach (array(/*'register_globals' => 0, */'magic_quotes_runtime' => 0, 'magic_quotes_gpc' => 0, 'short_open_tag' => 1) as $option => $value)
if ($value != ini_get($option))
throw new Core_Exception('Please verify your PHP configuration: '.$option.' should be "'.$value.'", but is "'.ini_get($option).'".');
}
}
// -----------------------------------------------------------------------------
/**
* Responsible for loading classes.
*/
class App_Autoloader {
// CUSTOM METHODS ----------------------------------------------------------
public static function loadClassesByURL() {
if ($classPaths = $GLOBALS['cache']->get('classPaths'.$_SERVER['REQUEST_URI'])) {
foreach ($classPaths as $classPath) {
if (file_exists($classPath)) {
require_once $classPath;
}
}
}
}
public static function autoload($className) {
$path = null;
// is path cached?
if (isset($GLOBALS['cache']) && $path = $GLOBALS['cache']->get($className)) {
if (file_exists($path)) {
// cache all class paths belonging to this url
$classPaths = $GLOBALS['cache']->get('classPaths'.$_SERVER['REQUEST_URI']);
if (!$classPaths || !in_array($path, $classPaths)) {
$classPaths[] = $path;
$GLOBALS['cache']->set('classPaths'.$_SERVER['REQUEST_URI'], $classPaths);
}
require $path;
}
}
// path not cached or wrong cached, search for class
if ((!class_exists($className, false) && !interface_exists($className, false)) || !$path) {
$parts = explode('_', $className);
$isProjectClass = ($parts[0] == PROJECT_NAME);
if ($isProjectClass) {
array_shift($parts);
$basePath = PROJECT_PATH.'/source';
}
else
$basePath = CORE_PATH;
$parts = array_map('strtolower', $parts);
$path = self::loadClass($className, $parts, $basePath);
if (!$isProjectClass) {
if (!$path)
$path = self::loadClass($className, $parts, $basePath.'/'.$parts[0]);
if (!$path)
$path = self::loadClass($className, $parts, $basePath.'/core');
if (!$path)
$path = self::loadClass($className, $parts, $basePath.'/core/'.$parts[0]);
}
}
if (!class_exists($className, false) && !interface_exists($className, false))
trigger_error(sprintf('Class \'%s\' not found', $className), E_USER_ERROR);
}
// TODO add public method to get a classes file location
/**
* Gets the path of the file a given class is located in
* @param $className the searched class
* @param $parts the splitted parts the classname is constructed of
* @param $basePath a base path relative to which the search is made
* @return String the path of the file the searched class is in
*/
private static function loadClass($className, Array $parts, $basePath) {
$partsCount = count($parts);
for ($i = $partsCount - 1; $i >= 0; $i--) {
// Just_Some_Class -> just/some/class.php, just/someclass.php, ...
$path = $basePath;
for ($j = 0; $j < $i; $j++)
$path .= '/'.$parts[$j];
$file = '';
for ($j = $i; $j < $partsCount; $j++)
$file .= $parts[$j];
$path .= '/'.$file.'.php';
if (self::correctClassPath($className, $path))
return $path;
if ($i != $partsCount - 1) {
// Just_Some_Class -> just/class.php, some.php, ...
$path = $basePath;
for ($j = 0; $j < $i; $j++)
$path .= '/'.$parts[$j];
$path .= '/'.$parts[$partsCount - 1].'.php';
if (self::correctClassPath($className, $path))
return $path;
}
}
if (!empty($parts)) {
// Just_Some_Class -> just/some/class/class.php
$path = $basePath.'/'.implode('/', $parts).'/'.$parts[$partsCount - 1].'.php';
if (self::correctClassPath($className, $path))
return $path;
// Maybe it's defined in the file of the parent class?
// Try searching Just_Some, Just, ... as well
array_pop($parts);
self::loadClass($className, $parts, $basePath);
}
return null;
}
/**
* Checks whether a given path really is the file in which the given class is
* located in. If the path is valid it is cached.
* @param $className the searched class
* @param $path the path the searched class might be in
* @return boolean true if the path is correct, false otherwhise
*/
private static function correctClassPath($className, $path) {
if (file_exists($path)) {
require_once $path;
if (class_exists($className, false) || interface_exists($className, false)) {
if (isset($GLOBALS['cache']))
$GLOBALS['cache']->set($className, $path);
return true;
}
}
return false;
}
}
// -----------------------------------------------------------------------------
/* GLOBALLY AVAILABLE FUNCTIONS */
/**
* Dumps detailed information about its input
*/
function dump() {
Core_Dump::dump(func_get_args());
}
/**
* For dumping objects in readable format
*/
function dump_flat() {
Core_Dump::dump_flat(func_get_args());
}
/**
* Prints a backtrace
*/
function backtrace() {
Core_Dump::backtrace();
}
?>