* @copyright Copyright (c) 2008-9 TypeOneError Studios (http://www.typeoneerror.com) * @license MIT ~ http://www.opensource.org/licenses/mit-license.php * @version $Id$ * @link http://typeoneerror.com/asra * @category Asra * @package Core */ /** * @see Asra_Core_Bootstrap */ require_once "Asra/Core/Bootstrap.php"; /** * @see Asra_Constants_Lang */ require_once "Asra/Constants/Lang.php"; /** * @see Asra_Db_Expr */ require_once "Asra/Db/Expr.php"; /** * @see Asra_Export_Data */ require_once "Asra/Export/Data.php"; /** * @see Asra_Export_Registrar */ require_once "Asra/Export/Registrar.php"; /** * @see Asra_Utils_Arrays */ require_once "Asra/Utils/Arrays.php"; /** * @see Asra_Utils_Filesystem */ require_once "Asra/Utils/Filesystem.php"; /** * @see Asra_Utils_String */ require_once "Asra/Utils/Strings.php"; /** * Performs querying and exporting. The core class of the ASRA app. * This class is final and so shouldn't/couldn't be extended. It * is implemented as a Singleton pattern and is initialized in the * Asra_Core_Bootstrap class. Many of the settings applied to the * bootstrap are mirrored to this class so there should be no need * to access it in any way. * * @todo rework all comments and clean * @todo authentication * @todo make config loading better * @todo autoloading instead of require_once (faster) * * @author Ben Borowski * @copyright Copyright (c) 2008-9 TypeOneError Studios (http://www.typeoneerror.com) * @license MIT ~ http://www.opensource.org/licenses/mit-license.php * @link http://typeoneerror.com/asra * @category Asra * @package Core */ final class Asra_Core_Api { //--------------------------------------- // PRIVATE VARIABLES //--------------------------------------- /** * Singleton instance * * @var Asra_Core_Api */ private static $INSTANCE; /** * Path to cache directory * * @var string */ private $__cacheDir; /** * Whether to cache output or not * * @var bool */ private $__cacheOutput = false; /** * Configuration for mysql * * @var Asra_Db_Connect */ private $__config = null; /** * Configuration array * * @var array */ private $__configArray = null; /** * There was an error * * @var bool */ private $__errorOccurred = false; /** * How to export the variables * * @var string */ private $__exportType = 'json'; /** * Stored last run query * * @var string */ private $__lastQuery = null; /** * Pat to logging directory * * @var string */ private $__logDir; /** * Switch on to log queries * * @var bool */ private $__logQueries = false; /** * Whether the view should cached * * @var bool */ private $__useCache = false; //--------------------------------------- // PUBLIC VARIABLES //--------------------------------------- /** * Time of caching per view in seconds * * @var int */ public $cacheLifetime = 1; /** * Path of cache file created * * @var string */ public $cachePath; /** * Database connection * * @var Asra_Db_MySQL */ public $db = null; /** * Whether to print results with print_r * * @var bool */ public $debug = false; /** * Last id inserted * * @var int */ public $last; /** * Whether results were printed * * @var bool */ public $wasPrinted = false; //--------------------------------------- // CONSTRUCTOR/SINGLETON //--------------------------------------- /** * Prevent cloning */ // @codeCoverageIgnoreStart final private function __clone() { } /** * prevent outside construction * @return void */ final private function __construct() { } // @codeCoverageIgnoreEnd /** * Get reference to class * * @return Asra_Core_Api */ public static function getInstance() { if (!self::$INSTANCE) { self::$INSTANCE = new Asra_Core_Api(); } return self::$INSTANCE; } //--------------------------------------- // METHODS //--------------------------------------- /** * Initialize API * * @param bool $debug Whether debugging is active * @param array $config Database connection params * @return Asra_Core_Api */ public function init($debug = false, $config = null) { /** * @see Asra_Db_Connect */ require_once "Asra/Db/Connect.php"; /** * @see Asra_Db_MySQL */ require_once "Asra/Db/MySQL.php"; $this->debug = $debug; $this->__configArray = $config; $this->__config = new Asra_Db_Connect($this->__configArray); $autoConnect = false; $this->db = new Asra_Db_MySQL($this->__config, $autoConnect); return $this; } /** * Handle misconnects * * @return Asra_Core_Api Provides fluent interface */ private function __cannotConnect() { $this->error("Could not connect to `{$this->db->getDb()}` on `{$this->db->getHost()}` as `{$this->db->getUser()}`", Asra_Constants_Lang::ERR_CONNECT); return $this; } /** * Clean an array * * @param array $row The rows to clean * @return Array The cleaned array */ public function clean(&$row) { foreach ($row as $key => $value) { if (!is_array($row[$key])) { $row[$key] = Asra_Utils_Strings::clean(Asra_Utils_Strings::flash($value)); } else { $this->clean($value); } } return $row; } /** * Clear a cached file * @param string $path // -- ASRA url of cache file to delete * @return Asra_Core_Api */ public function clearCache($path = null) { $filename = $this->getCachePath($path); Asra_Utils_Filesystem::fileDelete($filename); return $this; } /** * Removes all caches * * @return Asra_Core_Api Provides fluent interface */ public function clearCaches() { $cachedir = @opendir($this->getCacheDir()); while(false !== ($file = readdir($cachedir))) { if (is_dir($file)) continue; if ($file != "." && $file != "..") { Asra_Utils_Filesystem::fileDelete($this->getCacheDir() . $file); } } closedir($cachedir); return $this; } /** * Lazy connect to database * * @return Asra_Core_Api */ protected function _connect() { try { $this->db->connect(); } catch (Asra_Db_Exception $exception) { } return $this; } /** * Outputs a count of rows in a table. * * @param string $table Table name to count * @param string $print Print (true) or return (false) result * @return int Count of table if print is false */ public function count($table, $print = true) { $this->_connect(); if ($this->db->ready) { if ($this->db->exists($table)) { $query = "SELECT * FROM `{$table}`"; $results = $this->run($query); if ($this->__logQueries) $this->log($query); if ($print) { $this->printOut(array( "count" => count($results) )); } else { return count($results); } } else { $this->error("Table `{$table}` does not exist in `{$this->db->db}`", Asra_Constants_Lang::ERR_API_INPUT); } } else { $this->__cannotConnect(); } } /** * Delete a cached file * @param string $path // -- name of cache file to delete * @return Asra_Core_Api */ public function deleteCache($path) { $filename = $this->getCacheDir() . $path; Asra_Utils_Filesystem::fileDelete($filename); return $this; } /** * Just drop the whole table out * * @param string table -- name of the table to dump * @return Asra_Core_Api */ public function dump($table) { $this->_connect(); if ($this->db->ready) { // -- output database if exists if ($this->db->exists($table)) { $query = "SELECT * FROM `{$table}`"; $results = $this->run($query); if ($this->__logQueries) $this->log($query); $this->printOut($results); } else { $this->error("Table `{$table}` does not exist in `{$this->db->db}`", Asra_Constants_Lang::ERR_API_INPUT); } } else { $this->__cannotConnect(); } return $this; } /** * exports an http error with a message * * @param string $errorMsg -- message to display * @param string $errorCode -- defined in /core/lang.php * @return void */ public function error($errorMsg, $errorCode = null) { $this->__errorOccurred = true; $obj = array(); if (is_null($errorCode)) $errorCode = Asra_Constants_Lang::ERR_API; // -- need to make it a dimension for xml print $obj[0]['error'] = true; $obj[0]['code'] = $errorCode; $obj[0]['msg'] = $errorMsg; $this->printOut($obj); } /** * Find some results using a short search. * * @param string $table Table name to search * @param array $params An array of parameters * @return Asra_Core_Api */ public function find($table, $params = null) { $this->_connect(); if ($this->db->ready) { if (is_array($params) && !is_null($params)) { $countConditions = 0; $conditions = ""; $fields = "*"; $limit = ""; $order = ""; //Asra_Utils_Arrays::pr($params); foreach ($params as $var => $val) { switch ($var) { case "conditions": if (is_array($val)) { foreach ($val as $field => $fieldVal) { if ($countConditions != 0) $conditions .= " AND "; $conditions .= "`{$field}` = " . Asra_Utils_Strings::makesafe($fieldVal); $countConditions++; } } else if (!empty($val)) { $conditions = $val; } break; case "fields": if (is_array($val)) { $fields = implode(", ", $val); } else if (!empty($fields)) { $fields = $val; } break; case "limit": if (!is_null($val)) { if (is_array($val)) { $limitStart = (isset($val[0])) ? (int) $val[0] : 0; $limitLength = (isset($val[1])) ? (int) $val[1] : Asra_Constants_Lang::DEFAULT_LIMIT; $limit .= "LIMIT {$limitStart}, {$limitLength}"; } else { if (strlen($val) && !is_null($val)) $limit .= "LIMIT {$val}"; } } break; case "order": if (strlen($val) && !is_null($val)) { $order .= "ORDER BY {$val}"; } break; default: break; } } if (strlen($conditions)) $conditions = "WHERE {$conditions}"; if (!strlen($fields)) $fields = "*"; $query = "SELECT {$fields} FROM `{$table}` {$conditions} {$order} {$limit}"; $subquery = (isset($params['subquery'])) ? $params['subquery'] : null; $this->query($query, $subquery); } else { // -- empty params selects all if (is_null($params)) { $this->dump($table); } else { // -- string based query $query = "SELECT * FROM `{$table}` WHERE {$params}"; $this->query($query); } } } else { $this->__cannotConnect(); } return $this; } /** * return the cache directory * @return string */ public function getCacheDir() { return $this->__cacheDir; } /** * return the log directory * @return string */ public function getLogDir() { return $this->__logDir; } /** * Return the path of the current action cache or * return path of a cache based on a url. * *
// -- would return path to "sample_table.id.1.xml"
     * $this->getCachePath("/xml/sample_table/id/1/");
     * 
* * @param string $path Url to get cache for * @return string */ public function getCachePath($path = null) { if (is_null($path)) { $cachePath = $this->getCacheDir() . $this->cachePath . "." . $this->__exportType; } else { // -- return cache based on string path $path = trim($path, "/"); $parts = explode('/', $path); $type = strtolower(trim($parts[0])); if (!Asra_Export_Registrar::hasFormat($type)) { $type = Asra_Export_Registrar::$defaultType; } else array_shift($parts); $path = implode('/', $parts); $cachePath = $this->getCacheDir() . ereg_replace("/", ".", $path) . "." . $type; } return $cachePath; } /** * Get the export type for results * @return string type // -- type of results */ public function getExportType() { return $this->__exportType; } /** * fetch whether query logging is active * @return bool */ public function getLogQueries() { return $this->__logQueries; } /** * fetch whether Api is using cache * @return bool */ public function getUseCache() { return $this->__useCache; } /** * returns the last inserted mysql id * @return int */ public function id() { return $this->last; } /** * Write output line to log file * @param string $output // -- string to write to file * @return Asra_Core_Api */ public function log($output) { if (!$this->getLogDir()) { require_once 'Asra/Core/Exception.php'; throw new Asra_Core_Exception("Log directory was not specified but an attempt to log an object was made"); } $logFile = $this->getLogDir() . "trace.log"; Asra_Utils_Filesystem::trace("[" . date('Y-m-d h:i:s') . "] " . $output, $logFile); return $this; } /** * Selects paginated results based on a page id. * * @param string $table Table to select from * @param int $page Page to start from * @param int $perPage Number of results per page * @param array $params Params to apply to find method * @return void */ public function paginate($table, $page = 0, $perPage = 10, $params = null) { $start = (int) ($page * $perPage); $perPage = (int) $perPage; $pagination = array("limit" => array($start, $perPage)); if (!is_null($params)) { if (is_string($params)) { // -- assume it's a sql string $sql = $params .= " LIMIT {$start},{$perPage}"; $this->query($sql); // -- needs no further processing return; } else { // -- assume it's a find array $pagination = array_merge($params, $pagination); } } $this->find($table, $pagination); } /** * Prints a list of results * * @param array $results A list of results from the database * @return Asra_Core_Api Reference to API */ public function printOut($results) { // -- output for debugging if requested if ($this->debug) { echo $this->__lastQuery; Asra_Utils_Arrays::print_array($results); } if ($results == false) $results = array(); // -- ensure that this is a valid export if (!Asra_Export_Registrar::hasFormat($this->__exportType)) { require_once "Asra/Core/Exception.php"; throw new Asra_Core_Exception(Asra_Constants_Lang::ERR_STR_EXPORT_TYPE . " `" . $this->__exportType . "`"); //$this->error(Asra_Constants_Lang::ERR_STR_EXPORT_TYPE, Asra_Constants_Lang::ERR_EXPORT_TYPE); } // -- for caching/naming $filename = $this->getCachePath(); // -- handle the beginning of caching if ($this->__cacheOutput || $this->__useCache) { if (file_exists($filename) && time() - filemtime($filename) < $this->cacheLifetime) { switch ($this->__exportType) { case "xml": if (!$this->debug) header('Content-type: application/xml'); readfile($filename); echo "\n\n"; break; case "yaml": Asra_Utils_Filesystem::yaml($filename); readfile($filename); echo "\n# generated from cache " . filemtime($filename) . "\n"; break; default: readfile($filename); break; } exit(); } else { $newCache = true; ob_start(); } } // -- certain file types need specific headers here too switch ($this->__exportType) { case "yaml": Asra_Utils_Filesystem::yaml($filename); break; } // -- echo out the results $classFile = Asra_Utils_Strings::camelize($this->__exportType); $className = Asra_Export_Registrar::getClassName($this->__exportType); $classPath = Asra_Export_Registrar::getClassPath($this->__exportType); $data = new Asra_Export_Data($results); //echo $classFile . " | " . $className . " | " . $classPath; if (!is_null($classPath)) { $path = Asra_Utils_Strings::addTrailingSlash($classPath) . $classFile . ".php"; require_once $path; } if (!class_exists($className)) { require_once "Asra/Core/Exception.php"; throw new Asra_Core_Exception("Class or file `{$className}` did not exist"); } $class = new $className($data, $this->__errorOccurred); echo $class->export(); // -- wrap up caching if (($this->__cacheOutput || $this->__useCache) && isset($newCache) && $newCache) { Asra_Utils_Filesystem::fileWrite($filename, ob_get_contents()); ob_end_flush(); } // -- finish up $this->wasPrinted = true; return $this; } /** * prints a list of results using run() * @param string $query // -- sql query to run * @param mixed $subquery // -- sub query to apply to results * @return Asra_Core_Api */ public function query($query, $subquery = null) { $results = $this->run($query, $subquery); $this->printOut($results); return $this; } /** * Run some mysql queries and returns results. * * @param string $query A mysql SQL query * @param mixed $subquery Query to apply to results * @return array */ public function run($query, $subquery = null) { $this->_connect(); // -- check if mysql is ready if ($this->db->ready) { // -- format and save query results $this->__lastQuery = $query; $results = $this->db->query($query); if ($this->__logQueries) $this->log($query); if ($results != false) { $output = array(); foreach ($results as $row) { // -- clean results for flash? if ($this->__exportType == 'serial') { $row = $this->clean($row); } // -- if a subquery is defined, get that stuff too if (!is_null($subquery)) { if (is_array($subquery)) { if (isset($subquery['query'])) $nquery = $subquery['query']; if (isset($subquery['key'])) $key = $subquery['key']; else $key = "subquery"; if (isset($subquery['field'])) $field = $subquery['field']; else $field = "id"; } else { if (strlen($subquery)) $nquery = $subquery; $key = "subquery"; $field = "id"; } if (isset($nquery) && !empty($row[$field])) { $nquery = vsprintf($nquery, array($row[$field])); //echo $nquery; $temp = $this->db->query($nquery); if ($this->__logQueries) $this->log($nquery); if ($temp) { $sub = array(); foreach($temp as $item) $sub[] = $item; $row[$key] = $sub; } else $row[$key] = array(); } else $row[$key] = array(); } $output[] = $row; } return $output; } else { return false; } } else { $this->__cannotConnect(); return false; } } public function safe($value) { $this->_connect(); return Asra_Utils_Strings::makesafe($value); } /** * Save an simple array to a row * * @param array $data Data to save * @param string $table Table to write to * @param bool $outputResults Display an ASRA result set when complete * @return bool Success of insert */ public function save($data, $table, $outputResults = true) { $this->_connect(); if ($this->db->ready) { if (!is_null($data) && count($data)) { if (isset($data['id'])) $query = "UPDATE "; else $query = "INSERT INTO "; $query .= "`{$table}` SET "; $i = 0; foreach ($data as $key => $value) { if ($key != 'id') { if ($i) $query .= ", "; if ($value instanceof Asra_Db_Expr) $query .= "{$key} = " . $value->getValue(); else $query .= "{$key} = " . Asra_Utils_Strings::makesafe($value); } $i++; } if ($this->__logQueries) $this->log($query); $results = $this->db->run($query, true); $array = array(); $array[0] = array(); $array[0]['success'] = ($results != false) ? true : false; $array[0]['affected'] = $results; if ($results != false) $this->last = mysql_insert_id(); if ($outputResults == true) $this->printOut($array); return (($results != false) ? true : false); } } else { $this->__cannotConnect(); } return false; } /** * set the cache directory * * @param string $value -- path to cache dir * @return Asra_Core_Api */ public function setCacheDir($value) { if (!is_null($value) && is_string($value) && strlen($value)) { $this->__cacheDir = Asra_Utils_Strings::addTrailingSlash($value); } else { $this->__cacheDir = null; } return $this; } /** * Sets the cache lifetime for a view in seconds * - if seconds is a boolean it does NOT use caching (true|false) * - if seconds is a number it uses caching and sets the lifetime * * @param mixed $seconds * @return Asra_Core_Api */ public function setCacheLifeTime($seconds = false) { if (!isset($this->cachePath) || $this->cachePath == '') return; $this->cacheLifetime = 1; if (!is_bool($seconds)) { if (is_numeric($seconds)) { $this->cacheLifetime = (int) $seconds; $this->__useCache = true; } } else if ($seconds === false) { $this->__useCache = false; } else if ($seconds === true) { $this->__useCache = true; } return $this; } /** * Set flag set for caching output. * * @param bool $value * @return Asra_Core_Api */ public function setCacheOutput($value) { $this->__cacheOutput = $value; return $this; } /** * Set the export type for results. * * @param string $type Type of results. * @return Asra_Core_Api */ public function setExportType($type) { $type = strtolower($type); if (!Asra_Export_Registrar::hasFormat($type) || !strlen($type)) { $type = Asra_Export_Registrar::$defaultType; } $this->__exportType = $type; return $this; } /** * Set the log directory. * * @param string $value Absolute path of log directory * @return Asra_Core_Api */ public function setLogDir($value) { if (!is_null($value) && is_string($value) && strlen($value)) { $this->__logDir = Asra_Utils_Strings::addTrailingSlash($value); } else { $this->__logDir = null; } return $this; } /** * Set query logging flag. Setting this to * true will enable query logging in a trace.log * text file defined by you. * * @param bool $value true to enable logging. * @return Asra_Core_Api */ public function setLogQueries($value) { if (!is_bool($value)) { $value = (bool) $value; } $this->__logQueries = $value; return $this; } }