<?php
/**
* @module Q
*/
/**
* Q Bootstrap class
* @class Q_Bootstrap
*/
class Q_Bootstrap
{
/**
* Returns the array of ($pluginName => $pluginDir), in the order they were loaded.
* @return array
*/
static function plugins()
{
return self::$plugins;
}
/**
* @method setIncludePath
* @static
*/
static function setIncludePath()
{
$paths = array(APP_DIR, Q_DIR, get_include_path());
set_include_path(implode(PS, $paths));
chdir('/');
}
/**
* @method registerAutoload
* @static
*/
static function registerAutoload()
{
spl_autoload_register(array('Q', 'autoload'));
}
/**
* @method registerExceptionHandler
* @static
*/
static function registerExceptionHandler()
{
self::$prev_exception_handler
= set_exception_handler(array('Q', 'exceptionHandler'));
}
/**
* @method prevExceptionHandler
* @static
* @return {callable}
*/
static function prevExceptionHandler()
{
return self::$prev_exception_handler;
}
/**
* @method registerErrorHandler
* @static
*/
static function registerErrorHandler()
{
self::$prev_error_handler = set_error_handler(array('Q', 'errorHandler'));
}
/**
* @method prevErrorHandler
* @static
* @return {callable}
*/
static function prevErrorHandler()
{
return self::$prev_error_handler;
}
/**
* @method defineFunctions
* @static
*/
static function defineFunctions()
{
// We may need to define JSON functions ourselves on PHP < 5.2
if ( !function_exists('json_decode') ) {
function json_decode($content, $assoc=false){
if ( $assoc ){
$json = new Q_Json(SERVICES_JSON_LOOSE_TYPE);
} else {
$json = new Q_Json;
}
return $json->decode($content);
}
}
if ( !function_exists('json_encode') ) {
function json_encode($content){
$json = new Q_Json;
return $json->encode($content);
}
}
}
/**
* @method registerShutdownFunction
* @static
*/
static function registerShutdownFunction()
{
register_shutdown_function(array('Q_Bootstrap', 'shutdownFunction'));
}
/**
* @method shutdownFunction
* @static
*/
static function shutdownFunction()
{
if ($error = error_get_last()) {
Q::log($error, 'fatal');
header('PHP Fatal Error', true, 500); // do not expose the error contents
}
/**
* @event Q/shutdown {before}
*/
Q::event('Q/shutdown', compact('error'), 'before');
Q_Cache::shutdownFunction();
if (Q_Session::id()) {
session_write_close();
}
}
/**
* Used to undo the mangling done by magic_quotes_gpc
* @method revertSlashes
* @static
* @param {string} [$to_strip=null] The string or array to revert. If null, reverts all the PHP input arrays.
*/
static function revertSlashes($to_strip = null)
{
if (get_magic_quotes_gpc()) {
if (isset($to_strip)) {
return is_array($to_strip)
? array_map(array('Q_Bootstrap', 'revertSlashes'), $to_strip)
: stripslashes($to_strip);
}
$_COOKIE = self::revertSlashes($_COOKIE);
$_FILES = self::revertSlashes($_FILES);
$_GET = self::revertSlashes($_GET);
$_POST = self::revertSlashes($_POST);
$_REQUEST = self::revertSlashes($_REQUEST);
}
}
/**
* @method setDefaultTimezone
* @static
*/
static function setDefaultTimezone()
{
$script_tz = Q_Config::get('Q', 'defaultTimezone', 'UTC');
if (isset($script_tz)) {
date_default_timezone_set($script_tz);
}
Db::setTimezones();
}
/**
* Loads the configuration and plugins in the right order
* @method configure
* @static
* @param boolean [$force_reload=false] If true, forces the reload of the cache.
* Otherwise it happens only if Q/configServer/interval seconds
* have passed since the last time.
* @throws {Q_Exception_MissingPlugin}
*/
static function configure($force_reload = false)
{
$app_tree = new Q_Tree();
// check if config need to be reloaded
if (Q_Cache::connected()) {
// we need to know reload interval
$app_tree->load('config/Q.json');
$app_tree->load('config/app.json');
$app_tree->load('local/app.json');
$config_files = $app_tree->get('Q', 'configFiles', array());
foreach ($config_files as $cf) {
$app_tree->merge(Q_Config::getFromServer($cf));
}
// second round to catch configFiles inside configFiles
$config_files = $app_tree->get('Q', 'configFiles', array());
foreach ($config_files as $cf) {
$app_tree->merge(Q_Config::getFromServer($cf));
}
$interval = $app_tree->get('Q', 'configServer', 'interval', 60); // reload each minute by default
$app_tree->clear(null);
$timestamp = Q_Cache::get("Q_Config\tupdateTime");
if (!isset($timestamp) || (time() - $timestamp > $interval)) $force_reload = true;
}
if ($force_reload) {
$old_setting = Q_Cache::ignore(true);
}
Q_Config::clear(null); // clear the config
Q_Config::load('config/Q.json');
// Get the app config, but don't load it yet
$app_tree->load('config/app.json');
$app_tree->load('local/app.json');
// Load all the plugin config files first
$paths = explode(PS, get_include_path());
$plugins = $app_tree->get('Q', 'plugins', array());
if (!in_array('Q', $plugins)) {
array_unshift($plugins, 'Q');
}
global $Q_Bootstrap_config_plugin_limit;
$i = 0;
foreach ($plugins as $k => $v) {
++$i;
if (isset($Q_Bootstrap_config_plugin_limit)
and $i > $Q_Bootstrap_config_plugin_limit) {
continue;
}
$plugin = is_numeric($k) ? $v : $k;
$plugin_path = Q::realPath('plugins'.DS.$plugin);
if (!$plugin_path) {
throw new Q_Exception_MissingPlugin(compact('plugin'));
}
array_splice($paths, 1, 0, array($plugin_path));
$PLUGIN = strtoupper($plugin);
if (!defined($PLUGIN.'_PLUGIN_DIR'))
define($PLUGIN.'_PLUGIN_DIR', $plugin_path);
if (!defined($PLUGIN.'_PLUGIN_CONFIG_DIR'))
define($PLUGIN.'_PLUGIN_CONFIG_DIR', $plugin_path.DS.'config');
if (!defined($PLUGIN.'_PLUGIN_CLASSES_DIR'))
define($PLUGIN.'_PLUGIN_CLASSES_DIR', $plugin_path.DS.'classes');
if (!defined($PLUGIN.'_PLUGIN_FILES_DIR'))
define($PLUGIN.'_PLUGIN_FILES_DIR', $plugin_path.DS.'files');
if (!defined($PLUGIN.'_PLUGIN_HANDLERS_DIR'))
define($PLUGIN.'_PLUGIN_HANDLERS_DIR', $plugin_path.DS.'handlers');
if (!defined($PLUGIN.'_PLUGIN_PLUGINS_DIR'))
define($PLUGIN.'_PLUGIN_PLUGINS_DIR', $plugin_path.DS.'plugins');
if (!defined($PLUGIN.'_PLUGIN_SCRIPTS_DIR'))
define($PLUGIN.'_PLUGIN_SCRIPTS_DIR', $plugin_path.DS.'scripts');
if (!defined($PLUGIN.'_PLUGIN_VIEWS_DIR'))
define($PLUGIN.'_PLUGIN_VIEWS_DIR', $plugin_path.DS.'views');
if (!defined($PLUGIN.'_PLUGIN_TESTS_DIR'))
define($PLUGIN.'_PLUGIN_TESTS_DIR', $plugin_path.DS.'tests');
if (!defined($PLUGIN.'_PLUGIN_WEB_DIR'))
define($PLUGIN.'_PLUGIN_WEB_DIR', $plugin_path.DS.'web');
if (!defined($PLUGIN.'_PLUGIN_TEXT_DIR'))
define($PLUGIN.'_PLUGIN_TEXT_DIR', $plugin_path.DS.'text');
self::$plugins[$plugin] = $plugin_path;
}
$paths = array_unique($paths);
set_include_path(implode(PS, $paths));
foreach (self::$plugins as $plugin => $plugin_path) {
Q_Config::load($plugin_path.DS.'config'.DS.'plugin.json');
}
// Now, we can merge in our app's config
Q_Config::merge($app_tree);
// Now, load any other files we were supposed to load
$config_files = Q_Config::get('Q', 'configFiles', array());
foreach ($config_files as $cf) {
Q_Config::merge(Q_Config::getFromServer($cf));
}
// second round to catch configFiles inside configFiles
$config_files = Q_Config::get('Q', 'configFiles', array());
foreach ($config_files as $cf) {
Q_Config::merge(Q_Config::getFromServer($cf));
}
$script_files = Q_Config::get('Q', 'scriptFiles', array());
foreach ($script_files as $cf) {
Q::includeFile($cf);
}
error_reporting(Q_Config::get('Q', 'errorReporting', E_ALL));
if (isset($old_setting)) {
Q_Cache::ignore($old_setting);
}
set_time_limit(Q_Config::get('Q', 'internal', 'phpTimeout', 300));
self::setDefaultTimezone();
Q::event('Q/configure', compact(
'app_tree', 'config_files', 'script_files'
), 'after');
}
static function alertAboutLocalConfiguration()
{
if (defined('CONFIGURE_ORIGINAL_APP_NAME')) {
return; // let the configurarion happen without the alert
}
$app = Q_Config::get('Q', 'app', null);
$prefix = $app ? "$app/" : '';
if (Q_Config::get('Q', 'localNotYetConfigured', null)) {
throw new Q_Exception("Please edit local config in {$prefix}local/app.json");
}
}
/**
* @method checkRequirements
* @static
* @param {array} [$plugins=null]
* @return {boolean}
* @throws {Q_Exception_Requirement}
* @throws {Q_Exception_RequirementVersion}
*/
static function checkRequirements($plugins = null)
{
if (!isset($plugins)) {
$plugins = Q_Config::get('Q', 'plugins', array());
}
$infos = Q_Config::get('Q', 'pluginInfo', array());
$local = Q_Config::get('Q', 'pluginLocal', array());
foreach ($infos as $plugin => $info) {
if (!in_array($plugin, $plugins)) {
continue;
}
if (isset($info['requires']) and is_array($info['requires'])) {
foreach ($info['requires'] as $required_plugin => $required_version) {
if(!isset($local[$required_plugin])) {
throw new Q_Exception_Requirement(array(
'plugin' => $required_plugin,
'version' => $required_version,
'by' => $plugin,
));
}
$installed_version = isset($local[$required_plugin]['version']) ? $local[$required_plugin]['version'] : 0;
$compatible_version = isset($local[$required_plugin]['compatible']) ? $local[$required_plugin]['compatible'] : 0;
if (Q::compareVersion($installed_version, $required_version) < 0) {
throw new Q_Exception_RequirementVersion(array(
'plugin' => $required_plugin,
'version' => $required_version,
'by' => $plugin,
'installed' => $installed_version,
'compatible' => $compatible_version,
));
}
if (Q::compareVersion($compatible_version, $required_version) > 0) {
throw new Q_Exception_RequirementVersion(array(
'plugin' => $required_plugin,
'version' => $required_version,
'by' => $plugin,
'installed' => $installed_version,
'compatible' => $compatible_version,
));
}
}
}
}
return true;
}
/**
* @method checkRequirementsApp
* @static
* @throws {Q_Exception_AppRequirement}
* @throws {Q_Exception_AppRequirementVersion}
*/
static function checkRequirementsApp()
{
$title = Q_Config::expect('Q','app');
$required = Q_Config::get('Q', 'appInfo', 'requires', array());
$local = Q_Config::get('Q', 'pluginLocal', array());
foreach($required as $required_plugin => $required_version) {
if(!isset($local[$required_plugin])) {
throw new Q_Exception_AppRequirement(array(
'plugin' => $required_plugin,
'version' => $required_version,
'by' => $title
));
}
$installed_version = isset($local[$required_plugin]['version']) ? $local[$required_plugin]['version'] : 0;
$compatible_version = isset($local[$required_plugin]['compatible']) ? $local[$required_plugin]['compatible'] : 0;
if (Q::compareVersion($installed_version, $required_version) < 0) {
throw new Q_Exception_AppRequirementVersion(array(
'plugin' => $required_plugin,
'version' => $required_version,
'by' => $title,
'installed' => $installed_version,
'compatible' => $compatible_version,
));
}
if (Q::compareVersion($compatible_version, $required_version) > 0) {
throw new Q_Exception_AppRequirementVersion(array(
'plugin' => $required_plugin,
'version' => $required_version,
'by' => $title,
'installed' => $installed_version,
'compatible' => $compatible_version,
));
}
}
}
/**
* Adds the first alias to the configuration
* @method addAlias
* @static
*/
static function addAlias()
{
if (defined('APP_WEB_DIR')) {
Q_Config::set('Q', 'aliases', '', APP_WEB_DIR);
}
}
/**
* Sets whether the response is buffered from the config, if any
* @method setResponseBuffered
* @static
*/
static function setResponseBuffered()
{
$handler = Q_Config::get('Q', 'response', 'isBuffered', null);
if (isset($handler)) {
Q_Response::isBuffered($handler);
}
}
/**
* Sets whether the response is buffered from the config, if any
* @method setResponseBuffered
* @static
*/
static function setUrls()
{
$url = Q_Config::get('Q', 'response', 'cacheBaseUrl', null);
if (isset($url)) {
Q_Uri::cacheBaseUrl($url);
}
}
/**
* @property $prev_exception_handler
* @type callable
* @protected
* @static
*/
protected static $prev_exception_handler;
/**
* @property $prev_error_handler
* @type callable
* @protected
* @static
*/
protected static $prev_error_handler;
/**
* @property $plugins
* @type array
* @protected
* @static
*/
protected static $plugins = array();
}
if (!function_exists('t')) {
function t($text)
{
// Give handlers a chance to process this text
/**
* @event Q/text {before}
*/
Q::event('Q/text', array(), 'before', $text);
return $text;
}
}