<?php
/**
* This file is the initializer for the rest of the framework.
*
* This file holds the base class, {@link Krai}, which is responsible for
* configuration and initialization of the framework.
*
* @package Krai
* @author Greg McWhirter <hide@address.com>
* @copyright Copyright (c) 2008, Greg McWhirter
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
define_syslog_variables();
/**
* This is the framework configuration and initialization functionality class.
*
* This class is responsible for configuring and initializing the rest of the
* framework. Applications should include this file in the first script.
* Then the application should call {@link Krai::Setup()} with the name of the
* config file to use, followed by {@link Krai::Run()}.
*
* @package Krai
*/
class Krai
{
/**#@+
* Logger level constant. These are used with {@link Krai_Log}.
*
*/
const LOG_EMERG = LOG_EMERG;
const LOG_ALERT = LOG_ALERT;
const LOG_CRITICAL = LOG_CRIT;
const LOG_ERROR = LOG_ERR;
const LOG_WARNING = LOG_WARNING;
const LOG_NOTICE = LOG_NOTICE;
const LOG_INFO = LOG_INFO;
const LOG_DEBUG = LOG_DEBUG;
/**#@-*/
/**
* Application filesystem root directory variable
*
* This variable holds the value of the path to the application root directory.
* It can be in the config file which is passed to {@link Krai::Setup()}.
* If it is not set, it defaults to the directory holding {@link Krai.php}.
*
* @var string
*/
public static $APPDIR;
/**
* Framwork filesystem directory variable
*
* This variable holds the value of the path to the framework root directory.
* The value defaults to to the directory Krai within the directory holding
* {@link Krai.php}. There is currently not a way to alter this, but there is
* also no expectation that it should need to be altered. The value has no
* trailing slash.
*
* @var string
*/
public static $FRAMEWORK;
/**
* Application includes directory variable
*
* This variable holds the value of the path to the application includes
* directory. It gets set to {@link Krai::$APPDIR}/includes. The value has no
* trailing slash.
*
* @var string
*/
public static $INCLUDES;
/**
* Application modules directory variable
*
* This variable holds the value of the path to the application modules
* directory. It gets set to {@link Krai::$INCLUDES}/modules. The value has no
* trailing slash.
*
* @var string
*/
public static $MODULES;
/**
* Application layouts directory variable
*
* This variable holds the value of the path to the application layouts
* directory. It gets set to {@link Krai::$INCLUDES}/layouts. The value has no
* trailing slash.
*
* @var string
*/
public static $LAYOUTS;
/**
* Starting micro timestamp
*
* This is the {@link PHP_MANUAL#microtime} of more-or-less the start of the
* framework execution.
*
* @var float
*/
public static $STARTTIME;
/**
* Inflector instance
*
* This variable holds an instance of {@link Krai_Lib_Inflector}, which can be
* used to convert from WordsWithUnderscores |-> words_with_underscores and
* back, among other functionality.
*
* @var Krai_Lib_Inflector
*/
public static $INFLECTOR = null;
/**
* The page requested
*
* This variable holds the string of the page that was requested. It is
* determined either by passing a string to {@link Krai::Run()}, or else
* automatically set by the call to {@link Krai::DetermineRequest()} from
* within {@link Krai::Run()}.
*
* @var string
*/
public static $REQUEST;
/**
* Flag for whether a MIME type has been set or not
*
* This is a flag representing whether or not a Content-Type header has been
* sent yet in the script. It is manipulated through {@link Krai::SetMime()}.
*
* @var boolean
*/
private static $_MIMESET = false;
/**
* Framework logging flag
*
* This is a flag representing whether or not application-level logging has
* been enabled and initialized in the framework. It is used by
* {@link Krai::WriteLog()} to determine whether to try writing the log
* message or whether to store it for writing when the logger is initialized.
*
* @var boolean
*/
private static $_LOGGING;
/**
* Framework log message cache
*
* This is an array in which to hold log messages that are attempted to be
* sent to the logger for writing when the logger is not ready.
*
* @var array
*/
private static $_BACKLOGS = array();
/**
* Whether or not the application was run
*
* This is a flag representing whether or not {@link Krai::Run()} has yet been
* called. This prevents things from being run twice.
*
* @var boolean
*/
private static $_STARTED = false;
/**
* Whether or not the application was set up
*
* This is a flag representing whether or not {@link Krai::Setup()} has yet
* been called. This prevents things from being set up twice.
*
* @var boolean
*
*/
private static $_SETUP = false;
/**
* Holds the application configuration
*
* This is an array holding the data that was gleaned from a YAML file when
* {@link Krai::Setup()} was run.
*
* @var array
*
*/
private static $_CONFIG = array();
/**
* Input scrubbing class instance
*
* This variable holds an instance of the input scrubber. It is used in the
* {@link Krai::Run()} method.
*
* @var Nakor
*/
private static $_NAKOR_CORE;
/**
* Cleaned $_POST copy
*
* This is a copy of the $_POST data having been run through the input
* scrubber.
*
* @var array
*/
public static $POST = array();
/**
* Cleaned $_GET copy
*
* This is a copy of the $_GET data having been run through the input
* scrubber.
*
* @var array
*/
public static $GET = array();
/**
* Holds a merger of self::$GET, self::$POST, and some things the router finds
*
* This is a merger of {@link Krai::$GET}, {@link Krai::$POST}, and
* some other values as may be determined by {@link Krai_Router} in routing a
* request.
*
* @var array
*/
public static $PARAMS = array();
/**
* Holds the database connections
*
* This is either an associative array of database connections as defined in
* the config file and initialized in {@link Krai::Run()}, or a single
* database if only one was defined.
*
* @var mixed
*/
public static $DB = null;
/**
* Holds the default database connection by reference
*
* This variable holds the instance of the first defined database connection
* in {@link Krai::$DB}.
*
* @var Krai_Db
*/
public static $DB_DEFAULT = null;
/**
* Holds the errors and notices
*
* This array holds the error and notice messages reported by the framework
* and application.
*
* @var array
*/
private static $_MESSAGES = array(
"errors" => array(),
"notices" => array()
);
/**
* The router instance
*
* This variable holds the instance of the router to be used for this instance
* of the application.
*
* @var Krai_Router
*/
public static $ROUTER;
/**
* Gets information from the configuration array {@link Krai::$_CONFIG}.
*
* This method retrieves the values set in the configuration file passed to
* {@link Krai::Setup()}. For instance, if the configuration file had the line
* <code>
* # snippet from the config file
* BASEURI: myapp
* </code>
* Then you can do the following:
* <code>
* print Krai::GetConfig("BASEURI");
* # => myapp
* </code>
*
* You can only retrieve the first level of information from the configuration
* file in this manner. To get lower levels, do the following:
* <code>
* # snippet from the config file
* CONFIG_CACHE:
* DIR: /some/path
* TIMEOUT: 3600
*
* # Getting those configuration settings
* $config_cache = Krai::GetConfig("CONFIG_CACHE");
* print $config_cache["DIR"];
* # => /some/path
* </code>
*
* @param string $_key The name of the configuration key to get
* @return mixed The value of the key
* @throws Exception When the key is not found
*/
public static function GetConfig($_key)
{
if(array_key_exists($_key, self::$_CONFIG))
{
return self::$_CONFIG[$_key];
}
else
{
throw new Exception("Key not present in configuration.");
}
}
/**
* This is the configuration function for the framework.
*
* This function sets up the framework in preparation of being run.
* It expects a parameter of a string of the location and name of the YAML
* configuration file. If the application skeleton is being used, this should
* be "../includes/configs/krai.yml" from the usual
* {@link Demo#default.php default.php} location.
*
* The function sets up the {@link Krai::$APPDIR}, {@link Krai::$FRAMEWORK},
* {@link Krai::$INCLUDES}, {@link Krai::$MODULES}, and {@link Krai::$LAYOUTS}
* variables, as well as starts a PHP session (unless disabled) and an output
* buffer (unless disabled).
*
* @param string $_conf_file The path to the configuration file.
* @throws Exception
*/
public static function Setup($_conf_file)
{
if(!self::$_SETUP)
{
self::$STARTTIME = microtime(true);
self::$FRAMEWORK = realpath(dirname(__FILE__)."/Krai");
require_once self::$FRAMEWORK."/Lib.php";
if(!file_exists($_conf_file))
{
throw new Exception("Configuration file does not exist.");
}
self::$_CONFIG = Spyc::YAMLLoad($_conf_file);
//Output buffering setup
try
{
if(self::GetConfig("DISABLE_OB"))
{
$startob = false;
//Don't start output buffering
}
else
{
$startob = true;
}
}
catch(Exception $e)
{
$startob = true;
}
if($startob)
{
try
{
if(self::GetConfig("USE_OB_GZHANDLER"))
{
ob_start("ob_gzhandler");
}
else
{
ob_start();
}
}
catch(Exception $e)
{
ob_start("ob_gzhandler");
}
}
//Session setup
try
{
if(!self::GetConfig("DISABLE_SESSION"))
{
session_start();
}
}
catch(Exception $e)
{
session_start();
}
//Timezone setup
try
{
date_default_timezone_set(self::GetConfig("DEFAULT_TIMEZONE"));
}
catch(Exception $e)
{
date_default_timezone_set("America/New_York");
}
//Appdir setup
try
{
self::$APPDIR = realpath(self::GetConfig("APPDIR"));
}
catch(Exception $e)
{
self::$APPDIR = realpath(dirname(__FILE__));
}
self::$INCLUDES = self::$APPDIR."/includes";
self::$MODULES = self::$INCLUDES."/modules";
self::$LAYOUTS = self::$INCLUDES."/layouts";
self::Uses(self::$FRAMEWORK."/Exception.php");
spl_autoload_register(array("Krai","AutoLoad"));
if(function_exists("__autoload"))
{
spl_autoload_register("__autoload");
}
self::$_SETUP = true;
}
}
/**
* Makes everything start up and work
*
* This function starts everything in motion. According to the configuration,
* it loads and initializes all necessary and sufficient framework components.
* It initializes database connections, and finally initializes scrubbed
* versions of input variables.
*
* @param string $_uri An override for the usually determined request to
* process
* @throws Exception
*/
public static function Run($_uri = null)
{
try
{
if(!self::$_SETUP)
{
throw new Exception("You must run Krai::Setup() before Krai::Run");
}
if(self::$_STARTED)
{
throw new Exception("Application was already started.");
}
self::$_STARTED = true;
if(is_null($_uri))
{
$_uri = self::DetermineRequest();
}
self::$REQUEST = urldecode($_uri);
self::Uses(
self::$FRAMEWORK."/Struct.php"
);
if(self::GetConfig("USE_LOG"))
{
self::Uses(self::$FRAMEWORK."/Log.php");
$lconf = self::GetConfig("CONFIG_LOG");
$LOGINFO = new Krai_Struct_Loginfo();
$LOGINFO->types = $lconf["TYPES"];
$LOGINFO->configs = $lconf["CONFS"];
$LOGINFO->default = $lconf["DEFAULT"];
Krai_Log::Start($LOGINFO);
self::$_LOGGING = true;
}
else
{
self::$_LOGGING = false;
}
self::Uses(
self::$FRAMEWORK."/Router.php",
self::$FRAMEWORK."/Module.php",
self::$FRAMEWORK."/Markup.php"
);
if(self::GetConfig("USE_CACHE"))
{
$cconf = self::GetConfig("CONFIG_CACHE");
self::Uses(self::$FRAMEWORK."/Cache.php");
}
if(self::GetConfig("USE_DB"))
{
$dconf = self::GetConfig("CONFIG_DB");
self::Uses(self::$FRAMEWORK."/Db.php");
$DB = array();
foreach($dconf["DATA"] as $_dbn => $_dbd)
{
$_dbclass = Krai_Db::ClassLookup($_dbd["_type"]);
$DB[$_dbn] = new $_dbclass($_dbd);
}
}
else
{
$DB = array();
}
if(count($DB) == 1)
{
self::$DB = array_shift($DB);
self::$DB_DEFAULT =& self::$DB;
}
else
{
self::$DB = $DB;
self::$DB_DEFAULT =& self::$DB[array_shift(array_keys($DB))];
}
if(self::GetConfig("USE_MAIL"))
{
$mconf = self::GetConfig("CONFIG_MAIL");
self::Uses(self::$FRAMEWORK."/Mail.php");
$mconfstruct = new Krai_Struct_Mailconf($mconf["MAILER_CONFIG"]);
Krai_Mail::Configure(
$mconf["SEND_MAIL"],
$mconf["FROM_ADDR"],
$mconf["FROM_NAME"],
$mconfstruct
);
}
if(is_null(self::$REQUEST))
{
self::$REQUEST = "/";
}
if(!self::$INFLECTOR instanceOf Krai_Lib_Inflector)
{
self::$INFLECTOR = new Krai_Lib_Inflector();
}
self::$_NAKOR_CORE = new Nakor();
/* scrub input etc */
self::$POST = self::$_NAKOR_CORE->CleanInput("POST");
self::$GET = self::$_NAKOR_CORE->CleanInput("GET");
self::$PARAMS = array_merge(self::$GET, self::$POST);
self::$ROUTER = Krai_Router::Instance();
self::ReloadMessages();
self::$ROUTER->DoRoute(self::$REQUEST);
}
catch(Krai_Module_Exception_Mdone $e)
{
self::EndRun();
}
catch(Exception $e)
{
include self::$FRAMEWORK."/Exception.phtml";
exit(0);
}
self::EndRun();
}
/**
* Sets the mime-type header for the response
*
* This function is used to set the Content-type header for a response. It is
* most often used when the script is outputting an image or JSON. If it is
* not called by the application on a given request, the Content-type defaults
* to "text/html" for compatibility. However, some users may want to use
* "application/xhtml+xml" for strict XHTML compliance.
*
* @param string $type The mime-type to set
* @param boolean $force Flag to force a reset if the type had previously been
* set
*
*/
public static function SetMime($type, $force = false)
{
if(self::$_MIMESET && !$force)
{
return false;
}
else
{
header("Content-type: ".$type);
self::$_MIMESET = true;
}
}
/**
* Ends the application run after cleaning up
*
* This function cleans everything up, including closing logs, commiting the
* php session, and setting a Content-type if one was not set in the processed
* request. Additionally, it flushes the output buffer, and exits the program.
*
*/
private static function EndRun()
{
self::SaveMessages();
if(!self::$_MIMESET)
{
self::SetMime("text/html");
}
if(self::$_LOGGING)
{
Krai_Log::Close();
}
session_commit();
ob_end_flush();
exit(0);
}
/**
* Determines the request to be used from server variables.
*
* This function parses the {@link PHP_MANUAL#$_SERVER} REQUEST_URI variable
* in order to determine what request was actually made (taking into account
* the BASEURI set in the configuration file). It is called in
* {@link Krai::Run()} unless an overriding request is passed to that function
*
* @return string The actual request to parse.
*/
private static function DetermineRequest()
{
//get rid of the query string for these purposes
$the_request = preg_replace("#\?.*$#", "", $_SERVER['REQUEST_URI']);
$the_request_actual = preg_replace(array(
"#^/*#",
"#^".self::GetConfig("BASEURI")."#"
),
array(
"",
""
),
$the_request
);
return ($the_request_actual == "") ? "/" : $the_request_actual;
}
/**
* A wrapper for including files and logging such. Uses
* {@link PHP_MANUAL#func_get_args()} for variable argument number.
*
* This function accepts an unlimited number of arguments, each of which
* should be a string of a file that needs to be included into the application
* execution. The function uses {@link PHP_MANUAL#include_once}, so it is not
* suitable for files that need to be included multiple times.
*
* Additionally, the function provides logging for all the includes processed
* through it, at the {@link Krai::LOG_DEBUG} level.
*
* @return boolean Whether or not the file was included successfully
* @throws Exception
*/
public static function Uses()
{
$args = func_get_args();
foreach($args as $filename)
{
if(preg_match("#^pear://#", $filename))
{
try
{
$pp = self::GetConfig("PEAR_PATH");
}
catch(Exception $e)
{
$pp = "";
}
$filename = $pp.substr($filename, 7);
}
if(!(@include_once $filename))
{
Krai::WriteLog("Failed loading file: ".$filename);
throw new Exception("Load files failed for ".$filename);
return false;
}
Krai::WriteLog("Loading file: ".$filename, Krai::LOG_DEBUG);
}
return true;
}
/**
* A function to implode an associative array preserving keys
*
* This function is an extension of {@link PHP_MANUAL#implode} for use when
* you want to preserve the keys of the array as well.
*
* @param string $majorglue The glue for between the array entries
* @param string $minorglue The glue for between key => value pairs
* @param array $array The array to implode
* @return string
*/
public static function AssocImplode($majorglue, $minorglue, array $array)
{
$ret = array();
foreach($array as $k => $v)
{
$ret[] = $k.$minorglue.$v;
}
return implode($majorglue, $ret);
}
/**
* Provides an interface to the loghandler, whatever that might be.
*
* This function is a wrapper for the logger currently in use (namely,
* {@link Krai_Log}). If the logger has been started, it writes to the log
* right away. Otherwise, it writes to the {@link Krai::$_BACKLOGS} array, so
* the log entries can be written if/when the logger gets initialized.
*
* @param string $message The message to write
* @param integer $level The level of the message
* @param array $logs The identifiers of the logs to which to write (default
* is the default log)
* @param string $cat The category of the log message
* @param array $forces I forget... the logger is being rewritten anyhow, so
* this will probably change...
*
*/
public static function WriteLog($message,
$level = Krai::LOG_INFO,
array $logs = array(),
$cat = null,
array $forces = array())
{
if (self::$_LOGGING)
{
Krai_Log::Write($message,$level,$logs,$cat,$forces);
}
else
{
self::$_BACKLOGS[] = array($message, $level, $logs, $cat, $forces);
}
}
/**
* Writes the back logs to the logger
*
* This function writes the messages stored up in {@link Krai::$_BACKLOGS} to
* the logger. If this is called and the logger is still not initialized, an
* Exception is thrown.
*
* @throws Exception
*/
private static function WriteBackLogs()
{
if (self::$_LOGGING)
{
$keys = array_keys(self::$_BACKLOGS);
foreach($keys as $logkey)
{
$log = self::$_BACKLOGS[$logkey];
self::WriteLog($log[0], $log[1], $log[2], $log[3], $log[4]);
unset($log);
unset(self::$_BACKLOGS[$logkey]);
}
}
else
{
throw new Exception(
"Error: Called WriteBackLogs while logging was still off."
);
}
}
/**
* Loads the file for a module or action name
*
* This function attempts to load the file containing the class named by the
* parameter. It is configured to have success with modules and actions placed
* in the usual places.
*
* @param string $_class The name of the class
* @return boolean The success of the loading
* @throws Exception
*
*/
public static function AutoLoad($_class)
{
$_class = self::$INFLECTOR->Camel2Underscore($_class);
if(substr($_class,-6) == "module")
{
self::LoadModuleFile(substr($_class, 0, -7));
}
elseif(substr($_class, -6) == "action")
{
list($mod, $act) = explode("module", $_class, 2);
self::LoadActionFile(substr($mod,0,-1), substr($act, 1,-7));
}
else
{
//throw new Exception("Load failed for class ".$class);
}
}
/**
* Tries to load the file for a module
*
* This function attempts to load the file containing a certain module.
*
* @param string $_module The name of the module
* @return boolean The success of the loading
* @throws Krai_Router_Exception
*/
private static function LoadModuleFile($_module)
{
$f = self::$MODULES."/".$_module.".module/".$_module.".module.php";
if(self::Uses($f))
{
return true;
}
else
{
//throw new Exception("Module Load failed for file ".$f);
}
}
/**
* Tries to load the file for an action
*
* This function attempts to load the file containing a certain action.
*
* @param string $_module The name of the module of the action
* @param string $_action The name of the action
* @return boolean The success of the loading
* @throws Exception
*/
private static function LoadActionFile($_module, $_action)
{
$f = self::$MODULES."/".$_module.".module/actions/".$_action.".action.php";
if(self::Uses($f))
{
return true;
}
else
{
//throw new Exception("Action Load failed for file ".$f);
}
}
/**
* Reloads messages from session if available
*
* This function reloads notice and error messages that may have been saved in
* the php session.
*
*/
private static function ReloadMessages()
{
if(array_key_exists('krai_messages', $_SESSION))
{
if(array_key_exists('errors', $_SESSION['krai_messages']))
{
self::$_MESSAGES["errors"] = $_SESSION['krai_messages']["errors"];
}
if(array_key_exists('notices', $_SESSION['krai_messages']))
{
self::$_MESSAGES["notices"] = $_SESSION['krai_messages']["notices"];
}
unset($_SESSION['krai_messages']);
}
}
/**
* Saves messages to the session
*
* This function saves notice and error messages to the session for retrieval
* at the next request execution.
*
*/
private static function SaveMessages()
{
$_SESSION['krai_messages'] = self::$_MESSAGES;
}
/**
* Save an error message
*
* This function records an error message to the message queue
*
* @param string $message Message to save
*/
public static function Error($message)
{
self::$_MESSAGES["errors"][] = $message;
}
/**
* Save a notice message
*
* This function records a notice message to the message queue
*
* @param string $message Message to save
*/
public static function Notice($message)
{
self::$_MESSAGES["notices"][] = $message;
}
/**
* Determine whether there are errors or not
*
* This function determines whether any error messages are in the queue
*
* @return boolean Whether or not there are errors
*/
public static function IsErrors()
{
return(count(self::$_MESSAGES["errors"]) > 0);
}
/**
* Determine whether there are notices or not
*
* This function determines whether any notice messages are in the queue
*
* @return boolean Whether or not there are notices
*/
public static function IsNotices()
{
return(count(self::$_MESSAGES["notices"]) > 0);
}
/**
* Returns the logged errors
*
* This function returns the error messages currently in the queue and clears
* the queue.
*
* @return array The error messages
*/
public static function GetErrors()
{
$t = self::$_MESSAGES["errors"];
self::$_MESSAGES["errors"] = array();
return $t;
}
/**
* Returns the logged notices
*
* This function returns the notice messages currently in the queue and clears
* the queue.
*
* @return array The notice messages
*/
public static function GetNotices()
{
$t = self::$_MESSAGES["notices"];
self::$_MESSAGES["notices"] = array();
return $t;
}
}