diff --git a/classes/cache/cache_AbstractHandler.php b/classes/cache/cache_AbstractHandler.php index f322d8b..5940ab3 100644 --- a/classes/cache/cache_AbstractHandler.php +++ b/classes/cache/cache_AbstractHandler.php @@ -17,6 +17,8 @@ */ abstract class cache_AbstractHandler implements cache_HandlerInterface { + const DEFAULT_TTL = 0; + /** * Weather or not cache system should throw exceptions * @var bool @@ -60,7 +62,10 @@ final public function get($key, $default = null, $namespace = 'default') { * @param string $namespace The namespace under which the variable is stored [optional, default: 'default'] * @return boolen True on success or, False on failure */ - final public function set($key, $data, $ttl = 0, $namespace = 'default') { + final public function set($key, $data, $ttl = null, $namespace = 'default') { + + if (is_null($ttl)) $ttl = static::DEFAULT_TTL; + try { return $this->_set($key, $data, $ttl, $namespace); } catch (Exception $e) { diff --git a/classes/cache/cache_ApcHandler.php b/classes/cache/cache_ApcHandler.php index 4745705..53c3c1f 100644 --- a/classes/cache/cache_ApcHandler.php +++ b/classes/cache/cache_ApcHandler.php @@ -17,6 +17,9 @@ */ class cache_ApcHandler extends cache_AbstractHandler { /* CONSTANTS */ + + const DEFAULT_TTL = 0; + /* PROPERTIES */ /* CONSTRUCTOR & DESTRUCTOR */ diff --git a/classes/cache/cache_HandlerInterface.php b/classes/cache/cache_HandlerInterface.php index e4efc7f..acb03a8 100644 --- a/classes/cache/cache_HandlerInterface.php +++ b/classes/cache/cache_HandlerInterface.php @@ -18,6 +18,7 @@ */ interface cache_HandlerInterface { /* CONSTRUCTOR & DESTRUCTOR */ + /* GET METHODS */ /** diff --git a/classes/cache/cache_MemcacheHandler.php b/classes/cache/cache_MemcacheHandler.php index d9b4dd5..18a33db 100644 --- a/classes/cache/cache_MemcacheHandler.php +++ b/classes/cache/cache_MemcacheHandler.php @@ -19,6 +19,8 @@ class cache_MemcacheHandler extends cache_AbstractHandler { /* CONSTANTS */ + const DEFAULT_TTL = 0; + /** * Constant defining a minite storage duration * @var integer @@ -46,10 +48,17 @@ public function __construct($host, $port, $useExceptions = false) { $this->useExceptions = $useExceptions; $this->memcache = new Memcache; + if (is_null($host)) return; + if(!$this->memcache->connect($host, $port)) throw new cache_CouldNotConnectException('Could not connect to Memcache server'); } - + + // adds a memcached server to the connection pool + public function addServer ($host, $port = 11211) { + $this->memcache->addServer($host, $port); + } + /* GET METHODS */ /** @@ -65,7 +74,7 @@ protected function _get($key, $default = null, $namespace = 'default') { if(!is_array($return)) { if($this->useExceptions) - throw new cache_NotFoundException(sf('Could not fine \'%s\' variable', $key)); + throw new cache_NotFoundException(sf('Could not find \'%s\' variable', $key)); $return = array($default); } diff --git a/classes/cache/cache_TemporaryStorageHandler.php b/classes/cache/cache_TemporaryStorageHandler.php new file mode 100644 index 0000000..7ebd5da --- /dev/null +++ b/classes/cache/cache_TemporaryStorageHandler.php @@ -0,0 +1,61 @@ +store[$namespace][$key]) && + (is_null($this->store[$namespace][$key]['expires']) || $this->store[$namespace][$key]['expires'] > time())) + return $this->store[$namespace][$key]['value']; + + else return $default; + + } + protected function _set($key, $data, $ttl = null, $namespace = 'default') { + + if (!isset($this->store[$namespace])) { + $this->store[$namespace] = array(); + } + + $this->store[$namespace][$key] = array( + 'value' => $data, + 'expires' => is_null($ttl)?null:(time() + $ttl) + ); + + } + protected function _delete($key, $namespace = 'default') { + + if (isset($this->store[$namespace][$key])); + unset($this->store[$namespace][$key]); + + } + protected function _exists($key, $namespace = 'default'){ + + return isset($this->store[$namespace][$key]) && + (is_null($this->store[$namespace][$key]['expires']) || $this->store[$namespace][$key]['expires'] > time()); + } + + protected function _flush() { + $this->store = array(); + } + +} + +?> \ No newline at end of file diff --git a/classes/caster/caster_Abstract.php b/classes/caster/caster_Abstract.php index 61b7752..53ef7a5 100644 --- a/classes/caster/caster_Abstract.php +++ b/classes/caster/caster_Abstract.php @@ -32,12 +32,17 @@ abstract class caster_Abstract { /* MAGIC METHODS */ /* METHODS */ + static public function getSpec() { + $caster = new static; + return $caster->spec; + } + /** * Checks the given string for the number of caster characters. * @param string $s The string to parse */ protected function numberOfPercents($s) { - if(!is_string($s)) throw new caster_Exception('Parameter $s should be of type string'); + if(!is_string($s)) throw new caster_Exception('Caster expecting format string, received '.gettype($s).': '.$s); $pos = 0; $count = 0; while(true) { @@ -79,6 +84,46 @@ public function castString($string, $args = null, $_ = null) { } return implode('', $ret); } + /** + * Casts args into a given string/object by the class held caster spec + * @param string $string The string to cast + * @param mixed $args The args to be parsed into the string + * @return string The casted string + */ + public function castObject($string, $args = null, $_ = null) { + // Get args + $args = func_get_args(); + if (count($args[0]) !== 2) throw new caster_Exception('Cast object expects two parameters'); + + $ch = substr($args[0][0], 1); + + if(array_key_exists($ch, $this->spec) && method_exists($this,$this->spec[$ch])) { + $methodName = $this->spec[$ch]; + return $this->$methodName($args[0][1]); + } elseif($ch == '%') { + return '%'; + } else { + throw new caster_Exception("Caster received unexpected format character: %".$ch); + } + + } + + public function castArray ($params) { + $format = array_shift ($params); + return $this->castReal ($format, $params); + } + + public function castArraySets ($args) { + $ret = array (); + for ($i = 0; $i < count ($args); ) { + $format = $args [$i++]; + $params = array_slice ($args, $i, $this->numberOfPercents($format)); + $i += count ($params); + array_unshift ($params, $format); + $ret[] =$this->castArray ($params); + } + return $ret; + } /** * Casts args into a given string by the class held caster spec @@ -108,10 +153,14 @@ protected function castReal($format, $args) { if(array_key_exists($ch, $this->spec) && method_exists($this,$this->spec[$ch])) { $methodName = $this->spec[$ch]; $ret .= $this->$methodName($args[$i++]); + + } elseif(array_key_exists($ch, $this->spec) && method_exists($this,'cast_'.$this->spec[$ch])) { + $methodName = 'cast_'.$this->spec[$ch]; + $ret .= $this->$methodName($args[$i++]); } elseif($ch == '%') { $ret .= '%'; } else { - throw new caster_Exception("Invalid format string"); + throw new caster_Exception("Caster received unexpected format character: %".$ch); } $pos0 = $pos1 + 2; } @@ -151,6 +200,14 @@ function formatNewLines($str) { return $ret; } + static function getCastName($format) { + $caster = new static; + return $caster->getCastNameReal($format); + } + function getCastNameReal ($format) { + return $this->spec[$format]; + } + /* DEPRECATED METHODS */ } diff --git a/classes/caster/caster_Json.php b/classes/caster/caster_Json.php new file mode 100644 index 0000000..9426c4e --- /dev/null +++ b/classes/caster/caster_Json.php @@ -0,0 +1,57 @@ + 'string', + 'n' => 'number', + + /* these aren't real JSON types, but can validate on them */ + 'i' => 'integer', + 'f' => 'float', + + 'b' => 'boolean', + 'a' => 'array', + 'o' => 'object', + + 't' => 'timestamp' + ); + + /* annoyance due to PHP scope issue */ + static function cast ($args) { + $parser = new self(); + return (string) $parser->castString(func_get_args()); + } + + static function cast_string ($in) { + return sf("'%j'",$in); + } + + static function cast_integer ($in) { + return intval($in); + } + static function cast_float ($in) { + return floatval($in); + } + static function cast_number ($in) { + return ($in); + } + static function cast_boolean ($in) { + return boolval($in); + } + + static function cast_array ($in) { + return json_encode($in); + } + static function cast_timestamp ($in) { + return intval($in); + } + + static function cast_object ($in) { + return json_encode($in); + } + +} + +?> \ No newline at end of file diff --git a/classes/caster/caster_Php.php b/classes/caster/caster_Php.php new file mode 100644 index 0000000..bf1e0d3 --- /dev/null +++ b/classes/caster/caster_Php.php @@ -0,0 +1,63 @@ + 'string', + 'n' => 'number', + + /* these aren't real JSON types, but can validate on them */ + 'i' => 'integer', + 'f' => 'float', + + 'b' => 'boolean', + + 't' => 'timestamp', + + 'a' => 'array', + 'o' => 'object' + ); + + + /* returns a value */ + static function value ($format, $value) { + + $parser = new self(); + + $methodName = 'cast_'.$parser->spec[$format]; + return $parser->$methodName($value); + + } + + static function cast_string ($in) { + return strval($in); + } + + static function cast_integer ($in) { + return intval($in); + } + static function cast_float ($in) { + return floatval($in); + } + static function cast_number ($in) { + return ($in); + } + static function cast_boolean ($in) { + return boolval($in); + } + static function cast_timestamp ($in) { + return intval($in); + } + + static function cast_array ($in) { + return $in; + } + + static function cast_object ($in) { + return $in; + } + +} + +?> \ No newline at end of file diff --git a/classes/core/Atsumi.php b/classes/core/Atsumi.php index d294521..260e78d 100644 --- a/classes/core/Atsumi.php +++ b/classes/core/Atsumi.php @@ -41,6 +41,14 @@ class Atsumi { */ private $errorHandler; + + /** + * A data store + * @access private + * @var mvc_DynamicModel + */ + private $store; + /* CONSTRUCTOR & DESTRUCTOR */ /** @@ -53,7 +61,9 @@ private function __construct() { $this->errorHandler = new atsumi_ErrorHandler(); // Load some helpful files - atsumi_Loader::references(atsumi_Loader::getAtsumiDir(), 'caster helpers/http'); + \Atsumi\Core\Loader::references(\Atsumi\Core\Loader::getAtsumiDir(), 'caster utility/http'); + + } /* GET METHODS */ @@ -103,6 +113,9 @@ public static function __callStatic($name, $arguments) { if(preg_match('/^error__(.+)$/', $name, $matches)) return call_user_func_array(array($atsumi->errorHandler, $matches[1]), $arguments); + if(preg_match('/^store__(.+)$/', $name, $matches)) + return call_user_func_array(array($atsumi->store, $matches[1]), $arguments); + if(method_exists($atsumi, '_'.$name)) return call_user_func_array(array($atsumi, '_'.$name), $arguments); @@ -142,72 +155,16 @@ public function _initApp(atsumi_AbstractAppSettings $settings) { $this->errorHandler->setDisplayErrors(false); } - $this->appHandler = new atsumi_AppHandler($settings, $this->errorHandler); - } - - /* - * NOTE: Below are all static functions which will be removed when php 5.3.0+ becomes the - * common and the PHP magic function __callStatic works correctly - */ - public static function start() { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function initApp($settings, $debug = false) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function app__setUriParser($parser) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function app__setPath($path) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function app__go($path) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function app__render() { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function app__getParserMetaData() { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function app__createUri($controller, $method) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function error__setRecoverer($recoverer) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function error__addObserver($observer, $eventType) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public static function error__removeObserver($observer) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); + + $this->appHandler = new atsumi_AppHandler($settings, $this->errorHandler); + } - public static function error__setFloodControl(cache_HandlerInterface $cacheManager, $duration) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); + public function _initStore () { + if (class_exists('mvc_DynamicModel')) + $this->store = new mvc_DynamicModel(); } + } ?> \ No newline at end of file diff --git a/classes/core/app/atsumi_AbstractAppSettings.php b/classes/core/app/atsumi_AbstractAppSettings.php index 2c4b81c..80f037b 100644 --- a/classes/core/app/atsumi_AbstractAppSettings.php +++ b/classes/core/app/atsumi_AbstractAppSettings.php @@ -35,6 +35,7 @@ abstract class atsumi_AbstractAppSettings { protected $cache = array(); /* CONSTRUCTOR & DESTRUCTOR */ + public function __construct() { } /* GET METHODS */ /** @@ -134,4 +135,4 @@ protected function &initCache($name) { return $this->cache[$name]; } } -?> \ No newline at end of file +?> diff --git a/classes/core/app/atsumi_AppHandler.php b/classes/core/app/atsumi_AppHandler.php index 288f0da..eaa7814 100644 --- a/classes/core/app/atsumi_AppHandler.php +++ b/classes/core/app/atsumi_AppHandler.php @@ -44,6 +44,13 @@ class atsumi_AppHandler { */ private $uri; + /** + * The command to be parsed and processed + * @access private + * @var string + */ + private $command; + /** * Holds the base path of atsumi relative to the domain * @access private @@ -58,6 +65,13 @@ class atsumi_AppHandler { */ private $uriParser = 'uriparser_Gyokuro'; + /** + * The command parser classname or instance to use + * @access private + * @var string|object + */ + private $claParser = 'claparser_Standard'; + /** * The controller instance generated based on the parser data * @access private @@ -97,6 +111,15 @@ public function __construct($settings, $errorHandler) { /* GET METHODS */ + /** + * Returns the parser return data + * @access public + * @return array|null The parser return data, or null on error + */ + public function getParserData() { + return $this->parserData; + } + /** * Returns the parser return data * @access public @@ -106,8 +129,31 @@ public function getParserMetaData() { return $this->parserMetaData; } + /** + * get the base path of atsumi relative to the domain for if atsumi is running in a subdirectory + * @access public + * @param string $basePath + */ + public function getBaseUri() { + return $this->baseUri; + } + + public function getController() { + return $this->controller; + } + // SET FUNCTIONS + /** + * Sets the command to be parsed and processed + * @access public + * @param string $path + */ + public function setCommand($command) { + $this->command = $command; + } + + /** * Sets the uri to be parsed and processed * @access public @@ -166,13 +212,18 @@ public function createUri($controller, $method) { * @param string $uri The uri to processed */ public function go($uri) { - - $scriptArr = explode('/',$_SERVER['SCRIPT_NAME']); - $baseUri = str_replace(array($_SERVER['DOCUMENT_ROOT'], end($scriptArr)), '', $_SERVER['SCRIPT_FILENAME']); - $this->setBaseUri($baseUri); - - $this->setUri($uri); - $this->parseUri(); + if(isset($this->settings->get_cli) && $this->settings->get_cli === true) { + $this->setBaseUri('.'); + $this->setCommand($uri); + $this->parseCommand(); + } else { + $scriptArr = explode('/',$_SERVER['SCRIPT_NAME']); + $baseUri = str_replace(array($_SERVER['DOCUMENT_ROOT'], end($scriptArr)), '', $_SERVER['SCRIPT_FILENAME']); + $this->setBaseUri($baseUri); + + $this->setUri($uri); + $this->parseUri(); + } $this->process(); } @@ -181,7 +232,7 @@ public function go($uri) { * @access public */ public function parseUri() { - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:parse:uri'); if(!in_array('uriparser_Interface', class_implements($this->uriParser))) throw new Exception('URI Parser must implement uriparser_Interface'); @@ -202,7 +253,34 @@ public function parseUri() { atsumi_Debug::record('Uri Parsing', 'Uri was parsed to determine the controller, method and args.', - array_merge(array('path' => $this->uri), $this->parserData), true); + array_merge(array('path' => $this->uri), $this->parserData), + 'app:parse:uri' + ); + } + + public function parseCommand() { + atsumi_Debug::startTimer('app:parse:command'); + if(!in_array('claparser_Interface', class_implements($this->claParser))) { + throw new Exception('Command Line Argument parser must implement claparser_Interface'); + } + if(is_string($this->claParser)) { + $this->claParser = new $this->claParser(); + } + $parseData = $this->claParser->parseCommand($this->command, $this->settings->init_specification); + + $this->parserData = array( + 'controller' => $parseData['controller'], + 'method' => $parseData['method'], + 'args' => $parseData['args'] + ); + + atsumi_Debug::setParserData($parseData); + atsumi_Debug::record('Command Parsing', + 'Command was parsed to determine the controller, method and args.', + array_merge(array('path' => $this->command), $this->parserData), + 'app:parse:command' + ); + } /** @@ -211,12 +289,12 @@ public function parseUri() { * @access public */ public function process() { + // Could possibly be a fragment of the spec if(!is_string($this->parserData['controller'])) - throw new Exception('Path parsing error, please report to developement team'); - + throw new app_PageNotFoundException('Path parsing error, please report to developement team'); if(!class_exists($this->parserData['controller'])) - throw new Exception('Could not find required controller: '.$this->parserData['controller']); + throw new app_PageNotFoundException('Could not find required controller: '.$this->parserData['controller']); $classname = $this->parserData['controller']; $this->controller = new $classname($this->settings, $this->errorHandler); @@ -225,28 +303,28 @@ public function process() { throw new app_PageNotFoundException(); // Get the debugger and start a timer for processing - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:controller:processing'); // Add the method to the list of processed methods $this->controller->addProcessedMethod($this->parserData['method'], $this->parserData['args']); // Time and execute the pre process - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:controller:preProcess'); $this->controller->preProcess(); - atsumi_Debug::record('Controller PreProcess', 'Before the controllers method was called the pre-process function was executed', null, true); + atsumi_Debug::record('Controller PreProcess', 'Before the controllers method was called the pre-process function was executed', null, 'app:controller:preProcess'); // Time and execute the controllers method - atsumi_Debug::startTimer(); - call_user_func_array(array($this->controller, $this->parserData['method']), $this->parserData['args']); - atsumi_Debug::record('Controller Method', 'The controllers requested method was executed', null, true); + atsumi_Debug::startTimer('app:controller:method'); + $this->controller->processRequest($this->parserData['method'], $this->parserData['args']); + atsumi_Debug::record('Controller Method', 'The controllers requested method was executed', null, 'app:controller:method'); // Time and execute the post process - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:controller:postProcess'); $this->controller->postProcess(); - atsumi_Debug::record('Controller PostProcess', 'After the controllers method was called the post-process function was executed', null, true); + atsumi_Debug::record('Controller PostProcess', 'After the controllers method was called the post-process function was executed', null, 'app:controller:postProcess'); // Log the whole processing time - atsumi_Debug::record('Controller Processing Compleate', 'All processing was compleated successfully', null, true); + atsumi_Debug::record('Controller Processing Complete', 'All processing was completed successfully', null, 'app:controller:processing'); } /** @@ -257,9 +335,10 @@ public function process() { public function render() { // Time and execute the pre render - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:controller:preRender'); + $this->controller->publishFlashData(); $this->controller->preRender(); - atsumi_Debug::record('Controller PreRender', 'Before rendering was processed the pre-render function was executed', null, true); + atsumi_Debug::record('Controller PreRender', 'Before rendering was processed the pre-render function was executed', null, 'app:controller:preRender'); $viewHandler = $this->controller->getViewHandler(); $view = $this->controller->getView(); @@ -276,25 +355,48 @@ public function render() { // Get the debugger and start a timer for rendering - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:controller:rendering'); - $this->controller->publishFlashData(); $viewData = $this->controller->getViewData(); atsumi_Debug::setViewData($viewData); // Time and execute the view handler - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:controller:render'); $viewHandler->render($view, $viewData); - atsumi_Debug::record('Rendering', sf('Rendering was performed by the %s view handler', get_class($viewHandler)), null, true); + atsumi_Debug::record('Rendering', sf('Rendering was performed by the %s view handler', get_class($viewHandler)), null, 'app:controller:render'); + + + // tell the debug to print if required as connection will be closed by __destruct + atsumi_Debug::printIfRequired(); + // Time and execute the post render - atsumi_Debug::startTimer(); + atsumi_Debug::startTimer('app:controller:postRender'); $this->controller->postRender(); - atsumi_Debug::record('Controller PostRender', 'After the rendering was processed the post-render function was executed', null, true); + atsumi_Debug::record('Controller PostRender', 'After the rendering was processed the post-render function was executed', null, 'app:controller:postRender'); // Log the whole processing time - atsumi_Debug::record('Rendering Compleate', 'All rendering was compleated successfully', null, true); + atsumi_Debug::record('Rendering Complete', 'All rendering was completed successfully', null, 'app:controller:rendering'); + } + + public function dispatchConnection () { + + flush(); + ob_flush(); + if (session_id()) session_write_close(); + + fastcgi_finish_request(); + } + + public function postConnectionProcess () { + + // page_name turns in to after_name + $afterMethod = 'after_'.substr($this->parserData['method'],5); + + if (method_exists($this->controller, $afterMethod)) + $this->controller->processRequest($afterMethod, $this->parserData['args']); + } } -?> \ No newline at end of file +?> diff --git a/classes/core/app/exceptions/app_InvalidUsageException.php b/classes/core/app/exceptions/app_InvalidUsageException.php new file mode 100644 index 0000000..14c0fce --- /dev/null +++ b/classes/core/app/exceptions/app_InvalidUsageException.php @@ -0,0 +1,37 @@ + diff --git a/classes/core/app/parser/claparser_Interface.php b/classes/core/app/parser/claparser_Interface.php new file mode 100644 index 0000000..f9028ff --- /dev/null +++ b/classes/core/app/parser/claparser_Interface.php @@ -0,0 +1,23 @@ + diff --git a/classes/core/app/parser/claparser_Standard.php b/classes/core/app/parser/claparser_Standard.php new file mode 100644 index 0000000..52f034d --- /dev/null +++ b/classes/core/app/parser/claparser_Standard.php @@ -0,0 +1,19 @@ + diff --git a/classes/core/app/parser/uriparser_Gyokuro.php b/classes/core/app/parser/uriparser_Gyokuro.php index 612cc09..22cdb20 100644 --- a/classes/core/app/parser/uriparser_Gyokuro.php +++ b/classes/core/app/parser/uriparser_Gyokuro.php @@ -29,7 +29,8 @@ static function processArg ($arg) { // Numeric handling. 1e2 is not handled as numeric as can cause issues // with slices of hashes being unintentionally parsed a numeric - if (is_numeric($arg) && !strpos(strtolower($arg), 'e')) { + // does't process as numeric if leads with a 0 + if (is_numeric($arg) && !strpos(strtolower($arg), 'e') && !(strlen($arg) > 1 && substr($arg, 0,1) === '0')) { // check if is float if (strpos($arg, '.')) { @@ -121,9 +122,9 @@ public static function searchSpec($specification, $controller, $pathArr = array( */ public function createUri($specification, $controller, $method, $args = array()) { $components = self::searchSpec($specification, $controller); - // TODO: add args parser!! - $path = count($components) ? implode('/',$components).'/'. $method.'/':''; + $path = (count($components) ? implode('/',$components).'/':''). ($method ==''?'':$method.'/'); + return $path; } diff --git a/classes/core/app/parser/uriparser_Matcha.php b/classes/core/app/parser/uriparser_Matcha.php new file mode 100644 index 0000000..9490682 --- /dev/null +++ b/classes/core/app/parser/uriparser_Matcha.php @@ -0,0 +1,80 @@ +array('gb'), + 'fr'=>array() + ) + ) + ); + + This would accept: + + - en + - en-gb + - fr + + there is a locale array available in the meta + data that contains language & country + +*/ + +class uriparser_Matcha extends uriparser_Gyokuro { + + + private $locale = null; + private $supportedLanguages = array(); + + // you can optionally pass in the supported languages array + // this will prevent matching none supported 2 letter pages + public function __construct ($supportedLanguages = array()) { + $this->supportedLanguages = $supportedLanguages; + $this->locale = new mvc_LocaleModel(); + } + + + public function parseUri($uri, $specification) { + + + preg_match('@^/([a-z]{2})(-([a-z]{2})){0,1}/@', $uri, $localeMatch, PREG_OFFSET_CAPTURE); + + if (array_key_exists(0,$localeMatch) && ( + + // check that it's in the allowed languages + !count($this->supportedLanguages) || + in_array( + $localeMatch[1][0], + array_keys($this->supportedLanguages) + ) + + )) { + + $this->locale->set('language', $localeMatch[1][0]); + + if (array_key_exists(3,$localeMatch) && ( + + // check it's an allowed country for current language + !count($this->supportedLanguages) || + in_array( + $localeMatch[3][0], + $this->supportedLanguages[$this->locale->get('language')] + ) + )) { + $this->locale->set('country', $localeMatch[3][0]); + } + + $uri = substr($uri, strlen($localeMatch[0][0])-1); + } + + $output = parent::parseUri($uri, $specification); + $output['meta']['locale'] = $this->locale; + + return $output; + } +} +?> \ No newline at end of file diff --git a/classes/core/debug/atsumi_Debug.php b/classes/core/debug/atsumi_Debug.php old mode 100644 new mode 100755 index 1df4985..22717f3 --- a/classes/core/debug/atsumi_Debug.php +++ b/classes/core/debug/atsumi_Debug.php @@ -99,13 +99,20 @@ class atsumi_Debug { * @var array */ protected $timers = array(); + protected $timerMap = array(); /** - * Weather the debugger is active and recording information + * Is the debugger active and recording information * @var boolean */ protected $active = false; + /** + * If this is set to true then it will record data regardless of active + * @var boolean + */ + protected $record = false; + /** * A path to a log file used to output debug data * @var string @@ -144,12 +151,23 @@ private function __construct() {} * @access public */ public function __destruct() { + self::printIfRequired(); + } + + static function getConsoleData() { + $d = self::getInstance(); + return $d->_getConsoleData(); + } + + static function printIfRequired() { + + $d = self::getInstance(); try { - if($this->active && $this->autoRender) - echo $this->render(); + if($d->active && $d->autoRender) + echo $d->render(); } catch (Exception $e) { } } - + /* GET FUNCTIONS */ /** @@ -167,24 +185,28 @@ protected static function getInstance() { } /** - * Returns weather or not the debugger is actively recording data + * Returns debugger active * @access public * @return boolean */ public function _getActive() { - return $active; + return $this->active; } /* SET FUNCTIONS */ /** - * Sets weather or not the debugger is actively recording data + * Sets if debugger is actively recording data * @access public * @param boolean $val If the debugger should be recording [optional, default: true] */ public function _setActive($val = true) { $this->active = $val; } + + public function _setRecord($val = true) { + $this->record = $val; + } /** * Sets weather or not the debugger should automaticlly render on destruction @@ -302,8 +324,11 @@ public function _addDatabase( /*db_InterfaceDatabase */ $database) { * Adds a timer point onto a stack of timers * @access public */ - public function _startTimer() { - $this->timers[] = microtime(true); + public function _startTimer($key = null) { + if (!is_null($key)) + $this->timerMap[$key] = microtime(true); + else + $this->timers[] = microtime(true); } /** @@ -311,11 +336,20 @@ public function _startTimer() { * @access public * @return string */ - public function _endTimer() { + public function _endTimer($key = null) { + + if (!is_null($key)) { + if (!isset($this->timerMap[$key])) return '??'; + $ms = round((microtime(true) - $this->timerMap[$key]), 3); + unset($this->timerMap[$key]); + return $ms; + } + $startTime = array_pop($this->timers); return round((microtime(true) - $startTime), 3).' microseconds'; } + /** * Records an event along with optional data, timer and area the data belongs to * Note: This will do nothing if the debugger is not active @@ -327,14 +361,26 @@ public function _endTimer() { * @param string $area The area to add the data to */ public function _record($title, $desc, $data = null, $timer = false, $area = self::AREA_GENERAL) { - if(!$this->active) return; + if(!$this->active && !$this->record) return; + + if ($timer === true) + $time = self::_endTimer(); + else if (is_string($timer)) { + $time = self::_endTimer($timer); + } + else $time = 0; + + $timerString = '(Process Time: '.$time.')'; + + $this->consoleData[] = array( 'title' => $title, 'desc' => $desc, 'data' => $data, 'area' => $area, - 'timestamp' =>($timer ? '(Process Time: '.self::_endTimer().')' : '') + 'time' => $time, + 'timestamp' => $timerString ); } @@ -359,6 +405,10 @@ public function _addArea($name, $color) { $this->areas[$name] = $color; } + public function _getConsoleData() { + return $this->consoleData; + } + /** * Formats a variable into a html5 valid string representation * @access protected @@ -366,6 +416,7 @@ public function _addArea($name, $color) { * @return string A html5 valid string representation of the variable */ protected function format($value) { + if(is_null($value)) return 'NULL'; @@ -387,12 +438,24 @@ protected function format($value) { if(is_array($value)) { $ret = 'Array('; foreach($value as $key => $item) - $ret .= sf('
[%s] => %s
', $key, $this->format($item)); + $ret .= sf('
[%s] => %s
', $key, stripos($key,'password')?'*****':$this->format($item)); $ret .= ')'; return $ret; } - if(is_object($value)) { + if(is_object($value) && $value instanceof mvc_AbstractModel) { + + $objOutput = ''; + $objArray = $value->getStructure(); + foreach($objArray as $key => $item) + $objOutput .= sf('
[%s] => %s
', $key, stripos($key,'password')?'*****':$this->format($value->get($key))); + + + return sf('(Model) %s:%s', get_class($value), $objOutput); + + } elseif(is_object($value)) { + return sf('(Object) %s', get_class($value)); + /* if(method_exists($value,'toString')) return $value->toString(); @@ -405,6 +468,7 @@ protected function format($value) { $ret .= sf('
[%s] => %s
', $key, $this->format($item)); $ret .= ')'; return $ret; + */ } return sf('(%s)%s', gettype($value), $value); } @@ -646,8 +710,8 @@ protected function returnCss() { .debugConsole .typeUnknown {color: orange; } .logTitle {font-size: 14px; font-weight: bold;} .logTimestamp {font-size: 10px; font-weight: normal; color: #6677DD;} - .logDesc {font-size: 10px; font-style: italic; color:#777; margin-top:0.5em; } - .logData { background-color:#ddd; border:1px solid #ccc; padding:1em; margin-top:1em; } + .logDesc {font-size: 10px; font-style: italic; color:#777; margin-top:0.5em; white-space:pre-line; } + .logData { background-color:#ddd; border:1px solid #ccc; padding:1em; margin-top:1em; white-space:pre-line; } .debugDragToggle {height:5px; border-left:3px double #555; border-right:3px double #555; width:2px; margin:0 auto 0 auto; font-size:0px; display:block; } .debugFooter { background-color:#eee; border-top:2px inset #ccc; height:20px; padding:5px; text-align:right; color:#777; font-size: 12px; } .debugFooter span { color:#777; } @@ -664,9 +728,9 @@ protected function returnCss() { * @return string The debugger as a valid HTML5 string */ public function _render() { - if(!$this->active) return; + $this->startTimer(); $display =(isset($_COOKIE['debugDisplay']) ? strtolower($_COOKIE['debugDisplay']) : 'console'); ob_start(); @@ -700,13 +764,19 @@ public function _render() {
-consoleData as $data) : ?> -
+ +consoleData as $data) : + $color = dechex(crc32($data['area'])); + $color = '#'.substr($color, 0, 6); + + ?> +
+
%s
', $this->format($data['data'])) : '');?>
- +
@@ -768,16 +838,17 @@ public function _render() {
-databases as $key => $database) : $totalTime = 0; - foreach($database->getQueryTimes() as $query) - $totalTime += $query['time'] + foreach($database->getQueryTimes() as $query) { + $totalTime += $query['time']; + } ?> -

Database

-
Total Query time:
+

Database #

+
xgetQueryTimes())?> queries. Time: ms
format($database->getQueryTimes()));?> - +
@@ -825,6 +896,12 @@ public static function setViewData($data) { $args = func_get_args(); self::__callStatic(__FUNCTION__, $args); } + + public static function setRecord($data) { + $args = func_get_args(); + self::__callStatic(__FUNCTION__, $args); + } + public static function setParserData($data) { $args = func_get_args(); @@ -846,12 +923,12 @@ public static function addDatabase($database) { self::__callStatic(__FUNCTION__, $args); } - public static function startTimer() { - self::__callStatic(__FUNCTION__, array()); + public static function startTimer($key = null) { + self::__callStatic(__FUNCTION__, func_get_args()); } - public static function endTimer() { - return self::__callStatic(__FUNCTION__, array()); + public static function endTimer($key = null) { + return self::__callStatic(__FUNCTION__, func_get_args()); } public static function record($title, $desc, $data = null, $timer = false, $area = self::AREA_GENERAL) { @@ -867,5 +944,22 @@ public static function addArea($name, $color) { public static function render() { return self::__callStatic(__FUNCTION__, array()); } + + public static function getDbQueries() { + return self::__callStatic(__FUNCTION__, array()); + } + + public function _getDbQueries () { + + $out = array(); + foreach($this->databases as $key => $database) { + $db = array(); + foreach($database->getQueryTimes() as $query) { + $db[] = $query; + } + $out[] = $db; + } + return $out; + } } ?> \ No newline at end of file diff --git a/classes/core/error/atsumi_ErrorEventArgs.php b/classes/core/error/atsumi_ErrorEventArgs.php index a3e94ea..40a7ff3 100644 --- a/classes/core/error/atsumi_ErrorEventArgs.php +++ b/classes/core/error/atsumi_ErrorEventArgs.php @@ -37,7 +37,7 @@ class atsumi_ErrorEventArgs extends atsumi_EventArgs { * @param Exception $e The exception that was thrown * @param string $recover The recover that was used */ - public function __construct(Exception $e, $recoverer = '') { + public function __construct(Exception $e, $recoverer = null) { $this->exception = $e; $this->recoverer = $recoverer; } diff --git a/classes/core/error/atsumi_ErrorHandler.php b/classes/core/error/atsumi_ErrorHandler.php index d1bfd80..fff1ffc 100644 --- a/classes/core/error/atsumi_ErrorHandler.php +++ b/classes/core/error/atsumi_ErrorHandler.php @@ -139,7 +139,7 @@ private function blockedByFloodControl($type, $line, $file) { if($this->cacheHandler->get($key, false, 'errorHandler')) return true; else return false; - } catch(cache_NotInCacheException $e) { + } catch(cache_NotFoundException $e) { return false; } } @@ -182,17 +182,26 @@ public function handleError($errNumber, $errString, $errFile, $errLine, $errCont * @access public * @param Exception $e The exception that was thrown */ - public function handleException(Exception $e) { + public function handleException(Exception $e, $recover = true) { try { // fires the exception_fc event if not blocked by flood control if(!$this->blockedByFloodControl(get_class($e), $e->getLine(), $e->getFile())) - $this->fireEvent(self::EVENT_EXCEPTION_FC, new atsumi_ErrorEventArgs($e, &$this->recoverer)); + $this->fireEvent( + self::EVENT_EXCEPTION_FC, + new atsumi_ErrorEventArgs($e, $recover?$this->recoverer:null) + ); // fire the exception event regardless of flood control - $this->fireEvent(self::EVENT_EXCEPTION, new atsumi_ErrorEventArgs($e, &$this->recoverer)); - - $this->recoverer->recover($e); + $this->fireEvent( + self::EVENT_EXCEPTION, + new atsumi_ErrorEventArgs($e, $recover?$this->recoverer:null) + ); + + if ($recover) + $this->recoverer->recover($e); + else + return; } catch(Exception $e) { exit(__CLASS__.' Error: '.$e->getMessage()."\n".$e->getFile().' #'.$e->getLine(). PHP_EOL.$e->getTraceAsString()); } @@ -216,13 +225,30 @@ protected function fireEvent($eventType, atsumi_EventArgs $args = null) { // remove failed listener $this->removeObserver($listener); // handle the exception - $this->handleException(new errorHandler_Exception(sf("Listener '%s' failed: ", get_class($listener), $listenerException->getMessage()))); + $this->handleException( + new errorHandler_Exception( + sf("Listener '%s' failed: ", + get_class($listener), + $listenerException->getMessage() + ) + ) + ); } } // record exception as listened to if ($eventType == self::EVENT_EXCEPTION_FC) - $this->recordInFloodControl(get_class($args->exception), $args->exception->getLine(), $args->exception->getFile()); + $this->recordInFloodControl( + get_class($args->exception), + $args->exception->getLine(), + $args->exception->getFile() + ); + } + public function listen ($e) { + $this->handleException($e, false); + } + public function recover ($e) { + $this->recoverer->recover($e); } } ?> \ No newline at end of file diff --git a/classes/core/error/exceptions/exception_Duplicate.php b/classes/core/error/exceptions/exception_Duplicate.php new file mode 100644 index 0000000..1d4bd0a --- /dev/null +++ b/classes/core/error/exceptions/exception_Duplicate.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/classes/core/error/exceptions/exception_NotFound.php b/classes/core/error/exceptions/exception_NotFound.php new file mode 100644 index 0000000..50264e4 --- /dev/null +++ b/classes/core/error/exceptions/exception_NotFound.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/classes/core/error/listeners/listener_AddToSession.php b/classes/core/error/listeners/listener_AddToSession.php index 591e417..71c90ca 100644 --- a/classes/core/error/listeners/listener_AddToSession.php +++ b/classes/core/error/listeners/listener_AddToSession.php @@ -45,6 +45,7 @@ class listener_AddToSession implements atsumi_Observer { private $namespace; private $method; + private $onlyWhenRecovering; /* CONSTRUCTOR & DESTRUCTOR */ @@ -55,12 +56,13 @@ class listener_AddToSession implements atsumi_Observer { * @param mixed $data The data to add when the error occurs * @param string $namespace The namespace to add under if atsumi session is being used [optional, default: session_Handler::DEFAULT_NAMESPACE] */ - public function __construct($key, $data, $namespace = session_Handler::DEFAULT_NAMESPACE, $method= self::METHOD_SET) { + public function __construct($key, $data, $namespace = session_Handler::DEFAULT_NAMESPACE, $method= self::METHOD_SET, $onlyWhenRecovering = false) { $this->data = $data; $this->key = $key; $this->namespace = $namespace; $this->method = $method; + $this->onlyWhenRecovering = $onlyWhenRecovering; } /* GET METHODS */ @@ -96,6 +98,7 @@ protected function mergeDataToSession($errorText) { * @param atsumi_EventArgs $args Any args related to the event */ public function notify(atsumi_Observable $sender, atsumi_EventArgs $args) { + if ($this->onlyWhenRecovering && !$args->recoverer) return; $this->mergeDataToSession(atsumi_ErrorParser::parse($args->exception, atsumi_ErrorParser::PLAINTEXT, $args->recoverer)); } } diff --git a/classes/core/error/listeners/listener_LogToFile.php b/classes/core/error/listeners/listener_LogToFile.php index 1785cfd..4447966 100644 --- a/classes/core/error/listeners/listener_LogToFile.php +++ b/classes/core/error/listeners/listener_LogToFile.php @@ -26,6 +26,7 @@ class listener_LogToFile implements atsumi_Observer { * @var string */ private $logDir; + private $filePrefix; /* CONSTRUCTOR & DESTRUCTOR */ @@ -34,8 +35,9 @@ class listener_LogToFile implements atsumi_Observer { * @access public * @param string $logDir The directory to place the log files in */ - public function __construct($logDir) { + public function __construct($logDir, $filePrefix = '') { $this->logDir = $logDir; + $this->filePrefix = $filePrefix; } /* GET METHODS */ @@ -49,13 +51,14 @@ public function __construct($logDir) { * @param string $dataIn The data to write to the error log */ protected function writeToLog($dataIn) { - $filename = date('Y-m-d').'.log'; - $handle = @fopen($this->logDir.$filename, 'a'); + $filename = $this->filePrefix.($this->filePrefix?'-':'').date('Y-m-d').'.log'; + $filePath = $this->logDir.DIRECTORY_SEPARATOR.$filename; + $handle = @fopen($filePath, 'a'); if(!$handle) - throw new errorHandler_ListenerException('Cannot open log file: '.$this->logDir.$filename); + throw new errorHandler_ListenerException('Cannot open log file: '.$filePath); $write = fwrite($handle, $dataIn); - if ($write === false) throw new errorHandler_ListenerException('Cannot no write log file: '.$this->logDir.$filename); + if ($write === false) throw new errorHandler_ListenerException('Cannot no write log file: '.$filePath); fclose($handle); } diff --git a/classes/core/error/listeners/listener_NewRelic.php b/classes/core/error/listeners/listener_NewRelic.php new file mode 100644 index 0000000..e94e1e4 --- /dev/null +++ b/classes/core/error/listeners/listener_NewRelic.php @@ -0,0 +1,13 @@ +exception); + } + + } +} +?> \ No newline at end of file diff --git a/classes/core/error/listeners/listener_Sentry.php b/classes/core/error/listeners/listener_Sentry.php new file mode 100644 index 0000000..f94395b --- /dev/null +++ b/classes/core/error/listeners/listener_Sentry.php @@ -0,0 +1,29 @@ +dsn = $dsn; + $this->release = $release; + $this->tags = $tags; + } + + public function notify(atsumi_Observable $sender, atsumi_EventArgs $args) { + + $client = new Raven_Client( + $this->dsn, + array ( + 'release' => $this->release, + 'tags' => $this->tags + ) + ); + + $client->captureException($args->exception); + + } +} +?> \ No newline at end of file diff --git a/classes/core/error/parser/atsumi_ErrorParser.php b/classes/core/error/parser/atsumi_ErrorParser.php index 6b42268..5821a6e 100644 --- a/classes/core/error/parser/atsumi_ErrorParser.php +++ b/classes/core/error/parser/atsumi_ErrorParser.php @@ -65,7 +65,7 @@ public static function parse(Exception $e, $contentType = self::HTML, $recoverer case self::PLAINTEXT: $out .= sfl('###### ATSUMI has caught an Exception : %s', date(DATE_ATOM)); $out .= sfl("\n".' >> %s <<', $e->getMessage()); - $out .= sfl("\n".'Exception type: '.get_class($e)); + $out .= sfl("\n".'Exception type: %s',get_class($e)); if($e instanceof ErrorException) $out .= sfl("\n".'Severity level: '.$e->getSeverity()); @@ -85,11 +85,18 @@ public static function parse(Exception $e, $contentType = self::HTML, $recoverer $out .= sfl('Recoverer action: %s', $recoverer->getActionDetails()); } + if (isset($e->details) && !is_null($e->details)) { + $out .= sfl("\n".'-Additional Detail'); + $out .= sfl('%s', pretty($e->details)); + } + + if($e instanceof atsumi_AbstractException) { $out .= sfl("\n".'-How to resolve this issue'); $out .= sfl($e->getInstructions('text/plain')); } + $out .= sfl("\n".'-Stack Trace'); $out .= sfl('%s', atsumi_ErrorParser::formatTrace($e->getTrace())); $out .= sfl('###### End of Exception '."\n\n"); @@ -118,6 +125,11 @@ public static function parse(Exception $e, $contentType = self::HTML, $recoverer $out .= sfl('

Recoverer action: %s

', $recoverer->getActionDetails()); } + if (isset($e->details) && !is_null($e->details)) { + $out .= sfl('

Additional Detail

'); + $out .= sfl('
%s
', pretty($e->details)); + } + if($e instanceof atsumi_AbstractException) { $out .= sfl('

ATSUMI: How to resolve this issue

'); $out .= sfl('
%s
', $e->getInstructions('text/html')); diff --git a/classes/core/loader/Loader.php b/classes/core/loader/Loader.php new file mode 100644 index 0000000..38d4b6e --- /dev/null +++ b/classes/core/loader/Loader.php @@ -0,0 +1,413 @@ +workspace = $this->findWorkspace(); + } + + // GET FUNCTIONS + + /** + * Creates and/or returns a singlton instance of the class + * Note: Protected prevents anything externally calling this function. Use static functions. + * @access protected + * @return Loader A singlton instance of the loader + */ + protected static function getInstance() { + static $sInstance; + + if(!is_object($sInstance)) + $sInstance = new self; + + return $sInstance; + } + + /** + * Gets the name of the atsumi directory, incase it has been name differently by the developer + * @access public + * @return string The atsumi directory name + */ + private function _getAtsumiDir() { + return $this->atsumiDir; + } + + /** + * Gets the workspace of atsumi and the all related projects + * @access public + * @return string A absolute path to the workspace + */ + private function _getWorkspace() { + return $this->workspace; + } + + /* MAGIC METHODS */ + + /** + * PHP Magic function, used to call methods on singleton staticly + * @access public + * @param string $name The name of the method being called + * @param array $arguments The arguments being passed to the method + * @return mixed The result of the function call + */ + public static function __callStatic($name, $arguments) { + $instance = self::getInstance(); + + if(method_exists($instance, '_'.$name)) + return call_user_func_array(array($instance, '_'.$name), $arguments); + + // USe sprintf. No guarantee of sf at this point. + throw new \Exception(sprintf('Undefined call to %s::%s', get_class($instance), $name)); + } + + /* METHODS */ + + /** + * Return an absolute path to the base directory containing atsumi and developer projects + * which is used as the base folder for loading external project files from + * @access protected + * @return string An absolute path to the project base directory + */ + protected function findWorkspace() { + $matches = null; + + $file = str_replace('\\', '/', __FILE__); + + if(!preg_match('|^(.*)/classes/core/loader/Loader.php$|', $file, $matches)) + throw new \Exception('The atsumi loader cannot find its workspace!'); + + $matches = explode('/', $matches[1]); + $this->atsumiDir = array_pop($matches); + return implode('/', $matches); + } + + + /** + * Used to load or include a set of files as specified in the spec + * + * Examples: + * references('atsumi', 'utility mvc database'); + * references('atsumi', array('utility', 'mvc', 'database')); + * references(array('atsumi' => 'utility mvc database')); + * @access public + * @param $spec mixed See above for examples + */ + public function _references($spec) { + + + $args = func_get_args(); + switch(func_num_args()) { + case 1: + if(!is_array($args[0])) + throw new \Exception('Loader spec must be an associative array'); + $spec = $args[0]; + break; + case 2: + if(is_string($args[0]) && is_string($args[1])) { + $spec = array($args[0] => $args[1]); + } else { + $this->projectNamespace = $args[0]; + $spec = $args[1]; + } + break; + default: + throw new \Exception('Loader: Invalid number of args'); + break; + } + $domains = ''; + foreach($spec as $domain => $parts) { + + if(isset($this->processedPaths[$domain.':'.$parts])) continue; + + $this->processedPaths[$domain.':'.$parts] = true; + + if(!is_array($parts)) { + $parts = explode(' ', $parts); + } + + foreach($parts as $part) { + if (empty($part)) continue; + $success = false; + if($this->useRequire($domain, $part)) $success = true; + if($this->useDir($domain, $part, 'src')) $success = true; + if($this->useDir($domain, $part, 'classes')) $success = true; + + if(!$success) throw new \loader_ClassNotFoundException( + sprintf('Unknown reference in Loader::references() : %s:%s', $domain, $part), $domain); + } + } + + } + + /** + * Tries to find a class instance within the classes directory and add it to the autoloader + * @access protected + * @param string $domain The project domain folder to look in + * @param string $part The subfolder to process + * @return boolean If the part was found + */ + protected function useDir($domain, $part, $dirName) { + $path = sprintf('%s/%s/%s/%s', $this->workspace, $domain, $dirName, $part); + if(!is_dir($path)) return false; + self::useClassDir($path, $domain); + return true; + } + + /** + * Tries to find an include a required file in the includes directory + * @access protected + * @param string $domain The project domain folder to look in + * @param string $part The name of the file to look for + * @return boolean If the part was found + */ + protected function useRequire($domain, $part) { + $path = sprintf('%s/%s/include/%s.php', $this->workspace, $domain, $part); + if(!is_file($path)) return false; + + require_once($path); + return true; + } + + /** + * Process a directory presuming all files are named relative to the class they contain + * @access protected + * @param string $path The absolute path to the directory to process + */ + protected function useClassDir($path, $domain) { + $contents = scandir($path); + foreach($contents as $fileName) { + if(substr($fileName, 0, 1) == '.') continue; + + $fullPath = $path.'/'.$fileName; + + // Recursive loading of subdirectories + if(is_dir($fullPath)) { + self::useClassDir($fullPath, $domain); + continue; + } + if(is_file($fullPath) && preg_match('/^([a-zA-Z_][a-zA-Z0-9_]*)[\.inc\|]*.php$/', $fileName, $matches)) { + $className = str_replace('.php', '', $matches[0]); + $className = str_replace('.inc', '', $className); + self::_registerClass($className, $fullPath); + self::_registerNamespace($className, $fullPath, $domain); + } + } + } + + /** + * Used to register a class to the auto loader + * @access public + * @param string $classname The name of the class + * @param string $filePath An absolute path to the file containing the class + */ + public function _registerClass($classname, $filePath) { + $classname = strtolower($classname); + + /** DEPRECATED FOR NAMESPACES + if(array_key_exists($classname, $this->classes) && $filePath !== $this->classes[$classname]) { + throw new Loader\Exceptions\DuplicateClassException( + 'Duplicated class '.$classname.' in '.$this->classes[$classname].' and '.$filePath + ); + }**/ + + $this->classes[$classname] = $filePath; + } + + /** + * Used to register a namespace to the auto loader + * @access public + * @param string $classname The name of the class + * @param string $filePath An absolute path to the file containing the class + */ + public function _registerNamespace($classname, $filePath, $domain) { + + // get rid of folders + if (strpos($domain, '/')) { + $parts = explode('/', $domain); + $domain = array_pop($parts); + $folder = implode('/',$parts); + } else { + $folder = ''; + } + + // Strip out the base dir + $namespacePath = str_replace($this->_getWorkspace(), '', $filePath); + $namespacePath = str_replace($folder, '', $namespacePath); + + // Get the dir levels as array + $dirStruct = explode('/', trim($namespacePath, '/')); + + + // Work out if we're in Atsumi + $baseNamespace = array_shift($dirStruct); + if(trim($baseNamespace, '/') == trim($this->_getAtsumiDir(), '/')) { + $baseNamespace = $this->atsumiNamespace; + } else if($this->projectNamespace) { + $baseNamespace = $this->projectNamespace; + } + + $baseNamespace = '\\' . $baseNamespace; + + + $namespace = ''; + $i = 0; + foreach($dirStruct as $dir) { + + + // Ignore the standard classes dir + if($dir == 'classes' || $dir == 'src') { + continue; + } + + // if project name is same as first dir then assume namespace + if (++$i == 1 && strtolower($baseNamespace) == '\\' . strtolower($dir)) { + $baseNamespace = '\\' . ucfirst($dir); + continue; + } + $namespace .= '\\' . ucfirst($dir); + } + + $namespace = $baseNamespace . $namespace; + + // Finally, trim the file extension + $namespace = substr($namespace, 0, strrpos($namespace, '.')); + + if(array_key_exists($classname, $this->classes) && $filePath !== $this->classes[$classname]) { + throw new \loader_DuplicateClassException( + 'Duplicated class '.$classname.' in '.$this->classes[$classname].' and '.$filePath + ); + } + + $this->namespaces[trim($namespace, '\\')] = $filePath; + } + + /** + * Used by the PHP auto load function to load a class + * @access public + * @param string $classname The name of the class to load + */ + public function _loadClass($classname) { + if(strpos($classname, '\\') !== false) { + return $this->_loadClassByNamespace($classname); + } else { + return $this->_loadClassByName($classname); + } + } + + private function _loadClassByNamespace($namespace) { + if(!array_key_exists(trim($namespace, '\\'), $this->namespaces)) { + return; + } + require_once($this->namespaces[$namespace]); + } + + private function _loadClassByName($classname) { + $classname = strtolower($classname); + + if(!array_key_exists($classname, $this->classes)) + return; + + //throw new loader_ClassNotFoundException('Atsumi failed to find the class required \''.$classname.'\'', $classname); + + require_once($this->classes[$classname]); + } + + static public function loadClass($className) { + $args = func_get_args(); + return self::__callStatic(__FUNCTION__, $args); + } + +} + +/** + * register the autoloader + */ +spl_autoload_register(array('Atsumi\Core\Loader', 'loadClass')); + +?> \ No newline at end of file diff --git a/classes/core/loader/atsumi_Loader.php b/classes/core/loader/atsumi_Loader.php index 0ce3111..750b4f9 100644 --- a/classes/core/loader/atsumi_Loader.php +++ b/classes/core/loader/atsumi_Loader.php @@ -1,308 +1,15 @@ workspace = $this->findWorkspace(); - } - - // GET FUNCTIONS - - /** - * Creates and/or returns a singlton instance of the class - * Note: Protected prevents anything externally calling this function. Use static functions. - * @access protected - * @return atsumi_Loader A singlton instance of the loader - */ - protected static function getInstance() { - static $sInstance; - - if(!is_object($sInstance)) - $sInstance = new self; - - return $sInstance; - } - - /** - * Gets the name of the atsumi directory, incase it has been name differently by the developer - * @access public - * @return string The atsumi directory name - */ - public function _getAtsumiDir() { - return $this->atsumiDir; - } - - /** - * Gets the workspace of atsumi and the all related projects - * @access public - * @return string A absolute path to the workspace - */ - public function _getWorkspace() { - return $this->workspace; - } - - /* MAGIC METHODS */ - - /** - * PHP Magic function, used to call methods on singleton staticly - * @access public - * @param string $name The name of the method being called - * @param array $arguments The arguments being passed to the method - * @return mixed The result of the function call - */ - public static function __callStatic($name, $arguments) { - $instance = self::getInstance(); - - if(method_exists($instance, '_'.$name)) - return call_user_func_array(array($instance, '_'.$name), $arguments); - - throw new Exception('Undefined call to atsumi_Debug::'.$name); - } - - /* METHODS */ - - /** - * Return an absolute path to the base directory containing atsumi and developer projects - * which is used as the base folder for loading external project files from - * @access protected - * @return string An absolute path to the project base directory - */ - protected function findWorkspace() { - $matches = null; - - $file = str_replace('\\', '/', __FILE__); - - if(!preg_match('|^(.*)/classes/core/loader/atsumi_Loader.php$|', $file, $matches)) - throw new Exception('The atsumi loader cannot find its workspace!'); - - $matches = explode('/', $matches[1]); - $this->atsumiDir = array_pop($matches); - return implode('/', $matches); - } - - /** - * Used to load or include a set of files as specified in the spec - * - * Examples: - * references('atsumi', 'helpers mvc database'); - * references('atsumi', array('helpers', 'mvc', 'database')); - * references(array('atsumi' => 'helpers mvc database')); - * @access public - * @param $spec mixed See above for examples - */ - public function _references($spec) { - $args = func_get_args(); - switch(func_num_args()) { - case 1: - if(!is_array($args[0])) - throw new Exception('Loader spec must be an associative array'); - $spec = $args[0]; - break; - case 2: - $spec = array($args[0] => $args[1]); - break; - default: - throw new Exception('atsumi_Loader: Invalid number of args'); - break; - } - - $domains = ''; - $collections = ''; - foreach($spec as $domain => $parts) { - $domains .= $domain.', '; - if(isset($this->processedPaths[$domain.':'.$parts])) continue; - $this->processedPaths[$domain.':'.$parts] = true; - - if(!is_array($parts)) { - $collections .= '['.$parts.']'; - $parts = explode(' ', $parts); - } else { - $collections .= '['.implode(', ', $parts).']'; - } - - foreach($parts as $part) { - if (empty($part)) continue; - $success = false; - if($this->useRequire($domain, $part)) $success = true; - if($this->useClasses($domain, $part)) $success = true; - - if(!$success) throw new Exception(sprintf('Unknown reference in atsumi_Loader::references() : %s:%s', $domain, $part)); - } - } - } - - /** - * Tries to find a class instance within the classes directory and add it to the autoloader - * @access protected - * @param string $domain The project domain folder to look in - * @param string $part The subfolder to process - * @return boolean If the part was found - */ - protected function useClasses($domain, $part) { - $path = sprintf('%s/%s/classes/%s', $this->workspace, $domain, $part); - if(!is_dir($path)) return false; - self::useClassDir($path); - return true; - } - - /** - * Tries to find an include a required file in the includes directory - * @access protected - * @param string $domain The project domain folder to look in - * @param string $part The name of the file to look for - * @return boolean If the part was found - */ - protected function useRequire($domain, $part) { - $path = sprintf('%s/%s/include/%s.php', $this->workspace, $domain, $part); - if(!is_file($path)) return false; - - require_once($path); - return true; - } - - /** - * Process a directory presuming all files are named relative to the class they contain - * @access protected - * @param string $path The absolute path to the directory to process - */ - protected function useClassDir($path) { - $contents = scandir($path); - foreach($contents as $fileName) { - if(substr($fileName, 0, 1) == '.') continue; - - $fullPath = $path.'/'.$fileName; - - // Recursive loading of subdirectories - if(is_dir($fullPath)) { - self::useClassDir($fullPath); - continue; - } - if(is_file($fullPath) && preg_match('/^([a-zA-Z_][a-zA-Z0-9_]*)[\.inc\|]*.php$/', $fileName, $matches)) { - $className = str_replace('.php', '', $matches[0]); - $className = str_replace('.inc', '', $className); - self::_registerClass($className, $fullPath); - } - } - } - - /** - * Used to register a class to the auto loader - * @access public - * @param string $classname The name of the class - * @param string $filePath An absolute path to the file containing the class - */ - public function _registerClass($classname, $filePath) { - $classname = strtolower($classname); - - if(array_key_exists($classname, $this->classes) && $filePath !== $this->classes[$classname]) { - throw new loader_DuplicateClassException( - 'Duplicated class '.$classname.' in '.$this->classes[$classname].' and '.$filePath - ); - } - - $this->classes[$classname] = $filePath; - } - - /** - * Used by the PHP auto load function to load a class - * @access public - * @param string $classname The name of the class to load - */ - public function _loadClass($classname) { - $classname = strtolower($classname); - if(!array_key_exists($classname, $this->classes)) - throw new loader_ClassNotFoundException('Atsumi failed to find the class required \''.$classname.'\'', $classname); - - require_once($this->classes[$classname]); - } - - /* - * NOTE: Below are all static functions which will be removed when php 5.3.0+ becomes the - * common and the PHP magic function __callStatic works correctly - */ - public static function getAtsumiDir() { - return self::__callStatic(__FUNCTION__, array()); - } - - public static function getWorkspace() { - return self::__callStatic(__FUNCTION__, array()); + static public function references ($args) { + return \Atsumi\Core\Loader::references ($args); } - - public static function references($spec) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - public function registerClass($className, $filePath) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); - } - - static public function loadClass($className) { - $args = func_get_args(); - return self::__callStatic(__FUNCTION__, $args); + static public function getWorkspace () { + return \Atsumi\Core\Loader::getWorkspace (); } } -/** - * Used internally by PHP to help attempt to load missing classes - * @param $className The class to load - */ -function __autoload($classname) { - atsumi_Loader::loadClass($classname); -} + ?> \ No newline at end of file diff --git a/classes/database/adapter/mysql/caster_MySql.php b/classes/database/adapter/mysql/caster_MySql.php new file mode 100644 index 0000000..1c7ae18 --- /dev/null +++ b/classes/database/adapter/mysql/caster_MySql.php @@ -0,0 +1,229 @@ + 'tableName', + 'b' => 'boolean', + 'c' => 'character', + 'C' => 'characterVarying', + 'd' => 'datetime', + 'D' => 'date', + 'f' => 'float', + 'i' => 'integer', + 'I' => 'integerOrNull', + 'l' => 'literal', + 'n' => 'numeric', + 's' => 'text', + 'S' => 'textOrNull', + 't' => 'timestamp', + 'T' => 'time', + 'x' => 'binary', + 'z' => 'interval', + 'Z' => 'intervalOrNull' + ); + + /** + * Casts a string in a MySql format + * NOTE: Annoyance due to PHP scope issue + * @param string $string The string to cast + * @param mixed $args The args to be parsed into the string + * @param mixed $_ Repeated last arg as needed + * @return string The casted string + */ + static function cast($string, $args = null, $_ = null) { + $parser = new self(); + + /* 'func_get_args' cannot be called as function arg pre PHP5 */ + $func_args = func_get_args(); + return (string)$parser->castString($func_args); + } + + /** + * Casts a variable into a MySql table name + * @param string $in String to be casted + * @return string Casted string + */ + static function tableName($in) { + return sf('%s', $in); + } + + /** + * Casts a variable into a MySql text + * @param string $in String to be casted + * @return string Casted string + */ + static function text($in) { + if(!is_string($in)) throw new caster_StrictTypeException('Expected String'); + return sf("CAST('%s' as CHAR)", mysql_escape_string($in)); + } + + /** + * Casts a variable into a MySql text thats accepts NULL values + * @param string $in String to be casted or null + * @return string Casted string + */ + static function textOrNull($in) { + if (!is_string($in) && !is_null($in)) throw new caster_StrictTypeException('Expected String or Null'); + + if (is_string($in) && strlen($in)) return self::text($in); + elseif (is_null($in)) return 'NULL'; + } + + /** + * Casts a variable into a MySql character + * @param string $in String to be casted + * @return string Casted string + */ + static function character($in) { + return self::text($in); + } + + /** + * Casts a variable into a MySql character varying + * @param string $in String to be casted + * @return string Casted string + */ + static function characterVarying($in) { + return self::text($in); + } + + /** + * Casts a variable into a MySql float + * @param float $in Float to be casted + * @return string Casted string + */ + static function float($in) { + return sf("CAST('%s' as DECIMAL)", $in); + } + + /** + * Casts a variable into a MySql boolean + * @param bool $in Bool to be casted + * @return string Casted string + */ + static function boolean($in) { + return sf("%s", $in ? 'true':'false'); + } + + /** + * Casts a variable into a MySql integer + * @param int $in Int to be casted + * @return string Casted string + */ + static function integer($in) { + return sf("CAST('%s' as SIGNED)", intval($in)); + } + /** + * Casts a variable into a MySql integer or Null + * @param int $in Int to be casted + * @return string Casted string + */ + static function integerOrNull($in) { + if (is_null($in)) return 'NULL'; + return sf("CAST('%s' as SIGNED)", intval($in)); + } + + /** + * Casts a variable into a MySql numeric + * @param mixed $in Mixed to be casted + * @return string Casted string + */ + static function numeric($in) { + return sf("CAST('%s' as SIGNED)", $in); + } + + /** + * Casts a variable into a MySql binary + * @param string $in String to be casted + * @return string Casted string + */ + static function binary($in) { + return sf("CAST('%s' as BINARY)", pg_escape_bytea($in)); + } + + /** + * Casts a variable into a MySql timestamp with timezone + * @param int $in Int to be casted + * @return string Casted string + */ + static function timestamp($in) { + //TODO: convert datetime to timestamp or leave to user input + //return sf("'%s'::TIMESTAMP WITH TIME ZONE", mysql_escape_string(gmdate('Y-m-d H:i:s+00', $in))); + return sf("'%s'", $in); + } + + /** + * Casts a variable into a MySql datetime + * @param string $in String to be casted + * @return string Casted string + */ + static function datetime($in) { + return sf("CAST('%s' as DATETIME)", $in); + } + + /** + * Casts a variable into a MySql date + * @param string $in String to be casted + * @return string Casted string + */ + static function date($in) { + return sf("CAST('%s' as DATE)", $in); + } + + /** + * Casts a variable into a MySql time + * @param string $in String to be casted + * @return string Casted string + */ + static function time($in) { + return sf("CAST('%s' as TIME)", $in); + } + + /** + * Casts a variable into a MySql date + * @param string $in String to be casted + * @return string Casted string + */ + static function interval($in) { + //TODO: interval? + return sf("'%s'", $in); + } + static function intervalOrNull($in) { + //TODO: interval or null? + if (is_null($in)) return 'NULL'; + return sf("'%s'", $in); + } + + /** + * Performs no casting on the variable, leaving it as it is + * @param mixed $in mixed to be casted + * @return string Unaffected string + */ + static function literal($in) { + return $in; + } + +} +?> \ No newline at end of file diff --git a/classes/database/adapter/mysql/caster_MySqlToPhp.php b/classes/database/adapter/mysql/caster_MySqlToPhp.php new file mode 100644 index 0000000..bdc4456 --- /dev/null +++ b/classes/database/adapter/mysql/caster_MySqlToPhp.php @@ -0,0 +1,147 @@ + 'boolean', + 'd' => 'datetime', + 'D' => 'datetimeOrNull', + 'e' => 'date', //TODO: decide on letter for date in mysql + 'E' => 'dateOrNull', + 'i' => 'integer', + 'I' => 'integerOrNull', + 'f' => 'float', + 'F' => 'floatOrNull', + 's' => 'text', + 'S' => 'textOrNull', + 't' => 'timestamp', + 'T' => 'timestampOrNull', + 'u' => 'time', //TODO: decide in letter for time in mysql + 'U' => 'timeOrNull' + ); + + /** + * Casts a string in a MySql format + * NOTE: Annoyance due to PHP scope issue + * @param string $string The string to cast + * @param mixed $args The args to be parsed into the string + * @param mixed $_ Repeated last arg as needed + * @return string The casted string + */ + static function cast($string, $args = null, $_ = null) { + $parser = new self(); + + /* 'func_get_args' cannot be called as function arg pre PHP5 */ + $func_args = func_get_args(); + return $parser->castObject($func_args); + } + + + /** + * Casts a variable into a MySql text + * @param string $in String to be casted + * @return string Casted string + */ + static function text($in) { + if(!is_string($in)) throw new caster_StrictTypeException('Expected String, received: '.$in.' ('.gettype($in).')'); + return sf("%s", $in); + } + + /** + * Casts a variable into a MySql boolean + * @param bool $in Bool to be casted + * @return string Casted string + */ + static function boolean($in) { + if ($in==='0') $in = false; + if ($in==='1') $in = true; + if (!is_bool($in)) throw new caster_StrictTypeException('Expected Boolean, received: '.$in.' ('.gettype($in).')'); + return $in; + } + + /** + * Casts a variable into a MySql integer + * @param int $in Int to be casted + * @return string Casted string + */ + static function integer($in) { + if (!is_int(intval($in))) throw new caster_StrictTypeException('Expected Integer, received: '.$in.' ('.gettype($in).')'); + return intval($in); + } + + static function integerOrNull($in) { + if (is_null($in)) return null; + if (!is_int(intval($in))) throw new caster_StrictTypeException('Expected Integer or Null, received: '.$in.' ('.gettype($in).')'); + return intval($in); + } + + static function float($in) { + if (!is_numeric($in)) throw new caster_StrictTypeException('Expected Float, received: '.$in.' ('.gettype($in).')'); + setType($in, 'float'); + return $in; + } + + static function floatOrNull($in) { + if (is_null($in)) return null; + if (!is_numeric($in)) throw new caster_StrictTypeException('Expected Float or Null, received: '.$in.' ('.gettype($in).')'); + setType($in, 'float'); + return $in; + } + + static function datetime($in) { + #return atsumi_Date::fromYmd($in); + return new atsumi_DateTime(strtotime($in)); + } + + static function datetimeOrNull($in) { + if (is_null($in)) return null; + return new atsumi_DateTime(strtotime($in)); + } + + static function timestamp($in) { + return new atsumi_DateTime(strtotime($in)); + } + + static function timestampOrNull($in) { + if (is_null($in)) return null; + return self::timestamp($in); + } + + static function date($in) { + return atsumi_Date::fromYmd($in); + } + + static function time($in) { + if (is_null($in)) return null; + //TODO: time stuff + return $in; + } + + static function timeOrNull($in) { + //TODO: time (or null) stuff + if (is_null($in)) return null; + return $in; + } +} +?> diff --git a/classes/database/adapter/mysql/db_MySql.php b/classes/database/adapter/mysql/db_MySql.php new file mode 100755 index 0000000..74314e1 --- /dev/null +++ b/classes/database/adapter/mysql/db_MySql.php @@ -0,0 +1,115 @@ +connectReal($conString, $config); + } + + protected function createRow ($rowData) { + return new db_MySqlRow($rowData); + } + + /** + * Initalise the vender type caster + */ + protected function initCaster() { + $this->caster = new caster_MySql(); + } + + /** + * Returns true if the database supports transaction + * @return boolean If the database supports transactions + */ + public function transactionSupport() { + // MySam does't support transactions and will just ignore begin, commit, rollback, etc + // Innodb will work as expected + return true; + } + + /** + * Returns true if the database is in transaction + * @return boolean If the database is in transaction + */ + public function inTransaction() { + return $this->transaction; + } + + /** + * Begins a database transaction. Will fail if there is already a transaction in progress. + * @return boolean If beginning the transaction was successful + */ + public function transactionBegin() { + if($this->transaction) + throw new db_Exception('Cannot call beginTransaction() while already in transaction'); + + $this->query('BEGIN'); + $this->transaction = true; + return true; + } + + /** + * Commits a database transaction. Will fail if there is no transaction in progress. + * @return boolean If commiting the transaction was successful + */ + public function transactionCommit() { + if(!$this->transaction) + throw new db_Exception('Cannot call commitTransaction() while not in transaction'); + + $this->transaction = false; + return $this->query('COMMIT'); + } + + /** + * Rolls back a database transaction. Will fail if there is no transaction in progress. + * @return boolean If rolling back the transaction was successful + */ + public function transactionRollback() { + if(!$this->transaction) + throw new db_Exception('Cannot call rollbackTransaction() While not in transaction'); + + $this->transaction = false; + return $this->query('ROLLBACK'); + } + + /** + * Rolls back a database transaction, if there is one in progress. This is designed for use in + * catch blocks to ensure a rollback in case of error. + */ + public function transactionAutoRollback() { + if($this->transaction) + $this->transactionRollback(); + } + + public function nextval ($name) { + //TODO: return last id + 1? + return false; + } +} +?> \ No newline at end of file diff --git a/classes/database/adapter/mysql/db_MySqlRow.php b/classes/database/adapter/mysql/db_MySqlRow.php new file mode 100644 index 0000000..69b7576 --- /dev/null +++ b/classes/database/adapter/mysql/db_MySqlRow.php @@ -0,0 +1,12 @@ +caster)) + $this->caster = new caster_MySqlToPhp(); + } + +} + +?> \ No newline at end of file diff --git a/classes/database/vender/postgresql/caster_PostgreSQL.php b/classes/database/adapter/postgresql/caster_PostgreSql.php similarity index 72% rename from classes/database/vender/postgresql/caster_PostgreSQL.php rename to classes/database/adapter/postgresql/caster_PostgreSql.php index 3626e34..b3b00b9 100644 --- a/classes/database/vender/postgresql/caster_PostgreSQL.php +++ b/classes/database/adapter/postgresql/caster_PostgreSql.php @@ -16,7 +16,7 @@ * @subpackage Caster * @since 1.0 */ -class caster_PostgreSQL extends caster_Abstract { +class caster_PostgreSql extends caster_Abstract { /* CONSTANTS */ /* PROPERTIES */ @@ -27,20 +27,28 @@ class caster_PostgreSQL extends caster_Abstract { protected $spec = array( '@' => 'tableName', 'a' => 'sqlArray', + 'A' => 'sqlArrayOrNull', 'b' => 'boolean', 'c' => 'character', 'C' => 'characterVarying', 'd' => 'date', + 'e' => 'bigInteger', + 'E' => 'bigIntegerOrNull', 'f' => 'float', + 'g' => 'geometry', 'i' => 'integer', + 'I' => 'integerOrNull', 'l' => 'literal', 'n' => 'numeric', + 'N' => 'numericOrNull', 'q' => 'fullTextQuery', 's' => 'text', 'S' => 'textOrNull', 't' => 'timestampWithTimezone', 'v' => 'fullTextVector', 'x' => 'binary', + 'z' => 'interval', + 'Z' => 'intervalOrNull' ); /* CONSTRUCTOR & DESTRUCTOR */ @@ -105,11 +113,15 @@ static function sqlArray($in) { $sqlArr = ""; foreach($in as $item) { $sqlArr .= ($sqlArr == "") ? "" : ", "; - $sqlArr .= is_int($item) ? $item : "'".$item."'"; + $sqlArr .= is_int($item) ? $item : "'".pg_escape_string($item)."'"; } return "ARRAY[".$sqlArr."]"; } + static function sqlArrayOrNull($in) { + if (is_null($in)) return 'NULL'; + return self::sqlArray($in); + } /** * Casts a variable into a PostgreSQL character @@ -144,7 +156,17 @@ static function float($in) { * @return string Casted string */ static function boolean($in) { - return sf("%s::BOOLEAN", $in?'t':'f'); + return sf("'%s'::BOOLEAN", $in?'t':'f'); + } + + /** + * Casts a variable into a PostgreSQL geometry + * @param string $in String to be casted + * @return string Casted string + */ + static function geometry($in) { + if(!is_string($in)) throw new caster_StrictTypeException('Geom Expected String'); + return sf("'%s'::GEOMETRY", pg_escape_string($in)); } /** @@ -153,9 +175,36 @@ static function boolean($in) { * @return string Casted string */ static function integer($in) { - return sf("%s::INTEGER", $in); + return sf("%s::INTEGER", intval($in)); + } + /** + * Casts a variable into a PostgreSQL integer or Null + * @param int $in Int to be casted + * @return string Casted string + */ + static function integerOrNull($in) { + if (is_null($in)) return 'NULL'; + return sf("%s::INTEGER", intval($in)); } + /** + * Casts a variable into a PostgreSQL bigint + * @param int $in Int to be casted + * @return string Casted string + */ + static function bigInteger($in) { + return sf("%s::BIGINT", intval($in)); + } + /** + * Casts a variable into a PostgreSQL big int or Null + * @param int $in Int to be casted + * @return string Casted string + */ + static function bigIntegerOrNull($in) { + if (is_null($in)) return 'NULL'; + return sf("%s::BIGINT", intval($in)); + } + /** * Casts a variable into a PostgreSQL numeric * @param mixed $in Mixed to be casted @@ -164,6 +213,15 @@ static function integer($in) { static function numeric($in) { return sf("%s::NUMERIC", $in); } + /** + * Casts a variable into a PostgreSQL numeric or Null + * @param int $in Mixed to be casted + * @return string Casted string + */ + static function numericOrNull($in) { + if (is_null($in)) return 'NULL'; + return sf("%s::NUMERIC", $in); + } /** * Casts a variable into a PostgreSQL binary @@ -192,6 +250,19 @@ static function date($in) { return sf("'%s'::DATE", $in); } + /** + * Casts a variable into a PostgreSQL date + * @param string $in String to be casted + * @return string Casted string + */ + static function interval($in) { + return sf("'%s'::INTERVAL", $in); + } + static function intervalOrNull($in) { + if (is_null($in)) return 'NULL'; + return sf("'%s'::INTERVAL", $in); + } + /** * Performs no casting on the variable, leaving it as it is * @param mixed $in mixed to be casted diff --git a/classes/database/adapter/postgresql/caster_PostgreSqlToPhp.php b/classes/database/adapter/postgresql/caster_PostgreSqlToPhp.php new file mode 100644 index 0000000..05204ed --- /dev/null +++ b/classes/database/adapter/postgresql/caster_PostgreSqlToPhp.php @@ -0,0 +1,246 @@ + 'sqlArrayOrNull', + 'a' => 'sqlArray', + 'b' => 'boolean', + 'B' => 'booleanOrNull', + 'd' => 'date', + 'D' => 'dateOrNull', + 'e' => 'integer', + 'E' => 'integerOrNull', + 'g' => 'geometry', + 'G' => 'geometryOrNull', + 'i' => 'integer', + 'I' => 'integerOrNull', + 'n' => 'numeric', + 'N' => 'numericOrNull', + 'f' => 'float', + 'F' => 'floatOrNull', + 's' => 'text', + 'S' => 'textOrNull', + 't' => 'timestampWithTimezone', + 'T' => 'timestampWithTimezoneOrNull', + 'z' => 'interval', + 'Z' => 'intervalOrNull' + ); + + /* CONSTRUCTOR & DESTRUCTOR */ + /* GET METHODS */ + /* SET METHODS */ + /* MAGIC METHODS */ + /* METHODS */ + + /** + * Casts a string in a PostgreSQL format + * NOTE: Annoyance due to PHP scope issue + * @param string $string The string to cast + * @param mixed $args The args to be parsed into the string + * @param mixed $_ Repeated last arg as needed + * @return string The casted string + */ + static function cast($string, $args = null, $_ = null) { + $parser = new self(); + + /* 'func_get_args' cannot be called as function arg pre PHP5 */ + $func_args = func_get_args(); + return $parser->castObject($func_args); + } + + + /** + * Casts a variable into a PostgreSQL text + * @param string $in String to be casted + * @return string Casted string + */ + static function text($in) { + if(!is_string($in)) throw new caster_StrictTypeException('Expected String, received: '.$in.' ('.gettype($in).')'); + return sf("%s", $in); + } + + /** + * Casts a variable into a PostgreSQL text thats accepts NULL values + * @param string $in String to be casted or null + * @return string Casted string + */ + static function textOrNull($in) { + if (!is_string($in) && !is_null($in)) throw new caster_StrictTypeException('Expected String or Null, received: '.$in.' ('.gettype($in).')'); + + if (is_string($in) && strlen($in)) return self::text($in); + elseif (is_null($in)) return null; + } + + /** + * Casts a variable into a PostgreSQL array + * @param array $in Array to be casted + * @return string Casted string + */ + static function sqlArray($in) { + + if (!is_string($in)) throw new caster_StrictTypeException('Expected String, received: '.$in.' ('.gettype($in).')'); + if (is_null($in)) return array(); + + $in = str_replace(array("{","}"),"", $in); + + $arrayOut = array(); + $processing= $in; + + while(strlen($processing) > 0) { + + if (substr($processing, 0, 1) == ',') { + $processing = substr($processing, 1); + } + + preg_match('/^\"([^"]*)\"/', $processing, $match); + if (count($match)) { + + if (strlen($processing) === strlen($match[0])) + $processing = ''; + else $processing = substr($processing, strlen($match[0])); + + $arrayOut[] = $match[1]; + continue; + } + + preg_match('/^([^,]*)/', $processing, $match); + if (count($match)) { + + if (strlen($processing) === strlen($match[0])) + $processing = ''; + else $processing = substr($processing, strlen($match[0])); + + $arrayOut[] = $match[1]; + continue; + } + } + // int + /* if ($type == "integer") { + $newArr = array(); + foreach ($arr as $val) + $newArr[] = intval($val); + $arr = $newArr; + } */ + return $arrayOut; + } + + static function sqlArrayOrNull($in) { + if (!is_string($in) && !is_null($in)) throw new caster_StrictTypeException('Expected String or Null, received: '.$in.' ('.gettype($in).')'); + + if (is_string($in)) return self::sqlArray($in); + elseif (is_null($in)) return null; + } + + /** + * Casts a variable into a PostgreSQL boolean + * @param bool $in Bool to be casted + * @return string Casted string + */ + static function boolean($in) { + + if (is_string($in) && $in == 'f') $in = false; + if (is_string($in) && $in == 't') $in = true; + if (!is_bool($in)) throw new caster_StrictTypeException('Expected Boolean, received: '.$in.' ('.gettype($in).')'); + return $in; + } + static function booleanOrNull($in) { + if (is_null($in)) return null; + return self::boolean($in); + } + + static function interval($in) { + return atsumi_Interval::fromPostgresql(strval($in)); + } + static function intervalOrNull($in) { + if (is_null($in)) return null; + return self::interval($in); + } + + /** + * Casts a variable into a PostgreSQL integer + * @param int $in Int to be casted + * @return string Casted string + */ + static function integer($in) { + if (is_string($in)) $in = intval($in); + if (!is_int($in)) throw new caster_StrictTypeException('Expected Integer, received: '.$in.' ('.gettype($in).')'); + return intval($in); + } + static function integerOrNull($in) { + if (is_null($in)) return null; + if (!is_int(intval($in))) throw new caster_StrictTypeException('Expected Integer or Null, received: '.$in.' ('.gettype($in).')'); + return intval($in); + } + + static function numeric($in) { + if (!is_numeric($in)) throw new caster_StrictTypeException('Expected Numeric, received: '.$in.' ('.gettype($in).')'); + return $in; + } + static function numericOrNull($in) { + if (is_null($in)) return null; + if (!is_numeric($in)) throw new caster_StrictTypeException('Expected Numeric, received: '.$in.' ('.gettype($in).')'); + return $in; + } + + + static function float($in) { + if (!is_numeric($in)) throw new caster_StrictTypeException('Expected Float, received: '.$in.' ('.gettype($in).')'); + setType($in, 'float'); + return $in; + } + static function floatOrNull($in) { + if (is_null($in)) return null; + if (!is_numeric($in)) throw new caster_StrictTypeException('Expected Float or Null, received: '.$in.' ('.gettype($in).')'); + setType($in, 'float'); + return $in; + } + + static function date($in) { + return atsumi_Date::fromYmd($in); + } + static function dateOrNull($in) { + if (is_null($in)) return null; + return atsumi_Date::fromYmd($in); + } + + static function timestampWithTimezone($in) { + return new atsumi_DateTime(strtotime($in)); + } + static function timestampWithTimezoneOrNull($in) { + if (is_null($in)) return null; + return self::timestampWithTimezone($in); + } + + static function geometry($in) { + return self::text($in); + } + static function geometryOrNull($in) { + return self::textOrNull($in); + } + + /* DEPRECATED METHODS */ +} +?> \ No newline at end of file diff --git a/classes/database/vender/postgresql/db_PostgreSQL.php b/classes/database/adapter/postgresql/db_PostgreSql.php similarity index 90% rename from classes/database/vender/postgresql/db_PostgreSQL.php rename to classes/database/adapter/postgresql/db_PostgreSql.php index 95fa269..8459dba 100644 --- a/classes/database/vender/postgresql/db_PostgreSQL.php +++ b/classes/database/adapter/postgresql/db_PostgreSql.php @@ -42,11 +42,16 @@ public function connect($config = array()) { $this->connectReal($conString, $config); } + protected function createRow ($rowData) { + return new db_PostgreSQLRow($rowData); + } + + /** * Initalise the vender type caster */ protected function initCaster() { - $this->parser = new caster_PostgreSQL(); + $this->caster = new caster_PostgreSql(); } /** @@ -61,7 +66,7 @@ public function transactionSupport() { * Returns true if the database is in transaction * @return boolean If the database is in transaction */ - public function transaction() { + public function inTransaction() { return $this->transaction; } @@ -108,7 +113,13 @@ public function transactionRollback() { */ public function transactionAutoRollback() { if($this->transaction) - $this->rollbackTransaction(); + $this->transactionRollback(); + } + + public function nextval ($name) { + $row = $this->selectOne ("SELECT nextval (%s) AS id", $name); + return $row->cast('i', 'id'); } + } ?> \ No newline at end of file diff --git a/classes/database/adapter/postgresql/db_PostgreSqlRow.php b/classes/database/adapter/postgresql/db_PostgreSqlRow.php new file mode 100644 index 0000000..bf72e56 --- /dev/null +++ b/classes/database/adapter/postgresql/db_PostgreSqlRow.php @@ -0,0 +1,12 @@ +caster)) + $this->caster = new caster_PostgreSqlToPhp(); + } + +} + +?> \ No newline at end of file diff --git a/classes/database/vender/sqlite/db_SqlLite.php b/classes/database/adapter/sqlite/db_SqlLite.php similarity index 100% rename from classes/database/vender/sqlite/db_SqlLite.php rename to classes/database/adapter/sqlite/db_SqlLite.php diff --git a/classes/database/db_AbstractDatabase.php b/classes/database/db_AbstractDatabase.php index a437b13..f7d2df7 100644 --- a/classes/database/db_AbstractDatabase.php +++ b/classes/database/db_AbstractDatabase.php @@ -63,6 +63,10 @@ abstract class db_AbstractDatabase /* implements db_InterfaceDatabase */ { */ protected $caster; + + protected $transaction; + + /* CONSTRUCTOR & DESTRUCTOR */ /** @@ -157,7 +161,7 @@ protected function connectReal($conString, $config) { */ protected function cast($args) { if (is_null($this->caster)) - throw new db_Exception('Parser not loaded.'); + throw new db_Exception('Caster not loaded.'); return $this->caster->castString(func_get_args()); } @@ -170,6 +174,18 @@ public function disconnect() { $this->pdo = null; $this->connected = false; } + + + public function formatResult ($rows) { + + $rowArr = array(); + foreach ($rows as $idx => $row) + $rowArr[] = $this->createRow($row); + + return $rowArr; + } + + /** * Executes a basic query with casted variables @@ -179,7 +195,7 @@ public function disconnect() { */ public function query($query, $args = null, $_ = null) { $args = func_get_args(); - return $this->queryReal(call_user_func_array(array(&$this, 'cast'), $args)); + return $this->formatResult($this->queryReal(call_user_func_array(array($this, 'cast'), $args))); } /** @@ -207,8 +223,10 @@ public function queryReal($sql) { throw new PDOException((isset($error[2]) ? $error[2] : 'Database Error: '.$error[0])); } - $data = $return->fetchAll(PDO::FETCH_OBJ); - + $data = $return->fetchAll(PDO::FETCH_ASSOC); + + $this->affectedRows = count($data); + if(!is_array($data)) throw new PDOException('Failed to return data array'); @@ -232,51 +250,93 @@ public function queryReal($sql) { return $data; } catch(PDOException $e) { - throw new db_QueryFailedException($e->getMessage() . "
" . $sql); + throw new db_QueryFailedException($e->getMessage() . "

SQL:

" . $sql); } } - public function select($_) { + + + /* + * SELECT + * */ + public function select($args) { $args = func_get_args(); - return call_user_func_array(array(&$this, 'query'), $args); + return call_user_func_array(array($this, 'query'), $args); } - - /** - * Performs a query, returning a single result - * @param $colums string The colum or colums to select - * @param $tables string The table or tables to query from - * @param $where string The clause to query by - * @param $args mixed Any args that should be used in the clause - * @param $_ Arg repeater (Repeat the last arg as many times as needed) - * @return The query results - */ - public function selectOne($colums, $tables, $where, $args = null, $_ = null) { + public function selectOne($args) { $args = func_get_args(); - $result = call_user_func_array(array(&$this, 'select'), $args); + $result = call_user_func_array(array($this, 'select'), $args); if(count($result) > 1) - throw new db_QueryFailedException('selectOne returned more than one result'); + throw new db_UnexpectedResultException('selectOne returned more than one result'); return array_key_exists(0, $result) ? $result[0] : null; } - /** - * Performs a query, returning all colums of one result in an array - * @param $tables string The table or tables to query from - * @param $where string The clause to query by - * @param $args mixed Any args that should be used in the clause - * @param $_ Arg repeater (Repeat the last arg as many times as needed) - * @return The query results - */ - public function fetchOne($tables, $where, $args = null, $_ = null) { + + + + /* FETCH + * abstract select - for simple queries (no joins) */ + public function fetch ($cols, $table, $where = null, $orderBy = null, $offset = null, $limit = null) { + /* parse the query */ $args = func_get_args(); - array_unshift($args, '*'); + $query = call_user_func_array (array ($this, 'parseFetchQuery'), $args); - return call_user_func_array(array(&$this, 'selectOne'), $args); + /* perform query */ + $result = $this->query('%l', $query); + + return $result; } + public function fetchOne($cols, $table, $where = null) { + $args = func_get_args(); + $result = call_user_func_array(array($this, 'fetch'), $args); + if(count($result) > 1) + throw new db_UnexpectedResultException('fetchOne returned more than one result'); + return array_key_exists(0, $result) ? $result[0] : null; + } + public function parseFetchQuery($cols, $table, $where = null, $orderBy = null, $offset = null, $limit = null) { + + if (is_null($this->caster)) + throw new db_Exception('Caster not loaded.'); + + // strip out the nulls + $argsReal = func_get_args (); + $args = array(); + foreach ($argsReal as $arg) + $args[] = !is_null($arg)?$arg:''; + $cols = array_shift ($args); + $table = array_shift ($args); + + // handle offset limit + $offset = null; + $limit = null; + + if (is_int(end($args))) $limit = array_pop($args); + if (is_int(end($args))) $offset = array_pop($args); + + if (count($args) && $args[0] !== null) { + $sets = $this->caster->castArraySets($args); + $where = array_shift ($sets); + } + if (count($args) && $args[0] !== null) { + $orderBy = array_shift ($sets); + } + // return select query string + return $this->caster->castString( + 'SELECT %l FROM %@%l%l%l%l', $cols, $table, + empty($where)?'':$this->caster->castString(' WHERE %l', $where), + empty($orderBy)?'':$this->caster->castString(' ORDER BY %l', $orderBy), + !is_int($offset)?'':$this->caster->castString(' OFFSET %i', $offset), + !is_int($limit)?'':$this->caster->castString(' LIMIT %i', $limit) + + ); + } + + /** - * Performs a insert query + * INSERT * * Example: * $db->insert('tableA', 'column1 = %s', 'a string', 'column2 = %i', 12345); @@ -288,42 +348,61 @@ public function fetchOne($tables, $where, $args = null, $_ = null) { * @throws db_Exception If the number of values does not match the number of columns */ public function insert($table, $column, $value = null, $_ = null) { + /* parse the query */ $args = func_get_args(); + $query = call_user_func_array (array ($this, 'parseInsertQuery'), $args); - $table = array_shift($args); - - // If there are still args left we have variable pairs - $names = array(); - $types = array(); - $values = array(); - while(count($args) >= 2) { - $column = array_shift($args); - - $column = explode('=', $column, 2); + /* perform query */ + $this->query('%l', $query); - $names[] = trim($column[0]); - $types[] = trim($column[1]); - $values[] = array_shift($args); + return true; + } + public function insertOrUpdateOne ($table, $where, $values) { + + if (is_null($this->caster)) + throw new db_Exception('Caster not loaded.'); + + $args = func_get_args(); + $sets = $this->caster->castArraySets($args); + $table = $sets [0]; + $where = $sets [1]; + + $exists = $this->exists ($table, '%l', $where); + + call_user_func_array (array ($this, $exists? 'updateOne' : 'insert'), $args); + + } + public function deleteAndInsert ($args) { + throw new Expcetion ('TODO'); + } + public function parseInsertQuery($args) { + + if (is_null($this->caster)) + throw new db_Exception('Caster not loaded.'); + + $args = func_get_args(); + $table = array_shift($args); + + + $rows = $this->caster->castArraySets($args); + $column = array(); + $data = array(); + + foreach($rows as $row) { + $rowParts = preg_split('/=/', $row, 2); + $column[] = trim($rowParts[0]); + $data[] = trim($rowParts[1]); } - if(count($args) > 0) - throw new db_Exception('Uneven number of column value paires'); - - /* parse values */ - $valueString = implode(', ', $types); - array_unshift($values, $valueString); - $valueString = call_user_func_array(array($this, 'parse'), $values); - - - $query = $this->parse( - 'INSERT INTO %@ (%l) VALUES(%l)', $table, implode(', ', $names), $valueString + return $this->caster->castString( + 'INSERT INTO %@ (%l) VALUES(%l)', $table, implode(', ', $column), implode(', ', $data) ); - - $this->queryReal($query); } - // TODO: public function insert($table, $column, $value = null, $_ = null) - + /* + * UPDATE + * + * */ public function update($args) { /* parse the query */ $args = func_get_args(); @@ -334,54 +413,126 @@ public function update($args) { return true; } - public function updateOne($args) { /* call update */ $args = func_get_args (); $ret = call_user_func_array (array ($this, 'update'), $args); // ensure affected row count is correct - if($this->affected_rows () == 0) - throw new sql_Exception ('No rows affected in updateOne()'); - if($this->affected_rows () > 1) - throw new sql_Exception ('Multiple rows affected in updateOne()'); + if($this->getAffectedRows () == 0) + throw new db_UnexpectedResultException ('No rows affected in updateOne()'); + if($this->getAffectedRows () > 1) + throw new db_UnexpectedResultException ('Multiple rows affected in updateOne()'); return true; } - public function parseUpdateQuery($args) { + + if (is_null($this->caster)) + throw new db_Exception('Caster not loaded.'); + $args = func_get_args (); - $sets = $this->quotef_special($args); + $sets = $this->caster->castArraySets($args); $update = array_shift ($sets); $where = array_shift ($sets); if (count ($sets) == 0) $sets = array ($where); - + /* return update query string */ - return $this->format( - 'UPDATE %l SET %l WHERE %l', $update, implode(', ', $sets), $where + return $this->caster->castString( + 'UPDATE %@ SET %l WHERE %l', $update, implode(', ', $sets), $where ); } - /* DEPRECATED METHODS */ + /* + * EXISTS + * */ + public function exists ($table, $where) { + + if (is_null($this->caster)) + throw new db_Exception('Caster not loaded.'); + + $args = func_get_args(); + $table = array_shift($args); + $where = $this->caster->castArraySets($args); + + $result = $this->query( + 'SELECT CASE WHEN EXISTS ( + SELECT * + FROM %@ + WHERE %l + ) THEN %b ELSE %b END AS exists', + $table, $where[0], true, false + ); + + $exists = $result[0]; + return $exists->cast('b', 'exists'); + } + + /* + * COUNT + * */ + public function count ($table, $where = null) { + + if (is_null($this->caster)) + throw new db_Exception('Caster not loaded.'); + + $args = func_get_args(); + $table = array_shift($args); + + $where = (count($args) && !is_null($args[0]))?$this->caster->castArraySets($args):array('1=1'); + + $result = $this->query( + 'SELECT COUNT(*) AS count FROM %@ WHERE %l', + $table, $where[0] + ); + + $exists = $result[0]; + return $exists->cast('i', 'count'); + } - /** - * Selects a single record from the database - * @deprecated Back compatability functions. DO NOT USE! - * @param unknown_type $_ - */ - public function select_1($_) { - $args = func_get_args (); - return call_user_func_array (array (&$this, 'selectOne'), $args); - } - /** - * Updates a single record in the database. - * @deprecated Back compatability functions. DO NOT USE! - * @param unknown_type $_ - */ - public function update_1($_) { + + /* + * DELETE + * */ + public function delete ($args) { + /* parse the query */ + $args = func_get_args(); + $query = call_user_func_array (array ($this, 'parseDeleteQuery'), $args); + + /* perform query */ + $this->query('%l', $query); + + return true; + } + public function deleteOne ($args) { + throw new db_Exception ('TODO'); + } + public function parseDeleteQuery ($args) { + if (is_null($this->caster)) + throw new db_Exception('Caster not loaded.'); + $args = func_get_args (); - return call_user_func_array (array (&$this, 'updateOne'), $args); + $sets = $this->caster->castArraySets($args); + $deleteFrom = array_shift ($sets); + $where = array_shift ($sets); + + /* return update query string */ + return $this->caster->castString( + 'DELETE FROM %@ WHERE %l', $deleteFrom, $where + ); + } + + /* + * Table management + * */ + public function createTable ($args) { + throw new db_Exception ('TODO'); + } + public function dropTable ($args) { + throw new db_Exception ('TODO'); + } + } ?> \ No newline at end of file diff --git a/classes/database/db_AbstractRow.php b/classes/database/db_AbstractRow.php new file mode 100644 index 0000000..3ea87ff --- /dev/null +++ b/classes/database/db_AbstractRow.php @@ -0,0 +1,39 @@ + 'utility/calendar' +)); + +abstract class db_AbstractRow { + + private $rowData; + protected $caster; + + abstract protected function initCaster (); + + public function __construct ($rowData) { + $this->rowData = $rowData; + } + public function cast ($format, $column) { + + if (!array_key_exists($column, $this->rowData)) + throw new db_RowColumnNotFoundException('Column not found: '.$column); + + $data = $this->rowData[$column]; + $this->initCaster(); + + return $this->caster->cast('%'.$format, $data); + + } + public function getRaw ($column) { + return $data[$column]; + } + + public function __get($call) { + $pos = strpos($call, '_'); + return call_user_func_array (array($this, 'cast'), array(substr($call,0,$pos), substr($call,$pos+1))); + + } + +} + +?> \ No newline at end of file diff --git a/classes/database/exceptions/db_RowColumnNotFoundException.php b/classes/database/exceptions/db_RowColumnNotFoundException.php new file mode 100644 index 0000000..6882c0e --- /dev/null +++ b/classes/database/exceptions/db_RowColumnNotFoundException.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/classes/database/exceptions/db_UnexpectedResultException.php b/classes/database/exceptions/db_UnexpectedResultException.php new file mode 100644 index 0000000..cfd8a2e --- /dev/null +++ b/classes/database/exceptions/db_UnexpectedResultException.php @@ -0,0 +1,22 @@ +extendedInfo = $extendedInfo; + parent::__construct('Unexpected result from database query'); + } + + public function getInstructions($contentType) { + return $this->extendedInfo; + } +} +?> \ No newline at end of file diff --git a/classes/database/vender/mysql/db_MySql.php b/classes/database/vender/mysql/db_MySql.php deleted file mode 100755 index 24d067b..0000000 --- a/classes/database/vender/mysql/db_MySql.php +++ /dev/null @@ -1,408 +0,0 @@ -connectReal($conString, $config); - } - - /* CHARACTER ESCAPE FUNCTIONS */ - - /** - * Transforms a array into a query literal. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped and quoted array. - */ - protected function a($val, $nullValid = false) { - return $val; - } - - /** - * Transforms a boolean into a query literal. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped and quoted boolean. - */ - protected function b($val, $nullValid = false) { - return $val; - } - - /** - * Transforms a date into a query literal. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped and quoted date. - */ - protected function d($val, $nullValid = false) { - return $val; - } - - /** - * Transforms a string into a query literal ecripted representation. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped, quoted and encripted string. - */ - protected function e($val, $nullValid = false) { - return $val; - } - - /** - * Transforms a float into a query literal. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped and quoted float. - */ - protected function f($val, $nullValid = false) { - return $val; - } - - /** - * Transforms a string into a query literal hash. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped, quoted and hashed string. - */ - protected function h($val, $nullValid = false) { - return $val; - } - - /** - * Transforms an integer into a query literal. - * @param int $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped and quoted integer. - */ - protected function i($val, $nullValid = false) { - $val =(int)$val; - - if(!$nullValid && !is_int($val)) - throw new db_TypeException('Value not an Integer'); - elseif(!is_int($val) || is_null($val)) - throw new db_TypeException('Value not an Integer or Null '.$val); - - return sf('%s',(is_null($val) ? 'NULL' : $val)); - } - - /** - * Transforms a string into a query literal. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped and quoted string. - */ - protected function s($val, $nullValid = false) { - return sf("'%s'", $val); - } - - /** - * Transforms a timestamp into a query literal. - * @param string $val The value to escape. - * @param bool $nullValid If the value can be null - * @return string The escaped and quoted timestamp. - */ - protected function t($val, $nullValid = false) { - return $val; - } - - /* TRANSACTION FUNCTIONS */ - - /** - * Returns true if the database supports transaction - * - * @return boolean If the database supports transactions - */ - public function transactionSupport() { - return true; - } - - /** - * Returns true if the database is in transaction - * - * @return boolean If the database is in transaction - */ - public function transaction() { - return $this->transaction; - } - - /** - * Begins a database transaction. Will fail if there is already - * a transaction in progress - * - * @return boolean If beginning the transaction was successful - */ - public function transactionBegin() { - if($this->transaction) - throw new db_Exception('Cannot call beginTransaction() while already in transaction'); - - $this->query('BEGIN'); - $this->transaction = true; - return true; - } - - /** - * Commits a database transaction. Will fail if there is no - * transaction in progress - * - * @return boolean If commiting the transaction was successful - */ - public function transactionCommit() { - if(!$this->transaction) - throw new db_Exception('Cannot call commitTransaction() while not in transaction'); - - $this->transaction = false; - return $this->query('COMMIT'); - } - - /** - * Rolls back a database transaction. Will fail if there is no - * transaction in progress - * - * @return boolean If rolling back the transaction was successful - */ - public function transactionRollback() { - if(!$this->transaction) - throw new db_Exception('Cannot call rollbackTransaction() While not in transaction'); - - $this->transaction = false; - return $this->query('ROLLBACK'); - } - - /** - * Rolls back a database transaction, if there is one in - * progress. This is designed for use in catch blocks to ensure - * a rollback in case of error - * - * @return null - */ - public function transactionAutoRollback() { - if($this->transaction) - $this->rollbackTransaction(); - } - - /* SELECT QUERIES */ - - /** - * Performs a query, returning all results in an array - * - * @param $colums string The colum or colums to select - * @param $tables string The table or tables to query from - * @param $where string The clause to query by - * @param $args mixed Any args that should be used in the clause - * @param $_ - * @return The query results - */ - public function select($colums, $tables, $where = null, $args = null, $_ = null) { - $args = func_get_args(); - - $colums = array_shift($args); - $tables = array_shift($args); - - // If there are still args left we have a where clause and possible where args - if(count($args) > 0) - $where = array_shift($args); - - if(is_array($colums)) - $colums = implode(', ', $colums); - - $query = sf( - 'SELECT %s FROM %s%s', - $colums, - $tables, - (isset($where) ? sf(' WHERE %s', $where) : '') - ); - - array_unshift($args, $query); - - return call_user_func_array(array(&$this, 'query'), $args); - } - - /** - * Performs a query, returning all colums of all results in an array - * - * @param $tables string The table or tables to query from - * @param $where string The clause to query by - * @param $args mixed Any args that should be used in the clause - * @param $_ - * @return The query results - */ - public function fetch($tables, $where = null, $args = null, $_ = null) { - $args = func_get_args(); - - array_unshift($args, '*'); - - return call_user_func_array(array(&$this, 'select'), $args); - } - - /** - * Checks for the existance of a record - * - * @param $tables string The table or tables to query from - * @param $where string The clause to query by - * @param $args mixed Any args that should be used in the clause - * @param $_ - * @return boolean If the row exists - */ - public function exists($tables, $where = null, $args = null, $_ = null) { - $args = func_get_args(); - - $tables = array_shift($args); - - // If there are still args left we have a where clause and possible where args - if(count($args) > 0) - $where = array_shift($args); - - $query = sf( - 'SELECT EXISTS(SELECT * FROM %s%s) AS \'exists\'', - $tables, - (isset($where) ? sf(' WHERE %s', $where) : '') - ); - - array_unshift($args, $query); - - $result = call_user_func_array(array(&$this, 'query'), $args); - - return(bool)$result[0]->exists; - } - - /** - * Counts the number of records - * - * @param $tables string The table or tables to query from - * @param $where string The clause to query by - * @param $args mixed Any args that should be used in the clause - * @param $_ - * @return integer The number of records - */ - public function count($tables, $where = null, $args = null, $_ = null) { - $args = func_get_args(); - - array_unshift($args, 'COUNT(*) as \'count\''); - - $result = call_user_func_array(array(&$this, 'select'), $args); - - return(int)$result[0]->count; - } - - /* INSERT/UPDATE/DELETE QUERIES */ - - public function insert($table, $column, $value = null, $_ = null) { - $args = func_get_args(); - - $table = array_shift($args); - - // If there are still args left we have variable pairs - $names = array(); - $types = array(); - $values = array(); - while(count($args) >= 2) { - $column = array_shift($args); - - $column = explode('=', $column, 2); - - $names[] = trim($column[0]); - $types[] = trim($column[1]); - $values[] = array_shift($args); - } - - if(count($args) > 0) - throw new db_Exception('Uneven number of column value paires'); - - $query = sf( - 'INSERT INTO %s(%s) VALUES(%s)', - $table, - implode(', ', $names), - implode(', ', $types) - ); - - array_unshift($values, $query); - - return call_user_func_array(array(&$this, 'query'), $values); - } - - public function insertBulk($table, $columns, $data) {} - - public function insertIfNotExists($table, $where, $args = null, $_ = null) {} - - public function insertOrUpdateOne($table, $where, $args = null, $_ = null) {} - - public function update($table, $where, $args = null, $_ = null, $column = null, $value = null, $_ = null) { - $args = func_get_args(); - - $table = array_shift($args); - $where = array_shift($args); - - $where_arg_count = preg_match_all('/%[a-zA-Z]+/', $where, $matches); - if(count($args) < $where_arg_count) - throw new db_Exception('Incorrect number of args for where clause'); - - $where_args = array_slice($args, 0, $where_arg_count); - $args = array_slice($args, $where_arg_count); - - $names = array(); - $values = array(); - while(count($args) >= 2) { - $names[] = array_shift($args); - $values[] = array_shift($args); - } - - if(count($args) > 0) - throw new db_Exception('Uneven number of column value paires'); - - $query = sf( - 'UPDATE %s SET %s WHERE %s', $table, implode(', ', $names), $where - ); - - $query = array_merge(array($query), $values, $where_args); - - return call_user_func_array(array(&$this, 'query'), $query); - } - - public function updateOne($table, $where, $args = null, $_ = null, $column = null, $value = null, $_ = null) {} - - public function delete($table, $where, $args = null, $_ = null) {} - - public function deleteOne($table, $where, $args = null, $_ = null) {} - - public function deleteAndInsert($table, $where, $args = null, $_ = null) {} - - /* DATA DEFINITION */ - - public function tableCreate($table, $columns, $data = null) {} - - public function tableDrop($table) {} -} -?> \ No newline at end of file diff --git a/classes/helpers/http/atsumi_Http.php b/classes/helpers/http/atsumi_Http.php deleted file mode 100644 index 23a2231..0000000 --- a/classes/helpers/http/atsumi_Http.php +++ /dev/null @@ -1,66 +0,0 @@ - \ No newline at end of file diff --git a/classes/mvc/controllers/mvc_AbstractController.php b/classes/mvc/controllers/mvc_AbstractController.php index 9272ad4..07d9e03 100644 --- a/classes/mvc/controllers/mvc_AbstractController.php +++ b/classes/mvc/controllers/mvc_AbstractController.php @@ -12,12 +12,19 @@ abstract class mvc_AbstractController { // GET FUNCTIONS + public function has($key) { + return (array_key_exists($key, $this->data)); + } + public function get($key) { if(!array_key_exists($key, $this->data)) - throw new Exception("Item does not exist in view data.."); + throw new Exception(sf("'%s' does not exist in view data.",$key)); return $this->data[$key]; } + function __get ($key) { + return $this->get($key); + } public function getViewHandler() { return $this->viewHandler; } @@ -127,9 +134,12 @@ public function reload($anchor = null) { $anchor = !is_null($anchor) ? "#".$anchor:""; + if (!isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) != 'on') $protocol = 'http://'; + else $protocol = 'https://'; + array_key_exists('REDIRECT_SCRIPT_URI',$_SERVER) ? header('Location: ' . $_SERVER['REDIRECT_SCRIPT_URI']. $anchor) - : header('Location: ' . 'http://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']. $anchor); + : header('Location: ' . $protocol . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']. $anchor); exit; } @@ -137,14 +147,17 @@ public function reload($anchor = null) { // redirect to a new page public function redirect($url, $httpResponseCode = atsumi_Http::REDIRECT_FOUND) { - if(substr($url, 0, 7) == "http://") { + if(substr($url, 0, 7) == "http://" || substr($url, 0, 8) == "https://") { header('Location: ' . $url, true, $httpResponseCode); exit; } + if (!isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) != 'on') $protocol = 'http://'; + else $protocol = 'https://'; + $domain = $_SERVER['HTTP_HOST']; unset($_POST); - header('Location: ' . 'http://' . $domain.$url, true, $httpResponseCode); + header('Location: ' . $protocol . $domain.$url, true, $httpResponseCode); exit; } @@ -169,21 +182,33 @@ public function getData($method = null, $controller = null) { // TODO: This only works for page_ not other methods.... - $method = is_null($method)?substr($entity['method'][count($entity['method'])-1]['name'],5):$method; + // is method is an index? + if (is_integer($method)) + $method = $method = substr($entity['method'][count($entity['method'])-1-$method]['name'],5); + + // is it null? + else if (is_null($method)) + $method = substr($entity['method'][count($entity['method'])-1]['name'],5); + $methodName = sf('data_%s',$method); $pageMethod = null; - // loop through methods to find the corect one... - foreach($entity['method'] as $methodData) - if($methodData['name'] == sf('page_%s',$method)) { - $pageMethod = $methodData; - break; - } + + // loop through methods to find the correct one... + foreach($entity['method'] as $methodData) + if($methodData['name'] == sf('page_%s',$method)) { + $pageMethod = $methodData; + break; + } // TODO: Give this it's own exceptions! if(is_null($methodData)) throw new Exception("Method not found..."); return call_user_func_array(array($controller, $methodName), $methodData['args']); } + + public function processRequest ($method, $args) { + return call_user_func_array(array($this, $method), $args); + } } ?> \ No newline at end of file diff --git a/classes/mvc/models/core/mvc_LocaleModel.php b/classes/mvc/models/core/mvc_LocaleModel.php new file mode 100644 index 0000000..eb78a3e --- /dev/null +++ b/classes/mvc/models/core/mvc_LocaleModel.php @@ -0,0 +1,111 @@ + array( + 'type' => 's', + ), + 'country' => array( + 'type' => 's', + ), + 'localiseUris' => array( + 'type' => 'b', + 'default' => true + ) + ); + + function getLocaleString () { + return sf('%s%s', + strtolower($this->get('language')), + $this->has('country')? + sf('-%s', strtolower($this->get('country'))):'' + ); + } + + function uri ($uri) { + + if (!$this->get('localiseUris')) return $uri; + + return sf('/%s%s%s', + $this->getLocaleString(), + substr($uri, 0, 1) !== '/'?'/':'', + $uri + ); + } + static public function fromLocaleString ($localeString) { + + $locale = new self(); + preg_match('/([a-z]{2})(-([a-zA-Z]{2}))*/i', + $localeString, + $matches + ); + if (array_key_exists(1, $matches)) + $locale->set('language', strtolower($matches[1])); + + if (array_key_exists(3, $matches)) + $locale->set('country', strtolower($matches[3])); + + return $locale; + + } + static function parseHttpAcceptLanguage ($httpAcceptLanguage) { + + $langs = array(); + + // break up string into pieces (languages and q factors) + preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', + $httpAcceptLanguage, + $langMatches + ); + + if (count($langMatches[1])) { + // create a list like "en" => 0.8 + $langs = array_combine($langMatches[1], $langMatches[4]); + + // set default to 1 for any without q factor + foreach ($langs as $lang => $val) { + if ($val === '') $langs[$lang] = 1; + } + + // sort list based on value + arsort($langs, SORT_NUMERIC); + $langs = array_change_key_case($langs, CASE_LOWER); + } + + return $langs; + } + static function interpolate ($str, $vars) { + $originalStr = $str; + while (preg_match('/\$\{(.*?)\}/sm', $str, $m)) { + list($src, $var) = $m; + + // retrieve variable to interpolate in context, throw an exception + // if not found. + + if (!array_key_exists($var, $vars)) { + + // TODO: Add a custom exception + Atsumi::error__listen( + new Exception ( + sf('Locale interpolation failed in for var: "%s" in str: "%s"', + $var, $originalStr + ) + ) + ); + $value = ''; + } else $value = $vars[$var]; + $str = str_replace($src, $value, $str); + } + + foreach ($vars as $key => $replacement) { + $str = str_replace ('${'.$key.'}', $replacement, $str); + } + return $str; + + } + +} + +?> \ No newline at end of file diff --git a/classes/mvc/models/mvc_AbstractDaoModel.php b/classes/mvc/models/mvc_AbstractDaoModel.php index b8690a5..3fbc42a 100644 --- a/classes/mvc/models/mvc_AbstractDaoModel.php +++ b/classes/mvc/models/mvc_AbstractDaoModel.php @@ -1,77 +1,128 @@ select($this->selectQuery); - $returnArray = array(); - - foreach( $results as $row ) { - $forum = new gts_ForumAreaModel($db); - $forum->loadFromSqlRow($row); - $returnArray[] = $forum; +abstract class mvc_AbstractDaoModel extends mvc_AbstractModel { + + + static public function write ($db, $o) { + + + $method = !is_null($o->get('id', false))?'update':'insert'; + + $args = array(); + + if ($method == 'update') { + $args[] = sf('%s = %s', 'id', $o->get('id')); } - return $returnArray; - } - abstract protected function getSelectQuery(); +// $vars = $this->getChanges(); + foreach($o->data as $key => $value) { + if (!$o->structure[$key]['write']) continue; + $args[] = sf('%s = %%%s', $key, $o->structure[$key]['type']); + $args[] = $value; + } + + if (!count($args)) + throw new Exception ('Model has nothing to write...'); + + array_unshift($args, static::DB_TABLE_NAME); + + $result = call_user_func_array(array($db, $method), $args); + + if (is_null($o->get('id', false))) { + $id = $db->selectOne('select lastval()'); + $o->set('id', $id->i_lastval, true); + } + + } - final public function __construct($db) { - + /* + static public function writeInsert ($db, $o) { + + $args = array(); + foreach($o->data as $key => $value) { + if (!$o->structure[$key]['write']) continue; + $args[] = sf('%s = %%%s', $key, $o->structure[$key]['type']); + $args[] = $value; + } + + array_unshift($args, static::DB_TABLE_NAME); + + $result = call_user_func_array(array($db, 'insert'), $args); } - static public function load($db, $id) { - if(!is_int($id)) throw new Exception("Identifier must be of type Integer"); - die(get_class()); - $file = new gts_ForumAreaModel($db); - $file->loadFromId($id); - return $file; + */ + + /* generic */ + static protected function load ($db, $where = null, $orderBy = null, $offset = null, $limit = null) { + + if (is_null($where)) $where = ''; + else $where = ' where '.$where; + + if (is_null($orderBy)) $orderBy = ''; + else $orderBy = ' order by '.$orderBy; + + if (is_null($offset)) $offset = ''; + else $offset = ' offset '.$offset; + + if (is_null($limit)) $limit = ''; + else $limit = ' limit '.$limit; + + $rows = $db->select('%l %l %l %l %l', + static::DB_SELECT_QUERY, $where, $orderBy, $offset, $limit); + + $arrOut = array(); + + if (!$rows) return $arrOut; + + foreach ($rows as $row) { + $obj = new static(); + $obj->populateFromSqlRow($row); + $arrOut[] = $obj; + } + + return $arrOut; + + } + + /* generic */ + function populateFromSqlRow ($r) { + $rowData = $r->getData(); + + // itterate through the db columns and populate the object + foreach ($rowData as $k => $v) { + + // check we're expecting the currently column + if (!array_key_exists($k, $this->structure)) + throw new Exception (sf('Unexpected row column "%s", add this to the models \'structure\' member variable.', $k)); + + try { + + $this->data[$k] = caster_PostgreSqlToPhp::cast(sf('%%%s', $this->structure[$k]['type']), $v); + + // if casting error throw a useful exception + } catch (Exception $e) { + $spec = caster_PostgreSqlToPhp::getSpec(); + + if (!array_key_exists($this->structure[$k]['type'], $spec)) + throw new caster_Exception ( + sf("Caster doesn't support format: %%%s", + $this->structure[$k]['type'] + ) + ); + + throw new caster_Exception ( + sf("%s: '%s' could not be cast as '%s' (%%%s)", + $k, + is_null($v)?'NULL':$v, + $spec[$this->structure[$k]['type']], + $this->structure[$k]['type'] + ) + ); + } + } } - - public function loadFromId($id) { - - $row = $this->db->select_1(" - SELECT f.id, f.path, f.user_id, f.size, f.memorial_id, u.namefirst AS user_firstname, - u.namelast AS user_lastname, u.vc_urllocation AS user_url, - f.order, mime_type, uploaded, caption - FROM file f - INNER JOIN users u ON f.user_id = u.id - WHERE f.id = %i ", $id); - - $this->loadFromSqlRow($row); - - } - -} -/* - * ORM Implementation - put on ice - * -class gts_ForumAreaModel extends orm_AbstractModel { - public function __construct() { - - // TODO : This is only here as PHP won't let this be declared - // by default... - - $this->id = new orm_DataType_Integer(); - $this->name = new orm_DataType_Text(); - $this->sef_name = new orm_DataType_Text(); - $this->description = new orm_DataType_Text(); - - - - $this->map("id", new orm_DataType_Integer()); - $this->map("name", new orm_DataType_Text()); - $this->map("sef_name", new orm_DataType_Text()); - $this->map("description", new orm_DataType_Text()); - - } } - */ diff --git a/classes/mvc/models/mvc_AbstractModel.php b/classes/mvc/models/mvc_AbstractModel.php new file mode 100644 index 0000000..12a7fb3 --- /dev/null +++ b/classes/mvc/models/mvc_AbstractModel.php @@ -0,0 +1,307 @@ +_fromModel($data); + return $o; + } + + private function _fromModel (self $data) { + + foreach($this->structure as $key => $properties) { + switch ($properties['type']) { + case 'o': + if (isset($properties['model']) && $data->has($key)) { + $m = $properties['model']::from($data->get($key)); + + $this->set($key, $m, true); + break; + + } + default: + $this->set($key, $data->get($key), true); + + } + + } + return $this; + } + + /* generic */ + public function __construct ($data = array()) { + + foreach ($this->structure as $k => $properties) { + switch ($properties['type']) { + + case 'o': + + // do the objects want to be created + if (isset($properties['create']) && $properties['create'] == true) { + + if (isset($properties['model']) && !is_null($properties['model'])) { + $this->set($k, new $properties['model']); + + // if it's a dynamic model - create it + } else if (isset($properties['structure']) && !is_null($properties['structure'])) { + $this->set($k, new mvc_DynamicModel($properties['structure'])); + } + + } + + } + + $this->set( + $k, + isset($properties['default'])? + $properties['default']:null, + true + ); + + + } + + // set data + foreach ($data as $k => $value) { + $this->set($k, $value); + } + } + + function preOutput($outputType) { } + + + + /* generic */ + function has ($k) { + return array_key_exists($k, $this->data) && isset($this->data[$k]); + } + + /* generic */ + function set ($k, $v, $force = false) { + + if (!$force && + ( + isset($this->structure[$k]['write']) && + $this->structure[$k]['write'] == false + ) + ) + throw new Exception ('Column not writable'); + + + if (isset($this->structure[$k]['type']) && + $this->structure[$k]['type'] == 'o' && + isset($this->data[$k]) && $this->data[$k] instanceof mvc_AbstractModel + ) { + if (is_array($v)) { + $this->data[$k]->setArray($v); + } else if ($v instanceof mvc_AbstractModel) { + $this->data[$k] = $v; + } else if (is_null($v)) { + // $this->data[$k] = $v; + } else { + // at this point we don't have valud data for an object... + // dump(array('dont know what to do', $k, $v)); + } + } else + $this->data[$k] = $v; + } + + + // setup the properties that are objects + // TODO: this shouldn't be required - __construct should this automatically... + function setupObjectsFromStructure ($structure) { + foreach ($structure as $k => $v) + if ($this->structure[$k]['type'] == 'o' && isset($v['structure'])) { + $o = new mvc_DynamicModel($v['structure']); + $o->setupObjectsFromStructure($v['structure']); + $this->set($k, $o); + } + } + + + + function setArray ($assoc) { + foreach ($assoc as $k => $v) + $this->set($k, $v); + } + + function increment ($k, $v = 1) { + if (!$this->has($k)) throw new Exception('unknown key: '.$k); + + // increment different data types + switch ($this->structure[$k]['type']) { + + // INTERVAL + case 'z': + case 'Z': + + // check if it's null + if (is_null($this->data[$k])) + $this->data[$k] = new atsumi_Interval(0); + + $this->data[$k]->add(new atsumi_Interval($v)); + break; + + // NUMERIC + case 'i': + case 'I': + case 'e': + case 'E': + case 'n': + case 'N': + case 'f': + case 'F': + + // check if it's null + if (is_null($this->data[$k])) + $this->data[$k] = 0; + + $this->data[$k] += $v; + break; + + + default: + throw new Exception("Unexpected data type to increment %".$this->structure[$k]['type']); + + } + } + + function getStructure () { + return $this->structure; + } + + function __get ($key) { + return $this->get($key); + } + + /* generic */ + function get($key, $strict = true) { + + if (array_key_exists($key, $this->data)) + return $this->data[$key]; + + else if (!array_key_exists($key, $this->data) && !array_key_exists($key, $this->structure)) + throw new Exception ('>>'.$key. '<< is not a known property of >>' .get_called_class().'<<'); + + + else if (!array_key_exists($key, $this->data) && array_key_exists('default', $this->structure[$key])) + return $this->structure[$key]['default']; + + else if (!array_key_exists($key, $this->data)) + throw new Exception ('property: >>'.$key. '<< has no default value in >>' .get_called_class().'<<'); + + + + /* + * TODO: casting + try { + $value = caster_PostgreSqlToPhp::cast(sf('%%%s', $this->structure[$key]['type']), $this->data[$key]); + return $value; + } catch (Exception $e) { + if ($strict) throw $e; + return null; + } + */ + } + + static function outputItem ($value, $type) { + + // if abstract model then output that + if ($value instanceof mvc_AbstractModel) + return $value->output($type); + + elseif (is_object($value) && method_exists($value, 'output')) + return $value->output($type); + + // if array itterate through + elseif (is_array($value)) { + $arrayOut = array(); + foreach ($value as $item) + $arrayOut[] = self::outputItem($item, $type); + return $arrayOut; + + // otherwise just return value + } else + return $value; + + } + + function __toString () { + return pretty($this->output()); + } + + function output ($type = self::OUTPUT_FORMAT_ASSOC) { + + $this->preOutput($type); + + switch ($type) { + + // Native object + case self::OUTPUT_FORMAT_OBJECT: + + foreach($this->structure as $key => $properties) { + if (isset($this->structure[$key]['output']) && + $this->structure[$key]['output'] == false) + continue; + + self::outputItem($this->get($key), $type); + } + + return $this; + + // Associative array + case self::OUTPUT_FORMAT_ASSOC: + $out = array(); + foreach($this->structure as $key => $properties) { + if (isset($this->structure[$key]['output']) && + $this->structure[$key]['output'] == false) + continue; + + $out[$key] = self::outputItem($this->get($key), $type); + } + return $out; + + // Std Class + case self::OUTPUT_FORMAT_STD_CLASS: + $out = new stdClass(); + foreach($this->structure as $key => $properties) { + if (isset($this->structure[$key]['output']) && + $this->structure[$key]['output'] == false) + continue; + + $out[$key] = self::outputItem($this->get($key), $type); + } + return $out; + } + + } +} +?> \ No newline at end of file diff --git a/classes/mvc/models/mvc_DynamicModel.php b/classes/mvc/models/mvc_DynamicModel.php new file mode 100644 index 0000000..4926787 --- /dev/null +++ b/classes/mvc/models/mvc_DynamicModel.php @@ -0,0 +1,50 @@ + $properties) { + $this->add($k, $properties); + + if (isset($properties['value'])) + $data[$k] = $properties['value']; + elseif (isset($properties['default'])) + $data[$k] = $properties['default']; + } + + parent::__construct($data); + } + + + + public function add ($key, $properties) { + + $this->structure[$key] = array( + 'type' => $properties['type'], + 'create' => isset($properties['create'])?$properties['create']:false, + 'structure' => isset($properties['structure'])?$properties['structure']:null, + 'model' => isset($properties['model'])?$properties['model']:null, + 'default' => isset($properties['default'])?$properties['default']:null + ); + + if (isset($properties['default'])) + $this->set($key, $properties['default']); + + if (isset($properties['value'])) + $this->set($key, $properties['value']); + + } + + // is defined? + public function exists ($key) { + return array_key_exists($key, $this->structure); + } + + +} +?> \ No newline at end of file diff --git a/classes/mvc/view_handlers/mvc_PhpTemplateViewHandler.php b/classes/mvc/view_handlers/mvc_PhpTemplateViewHandler.php new file mode 100644 index 0000000..364b442 --- /dev/null +++ b/classes/mvc/view_handlers/mvc_PhpTemplateViewHandler.php @@ -0,0 +1,208 @@ + file path + private $templateFileMap = array(); + + // optional main template, template that includes $pageContent + private $mainTemplate = false; + + // view/page data + private $viewData = array(); + + private $surpressErrors = true; + + const TEMPLATE_TYPE_FILE = 1; + const TEMPLATE_TYPE_STRING = 2; + + + public function __construct($mainTemplate = false, $templateFileMap = array(), $surpressErrors = true) { + + $this->templateFileMap = $templateFileMap; + $this->mainTemplate = $mainTemplate; + $this->surpressErrors = $surpressErrors; + } + + + /* + * Static: Processes a specific template file + */ + static public function processTemplateFile ($templateFile, $data, $supressErrors = false) { + if (empty($templateFile)) return; + + extract($data, EXTR_SKIP); + ob_start(); + try { + + if ($supressErrors) + @include($templateFile); + else + include($templateFile); + + } catch (Exception $e) { + + Atsumi::error__listen($e); + + if (!$supressErrors) + throw $e; + else + Atsumi::error__recover($e); + + + /* + throw new mvc_ViewNotFoundException ( + "Can't find referenced template: ".$templateFile, + $templateFile + ); */ + } + return ob_get_clean(); + } + + /* + * Static: Processes a specific template string + * WARNING: You could create a PHP vulnerability by using this + */ + static public function processTemplateString ($templateString, $data, $supressErrors = false) { + + if (empty($templateString) || $templateString == '' || !$templateString) return; + + extract($data, EXTR_SKIP); + ob_start(); + try { + + eval("?>".$templateString.'viewData); + + if ($templateFile) { + + if (array_key_exists($templateFile, $this->templateFileMap)) + $templateFile = $this->templateFileMap[$templateFile]; + + + return self::processTemplateFile($templateFile, $data, $this->surpressErrors); + + } else if ($templateString) { + + return self::processTemplateString($templateString, $data, $this->surpressErrors); + + } else { + throw new Exception ('Unknown template type: '.$template); + } + } + + + /* + * prints a template-ref/template-file + * optionally includes view data + */ + public function renderTemplate ($template, $data = array(), $incViewData = false) { + print $this->processTemplate($template, $data, $incViewData); + } + + + /* + * returns processed template + * optionally uses main template + * $viewTemplate can be type string (file path) or array + */ + public function process ($viewTemplate, $viewData, $mainTemplateOverride = null) { + + $this->viewData = $viewData; + $mainTemplate = is_null($mainTemplateOverride)? + $this->mainTemplate: + $mainTemplateOverride; + + // process the view tempalte + $pageContent = $this->processTemplate( + $viewTemplate, + array(), + true + ); + // if there is a main template - process & render it + if ($mainTemplate) + return $this->processTemplate( + $mainTemplate, + array("pageContent"=>$pageContent), + true + ); + + // if not - render view template + else return $pageContent; + } + + /* + * prints a processed + * optionally uses main template + */ + public function render ($viewTemplate, $viewData, $mainTemplateOverride = null) { + print $this->process($viewTemplate, $viewData, $mainTemplateOverride); + } + + + /* + * set the main template + */ + public function setMainTemplate ($in) { + $this->mainTemplate = $in; + } + + + + + + +} + +?> \ No newline at end of file diff --git a/classes/mvc/view_handlers/mvc_SmartyViewHandler.php b/classes/mvc/view_handlers/mvc_SmartyViewHandler.php index bf52385..858ce7e 100644 --- a/classes/mvc/view_handlers/mvc_SmartyViewHandler.php +++ b/classes/mvc/view_handlers/mvc_SmartyViewHandler.php @@ -16,7 +16,7 @@ public function render($viewName, $viewData) { if(is_null($this->smarty)) throw new mvc_TemplateEngineNotSupplied("Smarty has not been supplied"); - $this->smarty->clear_all_assign(); + $this->smarty->clearAllAssign(); $this->smarty->assign($viewData); $this->smarty->display($viewName); diff --git a/classes/mvc/views/mvc_HtmlView.php b/classes/mvc/views/mvc_HtmlView.php index aafb059..9af3d42 100644 --- a/classes/mvc/views/mvc_HtmlView.php +++ b/classes/mvc/views/mvc_HtmlView.php @@ -10,6 +10,15 @@ abstract class mvc_HtmlView extends mvc_AbstractView { abstract protected function renderBodyContent(); + + // headers + public function setHeaders() { + header(sf('Content-Type: text/html; charset=%s', $this->getCharset())); + } + public function getCharset() { + return 'utf-8'; + } + public function render() { $this->renderDoctype(); $this->renderHtml(); diff --git a/classes/mvc/views/mvc_JsonView.php b/classes/mvc/views/mvc_JsonView.php index 863b88e..feed26b 100644 --- a/classes/mvc/views/mvc_JsonView.php +++ b/classes/mvc/views/mvc_JsonView.php @@ -9,4 +9,4 @@ public function setHeaders() { header(sf('Content-Type: application/json; charset=%s', $this->getCharset())); } } -?> +?> \ No newline at end of file diff --git a/classes/mvc/views/mvc_JsonpView.php b/classes/mvc/views/mvc_JsonpView.php new file mode 100644 index 0000000..e2009ab --- /dev/null +++ b/classes/mvc/views/mvc_JsonpView.php @@ -0,0 +1,12 @@ +get_callback)?'callback':$this->get_callback, $this->get_json); + } + + public function setHeaders() { + header(sf('Content-Type: application/json; charset=%s', $this->getCharset())); + } +} +?> \ No newline at end of file diff --git a/classes/mvc/views/mvc_TxtView.php b/classes/mvc/views/mvc_TxtView.php index 076cfcc..6ef2d4a 100644 --- a/classes/mvc/views/mvc_TxtView.php +++ b/classes/mvc/views/mvc_TxtView.php @@ -1,14 +1,11 @@ get_txt); } @@ -16,11 +13,9 @@ public function render() { // headers public function setHeaders() { header(sf('Content-Type: text/plain; charset=%s', $this->getCharset())); + header(sf('Content-Length: %s', strlen($this->get_txt))); } - - - - + } ?> diff --git a/classes/session/session_Handler.php b/classes/session/session_Handler.php index 1d0daa7..f7a90f0 100644 --- a/classes/session/session_Handler.php +++ b/classes/session/session_Handler.php @@ -1,15 +1,40 @@ configure($options); // Fetch the session storage handler - $storage =(isset($options['storage']) ? $options['storage'] : self::DEFAULT_STORAGE); + $storage = (isset($options['storage']) ? $options['storage'] : self::DEFAULT_STORAGE); if(is_subclass_of($storage, 'session_AbstractStorage')) $this->storage = new $storage($options); // Start the session - $this->start(); + $this->start($options); // Add debug information atsumi_Debug::record( 'Session Created', - 'The atsumi session constructor completed', true, + 'The atsumi session constructor completed', null, true, atsumi_Debug::AREA_SESSION ); } @@ -68,17 +95,14 @@ public function __destruct() { } catch (Exception $e) { } } - public function getId() { - return session_id(); - } + /* GET METHODS */ /** * Returns a singleton instance of atsumi session - * * @param $options Session setup options * @return object A session_Handler object */ - static public function getInstance($options = array()) { + public static function getInstance($options = array()) { static $instance; if(!is_object($instance)) @@ -87,8 +111,13 @@ static public function getInstance($options = array()) { return $instance; } - /* GET FUNCTIONS */ - + /** + * Returns a variable from the current active session. + * @param string $name The name of the variable to return. + * @param mixed $default The variable to return if the value does not exist. + * @param string $namespace The namespace under which the variable is stored. + * @return mixed The variable under that name or the default. + */ public function &get($name, $default = null, $namespace = self::DEFAULT_NAMESPACE) { // Add prefix to prevent namespace collisions $namespace = '__'.$namespace; @@ -99,7 +128,23 @@ public function &get($name, $default = null, $namespace = self::DEFAULT_NAMESPAC return $default; } - /* SET FUNCTIONS */ + /** + * Returns the current session id. + * @return string The session id. + */ + public function getId() { + return session_id(); + } + + /* SET METHODS */ + + /** + * Sets a variable to the current active session and return the old value if set. + * @param string $name The name of the variable to return. + * @param mixed $value The variable to be stored under that name. + * @param string $namespace The namespace under which the variable is stored. + * @return mixed The old value under the name if it exists. + */ public function set($name, $value, $namespace = self::DEFAULT_NAMESPACE) { // Add prefix to prevent namespace collisions $namespace = '__'.$namespace; @@ -114,6 +159,9 @@ public function set($name, $value, $namespace = self::DEFAULT_NAMESPACE) { return $old; } + /* MAGIC METHODS */ + /* METHODS */ + public function push($name, $value, $namespace = self::DEFAULT_NAMESPACE) { // Add prefix to prevent namespace collisions $namespace = '__'.$namespace; @@ -125,18 +173,17 @@ public function push($name, $value, $namespace = self::DEFAULT_NAMESPACE) { } - /* HAS FUNCTIONS */ - public function has($name, $namespace = self::DEFAULT_NAMESPACE) { + public function exists($name, $namespace = self::DEFAULT_NAMESPACE) { // Add prefix to prevent namespace collisions $namespace = '__'.$namespace; return isset($_SESSION[$namespace][$name]); } - public function del($name, $namespace = self::DEFAULT_NAMESPACE) { + public function delete($name, $namespace = self::DEFAULT_NAMESPACE) { $this->set($name, null, $namespace); } - protected function start() { + protected function start($options = array()) { if(headers_sent() === true) throw new session_HeadersSentException; @@ -146,6 +193,9 @@ protected function start() { session_cache_limiter('none'); session_start(); + if(isset($options['persistent']) && $options['persistent'] === true) + $this->renewCookie($options['life']); + $this->state = self::STATE_ACTIVE; } @@ -177,20 +227,51 @@ protected function configure($options = array()) { ini_set('session.save_handler', 'files'); ini_set('session.use_trans_sid', false); + if(isset($options['life'])) ini_set('session.cookie_lifetime', $options['life']); + + $this->configCookie($options); + if(isset($options['name'])) session_name($options['name']); else session_name('atsumi_session'); + } - if(isset($options['domain'])) - ini_set('session.cookie_domain', $options['domain']); + public function configCookie($options = array()) { + $old = session_get_cookie_params(); + session_set_cookie_params( + (isset($options['life']) ? $options['life'] : $old['lifetime']), + (isset($options['cookie_path']) ? $options['cookie_path'] : $old['path']), + (isset($options['cookie_domain']) ? $options['cookie_domain'] : $old['domain']), + (isset($options['cookie_secure']) ? $options['cookie_secure'] : $old['secure']), + (isset($options['cookie_httponly']) ? $options['cookie_httponly'] : $old['httponly']) + ); } protected function destroyCookie() { + $name = session_name(); + if(!isset($_COOKIE[$name])) return; + $params = session_get_cookie_params(); - setcookie(session_name(), '', time() - 42000, - $params["path"], $params["domain"], - $params["secure"], $params["httponly"] + setcookie($name, '', time() - 42000, + $params['path'], $params['domain'], + $params['secure'], $params['httponly'] + ); + } + + protected function renewCookie($life = 0) { + $name = session_name(); + if(!isset($_COOKIE[$name])) return; + + $params = session_get_cookie_params(); + setcookie( + $name, + session_id(), + ($life > 0 ? (time() + $life) : 0), + $params['path'], + $params['domain'], + $params['secure'], + $params['httponly'] ); } @@ -204,5 +285,15 @@ protected function generateId($len = 32) { session_id($id); return $id; } + + /* DEPRECATED METHODS */ + + public function has($name, $namespace = self::DEFAULT_NAMESPACE) { + $this->exists($name, $namespace); + } + + public function del($name, $namespace = self::DEFAULT_NAMESPACE) { + $this->delete($name, $namespace); + } } ?> \ No newline at end of file diff --git a/classes/session/storage/session_CacheStorage.php b/classes/session/storage/session_CacheStorage.php new file mode 100644 index 0000000..d94670f --- /dev/null +++ b/classes/session/storage/session_CacheStorage.php @@ -0,0 +1,55 @@ +cache = $cache; + $this->ttl = (isset($options['life']) ? $options['life'] : SELF::DEFAULT_MAX_LIFE); + + ini_set('session.gc_divisor', 1); + ini_set('session.gc_maxlifetime', $this->ttl); + ini_set('session.gc_probability', 100); + + parent::__construct($options); + } + + public function read($id) { + + atsumi_Debug::startTimer('SESSION_READ'); + + $result = $this->cache->get($id, null, 'SESSION'); + + if(is_null($result)) + return ''; + + atsumi_Debug::record( + 'Session loaded from cache', + sf('Session ID: %s', $id), + $result, + 'SESSION_READ', + atsumi_Debug::AREA_SESSION, + $result + ); + return $result; + } + + public function write($id, $sessionData) { + + $this->cache->set($id, $sessionData, $this->ttl, 'SESSION'); + return true; + } + + public function destroy($id) { + $this->cache->set($id, null, -1, 'SESSION'); + return true; + } + + public function gc($maxlifetime) { + return true; + } +} \ No newline at end of file diff --git a/classes/session/storage/session_DatabaseStorage.php b/classes/session/storage/session_DatabaseStorage.php index 2e8c219..2c529a5 100644 --- a/classes/session/storage/session_DatabaseStorage.php +++ b/classes/session/storage/session_DatabaseStorage.php @@ -11,7 +11,7 @@ public function __construct($options) { $this->database = $options['database']; ini_set('session.gc_divisor', 1); - ini_set('session.gc_maxlifetime', 1440); + ini_set('session.gc_maxlifetime', (isset($options['life']) ? $options['life'] : 7200)); ini_set('session.gc_probability', 100); parent::__construct($options); @@ -48,8 +48,6 @@ public function read($id) { public function write($id, $sessionData) { try { if($this->database->exists('session', 'checksum = %i AND session_id = %s', crc32($id), $id)) { - - atsumi_Debug::record( 'Updating Session', sf('The session(%s) is being updated to the DB', $id ), @@ -82,7 +80,7 @@ public function write($id, $sessionData) { return true; /* this is a little drastic but will most likley segfault if Exception bubbles up */ - } catch (Exception $e) { die("Could not write session to database."); } + } catch (Exception $e) { die('Could not write session to database.'); } } public function destroy($id) { diff --git a/classes/sitemap/exceptions/sitemap_Exception.php b/classes/sitemap/exceptions/sitemap_Exception.php new file mode 100644 index 0000000..c70c55b --- /dev/null +++ b/classes/sitemap/exceptions/sitemap_Exception.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/classes/sitemap/exceptions/sitemap_WriteException.php b/classes/sitemap/exceptions/sitemap_WriteException.php new file mode 100644 index 0000000..b249cf3 --- /dev/null +++ b/classes/sitemap/exceptions/sitemap_WriteException.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/classes/sitemap/sitemap_Handler.php b/classes/sitemap/sitemap_Handler.php index 5bba858..ac8e27a 100644 --- a/classes/sitemap/sitemap_Handler.php +++ b/classes/sitemap/sitemap_Handler.php @@ -6,51 +6,60 @@ class sitemap_Handler { /* deletes all urls for a host */ static public function purgeUrlsForHost($db, $host) { - $db->delete('sitemap', 'host = %s', $host); } /* adds a url to the sitemap table for inclusion next time the sitemap is generated */ static public function writeUrl(&$db, $loc, $lastMod = null, $changeFreq = null, $priority = null, $tableName = 'sitemap') { - $loc = trim($loc); - $checksum = crc32($loc); - - /* get the host name from url */ - preg_match('@^(?:http://)?([^/]+)@i', $loc, $matches); - $host = $matches[1]; - - $row = $db->select_1('select * from sitemap where checksum = \'%l\'::bigint AND loc = %s', $checksum, $loc); - - /* new location */ - if(is_null($row)) { - $db->insert( - 'sitemap', - 'checksum = \'%l\'::bigint', $checksum, - 'host = %s', $host, - 'loc = %s', $loc, - 'last_mod = %T', $lastMod, - 'change_freq = %S', $changeFreq, - 'priority = %l', is_null($priority)?'NULL':$priority - ); - - /* update as details have changed */ - } elseif($row->t_last_mod != $lastMod || $row->s_change_freq != $changeFreq || $row->s_priority != $priority) { - $db->update_1( - 'sitemap', - 'checksum = \'%l\'::bigint AND loc = %s', $row->i_checksum, $loc, - 'last_mod = %T', $lastMod, - 'change_freq = %S', $changeFreq, - 'priority = %l', is_null($priority)?'NULL':$priority + try { + + $loc = trim($loc); + $checksum = crc32($loc); + + /* get the host name from url */ + preg_match('@^(?:https?:\/\/)?((?:www\.)?[^\/]+)@i', $loc, $matches); + + $host = $matches[1]; + + $row = $db->select_1( + 'select * from sitemap where loc = %s', + $loc ); - + + /* new location */ + if(is_null($row)) { + $db->insert( + 'sitemap', + 'checksum = \'%l\'::bigint', $checksum, + 'host = %s', $host, + 'loc = %s', $loc, + 'last_mod = %T', $lastMod, + 'change_freq = %S', $changeFreq, + 'priority = %l', is_null($priority)?'NULL':$priority + ); + + /* update as details have changed */ + } elseif($row->t_last_mod != $lastMod || $row->s_change_freq != $changeFreq || $row->s_priority != $priority) { + $db->update_1( + 'sitemap', + 'loc = %s', $loc, + 'last_mod = %T', $lastMod, + 'change_freq = %S', $changeFreq, + 'priority = %l', is_null($priority)?'NULL':$priority + ); + + } + + /* nulling used variables (big sitemaps need every scrap of memory!) */ + $row = null; + $host = null; + $checksum = null; + $matches = null; + + } catch (Exception $e) { + Atsumi::error__listen($e); } - - /* nulling used variables (big sitemaps need every scrap of memory!) */ - $row = null; - $host = null; - $checksum = null; - $matches = null; } /* gets total number of urls for host */ @@ -63,15 +72,21 @@ static public function getUrlCount(&$db, $host, $tablename = 'sitemap') { /* writes sitemap files to disk */ static public function writeXml(&$db, $host, $xmlFilePath, $xmlUrlRoot, $maxUrlsPerSitemap = null, $compress = true, $tablename = 'sitemap') { + if (!is_dir($xmlFilePath) && !mkdir($xmlFilePath, 0777, true)) { + throw new sitemap_WriteException("Failed to create neccessary folders to save XML site map: ".$xmlFilePath); + } + /* clean up old sitemap files */ foreach(glob($xmlFilePath."sitemap*.xml*") as $filename) unlink($filename); if(is_null($maxUrlsPerSitemap)) $maxUrlsPerSitemap = self::MAX_URLS_PER_SITEMAP; + /* how many items required */ $count = self::getUrlCount($db, $host, $tablename); + /* array of sitemap files */ $siteMapArr = array(); diff --git a/classes/utility/calendar/atsumi_Date.php b/classes/utility/calendar/atsumi_Date.php new file mode 100644 index 0000000..1c94a2c --- /dev/null +++ b/classes/utility/calendar/atsumi_Date.php @@ -0,0 +1,61 @@ +year = (int) $year; + $this->month = (int) $month; + $this->day = (int) $day; + } + + public function __toString() { + return $this->getYmd(); + } + + public function getYear () { return $this->year; } + public function getMonth () { return $this->month; } + public function getDay () { return $this->day; } + + + public function adjust ($year = 0, $month = 0, $day = 0) { + $newDate = self::fromTimestamp(mktime (0,0,0, $this->month+$month, $this->day+$day, $this->year+$year)); + $this->year = $newDate->getYear(); + $this->month = $newDate->getMonth(); + $this->day = $newDate->getDay(); + } + + public function getTimestampDayStart () { + return mktime (0,0,0, $this->month, $this->day, $this->year); + } + public function getTimestampDayEnd () { + return mktime (0,0,0, $this->month, $this->day + 1, $this->year); + } + public function getYmd () { + return sprintf ('%04d-%02d-%02d', $this->year, $this->month, $this->day); + } +} +?> \ No newline at end of file diff --git a/classes/utility/calendar/atsumi_DateTime.php b/classes/utility/calendar/atsumi_DateTime.php new file mode 100644 index 0000000..dbceab8 --- /dev/null +++ b/classes/utility/calendar/atsumi_DateTime.php @@ -0,0 +1,36 @@ +timestamp = (int) $timestamp; + } + + public function __toString() { + return (String) $this->timestamp; + } + + public function format($formatString = null) { + if (is_null($formatString)) $formatString = self::FORMAT_FRIENDLY; + return date($formatString, $this->timestamp); + } + static public function formatFromTimestamp ($timestamp, $formatString = null) { + $d = new self($timestamp); + return $d->format($formatString); + } + + public function output ($outputType) { + return (String) $this->timestamp; + } +} +?> \ No newline at end of file diff --git a/classes/utility/calendar/atsumi_Interval.php b/classes/utility/calendar/atsumi_Interval.php new file mode 100644 index 0000000..8327022 --- /dev/null +++ b/classes/utility/calendar/atsumi_Interval.php @@ -0,0 +1,127 @@ +newInstanceArgs($intervalMatches); + return $intervalInstance; + + + } else + throw new Exception ('Invalid interval format'); + + } + + public function __construct ($seconds, $minutes = 0, $hours = 0, $days = 0, $years = 0) { + + // TODO: check date is valid + $this->seconds = floatval( + floatval($seconds) + + (intval($minutes) * self::DURATION_MINUTE) + + (intval($hours) * self::DURATION_HOUR) + + (intval($days) * self::DURATION_DAY) + + (intval($years) * self::DURATION_YEAR) + ); + } + + public function add (self $interval) { + $this->seconds += $interval->inSeconds(); + } + + public function __toString() { + return strval($this->inSeconds()); + } + + public function inSeconds() { + return floatval($this->seconds); + } + + public function inMinutes() { + return floatval($this->seconds / self::DURATION_MINUTE); + } + + public function inHours() { + return floatval($this->seconds / self::DURATION_HOUR); + } + + public function inDays() { + return floatval($this->seconds / self::DURATION_DAY); + } + + public function inYears() { + return floatval($this->seconds / self::DURATION_YEAR); + } + public function getFormatBreakdown() { + + $remainder = $this->inSeconds(); + + $years = floor($remainder / self::DURATION_YEAR); + $remainder -= $years * self::DURATION_YEAR; + + $days = floor($remainder / self::DURATION_DAY); + $remainder -= $days * self::DURATION_DAY; + + $hours = floor($remainder / self::DURATION_HOUR); + $remainder -= $hours * self::DURATION_HOUR; + + $minutes = floor($remainder / self::DURATION_MINUTE); + $remainder -= $minutes * self::DURATION_MINUTE; + + $seconds = floor($remainder); + + return array( + 'years' => $years, + 'days' => $days, + 'hours' => $hours, + 'minutes' => $minutes, + 'seconds' => $seconds, + ); + } + + public function format($compact = false) { + + $formatComponents = $this->getFormatBreakdown(); + + return sf('%s%s%s:%s:%s', + $formatComponents['years']?sf('%sy ',$formatComponents['years']):'', + $formatComponents['days']||$formatComponents['days']?sf('%sd ',$formatComponents['days']):'', + sprintf('%02d', $formatComponents['hours']), + sprintf('%02d', $formatComponents['minutes']), + sprintf('%02d', $formatComponents['seconds']) + + ); + + } + + +} +?> \ No newline at end of file diff --git a/classes/utility/http/atsumi_Http.php b/classes/utility/http/atsumi_Http.php new file mode 100644 index 0000000..afdd478 --- /dev/null +++ b/classes/utility/http/atsumi_Http.php @@ -0,0 +1,132 @@ + $val) + curl_setopt($ch, $opt, $val); + + + + $response = curl_exec($ch); + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if ($httpCode == 0) + throw new atsumi_HttpException ( + 'CURL error: #'.curl_errno($ch), + curl_errno($ch) + ); + + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $headerSize); + $body = substr($response, $headerSize); + + $httpRequestHeaders = curl_getinfo($ch, CURLINFO_HEADER_OUT ); + + //close connection + curl_close($ch); + + + return new atsumi_HttpResponse( + $httpCode, + $header, + $body + ); + + case self::POST_METHOD_PECL: + + return http_post_fields($url, $fields); + + } + } + + +} + + +?> \ No newline at end of file diff --git a/classes/utility/http/atsumi_HttpException.php b/classes/utility/http/atsumi_HttpException.php new file mode 100644 index 0000000..ba72037 --- /dev/null +++ b/classes/utility/http/atsumi_HttpException.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/classes/utility/http/atsumi_HttpResponse.php b/classes/utility/http/atsumi_HttpResponse.php new file mode 100644 index 0000000..4652f48 --- /dev/null +++ b/classes/utility/http/atsumi_HttpResponse.php @@ -0,0 +1,35 @@ +httpCode = $httpCode; + $this->header = $header; + $this->body = $body; + + } + + public function getHttpCode() { return $this->httpCode; } + public function getBody() { return $this->body; } + public function getHeader() { return $this->header; } + public function getSuccessResponse() { + return $this->httpCode >= 200 && $this->httpCode < 300; + } + + public function getSummaryString () { + + return sf('HTTP CODE: %s, SUCCESS: %s, BODY:%s', + $this->httpCode, + $this->getSuccessResponse()?'YES':'NO', + empty($this->body)?'EMPTY':sf('"%s"', $this->body) + ); + + } + + +} +?> \ No newline at end of file diff --git a/classes/utility/math/atsumi_Math.php b/classes/utility/math/atsumi_Math.php new file mode 100644 index 0000000..9094896 --- /dev/null +++ b/classes/utility/math/atsumi_Math.php @@ -0,0 +1,21 @@ + $intMax) $out = $in - $intMax * 2 - 2; + else $out = $in; + + return $out; + } + +} +?> \ No newline at end of file diff --git a/classes/utility/security/atsumI_Security.php b/classes/utility/security/atsumI_Security.php new file mode 100644 index 0000000..607a4d3 --- /dev/null +++ b/classes/utility/security/atsumI_Security.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/classes/validators/exceptions/ValidationException.php b/classes/validators/exceptions/ValidationException.php index 729e7dd..262eafa 100644 --- a/classes/validators/exceptions/ValidationException.php +++ b/classes/validators/exceptions/ValidationException.php @@ -2,7 +2,4 @@ class ValidationException extends Exception { - public function __construct($message = null, $code = null) { - parent::__construct('Validation Failed: '.$message, $code); - } } \ No newline at end of file diff --git a/classes/validators/exceptions/ValidationIncorrectFileTypeException.php b/classes/validators/exceptions/ValidationIncorrectFileTypeException.php new file mode 100644 index 0000000..3a7fa62 --- /dev/null +++ b/classes/validators/exceptions/ValidationIncorrectFileTypeException.php @@ -0,0 +1,5 @@ + +?> \ No newline at end of file diff --git a/classes/validators/validate_EmailAddress.php b/classes/validators/validate_EmailAddress.php index da30925..4d1f20e 100644 --- a/classes/validators/validate_EmailAddress.php +++ b/classes/validators/validate_EmailAddress.php @@ -20,7 +20,8 @@ public function __construct($testDomain = true, $arrayIndex = null) { public function validate($data) { if(is_array($data)) $data = $data[$this->arrayIndex]; - if(empty($data) || $this->validEmailAddress($data)) { + if (is_null($data) || !strlen($data)) return true; + if($this->validEmailAddress($data)) { if($this->testDomain) { if($this->checkDomainExists($data)) { return true; diff --git a/classes/validators/validate_FileType.php b/classes/validators/validate_FileType.php index 2c11b88..5f253b0 100644 --- a/classes/validators/validate_FileType.php +++ b/classes/validators/validate_FileType.php @@ -108,7 +108,7 @@ public function validate($data) { $incorrectExtensionArr = $this->getExtensionFromMime($data['type']); } - throw new Exception(sf('File should be a valid %s%s', $this->extensionText, count($incorrectExtensionArr)?sf('(not a %s)', implode('/', $incorrectExtensionArr)):'')); + throw new ValidationIncorrectFileTypeException(sf('File should be a valid %s%s', $this->extensionText, count($incorrectExtensionArr)?sf(' (not a %s)', implode('/', $incorrectExtensionArr)):'')); } diff --git a/classes/validators/validate_IpAddress.php b/classes/validators/validate_IpAddress.php new file mode 100644 index 0000000..ef335a5 --- /dev/null +++ b/classes/validators/validate_IpAddress.php @@ -0,0 +1,22 @@ + diff --git a/classes/validators/validate_MaxChars.php b/classes/validators/validate_MaxChars.php index 3d610e7..3bac03e 100644 --- a/classes/validators/validate_MaxChars.php +++ b/classes/validators/validate_MaxChars.php @@ -9,23 +9,27 @@ class validate_MaxChars extends validate_AbstractValidator { private $maxChars = 0; + private $encoding = 'UTF-8'; - public function __construct($max) { + public function __construct($max, $encoding = null) { $this->maxChars = $max; + + if (!is_null($encoding)) + $this->encoding = $encoding; } public function validate($data) { if(is_array($data))$data = $data[0]; $data = str_replace("\n", " ", str_replace("\r", "", $data)); - if(empty($data) || strlen($data) <= $this->maxChars) + if(empty($data) || mb_strlen($data, $this->encoding) <= $this->maxChars) return true; else - throw new Exception(sf("You must enter less than %s characters", + throw new Exception(sf("You must enter %s characters or less", $this->maxChars )); } } -?> +?> \ No newline at end of file diff --git a/classes/validators/validate_MinAge.php b/classes/validators/validate_MinAge.php index 3ec4fea..97e8087 100644 --- a/classes/validators/validate_MinAge.php +++ b/classes/validators/validate_MinAge.php @@ -2,7 +2,7 @@ class validate_MinAge extends validate_AbstractValidator { - protected $maxAge; + protected $minAge; public function __construct($minAge) { $this->minAge = $minAge; diff --git a/classes/validators/validate_MinChars.php b/classes/validators/validate_MinChars.php index 2be6a73..6c32261 100644 --- a/classes/validators/validate_MinChars.php +++ b/classes/validators/validate_MinChars.php @@ -19,7 +19,7 @@ public function validate($data) { if(empty($data) || strlen($data) >= $this->minChars) return true; - else throw new Exception(sf("You must enter more than %s characters", + else throw new Exception(sf("You must enter %s or more characters", $this->minChars )); diff --git a/classes/validators/validate_Recaptcha.php b/classes/validators/validate_Recaptcha.php new file mode 100644 index 0000000..8374b37 --- /dev/null +++ b/classes/validators/validate_Recaptcha.php @@ -0,0 +1,173 @@ +privateKey = $privateKey; + } + public function getUserIp () { + + // cloudflare + if (array_key_exists('HTTP_CF_CONNECTING_IP', $_SERVER)) + return $_SERVER['HTTP_CF_CONNECTING_IP']; + + // proxy + elseif (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) + return $_SERVER['HTTP_X_FORWARDED_FOR']; + + // direct IP + elseif (array_key_exists('REMOTE_ADDR', $_SERVER)) + return $_SERVER['REMOTE_ADDR']; + + else return false; + + } + public function validate($data) { + // We don't care about the data variable, check for... + if(!isset($_POST['recaptcha_challenge_field']) || !isset($_POST['recaptcha_response_field'])) { + throw new Exception('Missing required reCAPTCHA field'); + } + + $answer = $this->recaptcha_check_answer($this->privateKey, + $this->getUserIp(), + $_POST["recaptcha_challenge_field"], + $_POST["recaptcha_response_field"] + ); + if($answer->is_valid) { + return true; + } + throw new Exception('Invalid CAPTCHA answer'); + } + + /* + * Hacked up and class-ified functions from the recaptchalib.php written by + * Mike Crawford + * Ben Maurer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + /** + * The reCAPTCHA server URL's + */ + const RECAPTCHA_VERIFY_SERVER = 'www.google.com'; + + /** + * Encodes the given data into a query string format + * @param $data - array of string elements to be encoded + * @return string - encoded request + */ + function _recaptcha_qsencode ($data) { + $req = ""; + foreach ( $data as $key => $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; + } + + + /** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ + function _recaptcha_http_post($host, $path, $data, $port = 80) { + $req = $this->_recaptcha_qsencode ($data); + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; + } + + + /** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ + function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) { + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + $response = $this->_recaptcha_http_post (self::RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + } +} + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} +?> diff --git a/classes/validators/validate_Required.php b/classes/validators/validate_Required.php index 4ecc0dc..933396e 100644 --- a/classes/validators/validate_Required.php +++ b/classes/validators/validate_Required.php @@ -18,7 +18,7 @@ public function isRequired() { return $this->required; } public function validate($data) { - if(!$this->required ||(!is_null($data) && strval($data) != "" && !is_array($data))) + if(!$this->required || (!is_array($data) && !is_null($data) && trim(strval($data)) !== "")) return true; elseif(is_array($data)) { $empty = true; diff --git a/classes/validators/validation_Handler.php b/classes/validators/validation_Handler.php new file mode 100644 index 0000000..91a49e1 --- /dev/null +++ b/classes/validators/validation_Handler.php @@ -0,0 +1,31 @@ +validate($value); + } catch(Exception $e) { + $errors[] = $e->getMessage(); + $validates = false; + } + } + + return array ( + "validates" => $validates, + "errors" => $errors + ); + } + + + + +} + +?> \ No newline at end of file diff --git a/classes/widgets/form/elements/widget_AbstractElement.php b/classes/widgets/form/elements/widget_AbstractElement.php index c23c87b..3c28c1d 100644 --- a/classes/widgets/form/elements/widget_AbstractElement.php +++ b/classes/widgets/form/elements/widget_AbstractElement.php @@ -1,65 +1,105 @@ outputGeneric(), + + return array_merge( $this->outputGeneric(), $this->outputSpecific()); } protected function outputGeneric() { - + return array( "name" => $this->getName(), "value" => $this->getValue(), "validation" => $this->getValidationData() - ); + ); } */ - + protected function preRender() { } protected function postRender() { } - + static public function makeInputSafe($in) { $in = str_replace("'", "'", $in); $in = str_replace('"', '"', $in); return $in; } - - public function render() { - $out = $this->preRender(); - $out .= sfl('
%s%s%s
', - $this->style ? " " . $this->style : "", - ($this->submitted && !$this->validates) ? " error" : "", - $this->name, - $this->renderErrors(), - ($this->label != '' || $this->getRequired() ? $this->renderLabel() : ''), - $this->renderElement()); - $out .= $this->postRender(); - return $out; + + public function render($options = array()) { + // If elementOnly is specified, only the form element itself is returned + if (isset($options['elementOnly']) && $options['elementOnly']) { + $out = $this->renderElement(); + + } else { + $out = $this->preRender(); + $out .= sfl('
', + $this->style ? " " . $this->style : "", + $this->cssClass ? " " . $this->cssClass : "", + ($this->submitted && !$this->validates) ? " error" : "", + $this->name, + $this->cssStyle ? " style='" . $this->cssStyle . "'": "" + ); + + try { + $out .= sf('%s%s
%s
', + $this->renderErrors(), + (($this->label != '' || $this->getRequired()) && + (!array_key_exists('label', $options) || $options['label'] !== false) ? + $this->renderLabel() : ''), + $this->renderElement()); + } catch (Exception $e) { + + // if in debug mode display exception details + if (atsumi_Debug::getActive()) + $out .= sfl('Element Exception "%s": %s #%s', $e->getMessage(), $e->getFile(), $e->getLine()); + + // fire error listeners + Atsumi::error__listen($e); + } + $out .= '
'; + + $out .= $this->postRender(); + } + + return $out; + } - - protected function renderErrors() { + + public function hasErrors () { + return count($this->errors)?true:false; + } + public function renderErrors() { if(!count($this->errors) || !$this->submitted || $this->forceDefault) return; $div = "
"; $div .= "
    "; @@ -69,20 +109,20 @@ protected function renderErrors() { $div .= "
"; $div .= "
"; return $div; - + } - - protected function renderLabel() { + + protected function renderLabel() { return sf("", $this->getName(), $this->label, ($this->getRequired() && !$this->getValidates()) ? $this->goAsterisks() : ""); } - + protected function goAsterisks() { - return "*"; - } - + return "*"; + } + public function validate() { if(is_null($this->validators)) return true; @@ -95,12 +135,12 @@ public function validate() { foreach($this->validators as $validator) { if($validator instanceof validate_Required && $validator->isRequired()) $this->required = true; - + $elementValue =($this->forceDefault) ? null : $this->getValue(); - + if(is_null($elementValue) && !is_null($this->getDefault())) $elementValue = $this->getDefault(); - + try { $validator->validate($elementValue); } catch(Exception $e) { @@ -110,72 +150,94 @@ public function validate() { } } protected function getValidationData() { - - if($this->submitted && !$this->forceDefault) + + if($this->submitted && !$this->forceDefault) $validateArr = array( "validates" => $this->validates, "errors" => $this->errors ); else $validateArr = array( "validates" => $this->validates, "errors" => array()); - + return $validateArr; } function setSubmitted($in) { $this->submitted = $in; } + public function setValidators($in) { $this->validators = $in; } public function setName($in) { if(!is_string($in)) throw new Exception("Name must be of type String"); - $this->name = $in; + $this->name = $in; } + public function setLabel($in) { - $this->label = $in; + $this->label = $in; } + public function setError(Exception $e) { - $this->errors[] = $e->getMessage(); + $this->errors[] = $e->getMessage(); } - public function setStyle($in) { - $this->style = $in; + public function setErrors($errors) { + $this->errors = $errors; } - + public function setValue($input, $files = array()) { - $this->value = isset($input[$this->name]) ? $input[$this->name] : null; + $this->value = isset($input[$this->name]) ? $input[$this->name] : null; } - + public function setDefault($in) { - $this->defaultValue = $in; + $this->defaultValue = $in; + } + + public function setCssClass($in) { + $this->cssClass = $in; + } + + public function setCssStyle($in) { + $this->cssStyle = $in; + } + + public function setTabindex($in) { + $this->tabindex = (int) $in; } public function setForceDefault($in) { if(!is_bool($in)) throw new Exception("Force Default must be of type Bool"); - $this->forceDefault = $in; + $this->forceDefault = $in; } - + public function getName() { - return $this->name; + return $this->name; } public function getValue() { - return(is_null($this->value) || $this->getForceDefault()) ? - $this->defaultValue : - $this->value; + return(is_null($this->value) || $this->getForceDefault()) ? + $this->defaultValue : + $this->value; } public function getValidates() { - return $this->validates; + return $this->validates; } public function getDefault() { - return $this->defaultValue; + return $this->defaultValue; } public function getForceDefault() { - return $this->forceDefault; + return $this->forceDefault; } public function getRequired() { - return $this->required; + return $this->required; } - + + /** + * @deprecated + */ + public function setStyle($in) { + $this->style = $in; + } + } diff --git a/classes/widgets/form/elements/widget_AgeRangeElement.php b/classes/widgets/form/elements/widget_AgeRangeElement.php index 8c1c3cf..46aa769 100644 --- a/classes/widgets/form/elements/widget_AgeRangeElement.php +++ b/classes/widgets/form/elements/widget_AgeRangeElement.php @@ -16,10 +16,10 @@ function renderElement() { $date = $this->getValue(); - - $minValue = is_array($date) && array_key_exists(0,$date)?$date[0]:null; - $maxValue = is_array($date) && array_key_exists(1,$date)?$date[1]:null; - + + $minValue = is_array($date) && array_key_exists('min',$date)?$date['min']:null; + $maxValue = is_array($date) && array_key_exists('max',$date)?$date['max']:null; + $minOptions = sfl(""); @@ -45,12 +45,12 @@ function renderElement() { } - $out = sfl("", - $this->getName(), $this->getName(), $minOptions + $out = sfl("", + $this->getName(), $this->getName(), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $minOptions ); - $out .= sfl("%s ", - $this->separator, $this->getName(), $this->getName(), $maxOptions + $out .= sfl("%s ", + $this->separator, $this->getName(), $this->getName(), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $maxOptions ); return $out; diff --git a/classes/widgets/form/elements/widget_CheckBoxArrayElement.php b/classes/widgets/form/elements/widget_CheckBoxArrayElement.php index 2a9712c..2509d07 100644 --- a/classes/widgets/form/elements/widget_CheckBoxArrayElement.php +++ b/classes/widgets/form/elements/widget_CheckBoxArrayElement.php @@ -22,12 +22,20 @@ public function __construct($args) { } function setValue($input, $files = array()) { - if(empty($input[$this->name])) $input[$this->name] = $this->default; + + if (!isset($input[$this->name])) + $input[$this->name] = array(); + + if(empty($input[$this->name])) + $input[$this->name] = $this->default; + // creates an array of ids holding boolean values - foreach($this->options as $option => $name) { - if(!isset($input[$this->name][$option]) || !$input[$this->name][$option]) $input[$this->name][$option] = false; - else $input[$this->name][$option] = true; - } + if (count($input[$this->name])) + foreach($this->options as $option => $name) { + if(!isset($input[$this->name][$option]) || !$input[$this->name][$option]) $input[$this->name][$option] = false; + else $input[$this->name][$option] = true; + } + $this->value = $input[$this->name]; } @@ -36,19 +44,21 @@ function renderElement() { $valueArr = $this->getValue(); //sort the options into 3 cols - if($this->sort) { + if($this->sort) asort($this->options); - - } + + foreach($this->options as $value => $option) { - $out.=(sf("
%s
", + $out.=(sf("
%s
", $this->getName(),$value, (in_array($value, $valueArr) && $valueArr[$value]) ? "checked='checked'" : "", + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(),$value, $this->getName(),$value, $option, $this->delimiter )); } + return "
".$out."
"; } diff --git a/classes/widgets/form/elements/widget_CheckBoxElement.php b/classes/widgets/form/elements/widget_CheckBoxElement.php index 623a01c..38fe484 100644 --- a/classes/widgets/form/elements/widget_CheckBoxElement.php +++ b/classes/widgets/form/elements/widget_CheckBoxElement.php @@ -2,9 +2,13 @@ class widget_CheckBoxElement extends widget_AbstractElement { + protected $onClick; public function __construct($args) { + if (array_key_exists('onClick', $args) && strlen($args['onClick'])) + $this->onClick = $args['onClick']; + } public function getValue() { @@ -15,10 +19,12 @@ public function setValue($input, $files = null) { $this->value =(isset($input[$this->name]) && $input[$this->name] == 'on') ? true : false; } function renderElement() { - return(sf("", + return(sf("", $this->getName(), $this->getValue() ? "checked='checked'" : "", - $this->getName() + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', + $this->getName(), + !is_null($this->onClick) && strlen($this->onClick)?sf(' onClick="%s"', $this->onClick):'' )); } } diff --git a/classes/widgets/form/elements/widget_DateElement.php b/classes/widgets/form/elements/widget_DateElement.php index 5bb03ff..c75ebbe 100644 --- a/classes/widgets/form/elements/widget_DateElement.php +++ b/classes/widgets/form/elements/widget_DateElement.php @@ -51,20 +51,23 @@ function renderElement() { - $out = sfl("", + $out = sfl("", $this->getName(), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(), $dayOptions ); - $out .= sfl("", + $out .= sfl("", $this->getName(), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(), $monthOptions ); - $out .= sfl("", + $out .= sfl("", $this->getName(), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(), $yearOptions ); diff --git a/classes/widgets/form/elements/widget_FileElement.php b/classes/widgets/form/elements/widget_FileElement.php index c2ca34a..50b7f99 100644 --- a/classes/widgets/form/elements/widget_FileElement.php +++ b/classes/widgets/form/elements/widget_FileElement.php @@ -19,8 +19,8 @@ public function setValue($input = null, $files = null) { if(is_array($uploadedFile) && $uploadedFile['error'] == 0) { $this->fileToken = md5(microtime()); - copy($uploadedFile['tmp_name'], "/tmp/".$this->fileToken); - $uploadedFile['tmp_name'] = "/tmp/".$this->fileToken; + copy($uploadedFile['tmp_name'], ini_get('upload_tmp_dir')."/".$this->fileToken); + $uploadedFile['tmp_name'] = ini_get('upload_tmp_dir')."/".$this->fileToken; $this->originalName = $uploadedFile['name']; $_SESSION[$this->fileToken] = $uploadedFile; $this->value = $uploadedFile; @@ -32,6 +32,12 @@ public function setValue($input = null, $files = null) { } } + public function clear() { + $this->fileToken = null; + $this->originalName = ''; + $this->value = null; + } + protected function outputGeneric() { return array( "name" => $this->getName(), @@ -55,8 +61,9 @@ function renderElement() { $this->getName() )); else - return(sf("", + return(sf("", $this->getName(), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName() )); } diff --git a/classes/widgets/form/elements/widget_HiddenElement.php b/classes/widgets/form/elements/widget_HiddenElement.php index 7fe38f6..4bff36b 100644 --- a/classes/widgets/form/elements/widget_HiddenElement.php +++ b/classes/widgets/form/elements/widget_HiddenElement.php @@ -2,14 +2,25 @@ class widget_HiddenElement extends widget_AbstractElement { + protected $attributes = array(); + public function __construct($args) { + if (array_key_exists('attributes', $args) && is_array($args['attributes'])) + $this->attributes = $args['attributes']; } function renderElement() { - return(sf("", - $this->getName(), - parent::makeInputSafe($this->getValue()), - $this->getName() + + $attributesHtml = ''; + if (count($this->attributes)) + foreach ($this->attributes as $key => $val) + $attributesHtml.= sf(' %s="%s" ', $key, $val); + + return(sf("", + $this->getName(), + parent::makeInputSafe($this->getValue()), + $this->getName(), + $attributesHtml )); } } diff --git a/classes/widgets/form/elements/widget_HtmlElement.php b/classes/widgets/form/elements/widget_HtmlElement.php index c4d2870..6d05bf3 100644 --- a/classes/widgets/form/elements/widget_HtmlElement.php +++ b/classes/widgets/form/elements/widget_HtmlElement.php @@ -12,7 +12,7 @@ public function __construct($args) { function renderElement() { } - public function render() { + public function render($options = array()) { $out = $this->preRender(); $out .= sf("%s", $this->html); $out .= $this->postRender(); diff --git a/classes/widgets/form/elements/widget_ImagePickerElement.php b/classes/widgets/form/elements/widget_ImagePickerElement.php new file mode 100644 index 0000000..04608ca --- /dev/null +++ b/classes/widgets/form/elements/widget_ImagePickerElement.php @@ -0,0 +1,80 @@ +options = $args['options']; + if(array_key_exists('blank', $args)) + $this->blankMessage = $args['blank']; + + if(array_key_exists('width', $args)) + $this->width = $args['width']; + + if(array_key_exists('height', $args)) + $this->height = $args['height']; + } + function renderThumbnail ($value, $imageUrl, $selected) { + + // TODO: Would be nice to add option for multi select + // would convert to checkbox's rather than radio's + + return sfl('
+ + +
', + $this->width, + $this->getName(), + $value, + $imageUrl, + $this->width, + $this->height, + $this->getName(), + $value, + $this->getName(), + $value, + $selected?'checked="checked"':''); + + } + function renderElement() { + $htmlOut = ""; + $elementValue = $this->getValue(); + + foreach($this->options as $value => $imageUrl) { + + $htmlOut .= $this->renderThumbnail( + $value, + $imageUrl, + strval($elementValue) == strval($value) + ); + + } + + return( sfl("
%s
", + $this->getName(), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', + $this->getName(), + $htmlOut + + ) + ); + } +} + +?> \ No newline at end of file diff --git a/classes/widgets/form/elements/widget_NameElement.php b/classes/widgets/form/elements/widget_NameElement.php index 3bc8f23..77facba 100644 --- a/classes/widgets/form/elements/widget_NameElement.php +++ b/classes/widgets/form/elements/widget_NameElement.php @@ -42,16 +42,16 @@ function renderElement() { } - $out = sfl("", - $this->getName(), $this->getName(), $titleOptions + $out = sfl("", + $this->getName(), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(), $titleOptions ); - $out .= sfl("", - $this->getName(), parent::makeInputSafe($name['firstName']), $this->getName() + $out .= sfl("", + $this->getName(), parent::makeInputSafe($name['firstName']), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName() ); - $out .= sfl("", - $this->getName(), parent::makeInputSafe($name['lastName']), $this->getName() + $out .= sfl("", + $this->getName(), parent::makeInputSafe($name['lastName']), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName() ); return $out; diff --git a/classes/widgets/form/elements/widget_RadioElement.php b/classes/widgets/form/elements/widget_RadioElement.php index b89e016..736ad41 100644 --- a/classes/widgets/form/elements/widget_RadioElement.php +++ b/classes/widgets/form/elements/widget_RadioElement.php @@ -2,18 +2,33 @@ class widget_RadioElement extends widget_AbstractElement { private $options = array(); + private $onChange = null; public function __construct($args) { $this->options = $args['options']; + $this->onChange = isset($args['onChange'])?$args['onChange']:null; } function renderElement() { $html = ""; $elementValue = $this->getValue(); foreach($this->options as $value => $option) { - if(strval($elementValue) == strval($value)) - $html .= sfl('
', $this->getName(), $value, $this->getName(),$value, $this->getName(), $value, $option); - else - $html .= sfl('
', $this->getName(), $value, $this->getName(),$value, $this->getName(), $value, $option); + + $html .= sfl( + '
'. + ' '. + ' '. + '
', + $this->getName(), + $value, + $this->onChange?sf(' onchange="%s"', $this->onChange):'', + strval($elementValue) == strval($value) ? sf('checked="checked"') : '', + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', + $this->getName(), + $value, + $this->getName(), + $value, + $option + ); } return sf("
%s
",$html); diff --git a/classes/widgets/form/elements/widget_RecaptchaElement.php b/classes/widgets/form/elements/widget_RecaptchaElement.php new file mode 100644 index 0000000..718ab49 --- /dev/null +++ b/classes/widgets/form/elements/widget_RecaptchaElement.php @@ -0,0 +1,24 @@ +publicKey = $args['apiKey']; + } + function renderElement() { + $output = sf(' + + ', $this->publicKey, $this->publicKey); + return $output; + } +} +?> diff --git a/classes/widgets/form/elements/widget_SelectElement.php b/classes/widgets/form/elements/widget_SelectElement.php index 6dcda78..167ddbf 100644 --- a/classes/widgets/form/elements/widget_SelectElement.php +++ b/classes/widgets/form/elements/widget_SelectElement.php @@ -10,21 +10,21 @@ public function __construct($args) { $this->blankMessage = $args['blank']; } function renderElement() { - $opionHtml = ""; + $optionHtml = ""; $elementValue = $this->getValue(); if(!$this->defaultValue && $this->blankMessage) - $opionHtml .= sfl("", $this->blankMessage); + $optionHtml .= sfl("", $this->blankMessage); elseif($this->defaultValue) { // TODO: put this in a func... foreach($this->defaultValue as $value => $option) { if(strval($elementValue) == strval($value)) - $opionHtml .= sfl("", + $optionHtml .= sfl("", $value, $option ); else - $opionHtml .= sfl("", + $optionHtml .= sfl("", $value, $option ); @@ -33,21 +33,22 @@ function renderElement() { } foreach($this->options as $value => $option) { if(strval($elementValue) == strval($value)) - $opionHtml .= sfl("", + $optionHtml .= sfl("", $value, $option ); else - $opionHtml .= sfl("", + $optionHtml .= sfl("", $value, $option ); } - return( sfl("", + return( sfl("", $this->getName(), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(), - $opionHtml + $optionHtml ) ); diff --git a/classes/widgets/form/elements/widget_TermsAndConditionsElement.php b/classes/widgets/form/elements/widget_TermsAndConditionsElement.php index 90de7d4..8ea41ce 100644 --- a/classes/widgets/form/elements/widget_TermsAndConditionsElement.php +++ b/classes/widgets/form/elements/widget_TermsAndConditionsElement.php @@ -29,11 +29,12 @@ public function render() { } function renderElement() { - return(sf("
%s
%s
%s
", + return(sf("
%s
%s
%s
", $this->termsText, $this->renderErrors(), $this->getName(), $this->getValue() ? "checked='checked'" : "", + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(), $this->renderLabel() )); diff --git a/classes/widgets/form/elements/widget_TextAreaElement.php b/classes/widgets/form/elements/widget_TextAreaElement.php index a1e394c..43c6685 100644 --- a/classes/widgets/form/elements/widget_TextAreaElement.php +++ b/classes/widgets/form/elements/widget_TextAreaElement.php @@ -4,6 +4,7 @@ class widget_TextAreaElement extends widget_AbstractElement { private $disabled = false; private $rows = 7; + private $placeholder = ''; public function __construct($args) { if(array_key_exists("disabled", $args)) @@ -11,6 +12,9 @@ public function __construct($args) { if(array_key_exists('rows', $args)) $this->rows = $args['rows']; + + if(array_key_exists('placeholder', $args)) + $this->placeholder = $args['placeholder']; } public function outputSpecific() { @@ -18,8 +22,10 @@ public function outputSpecific() { } function renderElement() { - return( sf("", + return( sf("", $this->getName(), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', + strlen($this->placeholder)?sf(' placeholder="%s"', $this->placeholder):'', $this->getName(), $this->disabled?" disabled='true'":"", !is_null($this->rows)?sf(' rows="%s"',$this->rows):'', diff --git a/classes/widgets/form/elements/widget_TextConfirmElement.php b/classes/widgets/form/elements/widget_TextConfirmElement.php index 17fa42a..2da88e5 100644 --- a/classes/widgets/form/elements/widget_TextConfirmElement.php +++ b/classes/widgets/form/elements/widget_TextConfirmElement.php @@ -5,10 +5,15 @@ class widget_TextConfirmElement extends widget_AbstractElement { protected $htmlType = 'text'; protected $confirmText = "Please confirm"; protected $cssClassName = 'inputTextConfirm'; + protected $placeholder = ''; public function __construct($args) { if(array_key_exists("confirmText", $args)) $this->confirmText = $args['confirmText']; + + if (array_key_exists('placeholder', $args) && strlen($args['placeholder'])) + $this->placeholder = $args['placeholder']; + } function renderElement() { @@ -18,18 +23,19 @@ function renderElement() { if(!array_key_exists(0,$value)) $value[0] = ""; if(!array_key_exists(1,$value)) $value[1] = ""; - $out = sf("
", - $this->htmlType, $this->getName(), parent::makeInputSafe($value[0]), $this->getName(), - $this->cssClassName, $this->cssClassName + $out = sf("
", + $this->htmlType, $this->getName(), parent::makeInputSafe($value[0]), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', + $this->getName(), $this->cssClassName, $this->cssClassName, + strlen($this->placeholder)?sf(' placeholder="%s"', $this->placeholder):'' ); - $out .= sf("", + $out .= sf("", $this->getName(), $this->confirmText, ($this->getRequired() && !$this->getValidates()) ? $this->goAsterisks() : ""); - $out .= sf("", - $this->htmlType, $this->getName(), parent::makeInputSafe($value[1]), $this->getName(), - $this->cssClassName, $this->cssClassName + $out .= sf("", + $this->htmlType, $this->getName(), parent::makeInputSafe($value[1]), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', + $this->getName(), $this->cssClassName, $this->cssClassName ); return $out; diff --git a/classes/widgets/form/elements/widget_TextElement.php b/classes/widgets/form/elements/widget_TextElement.php index 81c6cae..209cae9 100644 --- a/classes/widgets/form/elements/widget_TextElement.php +++ b/classes/widgets/form/elements/widget_TextElement.php @@ -5,23 +5,69 @@ class widget_TextElement extends widget_AbstractElement { protected $htmlType = 'text'; protected $cssClassName = 'inputText'; protected $placeholder = ''; + protected $onChange = null; + protected $onKeydown = null; + protected $onFocus = null; + protected $onBlur = null; + protected $disabled = false; + protected $attributes = array(); protected $rows; public function __construct($args) { if (array_key_exists('placeholder', $args) && strlen($args['placeholder'])) $this->placeholder = $args['placeholder']; + + if (array_key_exists('type', $args) && strlen($args['type'])) + $this->htmlType = $args['type']; + + if (array_key_exists('onChange', $args) && strlen($args['onChange'])) + $this->onChange = $args['onChange']; + + if (array_key_exists('onKeydown', $args) && strlen($args['onKeydown'])) + $this->onKeydown = $args['onKeydown']; + + if (array_key_exists('onFocus', $args) && strlen($args['onFocus'])) + $this->onFocus = $args['onFocus']; + + if (array_key_exists('onBlur', $args) && strlen($args['onBlur'])) + $this->onBlur = $args['onBlur']; + + if (array_key_exists('disabled', $args) && is_bool($args['disabled'])) + $this->disabled = $args['disabled']; + + + if (array_key_exists('attributes', $args) && is_array($args['attributes'])) + $this->attributes = $args['attributes']; + + + } function renderElement() { - return(sf('', - $this->htmlType, - $this->getName(), - parent::makeInputSafe($this->getValue()), - $this->getName(), - $this->cssClassName, - strlen($this->placeholder)?sf(' placeholder="%s"', $this->placeholder):'' - )); + + $attributesHtml = ''; + if (count($this->attributes)) + foreach ($this->attributes as $key => $val) + $attributesHtml.= sf(' %s="%s" ', $key, $val); + + + return(sf('', + $this->htmlType, + $this->getName(), + parent::makeInputSafe($this->getValue()), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', + $this->getName(), + $this->cssClassName, + $attributesHtml, + strlen($this->placeholder)?sf(' placeholder="%s"', $this->placeholder):'', + !is_null($this->onChange) && strlen($this->onChange)?sf(' onChange="%s"', $this->onChange):'', + !is_null($this->onKeydown) && strlen($this->onKeydown)?sf(' onKeydown="%s"', $this->onKeydown):'', + !is_null($this->onFocus) && strlen($this->onFocus)?sf(' onFocus="%s"', $this->onFocus):'', + !is_null($this->onBlur) && strlen($this->onBlur)?sf(' onBlur="%s"', $this->onBlur):'', + $this->disabled?' disabled':'' + )); } + } ?> \ No newline at end of file diff --git a/classes/widgets/form/elements/widget_TextElementWithSurroundingText.php b/classes/widgets/form/elements/widget_TextElementWithSurroundingText.php index 9a5f2d7..3df56f8 100644 --- a/classes/widgets/form/elements/widget_TextElementWithSurroundingText.php +++ b/classes/widgets/form/elements/widget_TextElementWithSurroundingText.php @@ -17,10 +17,11 @@ function renderElement() { if( $this->beforeText != '' ) $ret .= sf('%s ', $this->beforeText); - $ret .= sf("", + $ret .= sf("", $this->htmlType, $this->getName(), parent::makeInputSafe($this->getValue()), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName(), $this->inputStyle ); diff --git a/classes/widgets/form/elements/widget_TextWithSuggestionsElement.php b/classes/widgets/form/elements/widget_TextWithSuggestionsElement.php index bc4974b..5e78fa6 100644 --- a/classes/widgets/form/elements/widget_TextWithSuggestionsElement.php +++ b/classes/widgets/form/elements/widget_TextWithSuggestionsElement.php @@ -24,15 +24,17 @@ function renderElement() { foreach($this->options as $value => $option) { if(strval($elementValue) == strval($value)) { $usedSuggested = true; - $html .= sfl('
%s
', + $html .= sfl('
%s
', $this->getName(), parent::makeInputSafe($value), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $option ); } else - $html .= sfl('
%s
', + $html .= sfl('
%s
', $this->getName(), parent::makeInputSafe($value), + ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $option ); } @@ -40,7 +42,7 @@ function renderElement() { return sf("
%s
%s %s%s
", $html, - sfl('', $this->getName(), $usedSuggested ? "" :'checked="checked"'), + sfl('', $this->getName(), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $usedSuggested ? "" :'checked="checked"'), $this->beforeText, $this->getName()."_custom", $usedSuggested ? "" : parent::makeInputSafe($this->getValue()), diff --git a/classes/widgets/form/elements/widget_UkAddressElement.php b/classes/widgets/form/elements/widget_UkAddressElement.php index ec53b33..686408d 100644 --- a/classes/widgets/form/elements/widget_UkAddressElement.php +++ b/classes/widgets/form/elements/widget_UkAddressElement.php @@ -23,24 +23,24 @@ function renderElement() { * The rendering of this element is SO messy due to IE compatibility - needs rewriting * */ - $out = sf("

", - $this->getName(), parent::makeInputSafe($address['address1']), $this->getName() + $out = sf("

", + $this->getName(), parent::makeInputSafe($address['address1']), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName() ); - $out .= sf("
", - $this->getName(), parent::makeInputSafe($address['address2']), $this->getName() + $out .= sf("
", + $this->getName(), parent::makeInputSafe($address['address2']), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName() ); $out .= sf("
", $this->getName(), "Post Code" ); - $out .= sfl("
", - $this->getName(), parent::makeInputSafe($address['postcode']), $this->getName() + $out .= sfl("
", + $this->getName(), parent::makeInputSafe($address['postcode']), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName() ); - $out .= sfl("
", - $this->getName(), parent::makeInputSafe($address['town']), $this->getName() + $out .= sfl("
", + $this->getName(), parent::makeInputSafe($address['town']), ($this->tabindex) ? sf('tabindex="%s"', $this->tabindex) : '', $this->getName() ); return $out; diff --git a/classes/widgets/form/widget_Form.php b/classes/widgets/form/widget_Form.php index 9e0a896..d115bcf 100644 --- a/classes/widgets/form/widget_Form.php +++ b/classes/widgets/form/widget_Form.php @@ -17,6 +17,8 @@ class widget_Form { private $submitted = false; private $ancorJump = false; // will the form jump to the ancor? private $actionPath = ''; + private $cssClasses = array(); + private $onSubmit = null; public function __construct($formName = null, $autoLoadFormData = true) { if(!is_null($formName)) $this->setName($formName); @@ -26,9 +28,12 @@ public function __construct($formName = null, $autoLoadFormData = true) { public function __get($name) { $matches = null; if(preg_match('/^value_(.+)$/', $name, $matches)) { - return call_user_func(array($this, 'value'), $matches [1]); - } - throw new Exception('Undefined method:' . $name); + return call_user_func(array($this, 'value'), $matches [1]); + } elseif(preg_match('/^valueOrNull_(.+)$/', $name, $matches)) { + return call_user_func(array($this, 'valueOrNull'), $matches [1]); + } elseif (preg_match('/^element_(.+)$/', $name, $matches)) { + return call_user_func(array($this, 'element'), $matches [1]); + } else throw new Exception('Undefined method:' . $name); } public function setName($in) { @@ -49,6 +54,10 @@ public function getTitle() { return $this->title; } + public function addCssClass ($className) { + $this->cssClasses[] = $className; + } + public function setFormDataFromMethod() { switch($this->method) { @@ -96,7 +105,19 @@ public function add($elementClass, $args) { // css style if(array_key_exists('style',$args)) $element->setStyle($args['style']); - + + // css style + if(array_key_exists('cssStyle',$args)) + $element->setCssStyle($args['cssStyle']); + + // tabindexes + if(array_key_exists('tabindex',$args)) + $element->setTabindex($args['tabindex']); + + // css style + if(array_key_exists('cssClass',$args)) + $element->setCssClass($args['cssClass']); + if(array_key_exists('force_default', $args)) $element->setForceDefault($args['force_default']); @@ -106,6 +127,7 @@ public function add($elementClass, $args) { try { $element->setValue($this->userInput, $this->userFiles); } catch(Exception $e) { + Atsumi::error__listen($e); $element->setError($e); } $element->validate(); @@ -115,13 +137,46 @@ public function add($elementClass, $args) { $this->elementMap[$element->getName()] = &$element; } + public function remove($elementName) { + if(!isset($this->elementMap[$elementName])) { + throw new Exception('Cannot remove non-existant element'); + } + for($i = 0; $i < count($this->elements); $i++) { + $done = false; + if($this->elementMap[$elementName] == $this->elements[$i]) { + unset($this->elementMap[$elementName]); + unset($this->elements[$i]); + $done = true; + } + if($done) { + $this->elements = array_values($this->elements); + break; + } + } + } + public function value($elementName) { if(!array_key_exists($elementName, $this->elementMap)) throw new Exception("Element not found: ".$elementName); - + return $this->elementMap[$elementName]->getValue(); } + public function valueOrNull($elementName) { + $value = $this->value($elementName); + + return trim($value)==''?null:$value; + } + + /* Returns an element */ + public function element ($elementName) { + + if(!array_key_exists($elementName, $this->elementMap)) + throw new Exception("Element not found: ".$elementName); + + return $this->elementMap[$elementName]; + } + public function getSubmitted() { return $this->submitted; } @@ -138,6 +193,10 @@ public function setAncorJump($jump) { $this->ancorJump = $jump; } + public function setOnSubmit($in) { + $this->onSubmit = $in; + } + public function setSubmit($in) { if(!is_string($in)) throw new Exception("Submit text should be of type String"); @@ -169,7 +228,7 @@ public function __toString() { return strval($this->render()); } - public function render() { + public function render($options = array()) { // totally forgotten what this force defaults is, do we need it?... // forceDefaults is if you want to force the form to show its default values on reload @@ -180,30 +239,38 @@ public function render() { $row->validate(); } - $html = $this->getFormTop(); + $html = $this->getFormTop(isset($options['top'])?$options['top']:array()); foreach($this->elements as $element) { $html .= sfl("%s", $element->render()); } - $html .= $this->getFormBottom(); + $html .= $this->getFormBottom(isset($options['bottom'])?$options['bottom']:array()); return $html; } - public function getElement($elementName) { - return $this->elementMap[$elementName]->render(); + public function hasElement($elementName) { + return array_key_exists($elementName, $this->elementMap); } - public function getFormTop() { + public function getElement($elementName, $options = array()) { + return $this->elementMap[$elementName]->render($options); + } - $html = sf('
', - $this->name, - $this->name, - $this->name, - $this->method, - $this->ancorJump ? sf('%s#form_%s', $this->actionPath, $this->name) : $this->actionPath, - $this->encoding - ); + public function getFormTop($options = array()) { + + $html = sf('', + $this->name, + $this->name, + $this->name, + $this->method, + $this->ancorJump ? sf('%s#form_%s', $this->actionPath, $this->name) : $this->actionPath, + $this->encoding, + count($this->cssClasses)?' '.implode(' ', $this->cssClasses):'', + isset($options['onSubmit'])? + sf(' onsubmit="%s"',$options['onSubmit']): + (isset($this->onSubmit)?sf(' onsubmit="%s"',$this->onSubmit):'') + ); // add a hidden field to verify if this form has been posted $html .= sfl("", $this->name); @@ -214,12 +281,24 @@ public function getFormTop() { return $html; } - public function getFormBottom() { + public function getFormBottom($options = array()) { // add the submit to the bottom of the form for now(will convert to element in next version) - $html = sfl('
'); - $html .= sfl(' ', $this->name, $this->getSubmit()); - $html .= sfl('
'); + $html = ''; + if (!isset($options['buttonOnly']) || $options['buttonOnly'] == false) + $html = sfl('
', array_key_exists('rowClasses',$options)?' '.$options['rowClasses']:''); + + $html .= sfl(' %s%s', + array_key_exists('preButtonHtml', $options)?$options['preButtonHtml']:'', + array_key_exists('buttonClasses',$options)?' '.$options['buttonClasses']:'button button-submit', + $this->name, + array_key_exists('innerButtonHtml', $options)?$options['innerButtonHtml']:$this->getSubmit(), + array_key_exists('postButtonHtml', $options)?$options['postButtonHtml']:'' + ); + + if (!isset($options['buttonOnly']) || $options['buttonOnly'] == false) + $html .= sfl('
'); + $html .= sfl('
'); return $html; diff --git a/classes/widgets/pagination/widget_Paginate.php b/classes/widgets/pagination/widget_Paginate.php index b4a5ce8..e523a23 100644 --- a/classes/widgets/pagination/widget_Paginate.php +++ b/classes/widgets/pagination/widget_Paginate.php @@ -26,13 +26,14 @@ class widget_Paginate { /* format presets/templates */ const TEMPLATE_CLASSIC = 1; const TEMPLATE_ARROWS = 2; + const TEMPLATE_SIMPLE = 3; /* member variables used for pagination calculations */ private $recordCount; private $pageCount; private $resultsPerPage = 10; private $currentPage = 0; - private $navLength = 7; + private $navLength = 4; private $url; /* formatting options */ @@ -67,7 +68,7 @@ public function __toString() { return $this->render(); } - private function generateUrl($page) { + public function generateUrl($page) { return str_replace("[PAGE]", $page, $this->url); } @@ -75,6 +76,14 @@ private function generateUrl($page) { public function getPageCount() { return $this->pageCount; } + public function getPage() { + return $this->currentPage; + } + + /* returns total page count */ + public function getTotalResults() { + return $this->recordCount; + } /* returns current page */ public function getCurrentPage() { @@ -96,24 +105,35 @@ public function getLimit() { return $this->resultsPerPage; } + + + public function getResultsPerPage() { + return $this->resultsPerPage; + } + - /* set the format template (default is classic) */ - public function setFormatTemplate ($template = 1) { + /* set the format template (default is simple) */ + public function setFormatTemplate ($template = 3) { switch ($template) { default: throw new Exception ('Unknown pagination template'); case self::TEMPLATE_CLASSIC: - $this->format = ''; + $this->format = ''; $this->formatNext = array('', ''); $this->formatPrevious = array('', ''); break; - case self::TEMPLATE_ARROWS: - $this->format = ''; - $this->formatNext = array('»', '»'); - $this->formatPrevious = array('«', '«'); + case self::TEMPLATE_ARROWS: + $this->format = ''; + $this->formatNext = array('»', '»'); + $this->formatPrevious = array('«', '«'); + break; + + case self::TEMPLATE_SIMPLE: + $this->format = ''; + $this->formatNext = array('Next', ''); break; } @@ -187,11 +207,11 @@ private function setPageCountByResultsPerPage($in) { /* returns a html page link for inclusion in the pagination output */ public function renderPageLink ($page) { - return sf("%s", + return sf("%s", ($this->currentPage == $page) ? " currentPage" : '', - ($this->currentPage == $page) ? - number_format($page) : - sprintf("%s", $this->generateUrl($page),number_format($page)) + $this->generateUrl($page), + ($this->currentPage == $page) ? " on":'', + number_format($page) ); } @@ -207,9 +227,15 @@ public function render () { $start = $this->renderPageLink(1); $end = $this->getPageCount() < 2?'':$this->renderPageLink($this->getPageCount()); + // params: Pages links $pageLength = $this->navLength; - if ($pageLength&1) { } else $pageLength++; + if ($pageLength&1 || + (array_key_exists('[START]', $options) && !array_key_exists('[END]', $options)) || + (!array_key_exists('[START]', $options) && array_key_exists('[END]', $options)) + ) { } + + else $pageLength++; if (array_key_exists('[START]', $options)) $pageLength--; if (array_key_exists('[END]', $options)) $pageLength--; @@ -230,23 +256,24 @@ public function render () { if ($pageLinkStart < 1) $pageLinkStart = 1; if (array_key_exists('[START]', $options) && $pageLinkStart == 1) $pageLinkStart = 2; + $pages = ''; if ($pageLinkStart <= $pageLinkEnd) for ($i = $pageLinkStart; $i <= $pageLinkEnd; $i++) $pages .= $this->renderPageLink($i); // params: start ellipses - if (array_key_exists('[START_ELLIPSES]', $options) && array_key_exists('[START]', $options) && + if (array_key_exists('[START_ELLIPSES]', $options) && $pageLinkStart > 2) $startEllipses = '...'; else $startEllipses = ''; // params: end ellipses - if (array_key_exists('[END_ELLIPSES]', $options) && array_key_exists('[END]', $options) && + if (array_key_exists('[END_ELLIPSES]', $options) && $pageLinkEnd < ($this->pageCount-1)) $endEllipses = '...'; else $endEllipses = ''; // params: next link - if ($this->currentPage == $this->pageCount) $next = $this->formatNext[1]; + if ($this->currentPage >= $this->pageCount) $next = $this->formatNext[1]; else $next = str_replace('[HREF]', $this->generateUrl($this->currentPage+1), $this->formatNext[0]); // params: previous link diff --git a/include/http.php b/include/http.php index 8b5fb81..68d8c27 100644 --- a/include/http.php +++ b/include/http.php @@ -1,4 +1,4 @@ -