├── .editorconfig ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── box.json ├── classes ├── CLI.php ├── CSV.php ├── Cache.php ├── Cache │ ├── Adapter.php │ ├── Files.php │ └── Memory.php ├── Check.php ├── Deferred.php ├── Dictionary.php ├── Email.php ├── Email │ ├── Driver.php │ ├── Envelope.php │ ├── Native.php │ ├── Proxy.php │ ├── Ses.php │ └── Smtp.php ├── Enum.php ├── Error.php ├── Errors.php ├── Event.php ├── Events.php ├── File.php ├── FileSystem │ ├── Adapter.php │ ├── Memory.php │ ├── Native.php │ └── ZIP.php ├── Filter.php ├── Filters.php ├── HTTP.php ├── Hash.php ├── Job.php ├── Loader.php ├── Map.php ├── Message.php ├── Model.php ├── Module.php ├── Negotiation.php ├── Options.php ├── Password.php ├── Persistence.php ├── Redirect.php ├── Relation.php ├── Request.php ├── Response.php ├── Route.php ├── SQL.php ├── Service.php ├── Session.php ├── Shell.php ├── Structure.php ├── Text.php ├── Token.php ├── URL.php ├── View.php ├── View │ ├── Adapter.php │ └── PHP.php ├── Work.php └── ZIP.php ├── composer.json ├── dist └── .gitkeep └── stub.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ###################### 2 | # Core 3 | # Caffeina - (c) 2016 4 | # http://caffeina.com 5 | ###################### 6 | root = true 7 | 8 | [*] 9 | indent_style = space 10 | indent_size = 2 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = false 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Caffeina s.r.l. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ---- 4 | 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/caffeina-core/core/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/caffeina-core/core/?branch=master) 6 | [![Build Status](https://travis-ci.org/caffeina-core/core.svg)](https://travis-ci.org/caffeina-core/core) 7 | [![Total Downloads](https://poser.pugx.org/caffeina-core/core/downloads.svg)](https://packagist.org/packages/caffeina-core/core) 8 | [![Latest Stable Version](https://poser.pugx.org/caffeina-core/core/v/stable.svg)](https://packagist.org/packages/caffeina-core/core) 9 | [![Latest Unstable Version](https://poser.pugx.org/caffeina-core/core/v/unstable.svg)](https://packagist.org/packages/caffeina-core/core) 10 | [![License](https://poser.pugx.org/caffeina-core/core/license.svg)](https://packagist.org/packages/caffeina-core/core) 11 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/caffeina-core/core?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 12 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcaffeina-core%2Fcore.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fcaffeina-core%2Fcore?ref=badge_shield) 13 | 14 | 15 | > Core is a platform, a collection of components for rapid application development. It doesn't make decisions for you; it gives you tools to build your own solutions. 16 | 17 | 18 | ## Installation 19 | 20 | Install via [composer](https://getcomposer.org/download/): 21 | 22 | ```bash 23 | $ composer require caffeina-core/core 24 | ``` 25 | 26 | ## Documentation 27 | 28 | See the [wiki](https://github.com/caffeina-core/core/wiki). 29 | 30 | 31 | ## Contributing 32 | 33 | How to get involved: 34 | 35 | 1. [Star](https://github.com/caffeina-core/core/stargazers) the project! 36 | 2. Answer questions that come through [GitHub issues](https://github.com/caffeina-core/core/issues?state=open) 37 | 3. [Report a bug](https://github.com/caffeina-core/core/issues/new) that you find 38 | 39 | Core follows the [GitFlow branching model](http://nvie.com/posts/a-successful-git-branching-model). The ```master``` branch always reflects a production-ready state while the latest development is taking place in the ```develop``` branch. 40 | 41 | Each time you want to work on a fix or a new feature, create a new branch based on the ```develop``` branch: ```git checkout -b BRANCH_NAME develop```. Only pull requests to the ```develop``` branch will be merged. 42 | 43 | Pull requests are **highly appreciated**. 44 | 45 | Solve a problem. Features are great, but even better is cleaning-up and fixing issues in the code that you discover. 46 | 47 | ## Versioning 48 | 49 | Core is maintained by using the [Semantic Versioning Specification (SemVer)](http://semver.org). 50 | 51 | 52 | ## Copyright and license 53 | 54 | Copyright 2014-2016 [Caffeina](http://caffeina.com) srl under the [MIT license](LICENSE.md). 55 | 56 | 57 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcaffeina-core%2Fcore.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fcaffeina-core%2Fcore?ref=badge_large) -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "directories": "classes", 3 | "main": "classes/Loader.php", 4 | "output": "dist/core.phar", 5 | "git-version": "package_version", 6 | "stub": "stub.php", 7 | "compactors": [ 8 | "Herrera\\Box\\Compactor\\Composer", 9 | "Herrera\\Box\\Compactor\\Php" 10 | ] 11 | } -------------------------------------------------------------------------------- /classes/CLI.php: -------------------------------------------------------------------------------- 1 | "\033[0;30m", 'DARKGRAY' =>"\033[1;30m", 25 | 'BLUE' =>"\033[0;34m", 'LIGHTBLUE' =>"\033[1;34m", 26 | 'GREEN' =>"\033[0;32m", 'LIGHTGREEN' =>"\033[1;32m", 27 | 'CYAN' =>"\033[0;36m", 'LIGHTCYAN' =>"\033[1;36m", 28 | 'RED' =>"\033[0;31m", 'LIGHTRED' =>"\033[1;31m", 29 | 'PURPLE' =>"\033[0;35m", 'LIGHTPURPLE' =>"\033[1;35m", 30 | 'BROWN' =>"\033[0;33m", 'YELLOW' =>"\033[1;33m", 31 | 'LIGHTGRAY' =>"\033[0;37m", 'WHITE' =>"\033[1;37m", 32 | 'NORMAL' =>"\033[0;37m", 'B' =>"\033[1m", 33 | 'ERROR' =>"\033[1;31m", 'INFO' =>"\033[0;36m", 34 | 'I' =>"\033[0;30;104m", 'IB' =>"\033[1;30;104m", 35 | 'U' =>"\033[4m", 'D' =>"\033[2m", 36 | ]; 37 | protected static $color_stack = ['NORMAL']; 38 | 39 | 40 | /** 41 | * Bind a callback to a command route 42 | * @param string $command The command route, use ":" before a parameter for extraction. 43 | * @param callable $callback The callback to be binded to the route. 44 | */ 45 | public static function on($command,callable $callback,$description=''){ 46 | $parts = preg_split('/\s+/',$command); 47 | static::$commands[array_shift($parts)] = [$parts,$callback,$description]; 48 | } 49 | 50 | /** 51 | * Bind a callback to the "help" route. 52 | * @param callable $callback The callback to be binded to the route. If omitted triggers the callback. 53 | */ 54 | public static function help(callable $callback = null){ 55 | $callback 56 | ? is_callable($callback) && static::$help = $callback 57 | : static::$help && call_user_func(static::$help); 58 | } 59 | 60 | /** 61 | * Bind a callback when an error occurs. 62 | * @param callable $callback The callback to be binded to the route. If omitted triggers the callback. 63 | */ 64 | public static function error(callable $callback = null){ 65 | $callback 66 | ? is_callable($callback) && static::$error = $callback 67 | : static::$error && call_user_func(static::$error); 68 | } 69 | 70 | /** 71 | * Returns the script name. 72 | * @return string 73 | */ 74 | public static function name(){ 75 | return static::$file; 76 | } 77 | 78 | /** 79 | * Triggers an error and exit 80 | * @param string $message 81 | */ 82 | protected static function triggerError($message){ 83 | is_callable(static::$error) && call_user_func(static::$error,$message); 84 | exit -1; 85 | } 86 | 87 | /** 88 | * Get a passed option 89 | * @param string $key The name of the option paramenter 90 | * @param mixed $default The default value if parameter is omitted. (if a callable it will be evaluated) 91 | * @return mixed 92 | */ 93 | public static function input($key=null,$default=null){ 94 | return $key ? (isset(static::$options[$key]) ? static::$options[$key] : (is_callable($default)?call_user_func($default):$default)) : static::$options; 95 | } 96 | 97 | /** 98 | * Returns an explanation for the supported commands 99 | * 100 | * @method commands 101 | * 102 | * @return array The commands and their description. 103 | */ 104 | public static function commands(){ 105 | $results = []; 106 | foreach(static::$commands as $name => $cmd){ 107 | $results[] = [ 108 | 'name' => $name, 109 | 'params' => preg_replace('/:(\w+)/','[$1]',implode(' ',$cmd[0])), 110 | 'description' => $cmd[2], 111 | ]; 112 | } 113 | return $results; 114 | } 115 | 116 | /** 117 | * Dispatch the router 118 | * @param string[] $args The arguments array. 119 | * @return boolean True if route was correctly dispatched. 120 | */ 121 | public static function run($args=null){ 122 | if (PHP_SAPI != 'cli') return false; 123 | if($args) { 124 | $_args = $args; 125 | static::$file = basename(isset($_SERVER['PHP_SELF'])?$_SERVER['PHP_SELF']:__FILE__); 126 | } else { 127 | $_args = $_SERVER['argv']; 128 | static::$file = basename(array_shift($_args)); 129 | } 130 | foreach($_args as $e) if(strpos($e,'-')===0) { 131 | $h = explode('=',$e); 132 | static::$options[ltrim(current($h),'-')] = isset($h[1])?$h[1]:true; 133 | } else { 134 | static::$arguments[] = $e; 135 | } 136 | 137 | if(isset(static::$arguments[0])){ 138 | $command = array_shift(static::$arguments); 139 | if (empty(static::$commands[$command])) 140 | return static::triggerError("Unknown command [".$command."]."); 141 | $cmd = static::$commands[$command]; 142 | $pars_vector = []; 143 | foreach ($cmd[0] as $_idx => $segment) { 144 | if ($segment[0]==':'){ 145 | // Extract paramenter 146 | if (isset(static::$arguments[$_idx])){ 147 | $pars_vector[] = static::$arguments[$_idx]; 148 | } else return static::triggerError("Command [".$command."] needs more parameters"); 149 | } else { 150 | // Match command 151 | if (empty(static::$arguments[$_idx]) || $segment!=static::$arguments[$_idx]) 152 | return static::triggerError("Command [".$command."] is incomplete."); 153 | } 154 | } 155 | $returns = call_user_func_array($cmd[1], $pars_vector); 156 | echo is_scalar($returns) ? "$returns" : json_encode($returns, JSON_PRETTY_PRINT); 157 | return true; 158 | } else { 159 | static::help(); 160 | return false; 161 | } 162 | } 163 | 164 | /** 165 | * Prints a message to the console with color formatting. 166 | * @param string $message The html-like encoded message 167 | * @return void 168 | */ 169 | public static function write($message){ 170 | if( preg_match('~<[^>]+>~',$message)) { 171 | // Use preg_replace_callback for fast regex matches navigation 172 | echo strtr(preg_replace_callback('~^(.*)<([^>]+)>(.+)(.*)$~USm',function($m){ 173 | static::write($m[1]); 174 | $color = strtoupper(trim($m[2])); 175 | if( isset(static::$shell_colors[$color]) ) echo static::$shell_colors[$color]; 176 | static::$color_stack[] = $color; 177 | static::write($m[3]); 178 | array_pop(static::$color_stack); 179 | $back_color = array_pop(static::$color_stack) ?: static::$color_stack[]='NORMAL'; 180 | if( isset(static::$shell_colors[$back_color]) ) echo static::$shell_colors[$back_color]; 181 | static::write($m[4]); 182 | },strtr($message,["\n"=>"&BR;"])),["&BR;"=>PHP_EOL]); 183 | } else { 184 | echo strtr($message,["&BR;"=>PHP_EOL]); 185 | } 186 | } 187 | 188 | /** 189 | * Like CLI::write, but appends a newline at the end. 190 | * @param string $message The html-like encoded message 191 | * @return void 192 | */ 193 | public static function writeln($message){ 194 | static::write($message . PHP_EOL); 195 | } 196 | 197 | /** 198 | * Set output ANSI color 199 | * @param string $color The color name constant. 200 | * @return void 201 | */ 202 | public static function color($color){ 203 | if ( isset(static::$shell_colors[$color]) ) echo static::$shell_colors[$color]; 204 | } 205 | 206 | /** 207 | * Edit a temporary block of text with $EDITOR (or nano as fallback) 208 | * @param string $text The initial text of the document. 209 | * @param string $filename The (fake) filename passed to the editor (for syntax highlighting hint). 210 | * @return string The edited contents 211 | */ 212 | public static function edit($text,$filename=''){ 213 | $EDITOR = getenv('EDITOR')?:'nano'; 214 | $tmp = tempnam(sys_get_temp_dir(), "E-").strtr($filename,'/','_'); 215 | file_put_contents($tmp, $text); 216 | passthru("$EDITOR $tmp"); 217 | $result = file_get_contents($tmp); 218 | unlink($tmp); 219 | return $result; 220 | } 221 | 222 | 223 | } 224 | 225 | 226 | // Standard Help Message 227 | CLI::help(function(){ 228 | echo 'Usage: ', CLI::name(),' [commands]', PHP_EOL, 229 | 'Commands:',PHP_EOL; 230 | foreach( CLI::commands() as $cmd ){ 231 | echo "\t", $cmd['name'], ' ' ,$cmd['params'], PHP_EOL; 232 | if($cmd['description']) 233 | echo "\t\t- ", str_replace("\n","\n\t\t ",$cmd['description']), PHP_EOL, PHP_EOL; 234 | } 235 | }); 236 | 237 | // Standard Error Message 238 | CLI::error(function($message){ 239 | echo 'Error: ',$message,PHP_EOL; 240 | }); 241 | -------------------------------------------------------------------------------- /classes/CSV.php: -------------------------------------------------------------------------------- 1 | write($row); 42 | }); 43 | return $this; 44 | } 45 | 46 | public static function fromSQL($sql, $format=self::AUTO){ 47 | return static::create(tempnam(sys_get_temp_dir(), 'CSVx'), $format)->SQL($sql); 48 | } 49 | 50 | public static function fromTable($table, $format=self::AUTO){ 51 | $csv = static::create(tempnam(sys_get_temp_dir(), 'CSVx'), $format); 52 | foreach($table as $row){ 53 | $csv->write($row); 54 | } 55 | return $csv; 56 | } 57 | 58 | public function __construct($file, $mode=self::READ, $format=self::AUTO){ 59 | $this->mode = $mode; 60 | $this->file = new \SplFileObject($file,'c+'); 61 | if (!$this->file->valid()) throw new Exception("Error opening CSV file [$file]", 1); 62 | $this->file->setFlags( 63 | \SplFileObject::READ_CSV | // set file reading mode to csv 64 | \SplFileObject::SKIP_EMPTY | // ignore empty lines 65 | \SplFileObject::DROP_NEW_LINE // drop new line from last column in record 66 | ); 67 | $this->format = ($format==self::AUTO ? $this->guessSeparator() : $format); 68 | $this->file->setCsvControl($this->format,'"',"\\"); 69 | } 70 | 71 | private function guessSeparator($checkLines = 2){ 72 | if ($this->mode == self::WRITE) return self::STANDARD; 73 | $delimiters = [",","\t",";"]; 74 | $results = []; 75 | $this->file->rewind(); 76 | while ($checkLines--) { 77 | $line = $this->file->fgets(); 78 | foreach ($delimiters as $delimiter){ 79 | $fields = preg_split('/['.$delimiter.']/', $line); 80 | if(count($fields) > 1){ 81 | if(empty($results[$delimiter])){ 82 | $results[$delimiter] = 1; 83 | } else { 84 | $results[$delimiter]++; 85 | } 86 | } 87 | } 88 | } 89 | $this->file->rewind(); 90 | $results = array_keys($results, max($results)); 91 | return $results[0]; 92 | } 93 | 94 | public function write($row){ 95 | if ($this->mode != self::WRITE) return; 96 | $row = (array)$row; 97 | if (false === $this->savedheaders) { 98 | $this->schema(array_keys($row)); 99 | } 100 | $row_t = $this->template; 101 | foreach ($this->headers as $key) { 102 | if (isset($row[$key])) $row_t[$key] = $row[$key]; 103 | } 104 | $this->file->fputcsv($row_t); 105 | } 106 | 107 | public function read(){ 108 | if ($this->mode != self::READ) return; 109 | foreach($this->file as $row){ 110 | if ($row){ 111 | if(!$this->headers) { 112 | $this->headers = $row; 113 | continue; 114 | } 115 | yield array_combine($this->headers, array_map('trim', $row)); 116 | } 117 | } 118 | return; 119 | } 120 | 121 | public function each(callable $looper = null){ 122 | if ($looper) { 123 | foreach($this->read() as $k => $row) $looper($row, (int)$k); 124 | return $this; 125 | } else { 126 | $results = []; 127 | foreach($this->read() as $row) $results[] = $row; 128 | return $results; 129 | } 130 | } 131 | 132 | public function convert($filename, $format=self::STANDARD){ 133 | if ($this->mode != self::READ) return; 134 | if ($format == self::AUTO) $format = self::STANDARD; 135 | $csv = CSV::create($filename, CSV::EXCEL); 136 | $this->each(function($row) use ($csv) { 137 | $csv->write($row); 138 | }); 139 | return $csv; 140 | } 141 | 142 | public function flush(){ 143 | if ($this->mode == self::WRITE) { 144 | $this->file->fflush(); 145 | } 146 | } 147 | 148 | public function schema($schema=null){ 149 | if($schema){ 150 | $this->headers = array_values((array)$schema); 151 | if ($this->mode == self::WRITE) { 152 | $this->savedheaders = true; 153 | $this->template = array_combine($this->headers, array_pad([],count($this->headers),'')); 154 | $this->file->fputcsv($this->headers); 155 | } 156 | return $this; 157 | } else { 158 | return $this->headers; 159 | } 160 | } 161 | 162 | public function asString(){ 163 | $this->flush(); 164 | return file_get_contents($this->file->getPathname()); 165 | } 166 | 167 | public function __toString(){ 168 | try { return $this->asString(); } catch(\Exception $e) { return ''; } 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /classes/Cache.php: -------------------------------------------------------------------------------- 1 | exists($hash) && $results = static::$driver->get($hash)){ 23 | return $results; 24 | } else { 25 | if($data = is_callable($default)?call_user_func($default):$default){ 26 | static::$driver->set($hash,$data,$expire); 27 | } 28 | return $data; 29 | } 30 | } else { 31 | return is_callable($default) ? call_user_func($default) : $default; 32 | } 33 | } 34 | 35 | /** 36 | * Load cache drivers with a FCFS strategy 37 | * 38 | * @method using 39 | * @param mixed $driver can be a single driver name string, an array of driver names or a map [ driver_name => driver_options array ] 40 | * @return bool true if a driver was loaded 41 | * @example 42 | * 43 | * Cache::using('redis'); 44 | * Cache::using(['redis','files','memory']); // Prefer "redis" over "files" over "memory" caching 45 | * Cache::using([ 46 | * 'redis' => [ 47 | * 'host' => '127.0.0.1', 48 | * 'prefix' => 'mycache', 49 | * ], 50 | * 'files' => [ 51 | * 'cache_dir' => '/tmp', 52 | * ], 53 | * 'memory' 54 | * ]); 55 | * 56 | */ 57 | public static function using($driver){ 58 | foreach((array)$driver as $key => $value){ 59 | if(is_numeric($key)){ 60 | $drv = $value; 61 | $conf = []; 62 | } else { 63 | $drv = $key; 64 | $conf = $value; 65 | } 66 | $class = 'Cache\\' . ucfirst(strtolower($drv)); 67 | if(class_exists($class) && $class::valid()) { 68 | static::$driver = new $class($conf); 69 | return true; 70 | } 71 | } 72 | return false; 73 | } 74 | 75 | /** 76 | * Returns/Set master switch on cache. 77 | * 78 | * @method enabled 79 | * 80 | * @param boolean $enabled Enable/Disable the cache status. 81 | * 82 | * @return boolean Cache on/off status 83 | */ 84 | public static function enabled($enabled=null){ 85 | return $enabled ? static::$enabled : static::$enabled = $enabled; 86 | } 87 | 88 | public static function set($key, $value, $expire=0){ 89 | return static::$driver->set(static::hash($key),$value,$expire); 90 | } 91 | 92 | public static function delete($key){ 93 | return static::$driver->delete(static::hash($key)); 94 | } 95 | 96 | public static function exists($key){ 97 | return static::$enabled && static::$driver->exists(static::hash($key)); 98 | } 99 | 100 | public static function flush(){ 101 | return static::$driver->flush(); 102 | } 103 | 104 | public static function inc($key, $value=1){ 105 | return static::$driver->inc(static::hash($key),$value); 106 | } 107 | 108 | public static function dec($key, $value=1){ 109 | return static::$driver->dec(static::hash($key),$value); 110 | } 111 | 112 | public static function hash($key, $group=null){ 113 | static $hashes = []; 114 | if (false === isset($hashes[$group][$key])){ 115 | $k = $key; 116 | if(is_array($key) && count($key)>1) list($group,$key) = $k; 117 | $hashes[$group][$key] = ($group?$group.'-':'') . md5($key); 118 | } 119 | return $hashes[$group][$key]; 120 | } 121 | } 122 | 123 | 124 | Cache::using(['files','memory']); 125 | -------------------------------------------------------------------------------- /classes/Cache/Adapter.php: -------------------------------------------------------------------------------- 1 | options = (object) array_merge($options,[ 24 | 'cache_dir' => sys_get_temp_dir().'/core_file_cache', 25 | ]); 26 | $this->options->cache_dir = rtrim($this->options->cache_dir,'/'); 27 | if(false===is_dir($this->options->cache_dir)) mkdir($this->options->cache_dir,0777,true); 28 | $this->options->cache_dir .= '/'; 29 | } 30 | 31 | public function get($key){ 32 | $cache_file_name = $this->options->cache_dir.$key.'.cache.php'; 33 | if(is_file($cache_file_name) && $data = @unserialize(file_get_contents($cache_file_name))){ 34 | if($data[0] && (time() > $data[0])) { 35 | unlink($cache_file_name); 36 | return null; 37 | } 38 | return $data[1]; 39 | } else { 40 | return null; 41 | } 42 | } 43 | 44 | public function set($key,$value,$expire=0){ 45 | $cache_file_name = $this->options->cache_dir.$key.'.cache.php'; 46 | file_put_contents($cache_file_name,serialize([$expire?time()+$expire:0,$value])); 47 | } 48 | 49 | public function delete($key){ 50 | $cache_file_name = $this->options->cache_dir.$key.'.cache.php'; 51 | if(is_file($cache_file_name)) unlink($cache_file_name); 52 | } 53 | 54 | public function exists($key){ 55 | $cache_file_name = $this->options->cache_dir.$key.'.cache.php'; 56 | if(false === is_file($cache_file_name)) return false; 57 | $peek = file_get_contents($cache_file_name,false,null,-1,32); 58 | $expire = explode('{i:0;i:',$peek,2); 59 | $expire = explode(';',end($expire),2); 60 | $expire = current($expire); 61 | if($expire && $expire < time()){ 62 | unlink($cache_file_name); 63 | return false; 64 | } else return true; 65 | } 66 | 67 | public function flush(){ 68 | exec('rm -f ' . $this->options->cache_dir . '*.cache.php'); 69 | } 70 | 71 | public function inc($key,$value=1){ 72 | if(null === ($current = $this->get($key))) $current = $value; else $current += $value; 73 | $this->set($key,$current); 74 | } 75 | 76 | public function dec($key,$value=1){ 77 | $this->inc($key,-abs($value)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /classes/Cache/Memory.php: -------------------------------------------------------------------------------- 1 | memory[$key])){ 25 | if($this->memory[$key][1] && (time() > $this->memory[$key][1])) { 26 | unset($this->memory[$key]); 27 | return null; 28 | } 29 | return $this->memory[$key][0]; 30 | } 31 | } 32 | 33 | public function set($key,$value,$expire=0){ 34 | $this->memory[$key] = [$value,$expire?time()+$expire:0]; 35 | } 36 | 37 | public function delete($key){ 38 | unset($this->memory[$key]); 39 | } 40 | 41 | public function exists($key){ 42 | return isset($this->memory[$key]) && (!$this->memory[$key][1] || (time() <= $this->memory[$key][1])); 43 | } 44 | 45 | public function flush(){ 46 | $this->memory = []; 47 | } 48 | 49 | public function inc($key,$value=1){ 50 | return isset($this->memory[$key]) ? $this->memory[$key][0] += $value : $this->memory[$key][0] = $value; 51 | } 52 | 53 | public function dec($key,$value=1){ 54 | $this->inc($key,-abs($value)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /classes/Check.php: -------------------------------------------------------------------------------- 1 | $rule) { 26 | 27 | $current = isset($data[$field_name]) ? $data[$field_name] : null; 28 | 29 | if (is_callable($rule)){ 30 | static::$errors[$field_name] = call_user_func($rule,$current); 31 | continue; 32 | } elseif (is_string($rule)) { 33 | $current_rules = array_flip(preg_split('/\s*\|\s*/', $rule)); 34 | } else { 35 | $current_rules = (array)$rule; 36 | } 37 | 38 | static::$errors[$field_name] = true; 39 | 40 | foreach($current_rules as $method => $message) { 41 | 42 | $meth_name = strtok($method, ':'); 43 | $opts = strtok(':') ?: ''; 44 | $opts = $opts ? json_decode("[$opts]") : []; 45 | $meth_opts = $opts ? array_merge([$current], $opts) : [$current]; 46 | 47 | if ( static::$errors[$field_name] !== true ) continue 2; 48 | 49 | if (empty(static::$methods[$meth_name])) { 50 | static::$errors[$field_name] = true; 51 | } else { 52 | if (call_user_func_array(static::$methods[$meth_name]->validate,$meth_opts)){ 53 | static::$errors[$field_name] = true; 54 | } else { 55 | $arg = []; 56 | foreach ($meth_opts as $key => $value) { 57 | $arg["arg_$key"] = $value; 58 | } 59 | static::$errors[$field_name] = Text::render(static::$methods[$meth_name]->message,$arg); 60 | } 61 | } 62 | } 63 | } 64 | 65 | self::$data = []; 66 | 67 | // Clean non-errors 68 | static::$errors = array_filter(static::$errors,function($v){ 69 | return $v !== true; 70 | }); 71 | 72 | return empty(static::$errors); 73 | } 74 | 75 | public static function method($name, $definition = null){ 76 | if (is_array($name)) { 77 | foreach ($name as $method_name => $method_definition){ 78 | if (is_callable($method_definition)) $method_definition = ['validate' => $method_definition]; 79 | if (empty($method_definition['validate']) || !is_callable($method_definition['validate'])) continue; 80 | $method_definition['message'] = Filter::with("core.check.error.$method_name",@$method_definition['message']?:'Field not valid.'); 81 | static::$methods[$method_name] = (object)$method_definition; 82 | } 83 | } else { 84 | if (is_callable($definition)) $definition = ['validate' => $definition]; 85 | if (empty($definition['validate']) || !is_callable($definition['validate'])) return; 86 | $methods['message'] = Filter::with("core.check.error.$name",@$methods['message']?:'Field not valid.'); 87 | static::$methods[$name] = (object)$definition; 88 | } 89 | } 90 | 91 | public static function errors() { 92 | return static::$errors; 93 | } 94 | 95 | } 96 | 97 | Check::on('init',function(){ 98 | 99 | Check::method([ 100 | 101 | 'required' => [ 102 | 'validate' => function($value) { 103 | return (is_numeric($value) && $value==0) || !empty($value); 104 | }, 105 | 'message' => "This value cannot be empty.", 106 | ], 107 | 108 | 'alphanumeric' => [ 109 | 'validate' => function($value) { 110 | return (bool)preg_match('/^[0-9a-zA-Z]+$/',$value); 111 | }, 112 | 'message' => "Value must be alphanumeric.", 113 | ], 114 | 115 | 'numeric' => [ 116 | 'validate' => function($value) { 117 | return (bool)preg_match('/^\d+$/',$value); 118 | }, 119 | 'message' => "Value must be numeric.", 120 | ], 121 | 122 | 'email' => [ 123 | 'validate' => function($value) { 124 | return (bool)filter_var($value, FILTER_VALIDATE_EMAIL); 125 | }, 126 | 'message' => "This is not a valid email.", 127 | ], 128 | 129 | 'url' => [ 130 | 'validate' => function($value) { 131 | return (bool)filter_var($value, FILTER_VALIDATE_URL); 132 | }, 133 | 'message' => "This is not a valid URL.", 134 | ], 135 | 136 | 'max' => [ 137 | 'validate' => function($value,$max) { 138 | return $value<=$max ? true : false; 139 | }, 140 | 'message' => "Value must be less than {{arg_1}}.", 141 | ], 142 | 143 | 'min' => [ 144 | 'validate' => function($value,$min) { 145 | return $value >= $min; 146 | }, 147 | 'message' => "Value must be greater than {{arg_1}}.", 148 | ], 149 | 150 | 'range' => [ 151 | 'validate' => function($value,$min,$max) { 152 | return ( $value >= $min ) && ( $value <= $max ); 153 | }, 154 | 'message' => "This value must be in [{{arg_1}},{{arg_2}}] range.", 155 | ], 156 | 157 | 'words' => [ 158 | 'validate' => function($value,$max) { 159 | return str_word_count($value) <= $max; 160 | }, 161 | 'message' => "Too many words, max count is {{arg_1}}.", 162 | ], 163 | 164 | 'length' => [ 165 | 'validate' => function($value,$length) { 166 | return strlen($value) == $length; 167 | }, 168 | 'message' => "This value must be {{arg_1}} characters.", 169 | ], 170 | 171 | 'min_length' => [ 172 | 'validate' => function($value,$min) { 173 | return strlen($value) >= $min; 174 | }, 175 | 'message' => "Too few characters, min count is {{arg_1}}.", 176 | ], 177 | 178 | 'max_length' => [ 179 | 'validate' => function($value,$max) { 180 | return strlen($value) <= $max; 181 | }, 182 | 'message' => "Too many characters, max count is {{arg_1}}.", 183 | ], 184 | 185 | 'true' => [ 186 | 'validate' => function($value) { 187 | return (bool)$value; 188 | }, 189 | 'message' => "This value must be true.", 190 | ], 191 | 192 | 'false' => [ 193 | 'validate' => function($value) { 194 | return !$value; 195 | }, 196 | 'message' => "This value must be false.", 197 | ], 198 | 199 | 'same_as' => [ 200 | 'validate' => function($value,$fieldname) { 201 | $x = isset(Check::$data[$fieldname]) ? Check::$data[$fieldname] : ''; 202 | return $value == $x; 203 | }, 204 | 'message' => "Field must be equal to {{arg_1}}.", 205 | ], 206 | 207 | 'in_array' => [ 208 | 'validate' => function($value,$array_values) { 209 | return in_array($value, $array_values); 210 | }, 211 | 'message' => "This value is forbidden.", 212 | ], 213 | 214 | ]); 215 | 216 | }); 217 | 218 | -------------------------------------------------------------------------------- /classes/Deferred.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 20 | } 21 | 22 | public function disarm() { 23 | $this->enabled = false; 24 | } 25 | 26 | public function prime() { 27 | $this->enabled = true; 28 | } 29 | 30 | public function __destruct() { 31 | if ( $this->enabled ) call_user_func( $this->callback ); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /classes/Dictionary.php: -------------------------------------------------------------------------------- 1 | 11 | * class MyConfig extends Dictionary {} 12 | * MyConfig::set('user',[ 'name' => 'Frank', 'surname' => 'Castle' ]); 13 | * echo "Hello, my name is ",MyConfig::get('user.name'),' ',MyConfig::get('user.surname'); 14 | * 15 | * 16 | * @package core 17 | * @author stefano.azzolini@caffeina.com 18 | * @copyright Caffeina srl - 2016 - http://caffeina.com 19 | */ 20 | 21 | abstract class Dictionary { 22 | use Events; 23 | use Filters; 24 | 25 | protected static $fields = null; 26 | 27 | public static function & all(){ 28 | if (!static::$fields) static::$fields = new Map(); 29 | return static::$fields->all(); 30 | } 31 | 32 | public static function get($key, $default=null){ 33 | if (!static::$fields) static::$fields = new Map(); 34 | if (is_array($key)){ 35 | $results = []; 36 | foreach ($key as $_dst_key => $_src_key) 37 | $results[$_dst_key] = static::$fields->get($_src_key); 38 | return $results; 39 | } else { 40 | return static::$fields->get($key, $default); 41 | } 42 | } 43 | 44 | public static function set($key, $value=null){ 45 | if (!static::$fields) static::$fields = new Map(); 46 | return static::$fields->set($key, $value); 47 | } 48 | 49 | public static function delete($key, $compact=true){ 50 | if (!static::$fields) static::$fields = new Map(); 51 | static::$fields->delete($key, $compact); 52 | } 53 | 54 | public static function exists($key){ 55 | if (!static::$fields) static::$fields = new Map(); 56 | return static::$fields->exists($key); 57 | } 58 | 59 | public static function clear(){ 60 | if (!static::$fields) static::$fields = new Map(); 61 | static::$fields->clear(); 62 | } 63 | 64 | public static function load($fields){ 65 | if (!static::$fields) static::$fields = new Map(); 66 | static::$fields->load($fields); 67 | } 68 | 69 | public static function merge(array $array, $merge_back=false){ 70 | if (!static::$fields) static::$fields = new Map(); 71 | static::$fields->merge($array, $merge_back); 72 | } 73 | 74 | protected static function compact(){ 75 | if (!static::$fields) static::$fields = new Map(); 76 | static::$fields->compact(); 77 | } 78 | 79 | protected static function & find($path, $create=false, callable $operation=null) { 80 | return static::$fields->find($path, $create, $operation); 81 | } 82 | 83 | public function jsonSerialize(){ 84 | if (!static::$fields) static::$fields = new Map(); 85 | return static::$fields->jsonSerialize(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /classes/Email.php: -------------------------------------------------------------------------------- 1 | onInit($options); 27 | } 28 | 29 | public static function create($mail=[]){ 30 | if (is_a($mail, 'Email\\Envelope')){ 31 | return $mail; 32 | } else { 33 | return new Email\Envelope(array_merge([ 34 | 'to' => false, 35 | 'from' => false, 36 | 'cc' => false, 37 | 'bcc' => false, 38 | 'replyTo' => false, 39 | 'subject' => false, 40 | 'message' => false, 41 | 'attachments' => [], 42 | ], $mail)); 43 | } 44 | } 45 | 46 | public static function send($mail){ 47 | $envelope = static::create($mail); 48 | $results = (array) static::$driver->onSend($envelope); 49 | static::trigger('send', $envelope->to(), $envelope, static::$driver_name, $results); 50 | Event::trigger('core.email.send', $envelope->to(), $envelope, static::$driver_name, $results); 51 | return count($results) && array_reduce( $results, function($carry, $item) { 52 | return $carry && $item; 53 | }, true ); 54 | } 55 | 56 | } 57 | 58 | Email::using('native'); 59 | -------------------------------------------------------------------------------- /classes/Email/Driver.php: -------------------------------------------------------------------------------- 1 | to)) $this->to($email->to); 34 | if(isset($email->from)) $this->from($email->from); 35 | if(isset($email->cc)) $this->cc($email->cc); 36 | if(isset($email->bcc)) $this->bcc($email->bcc); 37 | if(isset($email->replyTo)) $this->replyTo($email->replyTo); 38 | if(isset($email->subject)) $this->subject($email->subject); 39 | if(isset($email->message)) $this->message($email->message); 40 | if(isset($email->attachments)) $this->attach($email->attachments); 41 | } 42 | $this->uid = '_CORE_'.md5(uniqid(time())); 43 | 44 | } 45 | 46 | protected function add_emails(&$pool, $emails, $append=true){ 47 | $this->compiled_head = null; 48 | foreach ((array)$emails as $values) { 49 | foreach(preg_split('/\s*,\s*/',$values) as $value) { 50 | if(strpos($value,'<')!==false){ 51 | $value = str_replace('>','',$value); 52 | $parts = explode('<',$value,2); 53 | $name = trim(current($parts)); 54 | $email = trim(end($parts)); 55 | $address = "$name <{$email}>"; 56 | } else { 57 | $address = $value; 58 | } 59 | if ($append) $pool[] = $address; else $pool = $address; 60 | } 61 | } 62 | } 63 | 64 | public function from($value=null){ 65 | if ($value!==null && $value) { 66 | $this->add_emails($this->from, $value, false); 67 | } else if ($value===false) $this->from = ''; 68 | return $this->from; 69 | } 70 | 71 | public function to($value=null){ 72 | if ($value!==null && $value) { 73 | $this->add_emails($this->to, $value); 74 | } else if ($value===false) $this->to = []; 75 | return $this->to; 76 | } 77 | 78 | public function cc($value=null){ 79 | if ($value!==null && $value) { 80 | $this->add_emails($this->cc, $value); 81 | } else if ($value===false) $this->cc = []; 82 | return $this->cc; 83 | } 84 | 85 | public function bcc($value=null){ 86 | if ($value!==null && $value) { 87 | $this->add_emails($this->bcc, $value); 88 | } else if ($value===false) $this->bcc = []; 89 | return $this->bcc; 90 | } 91 | 92 | public function replyTo($value=null){ 93 | if ($value!==null && $value) { 94 | $this->add_emails($this->replyTo, $value, false); 95 | } else if ($value===false) $this->replyTo = ''; 96 | return $this->replyTo; 97 | } 98 | 99 | public function subject($value=null){ 100 | if ($value!==null && $value) { 101 | $this->compiled_head = null; 102 | $this->subject = $value; 103 | } else if ($value===false) $this->subject = ''; 104 | return $this->subject; 105 | } 106 | 107 | public function contentType($value=null){ 108 | if ($value!==null && $value) { 109 | if (empty($this->attachments)) $this->compiled_head = null; 110 | $this->compiled_body = null; 111 | $this->contentType = $value; 112 | } else if ($value===false) $this->contentType = ''; 113 | return $this->contentType; 114 | } 115 | 116 | public function message($value=null){ 117 | if ($value!==null && $value) { 118 | $this->compiled_body = null; 119 | $this->message = $value; 120 | } else if ($value===false) $this->message = ''; 121 | return $this->message; 122 | } 123 | 124 | public function attach($file){ 125 | $this->compiled_body = null; 126 | if (isset($file->content) || isset($file['content'])) { 127 | $this->attachments[] = $file; 128 | } else foreach ((array)$file as $curfile) { 129 | $this->attachments[] = $curfile; 130 | } 131 | } 132 | 133 | public function attachments($file=null){ 134 | if ($file!==null && $file) $this->attach($file); 135 | return $this->attachments ?: []; 136 | } 137 | 138 | public function head($recompile = false){ 139 | if ($recompile || (null === $this->compiled_head)){ 140 | $head = []; 141 | $head[] = "Subject: {$this->subject}"; 142 | if($this->from) $head[] = "From: {$this->from}"; 143 | if(is_array($this->to) && !empty($this->to)) $head[] = "To: " . implode(', ',$this->to); 144 | if(is_array($this->cc) && !empty($this->cc)) $head[] = "Cc: " . implode(', ',$this->cc); 145 | if(is_array($this->bcc) && !empty($this->bcc)) $head[] = "Bcc: " . implode(', ',$this->bcc); 146 | if($this->replyTo) $head[] = "Reply-To: {$this->replyTo}"; 147 | $head[] = "Content-Type: multipart/mixed; boundary=\"{$this->uid}\""; 148 | $head[] = 'MIME-Version: 1.0'; 149 | $head[] = ''; 150 | $this->compiled_head = implode("\r\n", $head); 151 | } 152 | return \Filter::with( 'core.email.source.head', $this->compiled_head); 153 | } 154 | 155 | public function body($recompile = false){ 156 | if ($recompile || (null === $this->compiled_body)){ 157 | $body = []; 158 | $body[] = "--{$this->uid}"; 159 | $body[] = "Content-Type: {$this->contentType}"; 160 | $body[] = "Content-Transfer-Encoding: quoted-printable"; 161 | $body[] = ''; 162 | $body[] = quoted_printable_encode($this->message); 163 | $body[] = ''; 164 | 165 | if (!empty($this->attachments)) foreach ((array)$this->attachments as $file) { 166 | 167 | if (is_string($file)) { 168 | $name = basename($file); 169 | $data = file_get_contents($file); 170 | } else { 171 | $name = isset($file['name']) ? $file['name'] : 'untitled'; 172 | $data = isset($file['content']) ? $file['content'] : ''; 173 | } 174 | 175 | $body[] = "--{$this->uid}"; 176 | $body[] = "Content-Type: application/octet-stream; name=\"{$name}\""; 177 | $body[] = "Content-Transfer-Encoding: base64"; 178 | $body[] = "Content-Disposition: attachment; filename=\"{$name}\""; 179 | $body[] = ''; 180 | $body[] = chunk_split(base64_encode($data)); 181 | $body[] = ''; 182 | } 183 | 184 | $body[] = "--{$this->uid}"; 185 | 186 | $this->compiled_body = implode("\r\n", $body); 187 | } 188 | return \Filter::with( 'core.email.source.body', $this->compiled_body); 189 | } 190 | 191 | public function build(){ 192 | return \Filter::with( 'core.email.source', $this->head() . "\r\n" . $this->body() ); 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /classes/Email/Native.php: -------------------------------------------------------------------------------- 1 | to(); 22 | $envelope->to(false); 23 | foreach ($recipients as $to) { 24 | $results[$to] = mail($to,$envelope->subject(),$envelope->body(),$envelope->head()); 25 | } 26 | return $results; 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /classes/Email/Proxy.php: -------------------------------------------------------------------------------- 1 | listener = $options['hook']; 21 | } 22 | 23 | public function onSend(Envelope $envelope){ 24 | return array_reduce( (array) \Event::trigger($this->listener, $envelope), function($carry, $item) { 25 | if (is_bool($item)) $carry[] = $item; 26 | return $carry; 27 | }, []); 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /classes/Email/Ses.php: -------------------------------------------------------------------------------- 1 | region) ? $options->region : 'eu-west-1'; 20 | if (empty($options->username) || empty($options->password)) 21 | throw new \Exception("[core.email.ses] You must provide an Amazon SES SMTP username and password", 1); 22 | 23 | Smtp::onInit([ 24 | 'host' => "email-smtp.{$region}.amazonaws.com", 25 | 'secure' => true, 26 | 'port' => 465, 27 | 'username' => $options->username, 28 | 'password' => $options->password, 29 | ]); 30 | 31 | } 32 | 33 | public function onSend(Envelope $envelope){ 34 | if (!$envelope->from()) 35 | throw new \Exception("[core.email.ses] Amazon SES needs a registered `from` address", 1); 36 | return Smtp::onSend($envelope); 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /classes/Email/Smtp.php: -------------------------------------------------------------------------------- 1 | host = isset($options->host) ? $options->host : 'localhost'; 30 | $this->username = isset($options->username) ? $options->username : false; 31 | $this->secure = isset($options->secure) ? $options->secure : !empty($this->username); 32 | $this->port = isset($options->port) ? $options->port : ($this->secure ? 465 : 25); 33 | $this->password = isset($options->password) ? $options->password : false; 34 | } 35 | 36 | protected function connect(){ 37 | if ($this->socket) $this->close(); 38 | $url = ($this->secure ? 'tls' : 'tcp') ."://{$this->host}"; 39 | $this->socket = fsockopen( $url, $this->port, $errno, $errstr, 30 ); 40 | if (!$this->socket) throw new \Exception("Unable to connect to $url on port {$this->port}."); 41 | $this->lastMessage = ''; 42 | $this->lastCode = 0; 43 | } 44 | 45 | public function close(){ 46 | $this->socket && @fclose($this->socket); 47 | } 48 | 49 | protected function write($data, $nl = 1){ 50 | $payload = $data . str_repeat("\r\n",$nl); 51 | fwrite($this->socket, $payload); 52 | \Email::trigger("smtp.console",$payload); 53 | } 54 | 55 | protected function expectCode($code){ 56 | 57 | $this->lastMessage = ''; 58 | while (substr($this->lastMessage, 3, 1) != ' '){ 59 | $this->lastMessage = fgets($this->socket, 256); 60 | } 61 | $this->lastCode = 1 * substr($this->lastMessage, 0, 3); 62 | \Email::trigger("smtp.console",$this->lastMessage); 63 | if ($code != $this->lastCode) { 64 | throw new \Exception("Expected $code returned {$this->lastMessage}"); 65 | } 66 | return true; 67 | } 68 | 69 | protected function cleanAddr($email){ 70 | return preg_replace('((.*?)<([\w.@-]+)>(.*?))','$2',$email); 71 | } 72 | 73 | protected function SMTPmail($from,$to,$body){ 74 | try { 75 | $this->connect(); 76 | $this->expectCode(220); 77 | 78 | $this->write("EHLO {$this->host}"); 79 | $this->expectCode(250); 80 | 81 | if ($this->username){ 82 | $this->write("AUTH LOGIN"); 83 | $this->expectCode(334); 84 | $this->write(base64_encode($this->username)); 85 | $this->expectCode(334); 86 | $this->write(base64_encode($this->password)); 87 | $this->expectCode(235); 88 | } 89 | 90 | $from = $this->cleanAddr($from); 91 | 92 | $this->write("MAIL FROM: <{$from}>"); 93 | $this->expectCode(250); 94 | 95 | $to = $this->cleanAddr($to); 96 | 97 | $this->write("RCPT TO: <{$to}>"); 98 | $this->expectCode(250); 99 | 100 | $this->write("DATA"); 101 | $this->expectCode(354); 102 | 103 | $this->write($body); 104 | 105 | $this->write("."); 106 | $this->expectCode(250); 107 | 108 | $this->write("QUIT"); 109 | 110 | $this->close(); 111 | } catch (\Exception $e) { 112 | \Email::trigger('error',$e->getMessage()); 113 | return false; 114 | } 115 | return true; 116 | } 117 | 118 | public function onSend(Envelope $envelope){ 119 | $results = []; 120 | foreach ($envelope->to() as $to) { 121 | $results[$to] = $this->SMTPmail($envelope->from(), $to, $envelope->build()); 122 | } 123 | return $results; 124 | } 125 | 126 | } 127 | 128 | -------------------------------------------------------------------------------- /classes/Enum.php: -------------------------------------------------------------------------------- 1 | getConstants() 22 | , CASE_UPPER); 23 | } 24 | return $_consts; 25 | } 26 | 27 | public static function key($value){ 28 | foreach (static::__constants() as $key => $const_val) { 29 | if ($const_val === $value) return $key; 30 | } 31 | return false; 32 | } 33 | 34 | public static function has($value){ 35 | return isset(static::__constants()[strtoupper($value)]); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /classes/Error.php: -------------------------------------------------------------------------------- 1 | ',$e->getMessage(),'',PHP_EOL; 71 | break; 72 | case self::JSON : 73 | echo json_encode(['error' => $e->getMessage()]); 74 | break; 75 | case self::SILENT : 76 | // Don't echo anything. 77 | break; 78 | case self::SIMPLE : 79 | default: 80 | echo $e->getMessage(),PHP_EOL; 81 | break; 82 | } 83 | return true; 84 | } 85 | 86 | /** 87 | * @deprecated Use Errors::on('fatal', $listener) 88 | */ 89 | public static function onFatal(callable $listener){ 90 | Event::on('core.error.fatal',$listener); 91 | } 92 | 93 | /** 94 | * @deprecated Use Errors::on('warning', $listener) 95 | */ 96 | public static function onWarning(callable $listener){ 97 | Event::on('core.error.warning',$listener); 98 | } 99 | 100 | /** 101 | * @deprecated Use Errors::on('notice', $listener) 102 | */ 103 | public static function onNotice(callable $listener){ 104 | Event::on('core.error.notice',$listener); 105 | } 106 | 107 | /** 108 | * @deprecated Use Errors::on('any', $listener) 109 | */ 110 | public static function onAny(callable $listener){ 111 | Event::on('core.error',$listener); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /classes/Event.php: -------------------------------------------------------------------------------- 1 | $fs){ 52 | if ($fs->exists($path)) return [$mount, $path]; 53 | } 54 | return false; 55 | } 56 | } 57 | 58 | public static function resolvePath($path) { 59 | $path = str_replace(['/', '\\'], '/', $path); 60 | $parts = array_filter(explode('/', $path), 'strlen'); 61 | $absolutes = []; 62 | foreach ($parts as $part) { 63 | if ('.' == $part) continue; 64 | if ('..' == $part) { 65 | array_pop($absolutes); 66 | } else { 67 | $absolutes[] = $part; 68 | } 69 | } 70 | return trim(implode('/', $absolutes),'/'); 71 | } 72 | 73 | public static function search($pattern, $recursive=true){ 74 | $results = []; 75 | foreach (static::$mount_points as $mount => $fs) { 76 | foreach($fs->search($pattern, $recursive) as $path) { 77 | $results[] = $mount.'://'.$path; 78 | } 79 | } 80 | return $results; 81 | } 82 | 83 | 84 | public static function move($old,$new) { 85 | $src = static::locate($old); 86 | $dest = static::locate($new); 87 | if ($src && $dest) { 88 | $_sfs = static::$mount_points[$src[0]]; 89 | $_dfs = static::$mount_points[$dest[0]]; 90 | if ($src[0] == $dest[0]) { 91 | // Same filesystem 92 | return $_sfs->move($src[1],$dest[1]); 93 | } else { 94 | return $_dfs->write($dest[1],$_sfs->read($src[1])) && $_sfs->delete($src[1]); 95 | } 96 | } else return false; 97 | } 98 | 99 | 100 | } 101 | 102 | -------------------------------------------------------------------------------- /classes/FileSystem/Adapter.php: -------------------------------------------------------------------------------- 1 | storage[$path]); 21 | } 22 | 23 | public function read($path){ 24 | return $this->exists($path) ? $this->storage[$path] : false; 25 | } 26 | 27 | public function write($path, $data){ 28 | $this->storage[$path] = $data; 29 | } 30 | 31 | public function append($path, $data){ 32 | @$this->storage[$path] .= $data; 33 | } 34 | 35 | public function move($old, $new){ 36 | if($this->exists($old)){ 37 | $this->storage[$new] = $this->storage[$old]; 38 | unset($this->storage[$old]); 39 | return true; 40 | } else return false; 41 | } 42 | 43 | public function delete($path){ 44 | unset($this->storage[$path]); 45 | return true; 46 | } 47 | 48 | public function search($pattern, $recursive=true){ 49 | $results = []; 50 | $rx_pattern = '('.strtr($pattern,['.'=>'\.','*'=>'.*','?'=>'.']).')Ai'; 51 | foreach (array_keys($this->storage) as $path) { 52 | if (preg_match($rx_pattern,$path)) $results[] = $path; 53 | } 54 | return $results; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /classes/FileSystem/Native.php: -------------------------------------------------------------------------------- 1 | root = empty($options['root'])?'/':(rtrim($options['root'],'/').'/'); 21 | } 22 | 23 | public function exists($path){ 24 | return file_exists($this->realPath($path)); 25 | } 26 | 27 | public function read($path){ 28 | return $this->exists($path) ? file_get_contents($this->realPath($path)) : false; 29 | } 30 | 31 | public function write($path, $data){ 32 | $r_path = $this->realPath($path); 33 | if ( ! is_dir($r_dir = dirname($r_path)) ) @mkdir($r_dir,0775,true); 34 | return file_put_contents($r_path, $data); 35 | } 36 | 37 | public function append($path, $data){ 38 | return file_put_contents($this->realPath($path), $data, FILE_APPEND); 39 | } 40 | 41 | public function move($old, $new){ 42 | // Atomic 43 | if($this->exists($old)){ 44 | return $this->write($new,$this->read($old)) && $this->delete($old); 45 | } else return false; 46 | } 47 | 48 | public function delete($path){ 49 | return $this->exists($path) ? unlink($this->realPath($path)) : false; 50 | } 51 | 52 | public function search($pattern, $recursive=true){ 53 | $results = []; 54 | $root_len = strlen($this->root); 55 | $rx_pattern = '('.strtr($pattern,['.'=>'\.','*'=>'.*','?'=>'.']).')Ai'; 56 | 57 | /* 58 | $files = new \RegexIterator(new \RecursiveDirectoryIterator($this->root, 59 | \RecursiveDirectoryIterator::SKIP_DOTS),$rx_pattern); 60 | foreach ($files as $path) { 61 | $results[] = trim(substr($path, $root_len),'/'); 62 | } 63 | return $results; 64 | */ 65 | 66 | $tree = new \RegexIterator( 67 | new \RecursiveIteratorIterator( 68 | new \RecursiveDirectoryIterator( 69 | $this->root, 70 | \RecursiveDirectoryIterator::SKIP_DOTS)), 71 | $rx_pattern,\RegexIterator::GET_MATCH); 72 | 73 | $fileList = []; 74 | foreach($tree as $group) { 75 | foreach($group as $path) { 76 | $results[] = trim(substr($path, $root_len),'/'); 77 | } 78 | } 79 | 80 | return $results; 81 | 82 | } 83 | 84 | protected function realPath($path){ 85 | return $this->root . $path; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /classes/FileSystem/ZIP.php: -------------------------------------------------------------------------------- 1 | path = empty($options['root'])?(tempnam(sys_get_temp_dir(), 'CFZ_').'.zip'):rtrim($options['root']); 23 | $this->zipfile = new \ZipArchive(); 24 | if ( !$this->zipfile->open($this->path, \ZipArchive::CREATE) ){ 25 | throw new \Exception("File::ZIP Cannot open or create ".$this->path); 26 | } 27 | } 28 | 29 | public function exists($path){ 30 | return false !== $this->zipfile->locateName($path); 31 | } 32 | 33 | public function read($path){ 34 | if (isset($this->fileCache[$path])) return $this->fileCache[$path]; 35 | return $this->exists($path) ? $this->zipfile->getFromName($path) : false; 36 | } 37 | 38 | public function write($path, $data){ 39 | // This is needed because we cant write and read from the same archive. 40 | $this->fileCache[$path] = $data; 41 | return $this->zipfile->addFromString($path, $data); 42 | } 43 | 44 | public function append($path, $data){ 45 | return $this->write($path, ($this->read($path) ?: '') . $data); 46 | } 47 | 48 | public function delete($path){ 49 | return $this->exists($path) ? $this->zipfile->deleteName($path) : false; 50 | } 51 | 52 | public function move($old, $new){ 53 | // Atomic rename 54 | // This is needed because we cant write and read from the same archive. 55 | return $this->write($new,$this->read($old)) && $this->delete($old); 56 | // return $this->zipfile->renameName($old, $new); 57 | } 58 | 59 | public function search($pattern, $recursive=true){ 60 | $results = []; 61 | $rx_pattern = '('.strtr($pattern,['.'=>'\.','*'=>'.*','?'=>'.']).')Ai'; 62 | 63 | for( $i = 0, $c = $this->zipfile->numFiles; $i < $c; $i++ ){ 64 | $stat = $this->zipfile->statIndex( $i ); 65 | if (preg_match($rx_pattern,$stat['name'])) $results[] = $stat['name']; 66 | } 67 | 68 | return $results; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /classes/Filter.php: -------------------------------------------------------------------------------- 1 | Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP 15 | $__old_er = error_reporting(); 16 | error_reporting(E_ALL & ~E_DEPRECATED); 17 | 18 | class Filter { 19 | use Module, 20 | Filters { 21 | filter as add; 22 | filterSingle as single; 23 | filterRemove as remove; 24 | filterWith as with; 25 | } 26 | } 27 | 28 | // Restore old ErrorReporting 29 | error_reporting($__old_er); 30 | -------------------------------------------------------------------------------- /classes/Filters.php: -------------------------------------------------------------------------------- 1 | $callback ) { 19 | static::$_modders[$name][] = $callback; 20 | } else foreach ( (array)$names as $name ) { 21 | static::$_modders[$name][] = $modder; 22 | } 23 | } 24 | 25 | public static function filterSingle($name, callable $modder){ 26 | static::$_modders[$name] = [$modder]; 27 | } 28 | 29 | public static function filterRemove($name,callable $modder = null){ 30 | if($modder === null) { 31 | unset(static::$_modders[$name]); 32 | } else { 33 | if ($idx = array_search($modder,static::$_modders[$name],true)) 34 | unset(static::$_modders[$name][$idx]); 35 | } 36 | } 37 | 38 | public static function filterWith($names, $default, ...$args){ 39 | foreach ((array)$names as $name) { 40 | if (!empty(static::$_modders[$name])) { 41 | $value = $default; 42 | foreach (static::$_modders[$name] as $modder) { 43 | $value = $modder($value, $args); 44 | } 45 | return $value; 46 | } 47 | } 48 | return $default; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /classes/HTTP.php: -------------------------------------------------------------------------------- 1 | 1 ? $match[1] : 80; 27 | $http_method = strtoupper($method); 28 | $ch = curl_init($url); 29 | $opt = [ 30 | CURLOPT_CUSTOMREQUEST => $http_method, 31 | CURLOPT_SSL_VERIFYHOST => false, 32 | CURLOPT_CONNECTTIMEOUT => 10, 33 | CURLOPT_RETURNTRANSFER => true, 34 | CURLOPT_USERAGENT => static::$UA, 35 | CURLOPT_HEADER => true, 36 | CURLOPT_VERBOSE => true, 37 | CURLOPT_MAXREDIRS => 10, 38 | CURLOPT_FOLLOWLOCATION => true, 39 | CURLOPT_ENCODING => '', 40 | CURLOPT_PROXY => static::$proxy, 41 | CURLOPT_PORT => $port 42 | ]; 43 | 44 | if($username && $password) { 45 | $opt[CURLOPT_USERPWD] = "$username:$password"; 46 | } 47 | 48 | $headers = array_merge($headers,static::$headers); 49 | 50 | if($http_method == 'GET'){ 51 | if($data && is_array($data)){ 52 | $tmp = []; 53 | $queried_url = $url; 54 | foreach($data as $key=>$val) $tmp[] = $key.'='.$val; 55 | $queried_url .= (strpos($queried_url,'?') === false) ? '?' : '&'; 56 | $queried_url .= implode('&',$tmp); 57 | $opt[CURLOPT_URL] = $queried_url; 58 | $opt[CURLOPT_HTTPGET] = true; 59 | unset($opt[CURLOPT_CUSTOMREQUEST]); 60 | } 61 | } else { 62 | $opt[CURLOPT_CUSTOMREQUEST] = $http_method; 63 | if($data_as_json or is_object($data)){ 64 | $headers['Content-Type'] = 'application/json'; 65 | $opt[CURLOPT_POSTFIELDS] = json_encode($data); 66 | } else { 67 | $opt[CURLOPT_POSTFIELDS] = http_build_query($data); 68 | } 69 | } 70 | 71 | curl_setopt_array($ch,$opt); 72 | $_harr = []; 73 | foreach($headers as $key=>$val) $_harr[] = $key.': '.$val; 74 | curl_setopt($ch, CURLOPT_HTTPHEADER, $_harr); 75 | $result = curl_exec($ch); 76 | $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); 77 | $contentType = strtolower(curl_getinfo($ch, CURLINFO_CONTENT_TYPE)); 78 | static::$last_response_header = substr($result, 0, $header_size); 79 | $result = substr($result, $header_size); 80 | static::$last_info = curl_getinfo($ch); 81 | if(false !== strpos($contentType,'json')) $result = json_decode($result); 82 | curl_close($ch); 83 | static::trigger("request", $result, static::$last_info); 84 | static::$last_response_body = $result; 85 | return $result; 86 | } 87 | 88 | public static function useJSON($value=null){ 89 | return $value===null ? static::$json_data : static::$json_data = $value; 90 | } 91 | 92 | protected static function trasformRawHeaders($headers) { 93 | foreach (explode("\r\n", trim($headers)) as $line) { 94 | if (empty($line)) continue; 95 | $splitted = explode(':', $line); 96 | $res[isset($splitted[1])? trim($splitted[0]) : 'extra'][] = trim(end($splitted)); 97 | } 98 | return $res; 99 | } 100 | 101 | public static function lastResponseBody(){ 102 | return static::$last_response_body; 103 | } 104 | 105 | public static function lastResponseHeader(){ 106 | if (static::$last_response_header && !is_array(static::$last_response_header)) { 107 | static::$last_response_header = static::trasformRawHeaders(static::$last_response_header); 108 | } 109 | return static::$last_response_header; 110 | } 111 | 112 | public static function addHeader($name,$value){ 113 | static::$headers[$name] = $value; 114 | } 115 | 116 | public static function removeHeader($name){ 117 | unset(static::$headers[$name]); 118 | } 119 | 120 | public static function headers($name=null){ 121 | // null === $name ?? static::$headers ?? static::$headers[$name] 122 | return null === $name 123 | ? static::$headers 124 | : ( isset(static::$headers[$name]) ? static::$headers[$name] : '' ); 125 | } 126 | 127 | public static function userAgent($value=null){ 128 | return $value===null ? static::$UA : static::$UA = $value; 129 | } 130 | 131 | public static function proxy($value=false){ 132 | return $value===false ? static::$proxy : static::$proxy = $value; 133 | } 134 | 135 | public static function get($url, $data=null, array $headers=[], $username = null, $password = null){ 136 | return static::request('get',$url,$data,$headers,false,$username,$password); 137 | } 138 | 139 | public static function post($url, $data=null, array $headers=[], $username = null, $password = null){ 140 | return static::request('post',$url,$data,$headers,static::$json_data,$username,$password); 141 | } 142 | 143 | public static function put($url, $data=null, array $headers=[], $username = null, $password = null){ 144 | return static::request('put',$url,$data,$headers,static::$json_data,$username,$password); 145 | } 146 | 147 | public static function delete($url, $data=null, array $headers=[], $username = null, $password = null){ 148 | return static::request('delete',$url,$data,$headers,static::$json_data,$username,$password); 149 | } 150 | 151 | public static function info($url = null){ 152 | if ($url){ 153 | curl_setopt_array($ch = curl_init($url), [ 154 | CURLOPT_SSL_VERIFYHOST => false, 155 | CURLOPT_CONNECTTIMEOUT => 10, 156 | CURLOPT_RETURNTRANSFER => true, 157 | CURLOPT_USERAGENT => static::$UA, 158 | CURLOPT_HEADER => false, 159 | CURLOPT_ENCODING => '', 160 | CURLOPT_FILETIME => true, 161 | CURLOPT_NOBODY => true, 162 | CURLOPT_PROXY => static::$proxy, 163 | ]); 164 | curl_exec($ch); 165 | $info = curl_getinfo($ch); 166 | curl_close($ch); 167 | return $info; 168 | } else { 169 | return static::$last_info; 170 | } 171 | } 172 | 173 | } 174 | 175 | class HTTP_Request { 176 | public $method = 'GET', 177 | $url = null, 178 | $headers = [], 179 | $body = ''; 180 | 181 | public function __construct($method, $url, $headers=[], $data=null){ 182 | $this->method = strtoupper($method); 183 | $this->url = new URL($this->url); 184 | $this->headers = (array)$headers; 185 | if ($data) { 186 | if (isset($this->headers["Content-Type"]) && $this->headers["Content-Type"]=='application/json') 187 | $this->body = json_encode($data); 188 | else 189 | $this->body = http_build_query($data); 190 | } 191 | } 192 | 193 | public function __toString(){ 194 | return "$this->method {$this->url->path}{$this->url->query} HTTP/1.1\r\n" 195 | ."Host: {$this->url->host}\r\n" 196 | .($this->headers ? implode("\r\n",$this->headers) . "\r\n" : '') 197 | ."\r\n{$this->body}"; 198 | } 199 | } 200 | 201 | 202 | class HTTP_Response { 203 | public $status = 200, 204 | $headers = [], 205 | $contents = ''; 206 | 207 | public function __construct($contents, $status, $headers){ 208 | $this->status = $status; 209 | $this->contents = $contents; 210 | $this->headers = (array)$headers; 211 | } 212 | 213 | public function __toString(){ 214 | return $this->contents; 215 | } 216 | } 217 | 218 | -------------------------------------------------------------------------------- /classes/Hash.php: -------------------------------------------------------------------------------- 1 | = 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0xcc9e2d51) & 0xffff) << 16))) & 0xffffffff; 125 | $k1 = $k1 << 15 | ($k1 >= 0 ? $k1 >> 17 : (($k1 & 0x7fffffff) >> 17) | 0x4000); 126 | $k1 = (((($k1 & 0xffff) * 0x1b873593) + ((((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0x1b873593) & 0xffff) << 16))) & 0xffffffff; 127 | $h1 ^= $k1; 128 | $h1 = $h1 << 13 | ($h1 >= 0 ? $h1 >> 19 : (($h1 & 0x7fffffff) >> 19) | 0x1000); 129 | $h1b = (((($h1 & 0xffff) * 5) + ((((($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000)) * 5) & 0xffff) << 16))) & 0xffffffff; 130 | $h1 = ((($h1b & 0xffff) + 0x6b64) + ((((($h1b >= 0 ? $h1b >> 16 : (($h1b & 0x7fffffff) >> 16) | 0x8000)) + 0xe654) & 0xffff) << 16)); 131 | } 132 | $k1 = 0; 133 | switch ($remainder) { 134 | case 3: $k1 ^= $key[$i + 2] << 16; 135 | case 2: $k1 ^= $key[$i + 1] << 8; 136 | case 1: $k1 ^= $key[$i]; 137 | $k1 = ((($k1 & 0xffff) * 0xcc9e2d51) + ((((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0xcc9e2d51) & 0xffff) << 16)) & 0xffffffff; 138 | $k1 = $k1 << 15 | ($k1 >= 0 ? $k1 >> 17 : (($k1 & 0x7fffffff) >> 17) | 0x4000); 139 | $k1 = ((($k1 & 0xffff) * 0x1b873593) + ((((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0x1b873593) & 0xffff) << 16)) & 0xffffffff; 140 | $h1 ^= $k1; 141 | } 142 | $h1 ^= $klen; 143 | $h1 ^= ($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000); 144 | $h1 = ((($h1 & 0xffff) * 0x85ebca6b) + ((((($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000)) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; 145 | $h1 ^= ($h1 >= 0 ? $h1 >> 13 : (($h1 & 0x7fffffff) >> 13) | 0x40000); 146 | $h1 = (((($h1 & 0xffff) * 0xc2b2ae35) + ((((($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000)) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; 147 | $h1 ^= ($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000); 148 | 149 | return $as_integer ? $h1 : base_convert($h1 ,10, 32); 150 | } 151 | 152 | public static function random($bytes=9){ 153 | return strtr(base64_encode(static::random_bytes($bytes)),'+/=','-_'); 154 | } 155 | 156 | public static function random_bytes($bytes){ 157 | static $randf = null; 158 | if (function_exists('random_bytes')) { 159 | return \random_bytes($bytes); 160 | } else if (function_exists('mcrypt_create_iv')) { 161 | return @\mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); 162 | } else { 163 | if (null === $randf) { 164 | if ($randf = fopen('/dev/urandom', 'rb')) { 165 | $st = fstat($randf); 166 | function_exists('stream_set_read_buffer') 167 | && stream_set_read_buffer($randf, 8); 168 | function_exists('stream_set_chunk_size') 169 | && stream_set_chunk_size($randf, 8); 170 | if (($st['mode'] & 0170000) !== 020000) { 171 | fclose($randf); 172 | $randf = false; 173 | } 174 | } 175 | } 176 | if ($randf) { 177 | $remaining = $bytes; 178 | $buf = ''; 179 | do { 180 | $read = fread($randf, $remaining); 181 | if ($read === false) { 182 | $buf = false; 183 | break; 184 | } 185 | $remaining -= strlen($read); 186 | $buf .= $read; 187 | } while ($remaining > 0); 188 | if ($buf !== false) { 189 | if (strlen($buf) === $bytes) { 190 | return $buf; 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /classes/Job.php: -------------------------------------------------------------------------------- 1 | type = $type; 53 | $job->created_at = $now; 54 | $job->scheduled_at = $when ? gmdate("Y-m-d H:i:s",(is_int($when) ? $when : (strtotime($when)?:time()))) : $now; 55 | $job->payload = $payload !== null ? serialize($payload) : null; 56 | $job->save(); 57 | } 58 | 59 | public static function register($type, $callback){ 60 | self::on("worker[{$type}]", $callback); 61 | } 62 | 63 | public static function cleanQueue($all=false){ 64 | $statuses = $all ? "'DONE','ERROR'" : "'DONE'"; 65 | return SQL::exec("DELETE FROM `".static::persistenceOptions('table')."` WHERE `status` IN ($statuses)"); 66 | } 67 | 68 | public static function execute(){ 69 | $condition = "status = 'PENDING' and `scheduled_at` <= NOW() ORDER BY `scheduled_at` ASC LIMIT 1"; 70 | if (($job = static::where($condition)) && ($job = current($job))){ 71 | // Lock chosen job rapidly 72 | SQL::update(static::persistenceOptions('table'),['id' => $job->id, 'status' => 'ACTIVE']); 73 | $job->run(); 74 | return $job; 75 | } else return false; 76 | } 77 | 78 | public function run(){ 79 | $this->status = 'ACTIVE'; 80 | $this->activated_at = gmdate("Y-m-d H:i:s"); 81 | $this->tries++; 82 | $this->save(); 83 | $this->status = 'DONE'; 84 | self::trigger("worker[{$this->type}]", $this, $this->payload ? unserialize($this->payload) : null); 85 | $this->save(); 86 | } 87 | 88 | public function error($message=null){ 89 | $this->status = 'ERROR'; 90 | $this->error = $message; 91 | } 92 | 93 | public function retry($message=null){ 94 | $this->status = 'PENDING'; 95 | $this->error = $message; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /classes/Loader.php: -------------------------------------------------------------------------------- 1 | $v) { 34 | $file = rtrim($path,'/').'/'.$cfile; 35 | if(is_file($file)) return include($file); 36 | } 37 | return false; 38 | },false,true); 39 | } 40 | 41 | } 42 | 43 | // Automatically register core classes. 44 | Loader::addPath(__DIR__); 45 | Loader::register(); 46 | -------------------------------------------------------------------------------- /classes/Map.php: -------------------------------------------------------------------------------- 1 | fields; 24 | } 25 | 26 | /** 27 | * Get a value assigned to a key path from the map 28 | * @param string $key The key path of the value in dot notation 29 | * @param mixed $default (optional) The default value. If is a callable it will executed and the return value will be used. 30 | * @return mixed The value of the key or the default (resolved) value if the key not existed. 31 | */ 32 | public function get($key, $default=null){ 33 | if (null !== ($ptr =& $this->find($key,false))){ 34 | return $ptr; 35 | } else { 36 | if ($default !== null){ 37 | return $this->set($key, is_callable($default) ? call_user_func($default) : $default); 38 | } else { 39 | return null; 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Set a value for a key path from map 46 | * @param string $key The key path of the value in dot notation 47 | * @param mixed $value (optional) The value. If is a callable it will executed and the return value will be used. 48 | * @return mixed The value of the key or the default (resolved) value if the key not existed. 49 | */ 50 | public function set($key, $value=null){ 51 | if (is_array($key)) { 52 | return $this->merge($key); 53 | } else { 54 | $ptr =& $this->find($key, true); 55 | return $ptr = $value; 56 | } 57 | } 58 | 59 | /** 60 | * Delete a value and the key path from map. 61 | * @param string $key The key path in dot notation 62 | * @param boolean $compact (optional) Compact map removing empty paths. 63 | */ 64 | public function delete($key, $compact=true){ 65 | $this->set($key, null); 66 | if ($compact) $this->compact(); 67 | } 68 | 69 | /** 70 | * Check if a key path exists in map. 71 | * @param string $key The key path in dot notation 72 | * @return boolean 73 | */ 74 | public function exists($key){ 75 | return null !== $this->find($key, false); 76 | } 77 | 78 | /** 79 | * Clear all key path in map. 80 | */ 81 | public function clear(){ 82 | $this->fields = []; 83 | } 84 | 85 | public function __construct($fields=null){ 86 | $this->load($fields); 87 | } 88 | 89 | /** 90 | * Load an associative array/object as the map source. 91 | * @param string $fields The array to merge 92 | */ 93 | public function load($fields){ 94 | if ($fields) $this->fields = (array)$fields; 95 | } 96 | 97 | /** 98 | * Merge an associative array to the map. 99 | * @param array $array The array to merge 100 | * @param boolean $merge_back If `true` merge the map over the $array, if `false` (default) the reverse. 101 | */ 102 | public function merge($array, $merge_back=false){ 103 | $this->fields = $merge_back 104 | ? array_replace_recursive((array)$array, $this->fields) 105 | : array_replace_recursive($this->fields, (array)$array); 106 | } 107 | 108 | /** 109 | * Compact map removing empty paths 110 | */ 111 | 112 | public function compact(){ 113 | 114 | $array_filter_rec = function($input, $callback = null) use (&$array_filter_rec) { 115 | foreach ($input as &$value) { 116 | if (is_array($value)) { 117 | $value = $array_filter_rec($value, $callback); 118 | } 119 | } 120 | return array_filter($input, $callback); 121 | }; 122 | 123 | $this->fields = $array_filter_rec($this->fields,function($a){ return $a !== null; }); 124 | } 125 | 126 | /** 127 | * Navigate map and find the element from the path in dot notation. 128 | * @param string $path Key path in dot notation. 129 | * @param boolean $create If true will create empty paths. 130 | * @param callable If passed this callback will be applied to the founded value. 131 | * @return mixed The founded value. 132 | */ 133 | public function & find($path, $create=false, callable $operation=null) { 134 | $create ? $value =& $this->fields : $value = $this->fields; 135 | foreach (explode('.',$path) as $tok) if ($create || isset($value[$tok])) { 136 | $value =& $value[$tok]; 137 | } else { 138 | $value = $create ? $value : null; break; 139 | } 140 | if ( is_callable($operation) ) $operation($value); 141 | return $value; 142 | } 143 | 144 | /** 145 | * JsonSerializable Interface handler 146 | * 147 | * @method jsonSerialize 148 | * 149 | * @return string The json object 150 | */ 151 | public function jsonSerialize(){ 152 | return $this->fields; 153 | } 154 | 155 | 156 | } 157 | -------------------------------------------------------------------------------- /classes/Message.php: -------------------------------------------------------------------------------- 1 | {$key}); 21 | return $results; 22 | }, []); 23 | } 24 | 25 | public static function count($where_sql = false, $params = []) { 26 | return (int) SQL::value('SELECT COUNT(1) FROM ' . static::persistenceOptions('table') . ($where_sql ? " where {$where_sql}" : ''), $params); 27 | } 28 | 29 | public static function all($page=1, $limit=-1){ 30 | return static::where($limit < 1 ? "" : "1 limit {$limit} offset " . (max(1,$page)-1)*$limit); 31 | } 32 | 33 | public function primaryKey(){ 34 | $key = static::persistenceOptions('key'); 35 | return $this->{$key}; 36 | } 37 | 38 | public static function create($data){ 39 | $tmp = new static; 40 | $data = (object)$data; 41 | foreach (array_keys(get_object_vars($tmp)) as $key) { 42 | if (isset($data->{$key})) $tmp->{$key} = $data->{$key}; 43 | } 44 | $tmp->save(); 45 | static::trigger('create',$tmp,$data); 46 | return $tmp; 47 | } 48 | 49 | public function export($transformer=null, $disabled_relations=[]){ 50 | $data = []; 51 | if (!is_callable($transformer)) $transformer = function($k,$v){ return [$k=>$v]; }; 52 | foreach (get_object_vars($this) as $key => $value) { 53 | if ($res = $transformer($key, $value)){ 54 | $data[key($res)] = current($res); 55 | } 56 | } 57 | 58 | foreach (static::relationOptions()->links as $hash => $link) { 59 | $relation = $link->method; 60 | // Expand relations but protect from self-references loop 61 | if (isset($disabled_relations[$hash])) continue; 62 | $disabled_relations[$hash] = true; 63 | $value = $this->$relation; 64 | if ($value && is_array($value)) 65 | foreach ($value as &$val) $val = $val->export(null,$disabled_relations); 66 | else 67 | $value = $value ? $value->export(null,$disabled_relations) : false; 68 | unset($disabled_relations[$hash]); 69 | 70 | if ($res = $transformer($relation, $value)){ 71 | $data[key($res)] = current($res); 72 | } 73 | 74 | } 75 | return $data; 76 | } 77 | 78 | public function jsonSerialize() { 79 | return $this->export(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /classes/Module.php: -------------------------------------------------------------------------------- 1 | bindTo($this, $this), $args); 19 | if (get_parent_class()) 20 | return parent::__call($name, $args); 21 | else throw new \BadMethodCallException; 22 | } 23 | 24 | final public static function __callStatic($name, $args){ 25 | if (isset(static::$__PROTOTYPE__[$name]) && static::$__PROTOTYPE__[$name] instanceof \Closure) 26 | return forward_static_call_array(static::$__PROTOTYPE__[$name], $args); 27 | if (get_parent_class()) return parent::__callStatic($name, $args); 28 | else throw new \BadMethodCallException; 29 | } 30 | 31 | public static function extend($method, $callback=null){ 32 | $methods = ($callback === null && is_array($method)) ? $method : [$method=>$callback]; 33 | foreach ($methods as $name => $meth) { 34 | if ($meth && $meth instanceof \Closure) 35 | static::$__PROTOTYPE__[$name] = $meth; 36 | else throw new \BadMethodCallException; 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /classes/Negotiation.php: -------------------------------------------------------------------------------- 1 | current($p[0]) ], array_combine($p[2], $p[3])) 22 | ); 23 | unset($params['']); 24 | $params['q'] = isset($params['q']) ? 1.0*$params['q'] : $params['q'] = 1.0; 25 | $list->insert($params, $params['q']); 26 | },preg_split('(\s*,\s*)', $query)); 27 | return array_values(iterator_to_array($list)); 28 | } 29 | 30 | public static function bestMatch($acceptables, $choices) { 31 | return (new self($acceptables))->best($choices); 32 | } 33 | 34 | public function __construct($query) { 35 | $this->list = self::parse(trim($query)); 36 | } 37 | 38 | public function preferred(){ 39 | return self::encodeParsedValue(current($this->list)); 40 | } 41 | 42 | protected static function encodeParsedValue($parsed){ 43 | unset($parsed['q']); // Hide quality key from output 44 | $type = $parsed['type']; // Extract type 45 | unset($parsed['type']); 46 | return implode(';', array_merge([$type], array_map(function($k,$v){ 47 | return "$k=$v"; 48 | }, array_keys($parsed), $parsed))); 49 | } 50 | 51 | public function best($choices){ 52 | $_choices = self::parse(trim($choices)); 53 | foreach ($this->list as $accept){ 54 | foreach ($_choices as $choice){ 55 | if (preg_match('('.strtr($accept["type"], 56 | [ '.' => '\.', '+' => '\+', '*' => '.+' ] 57 | ).')', $choice["type"])){ 58 | return self::encodeParsedValue($choice); 59 | } 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | } /* End of class */ 66 | 67 | -------------------------------------------------------------------------------- /classes/Options.php: -------------------------------------------------------------------------------- 1 | 12]); 27 | } 28 | } 29 | 30 | /** 31 | * Verify if password match a given hash 32 | * @param string $password The password to check 33 | * @param string $hash The hash to match against 34 | * @return bool Returns `true` if hash match password 35 | */ 36 | public static function verify($password, $hash){ 37 | // Pre PHP 5.5 support 38 | if (!defined('PASSWORD_DEFAULT') || substr($hash,0,4)=='$5h$') { 39 | return '$5h$'.hash('sha1',$password) == $hash; 40 | } else { 41 | return password_verify($password,$hash); 42 | } 43 | } 44 | 45 | /** 46 | * Helper for secure time-constant string comparison 47 | * Protect from time-based brute force attacks. 48 | * @param string $a First string to compare 49 | * @param string $b Second string to compare 50 | * @return boll Returns `true` if strings are the same 51 | */ 52 | public static function compare($a, $b){ 53 | return hash_equals($a, $b); 54 | } 55 | 56 | } 57 | 58 | 59 | // Polyfill hash_equals (PHP < 5.6.0) 60 | // http://php.net/manual/en/function.hash-equals.php 61 | if(!function_exists('hash_equals')) { 62 | function hash_equals($a, $b) { 63 | return substr_count("$a" ^ "$b", "\0") * 2 === strlen("$a$b"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /classes/Persistence.php: -------------------------------------------------------------------------------- 1 | null,'key'=>'id']; 25 | if ($options === null) return $_options; 26 | 27 | if (is_array($options)) { 28 | foreach ($_options as $key => &$value) { 29 | if (isset($options[$key])) $value = $options[$key]; 30 | } 31 | return $_options; 32 | } else { 33 | if (empty($_options['table'])) { 34 | $self = get_called_class(); 35 | if (defined("$self::_PRIMARY_KEY_")){ 36 | $x = explode('.', $self::_PRIMARY_KEY_); 37 | $_options = [ 38 | 'table' => current($x), 39 | 'key' => isset($x[1])?$x[1]:'id', 40 | ]; 41 | } else { 42 | // User pluralized class name as default table 43 | switch(substr($s = strtolower($self),-1)){ 44 | case 'y': $table = substr($s,0,-1).'ies'; break; 45 | case 's': $table = substr($s,0,-1).'es'; break; 46 | default: $table = $s.'s'; break; 47 | } 48 | // Default ID 49 | $_options = [ 50 | 'table' => $table, 51 | 'key' => 'id', 52 | ]; 53 | } 54 | } 55 | return isset($_options[$options]) ? $_options[$options] : ''; 56 | } 57 | } 58 | 59 | /** 60 | * [Internal] : Assigns or retrieve the Save callback 61 | * The save callback interface is 62 | * function($table, array $options) 63 | * 64 | * @param callable $callback The callback to use on model save 65 | * @return callable Current save callback 66 | */ 67 | protected static function persistenceSave(callable $callback=null){ 68 | static $save_cb = null; 69 | return $callback ? $save_cb = $callback : $save_cb; 70 | } 71 | 72 | /** 73 | * [Internal] : Assigns or load the Load callback 74 | * The load callback interface is 75 | * function($table, array $options) 76 | * 77 | * @param callable $callback The callback to use on model load 78 | * @return callable Current load callback 79 | */ 80 | protected static function persistenceLoad(callable $callback=null){ 81 | static $retrieve_cb = null; 82 | return $callback ? $retrieve_cb = $callback : $retrieve_cb; 83 | } 84 | 85 | 86 | /** 87 | * Enable peristence on `$table` with `$options` 88 | * Avaiable options: 89 | * `key` : The column name of the primary key, default to `id`. 90 | * 91 | * @param string $table The table name 92 | * @param array $options An associative array with options for the persistance layer. 93 | * @return void 94 | */ 95 | public static function persistOn($table, array $options=[]){ 96 | $options['table'] = $table; 97 | static::persistenceOptions($options); 98 | } 99 | 100 | 101 | /** 102 | * Override standard save function with a new callback 103 | * @param callable $callback The callback to use on model save 104 | * @return void 105 | */ 106 | public static function onSave(callable $callback){ 107 | static::persistenceSave($callback); 108 | } 109 | 110 | /** 111 | * Override standard load function with a new callback 112 | * @param callable $callback The callback to use on model load 113 | * @return void 114 | */ 115 | public static function onLoad(callable $callback){ 116 | static::persistenceLoad($callback); 117 | } 118 | 119 | /** 120 | * Load the model from the persistence layer 121 | * @return mixed The retrieved object 122 | */ 123 | public static function load($pk){ 124 | $table = static::persistenceOptions('table'); 125 | $cb = static::persistenceLoad(); 126 | $op = static::persistenceOptions(); 127 | 128 | // Use standard persistence on DB layer 129 | return ( false == is_callable($cb) ) ? 130 | static::persistenceLoadDefault($pk,$table,$op) : $cb($pk,$table,$op); 131 | } 132 | 133 | /** 134 | * Private Standard Load Method 135 | */ 136 | private static function persistenceLoadDefault($pk, $table, $options){ 137 | if ( $data = SQL::single("SELECT * FROM $table WHERE {$options['key']}=? LIMIT 1",[$pk]) ){ 138 | $obj = new static; 139 | foreach ((array)$data as $key => $value) { 140 | $obj->$key = $value; 141 | } 142 | if (is_callable(($c=get_called_class())."::trigger")) $c::trigger("load", $obj, $table, $options['key']); 143 | return $obj; 144 | } else { 145 | return null; 146 | } 147 | } 148 | 149 | /** 150 | * Save the model to the persistence layer 151 | * @return mixed The results from the save callback. (default: lastInsertID) 152 | */ 153 | public function save(){ 154 | $table = static::persistenceOptions('table'); 155 | $op = static::persistenceOptions(); 156 | $cb = static::persistenceSave(); 157 | 158 | // Use standard persistence on DB layer 159 | $cb = $cb ? Closure::bind($cb, $this) : [$this,'persistenceSaveDefault']; 160 | return $cb($table,$op); 161 | } 162 | 163 | /** 164 | * Private Standard Save Method 165 | */ 166 | private function persistenceSaveDefault($table,$options){ 167 | if (is_callable(($c=get_called_class())."::trigger")) $c::trigger("save", $this, $table, $options['key']); 168 | $id = SQL::insertOrUpdate($table,array_filter((array)$this, function($var) { 169 | return !is_null($var); 170 | }),$options['key']); 171 | // Populate retrieved primary key in Models. 172 | if (null !== $id && is_a($this, 'Model')) $this->{$options['key']} = $id; 173 | return $id; 174 | } 175 | 176 | 177 | } 178 | -------------------------------------------------------------------------------- /classes/Redirect.php: -------------------------------------------------------------------------------- 1 | '.($parent?'parent.':'').'location.href="'.addslashes($link).'"'); 39 | Response::send(); 40 | exit; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /classes/Relation.php: -------------------------------------------------------------------------------- 1 | [], 'relations' => [] ]; 26 | return $_options; 27 | } 28 | 29 | /** 30 | * [Internal] : Assigns or retrieve the Save callback 31 | * The save callback interface is 32 | * function($table, array $options) 33 | * 34 | * @param callable $callback The callback to use on model save 35 | * @return callable Current save callback 36 | */ 37 | private static function relationAddRelationshipTo($link, $plurality, $extra=[]){ 38 | $options = static::relationOptions(); 39 | 40 | preg_match('((?\w+)(\.(?\w+))?(:(?\w+))?)', $link, $parts); 41 | 42 | $foreign_class = isset($parts['FOREIGN_CLASS']) ? $parts['FOREIGN_CLASS'] : false; 43 | $foreign_key = isset($parts['FOREIGN_KEY']) ? $parts['FOREIGN_KEY'] : false; 44 | $local_key = isset($parts['LOCAL_KEY']) ? $parts['LOCAL_KEY'] : false; 45 | 46 | if ( ! $foreign_class ) 47 | throw new Exception("[Core.Relation] Class ".get_called_class()." must define a foreign Model when assigning a relation.", 1); 48 | 49 | if ( ! is_subclass_of($foreign_class,"Model") ) 50 | throw new Exception("[Core.Relation] Class ".get_called_class()." want to relate to $foreign_class but it's not a Model.", 1); 51 | 52 | if ( ! $foreign_key ) { 53 | // Retrieve primary key from foreign class 54 | $foreign_key = $foreign_class::persistenceOptions("key"); 55 | } 56 | 57 | if ( ! $local_key ) { 58 | // Retrieve local primary key 59 | $local_key = static::persistenceOptions("key"); 60 | } 61 | 62 | $single = $plurality == 'single'; 63 | 64 | $method = preg_replace_callback('([A-Z])', function($m){ 65 | return "_" . strtolower($m[0]); 66 | }, lcfirst($foreign_class) . ($single ? '' : 's')); 67 | 68 | $hh = [$foreign_class,$foreign_key,$local_key]; 69 | sort($hh); 70 | $options->links[md5(serialize($hh))] = $rel = (object)[ 71 | 'foreign_class' => $foreign_class, 72 | 'foreign_key' => $foreign_key, 73 | 'local_key' => $local_key, 74 | 'single' => $single, 75 | 'method' => $method, 76 | 'extra' => (object) $extra, 77 | ]; 78 | 79 | if (empty($options->relations)) $options->relations = (object)[]; 80 | $options->relations->$method = $getset = (object)[ 81 | 'get' => function($self) use ($foreign_class, $rel) { 82 | $val = $self->{$rel->local_key}; 83 | $val = is_numeric($val) ? $val : "'" . addslashes($val) . "'"; 84 | $data = $foreign_class::where("{$rel->foreign_key} = {$val}"); 85 | return $rel->single ? current($data) : $data; 86 | }, 87 | 'set' => function($value, $self) use ($foreign_class, $rel) { 88 | if (!is_a($value, $foreign_class)) 89 | throw new Exception("[Core.Relation] Relationship for {$rel->method} must be of class $foreign_class.", 1); 90 | $self->local_key = $value->foreign_key; 91 | return $value; 92 | }, 93 | ]; 94 | 95 | } 96 | 97 | public function __get($name){ 98 | $options = static::relationOptions(); 99 | if (isset($options->relations->$name)) 100 | return call_user_func($options->relations->$name->get, $this); 101 | } 102 | 103 | public function __set($name, $value){ 104 | $options = static::relationOptions(); 105 | if (isset($options->relations->$name)) 106 | call_user_func($options->relations->$name->set, $value, $this); 107 | } 108 | 109 | public function __isset($name){ 110 | $options = static::relationOptions(); 111 | return isset($options->relations->$name); 112 | } 113 | 114 | public static function hasOne($modelName, $extra=[]){ 115 | return static::relationAddRelationshipTo($modelName, 'single', $extra); 116 | } 117 | 118 | public static function hasMany($modelName, $extra=[]){ 119 | return static::relationAddRelationshipTo($modelName, 'multiple', $extra); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /classes/Request.php: -------------------------------------------------------------------------------- 1 | new Negotiation(isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : ''), 30 | 'language' => new Negotiation(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : ''), 31 | 'encoding' => new Negotiation(isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : ''), 32 | 'charset' => new Negotiation(isset($_SERVER['HTTP_ACCEPT_CHARSET']) ? $_SERVER['HTTP_ACCEPT_CHARSET'] : ''), 33 | ]; 34 | return empty(static::$accepts[$key]) 35 | ? false 36 | : ( empty($choices) 37 | ? static::$accepts[$key]->preferred() 38 | : static::$accepts[$key]->best($choices) 39 | ); 40 | } 41 | 42 | /** 43 | * Retrive a value from generic input (from the $_REQUEST array) 44 | * Returns all elements if you pass `null` as $key 45 | * 46 | * @param string $key The name of the input value 47 | * 48 | * @return Structure The returned value or $default. 49 | */ 50 | public static function input($key=null,$default=null){ 51 | return $key ? (isset($_REQUEST[$key]) ? new Structure($_REQUEST[$key]) : (is_callable($default)?call_user_func($default):$default)) : new Structure($_REQUEST[$key]); 52 | } 53 | 54 | /** 55 | * Retrive a value from environment (from the $_ENV array) 56 | * Returns all elements if you pass `null` as $key 57 | * 58 | * @param string $key The name of the input value 59 | * 60 | * @return Object The returned value or $default. 61 | */ 62 | public static function env($key=null,$default=null){ 63 | return $key ? (filter_input(INPUT_ENV,$key) ?: (is_callable($default)?call_user_func($default):$default)) : $_ENV; 64 | } 65 | 66 | /** 67 | * Retrive a value from server (from the $_SERVER array) 68 | * Returns all elements if you pass `null` as $key 69 | * 70 | * @param string $key The name of the input value 71 | * 72 | * @return Object The returned value or $default. 73 | */ 74 | public static function server($key=null,$default=null){ 75 | return $key ? (isset($_SERVER[$key]) ? $_SERVER[$key] : (is_callable($default)?call_user_func($default):$default)) : $_SERVER; 76 | } 77 | 78 | /** 79 | * Retrive a value from generic input (from the $_POST array) 80 | * Returns all elements if you pass `null` as $key 81 | * 82 | * @param string $key The name of the input value 83 | * 84 | * @return Object The returned value or $default. 85 | */ 86 | public static function post($key=null,$default=null){ 87 | return $key ? (filter_input(INPUT_POST,$key) ?: (is_callable($default)?call_user_func($default):$default)) : $_POST; 88 | } 89 | 90 | /** 91 | * Retrive a value from generic input (from the $_GET array) 92 | * Returns all elements if you pass `null` as $key 93 | * 94 | * @param string $key The name of the input value 95 | * 96 | * @return Object The returned value or $default. 97 | */ 98 | public static function get($key=null,$default=null){ 99 | return $key ? (filter_input(INPUT_GET,$key) ?: (is_callable($default)?call_user_func($default):$default)) : $_GET; 100 | } 101 | 102 | /** 103 | * Retrive uploaded file (from the $_FILES array) 104 | * Returns all uploaded files if you pass `null` as $key 105 | * 106 | * @param string $key The name of the input value 107 | * 108 | * @return Object The returned value or $default. 109 | */ 110 | public static function files($key=null,$default=null){ 111 | return $key ? (isset($_FILES[$key]) ? $_FILES[$key] : (is_callable($default)?call_user_func($default):$default)) : $_FILES; 112 | } 113 | 114 | /** 115 | * Retrive cookie (from the $_COOKIE array) 116 | * Returns all cookies if you pass `null` as $key 117 | * 118 | * @param string $key The name of the input value 119 | * 120 | * @return Object The returned value or $default. 121 | */ 122 | public static function cookie($key=null,$default=null){ 123 | return $key ? (filter_input(INPUT_COOKIE,$key) ?: (is_callable($default)?call_user_func($default):$default)) : $_COOKIE; 124 | } 125 | 126 | /** 127 | * Returns the current host and port (omitted if port 80). 128 | * 129 | * @param bool $with_protocol Pass true to prepend protocol to hostname 130 | * 131 | * @return string 132 | */ 133 | public static function host($with_protocol=true){ 134 | switch(true){ 135 | case !empty($_SERVER['HTTP_X_FORWARDED_HOST']) : 136 | $host = trim(substr(strrchr($_SERVER['HTTP_X_FORWARDED_HOST'],','),1) ?: $_SERVER['HTTP_X_FORWARDED_HOST']); 137 | break; 138 | case !empty($_SERVER['HTTP_HOST']) : $host = $_SERVER['HTTP_HOST']; break; 139 | case !empty($_SERVER['SERVER_NAME']) : $host = $_SERVER['SERVER_NAME']; break; 140 | case !empty($_SERVER['HOSTNAME']) : $host = $_SERVER['HOSTNAME']; break; 141 | default : $host = 'localhost'; break; 142 | } 143 | $host = explode(':',$host,2); 144 | $port = isset($host[1]) ? (int)$host[1] : (isset($_SERVER['SERVER_PORT'])?$_SERVER['SERVER_PORT']:80); 145 | $host = $host[0] . (($port && $port != 80) ? ":$port" : ''); 146 | if ($port == 80) $port = ''; 147 | return ($with_protocol ? 'http' . (!empty($_SERVER['HTTPS'])&&(strtolower($_SERVER['HTTPS'])!=='off')?'s':'') . '://' : '') 148 | . Filter::with('core.request.host',$host); 149 | } 150 | 151 | /** 152 | * Returns the current request URL, complete with host and protocol. 153 | * 154 | * @return string 155 | */ 156 | public static function URL(){ 157 | return static::host(true) . static::URI(); 158 | } 159 | 160 | /** 161 | * Retrive header 162 | * Returns all headers if you pass `null` as $key 163 | * 164 | * @param string $key The name of the input value 165 | * 166 | * @return Object The returned value or null. 167 | */ 168 | public static function header($key=null,$default=null){ 169 | if ($key) $key = 'HTTP_'.strtr(strtoupper($key),'-','_'); 170 | return $key ? (isset($_SERVER[$key])? $_SERVER[$key] : (is_callable($default)?call_user_func($default):$default)) : array_filter($_SERVER, function($k) { 171 | return strrpos($k, "HTTP_") === 0; 172 | }, ARRAY_FILTER_USE_KEY); 173 | } 174 | 175 | /** 176 | * Returns the current request URI. 177 | * 178 | * @return string 179 | */ 180 | public static function URI(){ 181 | switch(true){ 182 | case !empty($_SERVER['REQUEST_URI']): $serv_uri = $_SERVER['REQUEST_URI']; break; 183 | case !empty($_SERVER['ORIG_PATH_INFO']): $serv_uri = $_SERVER['ORIG_PATH_INFO']; break; 184 | case !empty($_SERVER['PATH_INFO']): $serv_uri = $_SERVER['PATH_INFO']; break; 185 | default: $serv_uri = '/'; break; 186 | } 187 | $uri = rtrim(strtok($serv_uri,'?'),'/') ?: '/'; 188 | return Filter::with('core.request.URI', $uri) ?: '/'; 189 | } 190 | 191 | /** 192 | * Returns the current base URI (The front-controller directory) 193 | * 194 | * @return string 195 | */ 196 | public static function baseURI(){ 197 | return dirname(empty($_SERVER['SCRIPT_NAME']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME']) ?: '/'; 198 | } 199 | 200 | /** 201 | * Returns the HTTP Method 202 | * 203 | * @return string 204 | */ 205 | public static function method(){ 206 | return Filter::with('core.request.method',strtolower(empty($_SERVER['REQUEST_METHOD'])?'get':$_SERVER['REQUEST_METHOD'])); 207 | } 208 | 209 | /** 210 | * Returns the remote IP 211 | * 212 | * @return string 213 | */ 214 | public static function IP(){ 215 | switch(true){ 216 | case !empty($_SERVER['HTTP_X_FORWARDED_FOR']): 217 | $ip = trim(substr(strrchr($_SERVER['HTTP_X_FORWARDED_FOR'],','),1) ?: $_SERVER['HTTP_X_FORWARDED_FOR']); 218 | break; 219 | case !empty($_SERVER['HTTP_X_FORWARDED_HOST']): 220 | $ip = trim(substr(strrchr($_SERVER['HTTP_X_FORWARDED_HOST'],','),1) ?: $_SERVER['HTTP_X_FORWARDED_HOST']); 221 | break; 222 | case !empty($_SERVER['REMOTE_ADDR']): $ip = $_SERVER['REMOTE_ADDR']; break; 223 | case !empty($_SERVER['HTTP_CLIENT_IP']): $ip = $_SERVER['HTTP_CLIENT_IP']; break; 224 | default: $ip = ''; break; 225 | } 226 | return Filter::with('core.request.IP',$ip); 227 | } 228 | 229 | /** 230 | * Returns the remote UserAgent 231 | * 232 | * @return string 233 | */ 234 | public static function UA(){ 235 | return Filter::with('core.request.UA',strtolower(empty($_SERVER['HTTP_USER_AGENT'])?'':$_SERVER['HTTP_USER_AGENT'])); 236 | } 237 | 238 | /** 239 | * Returns request body data, convert to object if content type is JSON 240 | * Gives you all request data if you pass `null` as $key 241 | * 242 | * @param string $key The name of the key requested 243 | * 244 | * @return mixed The request body data 245 | */ 246 | public static function data($key=null,$default=null){ 247 | if (null===static::$body){ 248 | $json = (false !== stripos(empty($_SERVER['HTTP_CONTENT_TYPE'])?'':$_SERVER['HTTP_CONTENT_TYPE'],'json')) 249 | || (false !== stripos(empty($_SERVER['CONTENT_TYPE'])?'':$_SERVER['CONTENT_TYPE'],'json')); 250 | if ($json) { 251 | static::$body = json_decode(file_get_contents("php://input")); 252 | } else { 253 | if (empty($_POST)) { 254 | static::$body = file_get_contents("php://input"); 255 | } else { 256 | static::$body = (object)$_POST; 257 | } 258 | } 259 | } 260 | return $key ? (isset(static::$body->$key) ? static::$body->$key : (is_callable($default)?call_user_func($default):$default)) : static::$body; 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /classes/Response.php: -------------------------------------------------------------------------------- 1 | ['text/html; charset=utf-8']], 29 | $buffer = null, 30 | $force_dl = false, 31 | $link = null, 32 | $sent = false, 33 | $links = []; 34 | 35 | 36 | public static function charset($charset){ 37 | static::$charset = $charset; 38 | } 39 | 40 | public static function type($mime){ 41 | static::header('Content-Type',$mime . (static::$charset ? '; charset='.static::$charset : '')); 42 | } 43 | 44 | /** 45 | * Force download of Response body 46 | * @param mixed $data Pass a falsy value to disable download, pass a filename for exporting content or array with raw string data 47 | * @return void 48 | */ 49 | public static function download($data){ 50 | if (is_array($data)) { 51 | if (isset($data['filename'])) static::$force_dl = $data['filename']; 52 | if (isset($data['charset'])) static::charset($data['charset']); 53 | if (isset($data['mime'])) static::type($data['mime']); 54 | if (isset($data['body'])) static::body($data['body']); 55 | } else static::$force_dl = $data; 56 | } 57 | 58 | /** 59 | * Start capturing output 60 | */ 61 | public static function start(){ 62 | static::$buffer = ob_start(); 63 | } 64 | 65 | /** 66 | * Enable CORS HTTP headers. 67 | */ 68 | public static function enableCORS($origin='*'){ 69 | 70 | // Allow from any origin 71 | if ($origin = $origin ?:( isset($_SERVER['HTTP_ORIGIN']) 72 | ? $_SERVER['HTTP_ORIGIN'] 73 | : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '*') 74 | )) { 75 | static::header('Access-Control-Allow-Origin', $origin); 76 | static::header('Access-Control-Allow-Credentials', 'true'); 77 | static::header('Access-Control-Max-Age', 86400); 78 | } 79 | 80 | // Access-Control headers are received during OPTIONS requests 81 | if (filter_input(INPUT_SERVER,'REQUEST_METHOD') == 'OPTIONS') { 82 | static::clean(); 83 | 84 | if (filter_input(INPUT_SERVER,'HTTP_ACCESS_CONTROL_REQUEST_METHOD')) { 85 | static::header('Access-Control-Allow-Methods', 86 | 'GET, POST, PUT, DELETE, OPTIONS, HEAD, CONNECT, PATCH, TRACE'); 87 | } 88 | if ($req_h = filter_input(INPUT_SERVER,'HTTP_ACCESS_CONTROL_REQUEST_HEADERS')) { 89 | static::header('Access-Control-Allow-Headers',$req_h); 90 | } 91 | 92 | self::trigger('cors.preflight'); 93 | static::send(); 94 | exit; 95 | } 96 | } 97 | 98 | public static function sent() { 99 | return static::$sent; 100 | } 101 | 102 | /** 103 | * Finish the output buffer capturing. 104 | * @return string The captured buffer 105 | */ 106 | public static function end(){ 107 | if (static::$buffer){ 108 | static::$payload[] = ob_get_contents(); 109 | ob_end_clean(); 110 | static::$buffer = null; 111 | return end(static::$payload); 112 | } 113 | } 114 | 115 | /** 116 | * Check if an response output buffering is active. 117 | * @return boolean 118 | */ 119 | public static function isBuffering(){ 120 | return static::$buffer; 121 | } 122 | 123 | /** 124 | * Clear the response body 125 | */ 126 | public static function clean(){ 127 | static::$payload = []; 128 | static::$headers = []; 129 | } 130 | 131 | /** 132 | * Append a JSON object to the buffer. 133 | * @param mixed $payload Data to append to the response buffer 134 | */ 135 | public static function json($payload){ 136 | static::type(static::TYPE_JSON); 137 | static::$payload[] = json_encode($payload, Options::get('core.response.json_flags',JSON_NUMERIC_CHECK|JSON_BIGINT_AS_STRING)); 138 | } 139 | 140 | /** 141 | * Append a text to the buffer. 142 | * @param mixed $payload Text to append to the response buffer 143 | */ 144 | public static function text(...$args){ 145 | static::type(static::TYPE_TEXT); 146 | static::$payload[] = implode('',$args); 147 | } 148 | 149 | /** 150 | * Append an XML string to the buffer. 151 | * @param mixed $payload Data to append to the response buffer 152 | */ 153 | public static function xml(...$args){ 154 | static::type(static::TYPE_XML); 155 | static::$payload[] = implode('', $args); 156 | } 157 | 158 | /** 159 | * Append a SVG string to the buffer. 160 | * @param mixed $payload Data to append to the response buffer 161 | */ 162 | public static function svg(...$args){ 163 | static::type(static::TYPE_SVG); 164 | static::$payload[] = implode('', $args); 165 | } 166 | 167 | /** 168 | * Append an HTML string to the buffer. 169 | * @param mixed $payload Data to append to the response buffer 170 | */ 171 | public static function html(...$args){ 172 | static::type(static::TYPE_HTML); 173 | static::$payload[] = implode('', $args); 174 | } 175 | 176 | /** 177 | * Append data to the buffer. 178 | * Rules : 179 | * - Callables will be called and their results added (recursive) 180 | * - Views will be rendered 181 | * - Objects, arrays and bools will be JSON encoded 182 | * - Strings and numbers will be appendend to the response 183 | * 184 | * @param mixed $payload Data to append to the response buffer 185 | */ 186 | public static function add(){ 187 | foreach(func_get_args() as $data){ 188 | switch (true) { 189 | case is_callable($data) : 190 | return static::add($data()); 191 | case is_a($data, 'View') : 192 | return static::$payload[] = "$data"; 193 | case is_object($data) || is_array($data) || is_bool($data): 194 | return static::json($data); 195 | default: 196 | return static::$payload[] = $data; 197 | } 198 | } 199 | } 200 | 201 | public static function status($code,$message=''){ 202 | static::header('Status',$message?:$code,$code); 203 | } 204 | 205 | public static function header($name,$value,$code=null){ 206 | if (empty(static::$headers[$name])){ 207 | static::$headers[$name] = [[$value,$code]]; 208 | } else { 209 | static::$headers[$name][] = [$value,$code]; 210 | } 211 | } 212 | 213 | public static function error($code=500,$message='Application Error'){ 214 | static::trigger('error',$code,$message); 215 | Event::trigger('core.response.error',$code,$message); 216 | static::status($code,$message); 217 | } 218 | 219 | public static function body($setBody=null){ 220 | if ($setBody) static::$payload = [$setBody]; 221 | return Filter::with('core.response.body', 222 | is_array(static::$payload) ? implode('',static::$payload) : static::$payload 223 | ); 224 | } 225 | 226 | public static function headers($setHeaders=null){ 227 | if ($setHeaders) static::$headers = $setHeaders; 228 | return static::$headers; 229 | } 230 | 231 | /** 232 | * Save response as an object, for serialization or cache storage 233 | * 234 | * @method save 235 | * 236 | * @return array Headers and body of the response 237 | */ 238 | public static function save(){ 239 | return [ 240 | 'head' => static::$headers, 241 | 'body' => static::body(), 242 | ]; 243 | } 244 | 245 | /** 246 | * Load response from a saved state 247 | * 248 | * @method load 249 | * 250 | * @param array $data head/body saved state 251 | */ 252 | public static function load($data){ 253 | $data = (object)$data; 254 | if (isset($data->head)) static::headers($data->head); 255 | if (isset($data->body)) static::body($data->body); 256 | } 257 | 258 | public static function send($force = false){ 259 | if (!static::$sent || $force) { 260 | static::$sent = true; 261 | static::trigger('send'); 262 | Event::trigger('core.response.send'); 263 | if (false === headers_sent()) foreach (static::$headers as $name => $family) 264 | foreach ($family as $value_code) { 265 | 266 | if (is_array($value_code)) { 267 | list($value, $code) = (count($value_code) > 1) ? $value_code : [current($value_code), 200]; 268 | } else { 269 | $value = $value_code; 270 | $code = null; 271 | } 272 | 273 | switch($value){ 274 | case "Status": 275 | if (function_exists('http_response_code')){ 276 | http_response_code($code); 277 | } else { 278 | header("Status: $code", true, $code); 279 | } 280 | break; 281 | case "Link": 282 | header("Link: $value", false); 283 | break; 284 | default: 285 | if ($code) { 286 | header("$name: $value", true, $code); 287 | } else { 288 | header("$name: $value", true); 289 | } 290 | break; 291 | } 292 | } 293 | if (static::$force_dl) header('Content-Disposition: attachment; filename="'.static::$force_dl.'"'); 294 | echo static::body(); 295 | static::trigger('sent'); 296 | } 297 | } 298 | 299 | 300 | /** 301 | * Push resources to client (HTTP/2 spec) 302 | * @param string/array $links The link(s) to the resources to push. 303 | * @return Response The Route object 304 | */ 305 | public static function push($links, $type='text'){ 306 | if (is_array($links)){ 307 | foreach($links as $_type => $link) { 308 | // Extract URL basename extension (query-safe version) 309 | if (is_numeric($_type)) switch(strtolower(substr(strrchr(strtok(basename($link),'?'),'.'),1))) { 310 | case 'js': $_type = 'script'; break; 311 | case 'css': $_type = 'style'; break; 312 | case 'png': case 'svg': case 'gif': case 'jpg': $_type = 'image'; break; 313 | case 'woff': case 'woff2': case 'ttf': case 'eof': $_type = 'font'; break; 314 | default: $_type = 'text'; break; 315 | } 316 | foreach ((array)$link as $link_val) { 317 | static::header("Link","<$link_val>; rel=preload; as=$_type"); 318 | } 319 | } 320 | } else { 321 | static::header("Link","<".((string)$links).">; rel=preload; as=$type"); 322 | } 323 | } 324 | 325 | } 326 | -------------------------------------------------------------------------------- /classes/Route.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 53 | 54 | // Adjust / optionality with dynamic patterns 55 | // Ex: /test/(:a) ===> /test(/:a) 56 | $this->URLPattern = str_replace('//','/',str_replace('/(','(/', rtrim("{$prefix}{$pattern}","/"))); 57 | 58 | $this->dynamic = $this->isDynamic($this->URLPattern); 59 | 60 | $this->pattern = $this->dynamic 61 | ? $this->compilePatternAsRegex($this->URLPattern, $this->rules) 62 | : $this->URLPattern; 63 | 64 | $this->matcher_pattern = $this->dynamic 65 | ? $this->compilePatternAsRegex($this->URLPattern, $this->rules, false) 66 | : ''; 67 | 68 | // We will use hash-checks, for O(1) complexity vs O(n) 69 | $this->methods[$method] = 1; 70 | return static::add($this); 71 | } 72 | 73 | /** 74 | * Check if route match on a specified URL and HTTP Method. 75 | * @param [type] $URL The URL to check against. 76 | * @param string $method The HTTP Method to check against. 77 | * @return boolean 78 | */ 79 | public function match($URL, $method='get'){ 80 | $method = strtolower($method); 81 | 82 | // * is an http method wildcard 83 | if (empty($this->methods[$method]) && empty($this->methods['*'])) return false; 84 | 85 | return (bool) ( 86 | $this->dynamic 87 | ? preg_match($this->matcher_pattern, '/'.trim($URL,'/')) 88 | : rtrim($URL,'/') == rtrim($this->pattern,'/') 89 | ); 90 | } 91 | 92 | /** 93 | * Clears all stored routes definitions to pristine conditions. 94 | * @return void 95 | */ 96 | public static function reset(){ 97 | static::$routes = []; 98 | static::$base = ''; 99 | static::$prefix = []; 100 | static::$group = []; 101 | static::$optimized_tree = []; 102 | } 103 | 104 | /** 105 | * Run one of the mapped callbacks to a passed HTTP Method. 106 | * @param array $args The arguments to be passed to the callback 107 | * @param string $method The HTTP Method requested. 108 | * @return array The callback response. 109 | */ 110 | public function run(array $args, $method='get'){ 111 | $method = strtolower($method); 112 | $append_echoed_text = Options::get('core.route.append_echoed_text',true); 113 | static::trigger('start', $this, $args, $method); 114 | 115 | // Call direct befores 116 | if ( $this->befores ) { 117 | // Reverse befores order 118 | foreach (array_reverse($this->befores) as $mw) { 119 | static::trigger('before', $this, $mw); 120 | Event::trigger('core.route.before', $this, $mw); 121 | ob_start(); 122 | $mw_result = call_user_func($mw); 123 | $raw_echoed = ob_get_clean(); 124 | if ($append_echoed_text) Response::add($raw_echoed); 125 | if ( false === $mw_result ) { 126 | return ['']; 127 | } else { 128 | Response::add($mw_result); 129 | } 130 | } 131 | } 132 | 133 | $callback = (is_array($this->callback) && isset($this->callback[$method])) 134 | ? $this->callback[$method] 135 | : $this->callback; 136 | 137 | if (is_callable($callback) || is_a($callback, "View") ) { 138 | Response::type( Options::get('core.route.response_default_type', Response::TYPE_HTML) ); 139 | 140 | ob_start(); 141 | if (is_a($callback, "View")) { 142 | // Get the rendered view 143 | $view_results = (string)$callback; 144 | } else { 145 | $view_results = call_user_func_array($callback, $args); 146 | } 147 | $raw_echoed = ob_get_clean(); 148 | 149 | if ($append_echoed_text) Response::add($raw_echoed); 150 | Response::add($view_results); 151 | } 152 | 153 | // Apply afters 154 | if ( $this->afters ) { 155 | foreach ($this->afters as $mw) { 156 | static::trigger('after', $this, $mw); 157 | Event::trigger('core.route.after', $this, $mw); 158 | ob_start(); 159 | $mw_result = call_user_func($mw); 160 | $raw_echoed = ob_get_clean(); 161 | if ($append_echoed_text) Response::add($raw_echoed); 162 | if ( false === $mw_result ) { 163 | return ['']; 164 | } else { 165 | Response::add($mw_result); 166 | } 167 | } 168 | } 169 | 170 | static::trigger('end', $this, $args, $method); 171 | Event::trigger('core.route.end', $this); 172 | 173 | return [Filter::with('core.route.response', Response::body())]; 174 | } 175 | 176 | /** 177 | * Check if route match URL and HTTP Method and run if it is valid. 178 | * @param [type] $URL The URL to check against. 179 | * @param string $method The HTTP Method to check against. 180 | * @return array The callback response. 181 | */ 182 | public function runIfMatch($URL, $method='get'){ 183 | return $this->match($URL,$method) ? $this->run($this->extractArgs($URL),$method) : null; 184 | } 185 | 186 | /** 187 | * Start a route definition, default to HTTP GET. 188 | * @param string $URLPattern The URL to match against, you can define named segments to be extracted and passed to the callback. 189 | * @param $callback The callback to be invoked (with variables extracted from the route if present) when the route match the request URI. 190 | * @return Route 191 | */ 192 | public static function on($URLPattern, $callback = null){ 193 | return new Route($URLPattern,$callback); 194 | } 195 | 196 | /** 197 | * Start a route definition with HTTP Method via GET. 198 | * @param string $URLPattern The URL to match against, you can define named segments to be extracted and passed to the callback. 199 | * @param $callback The callback to be invoked (with variables extracted from the route if present) when the route match the request URI. 200 | * @return Route 201 | */ 202 | public static function get($URLPattern, $callback = null){ 203 | return (new Route($URLPattern,$callback))->via('get'); 204 | } 205 | 206 | /** 207 | * Start a route definition with HTTP Method via POST. 208 | * @param string $URLPattern The URL to match against, you can define named segments to be extracted and passed to the callback. 209 | * @param $callback The callback to be invoked (with variables extracted from the route if present) when the route match the request URI. 210 | * @return Route 211 | */ 212 | public static function post($URLPattern, $callback = null){ 213 | return (new Route($URLPattern,$callback))->via('post'); 214 | } 215 | 216 | /** 217 | * Start a route definition, for any HTTP Method (using * wildcard). 218 | * @param string $URLPattern The URL to match against, you can define named segments to be extracted and passed to the callback. 219 | * @param $callback The callback to be invoked (with variables extracted from the route if present) when the route match the request URI. 220 | * @return Route 221 | */ 222 | public static function any($URLPattern, $callback = null){ 223 | return (new Route($URLPattern,$callback))->via('*'); 224 | } 225 | 226 | /** 227 | * Bind a callback to the route definition 228 | * @param $callback The callback to be invoked (with variables extracted from the route if present) when the route match the request URI. 229 | * @return Route 230 | */ 231 | public function & with($callback){ 232 | $this->callback = $callback; 233 | return $this; 234 | } 235 | 236 | /** 237 | * Bind a middleware callback to invoked before the route definition 238 | * @param callable $before The callback to be invoked ($this is binded to the route object). 239 | * @return Route 240 | */ 241 | public function & before($callback){ 242 | $this->befores[] = $callback; 243 | return $this; 244 | } 245 | 246 | /** 247 | * Bind a middleware callback to invoked after the route definition 248 | * @param $callback The callback to be invoked ($this is binded to the route object). 249 | * @return Route 250 | */ 251 | public function & after($callback){ 252 | $this->afters[] = $callback; 253 | return $this; 254 | } 255 | 256 | /** 257 | * Defines the HTTP Methods to bind the route onto. 258 | * 259 | * Example: 260 | * 261 | * Route::on('/test')->via('get','post','delete'); 262 | * 263 | * 264 | * @return Route 265 | */ 266 | public function & via(...$methods){ 267 | $this->methods = []; 268 | foreach ($methods as $method){ 269 | $this->methods[strtolower($method)] = true; 270 | } 271 | return $this; 272 | } 273 | 274 | /** 275 | * Defines the regex rules for the named parameter in the current URL pattern 276 | * 277 | * Example: 278 | * 279 | * Route::on('/proxy/:number/:url') 280 | * ->rules([ 281 | * 'number' => '\d+', 282 | * 'url' => '.+', 283 | * ]); 284 | * 285 | * 286 | * @param array $rules The regex rules 287 | * @return Route 288 | */ 289 | public function & rules(array $rules){ 290 | foreach ((array)$rules as $varname => $rule){ 291 | $this->rules[$varname] = $rule; 292 | } 293 | $this->pattern = $this->compilePatternAsRegex( $this->URLPattern, $this->rules ); 294 | $this->matcher_pattern = $this->compilePatternAsRegex( $this->URLPattern, $this->rules, false ); 295 | return $this; 296 | } 297 | 298 | /** 299 | * Map a HTTP Method => callable array to a route. 300 | * 301 | * Example: 302 | * 303 | * Route::map('/test'[ 304 | * 'get' => function(){ echo "HTTP GET"; }, 305 | * 'post' => function(){ echo "HTTP POST"; }, 306 | * 'put' => function(){ echo "HTTP PUT"; }, 307 | * 'delete' => function(){ echo "HTTP DELETE"; }, 308 | * ]); 309 | * 310 | * 311 | * @param string $URLPattern The URL to match against, you can define named segments to be extracted and passed to the callback. 312 | * @param array $callbacks The HTTP Method => callable map. 313 | * @return Route 314 | */ 315 | public static function & map($URLPattern, $callbacks = []){ 316 | $route = new static($URLPattern); 317 | $route->callback = []; 318 | foreach ($callbacks as $method => $callback) { 319 | $method = strtolower($method); 320 | if (Request::method() !== $method) continue; 321 | $route->callback[$method] = $callback; 322 | $route->methods[$method] = 1; 323 | } 324 | return $route; 325 | } 326 | 327 | /** 328 | * Assign a name tag to the route 329 | * @param string $name The name tag of the route. 330 | * @return Route 331 | */ 332 | public function & tag($name){ 333 | if ($this->tag = $name) static::$tags[$name] =& $this; 334 | return $this; 335 | } 336 | 337 | /** 338 | * Reverse routing : obtain a complete URL for a named route with passed parameters 339 | * @param array $params The parameter map of the route dynamic values. 340 | * @return URL 341 | */ 342 | public function getURL($params = []){ 343 | $params = (array)$params; 344 | return new URL(rtrim(preg_replace('(/+)','/',preg_replace_callback('(:(\w+))',function($m) use ($params){ 345 | return isset($params[$m[1]]) ? $params[$m[1]].'/' : ''; 346 | },strtr($this->URLPattern,['('=>'',')'=>'']))),'/')?:'/'); 347 | } 348 | 349 | /** 350 | * Get a named route 351 | * @param string $name The name tag of the route. 352 | * @return Route or false if not found 353 | */ 354 | public static function tagged($name){ 355 | return isset(static::$tags[$name]) ? static::$tags[$name] : false; 356 | } 357 | 358 | /** 359 | * Helper for reverse routing : obtain a complete URL for a named route with passed parameters 360 | * @param string $name The name tag of the route. 361 | * @param array $params The parameter map of the route dynamic values. 362 | * @return string 363 | */ 364 | public static function URL($name, $params = []){ 365 | return ($r = static::tagged($name)) ? $r-> getURL($params) : new URL(); 366 | } 367 | 368 | /** 369 | * Compile an URL schema to a PREG regular expression. 370 | * @param string $pattern The URL schema. 371 | * @return string The compiled PREG RegEx. 372 | */ 373 | protected static function compilePatternAsRegex($pattern, $rules=[], $extract_params=true){ 374 | 375 | return '#^'.preg_replace_callback('#:([a-zA-Z]\w*)#',$extract_params 376 | // Extract named parameters 377 | ? function($g) use (&$rules){ 378 | return '(?<' . $g[1] . '>' . (isset($rules[$g[1]])?$rules[$g[1]]:'[^/]+') .')'; 379 | } 380 | // Optimized for matching 381 | : function($g) use (&$rules){ 382 | return isset($rules[$g[1]]) ? $rules[$g[1]] : '[^/]+'; 383 | }, 384 | str_replace(['.',')','*'],['\.',')?','.+'],$pattern)).'$#'; 385 | } 386 | 387 | /** 388 | * Extract the URL schema variables from the passed URL. 389 | * @param string $pattern The URL schema with the named parameters 390 | * @param string $URL The URL to process, if omitted the current request URI will be used. 391 | * @param boolean $cut If true don't limit the matching to the whole URL (used for group pattern extraction) 392 | * @return array The extracted variables 393 | */ 394 | protected static function extractVariablesFromURL($pattern, $URL=null, $cut=false){ 395 | $URL = $URL ?: Request::URI(); 396 | $pattern = $cut ? str_replace('$#','',$pattern).'#' : $pattern; 397 | $args = []; 398 | if ( !preg_match($pattern,'/'.trim($URL,'/'),$args) ) return false; 399 | foreach ($args as $key => $value) { 400 | if (false === is_string($key)) unset($args[$key]); 401 | } 402 | return $args; 403 | } 404 | 405 | 406 | public function extractArgs($URL){ 407 | $args = []; 408 | if ( $this->dynamic ) { 409 | preg_match($this->pattern, '/'.trim($URL,'/'), $args); 410 | foreach ($args as $key => $value) { 411 | if (false === is_string($key)) unset($args[$key]); 412 | } 413 | } 414 | return $args; 415 | } 416 | 417 | /** 418 | * Check if an URL schema need dynamic matching (regex). 419 | * @param string $pattern The URL schema. 420 | * @return boolean 421 | */ 422 | protected static function isDynamic($pattern){ 423 | return strlen($pattern) != strcspn($pattern,':(?[*+'); 424 | } 425 | 426 | /** 427 | * Add a route to the internal route repository. 428 | * @param Route $route 429 | * @return Route 430 | */ 431 | public static function add($route){ 432 | if (is_a($route, 'Route')){ 433 | 434 | // Add to tag map 435 | if ($route->tag) static::$tags[$route->tag] =& $route; 436 | 437 | // Optimize tree 438 | if (Options::get('core.route.auto_optimize', true)){ 439 | $base =& static::$optimized_tree; 440 | foreach (explode('/',trim(preg_replace('#^(.+?)\(?:.+$#','$1',$route->URLPattern),'/')) as $segment) { 441 | $segment = trim($segment,'('); 442 | if (!isset($base[$segment])) $base[$segment] = []; 443 | $base =& $base[$segment]; 444 | } 445 | $base[] =& $route; 446 | } 447 | } 448 | 449 | // Add route to active group 450 | if ( isset(static::$group[0]) ) static::$group[0]->add($route); 451 | 452 | return static::$routes[implode('', static::$prefix)][] = $route; 453 | } 454 | 455 | /** 456 | * Define a route group, if not immediately matched internal code will not be invoked. 457 | * @param string $prefix The url prefix for the internal route definitions. 458 | * @param string $callback This callback is invoked on $prefix match of the current request URI. 459 | */ 460 | public static function group($prefix, $callback){ 461 | 462 | // Skip definition if current request doesn't match group. 463 | $pre_prefix = rtrim(implode('',static::$prefix),'/'); 464 | $URI = Request::URI(); 465 | $args = []; 466 | $group = false; 467 | 468 | switch (true) { 469 | 470 | // Dynamic group 471 | case static::isDynamic($prefix) : 472 | $args = static::extractVariablesFromURL($prx=static::compilePatternAsRegex("$pre_prefix$prefix"), null, true); 473 | if ( $args !== false ) { 474 | // Burn-in $prefix as static string 475 | $partial = preg_match_all(str_replace('$#', '#', $prx), $URI, $partial) ? $partial[0][0] : ''; 476 | $prefix = $partial ? preg_replace('#^'.implode('',static::$prefix).'#', '', $partial) : $prefix; 477 | } 478 | 479 | // Static group 480 | case ( 0 === strpos("$URI/", "$pre_prefix$prefix/") ) 481 | || ( ! Options::get('core.route.pruning', true) ) : 482 | 483 | static::$prefix[] = $prefix; 484 | if (empty(static::$group)) static::$group = []; 485 | array_unshift(static::$group, $group = new RouteGroup()); 486 | 487 | // Call the group body function 488 | call_user_func_array($callback, $args ?: []); 489 | 490 | array_shift(static::$group); 491 | array_pop(static::$prefix); 492 | if (empty(static::$prefix)) static::$prefix = ['']; 493 | break; 494 | 495 | } 496 | 497 | return $group ?: new RouteGroup(); 498 | } 499 | 500 | public static function exitWithError($code, $message="Application Error"){ 501 | Response::error($code,$message); 502 | Response::send(); 503 | exit; 504 | } 505 | 506 | /** 507 | * Start the route dispatcher and resolve the URL request. 508 | * @param string $URL The URL to match onto. 509 | * @param string $method The HTTP method. 510 | * @param bool $return_route If setted to true it will *NOT* execute the route but it will return her. 511 | * @return boolean true if a route callback was executed. 512 | */ 513 | public static function dispatch($URL=null, $method=null, $return_route=false){ 514 | if (!$URL) $URL = Request::URI(); 515 | if (!$method) $method = Request::method(); 516 | 517 | $__deferred_send = new Deferred(function(){ 518 | if (Options::get('core.response.autosend',true)){ 519 | Response::send(); 520 | } 521 | }); 522 | 523 | if (empty(static::$optimized_tree)) { 524 | foreach ((array)static::$routes as $group => $routes){ 525 | foreach ($routes as $route) { 526 | if (is_a($route, 'Route') && $route->match($URL,$method)){ 527 | if ($return_route){ 528 | return $route; 529 | } else { 530 | $route->run($route->extractArgs($URL),$method); 531 | return true; 532 | } 533 | } 534 | } 535 | } 536 | } else { 537 | $routes =& static::$optimized_tree; 538 | foreach (explode('/',trim($URL,'/')) as $segment) { 539 | if (is_array($routes) && isset($routes[$segment])) $routes =& $routes[$segment]; 540 | // Root-level dynamic routes Ex: "/:param" 541 | else if (is_array($routes) && isset($routes[''])) $routes =& $routes['']; 542 | else break; 543 | } 544 | if (is_array($routes) && isset($routes[0]) && !is_array($routes[0])) foreach ((array)$routes as $route) { 545 | if (is_a($route, __CLASS__) && $route->match($URL, $method)){ 546 | if ($return_route){ 547 | return $route; 548 | } else { 549 | $route->run($route->extractArgs($URL),$method); 550 | return true; 551 | } 552 | } 553 | } 554 | } 555 | 556 | Response::status(404, '404 Resource not found.'); 557 | foreach (array_filter(array_merge( 558 | (static::trigger(404)?:[]), 559 | (Event::trigger(404)?:[]) 560 | )) as $res){ 561 | Response::add($res); 562 | } 563 | return false; 564 | } 565 | 566 | public function push($links, $type = 'text'){ 567 | Response::push($links, $type); 568 | return $this; 569 | } 570 | 571 | } 572 | 573 | class RouteGroup { 574 | protected $routes; 575 | 576 | public function __construct(){ 577 | $this->routes = new SplObjectStorage; 578 | return Route::add($this); 579 | } 580 | 581 | public function has($r){ 582 | return $this->routes->contains($r); 583 | } 584 | 585 | public function add($r){ 586 | $this->routes->attach($r); 587 | return $this; 588 | } 589 | 590 | public function remove($r){ 591 | if ($this->routes->contains($r)) $this->routes->detach($r); 592 | return $this; 593 | } 594 | 595 | public function before($callbacks){ 596 | foreach ($this->routes as $route){ 597 | $route->before($callbacks); 598 | } 599 | return $this; 600 | } 601 | 602 | public function after($callbacks){ 603 | foreach ($this->routes as $route){ 604 | $route->after($callbacks); 605 | } 606 | return $this; 607 | } 608 | 609 | public function push($links, $type = 'text'){ 610 | Response::push($links, $type); 611 | return $this; 612 | } 613 | 614 | } 615 | 616 | -------------------------------------------------------------------------------- /classes/SQL.php: -------------------------------------------------------------------------------- 1 | close(); 65 | return true; 66 | } else if (isset(self::$connections[$name])){ 67 | self::$connections[$name]->close(); 68 | return true; 69 | } else return false; 70 | } 71 | 72 | /** 73 | * Datasource connection accessor 74 | * @param strinf $name The datasource name 75 | * @return SQLConnect The datasource connection 76 | */ 77 | public static function using($name){ 78 | if (empty(self::$connections[$name])) throw new \Exception("[SQL] Unknown connection named '$name'."); 79 | return self::$connections[$name]; 80 | } 81 | 82 | /** 83 | * Proxy all direct static calls to the SQL module to the `default` datasource 84 | * @param string $method The method name 85 | * @param array $args The method arguments 86 | * @return mixed The method return value 87 | */ 88 | public static function __callStatic($method, $args){ 89 | if (empty(self::$connections[self::$current])) throw new \Exception("[SQL] No default connection defined."); 90 | return call_user_func_array([self::$connections[self::$current],$method],$args); 91 | } 92 | 93 | } 94 | 95 | // Default connection to in-memory ephemeral database 96 | SQL::connect('sqlite::memory:'); 97 | 98 | class SQLConnection { 99 | 100 | protected $connection = [], 101 | $queries = [], 102 | $last_exec_success = true; 103 | 104 | public function __construct($dsn, $username=null, $password=null, $options=[]){ 105 | $this->connection = [ 106 | 'dsn' => $dsn, 107 | 'pdo' => null, 108 | 'username' => $username, 109 | 'password' => $password, 110 | 'options' => array_merge([ 111 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 112 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 113 | PDO::ATTR_EMULATE_PREPARES => true, 114 | PDO::MYSQL_ATTR_MULTI_STATEMENTS => true, 115 | ], $options), 116 | ]; 117 | // "The auto-commit mode cannot be changed for this driver" SQLite workaround 118 | if (strpos($dsn,'sqlite:') === 0) { 119 | $this->connection['options'] = $options; 120 | } 121 | } 122 | 123 | public function close(){ 124 | $this->connection['pdo'] = null; 125 | } 126 | 127 | public function connection(){ 128 | if(empty($this->connection['pdo'])) { 129 | try { 130 | $this->connection['pdo'] = new PDO( 131 | $this->connection['dsn'], 132 | $this->connection['username'], 133 | $this->connection['password'], 134 | $this->connection['options'] 135 | 136 | ); 137 | SQL::trigger('connect',$this); 138 | Event::trigger('core.sql.connect',$this); 139 | } catch(Exception $e) { 140 | $this->connection['pdo'] = null; 141 | } 142 | } 143 | return $this->connection['pdo']; 144 | } 145 | 146 | 147 | /** 148 | * Prepares a SQL query string 149 | * 150 | * @param string $query The query 151 | * @param array $pdo_params The extra PDO parameters 152 | * 153 | * @return boolean 154 | */ 155 | public function prepare($query, $pdo_params=[]){ 156 | if(!$this->connection()) return false; 157 | return isset($this->queries[$query]) ? $this->queries[$query] : ($this->queries[$query] = $this->connection()->prepare($query, $pdo_params)); 158 | } 159 | 160 | public function exec($query, $params=[], $pdo_params=[]){ 161 | if(!$this->connection()) return false; 162 | 163 | if (false==is_array($params)) $params = (array)$params; 164 | $query = Filter::with('core.sql.query',$query); 165 | 166 | if($statement = $this->prepare($query, $pdo_params)){ 167 | SQL::trigger('query',$query,$params,(bool)$statement); 168 | Event::trigger('core.sql.query',$query,$params,(bool)$statement); 169 | 170 | foreach ($params as $key => $val) { 171 | $type = PDO::PARAM_STR; 172 | if (is_bool($val)) { 173 | $type = PDO::PARAM_BOOL; 174 | } elseif (is_null($val)) { 175 | $type = PDO::PARAM_NULL; 176 | } elseif (is_int($val)) { 177 | $type = PDO::PARAM_INT; 178 | } 179 | 180 | // bindValue need a 1-based numeric parameter 181 | $statement->bindValue(is_numeric($key)?$key+1:':'.$key, $val, $type); 182 | } 183 | } else { 184 | $error = $this->connection['pdo']->errorInfo(); 185 | SQL::trigger('error',$error[2], $query, $params, $error); 186 | Event::trigger('core.sql.error',$error[2], $query, $params, $error); 187 | return false; 188 | } 189 | 190 | $this->last_exec_success = $statement && $statement->execute(); 191 | return $statement; 192 | } 193 | 194 | public function value($query, $params=[], $column=0){ 195 | if(!$this->connection()) return false; 196 | 197 | $res = $this->exec($query,$params); 198 | return $res ? $res->fetchColumn($column) : null; 199 | } 200 | 201 | public function column($query, $params=[], $column=0){ 202 | if(!$this->connection()) return false; 203 | 204 | $results = []; 205 | $res = $this->exec($query,$params); 206 | 207 | if (is_string($column)) 208 | while ($x = $res->fetch(PDO::FETCH_OBJ)) $results[] = $x->$column; 209 | else 210 | while ($x = $res->fetchColumn($column)) $results[] = $x; 211 | 212 | return $results; 213 | } 214 | 215 | public function reduce($query, $params=[], $looper = null, $initial = null){ 216 | if(!$this->connection()) return false; 217 | 218 | // ($query,$looper,$initial) shorthand 219 | if (is_callable($params)) { $initial = $looper; $looper = $params; $params = []; } 220 | if(( $res = $this->exec($query,$params, [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true]) ) && is_callable($looper) ){ 221 | while ($row = $res->fetchObject()) { $initial = $looper($initial, $row); } 222 | return $initial; 223 | } else return false; 224 | } 225 | 226 | public function each($query, $params=[], callable $looper = null){ 227 | if(!$this->connection()) return false; 228 | 229 | // ($query,$looper) shorthand 230 | if ($looper===null && is_callable($params)) {$looper = $params; $params = [];} 231 | if( $res = $this->exec($query,$params, [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true]) ){ 232 | if(is_callable($looper)) { 233 | while ($row = $res->fetchObject()) $looper($row); 234 | return true; 235 | } else return $res->fetchAll(PDO::FETCH_CLASS); 236 | } else return false; 237 | } 238 | 239 | public function single($query, $params=[], callable $handler = null){ 240 | if(!$this->connection()) return false; 241 | 242 | // ($query,$handler) shorthand 243 | if ($handler===null && is_callable($params)) {$handler = $params; $params = [];} 244 | if( $res = $this->exec($query,$params, [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true]) ){ 245 | if (is_callable($handler)) 246 | return $handler($res->fetchObject()); 247 | else 248 | return $res->fetchObject(); 249 | } 250 | } 251 | 252 | public function run($script){ 253 | if(!$this->connection()) return false; 254 | 255 | $sql_path = Options::get('database.sql.path',APP_DIR.'/sql'); 256 | $sql_sep = Options::get('database.sql.separator',';'); 257 | if (is_file($f = "$sql_path/$script.sql")){ 258 | $result = true; 259 | foreach(explode($sql_sep,file_get_contents($f)) as $statement) { 260 | $result = $this->exec($statement); 261 | } 262 | return $result; 263 | } else return false; 264 | } 265 | 266 | public function all($query, $params=[], callable $looper = null){ 267 | if(!$this->connection()) return false; 268 | return $this->each($query,$params,$looper); 269 | } 270 | 271 | public function delete($table, $pks=null, $pk='id', $inclusive=true){ 272 | if(!$this->connection()) return false; 273 | 274 | if (null===$pks) { 275 | return $this->exec("DELETE FROM `$table`"); 276 | } else { 277 | return $this->exec("DELETE FROM `$table` WHERE `$pk` ".($inclusive ? "" : "NOT " )."IN (" . implode( ',', array_fill_keys( (array)$pks, '?' ) ) . ")",(array)$pks); 278 | } 279 | } 280 | 281 | public function insert($table, $data, $pk='id'){ 282 | if(!$this->connection()) return false; 283 | 284 | if (false==is_array($data)) $data = (array)$data; 285 | $k = array_keys($data); 286 | asort($k); 287 | $pk_a = $k; 288 | array_walk($pk_a,function(&$e){ $e = ':'.$e;}); 289 | $q = "INSERT INTO `$table` (`".implode('`,`',$k)."`) VALUES (".implode(',',$pk_a).")"; 290 | $this->exec($q,$data); 291 | return $this->last_exec_success ? $this->connection()->lastInsertId($pk) : false; 292 | } 293 | 294 | public function updateWhere($table, $data, $where, $pk='id'){ 295 | if(!$this->connection()) return false; 296 | 297 | if (false==is_array($data)) $data = (array)$data; 298 | if (empty($data)) return false; 299 | $k = array_keys($data); 300 | asort($k); 301 | 302 | // Remove primary key from SET 303 | array_walk($k,function(&$e) use ($pk) { 304 | $e = ($e==$pk) ? null : "`$e`=:$e"; 305 | }); 306 | 307 | $q = "UPDATE `$table` SET ".implode(', ',array_filter($k))." WHERE $where"; 308 | $this->exec($q, $data); 309 | $data = (object)$data; 310 | return $this->last_exec_success ? $data->$pk : false; 311 | } 312 | 313 | public function update($table, $data, $pk='id', $extra_where=''){ 314 | return $this->updateWhere($table, $data, "`$pk`=:$pk $extra_where", $pk); 315 | } 316 | 317 | public function insertOrUpdate($table, $data=[], $pk='id', $extra_where=''){ 318 | if(!$this->connection()) return false; 319 | 320 | if (false==is_array($data)) $data = (array)$data; 321 | if (empty($data[$pk])) return $this->insert($table, $data); 322 | if( (string) $this->value("SELECT `$pk` FROM `$table` WHERE `$pk`=? LIMIT 1", [$data[$pk]]) === (string) $data[$pk] ){ 323 | return $this->update($table, $data, $pk, $extra_where); 324 | } else { 325 | return $this->insert($table, $data, $pk); 326 | } 327 | } 328 | } 329 | 330 | -------------------------------------------------------------------------------- /classes/Service.php: -------------------------------------------------------------------------------- 1 | 600, 44 | * "path" => "/", 45 | * "domain" => ".caffeina.com", 46 | * "secure" => true, 47 | * "httponly" => false 48 | * ]; 49 | * 50 | * @return array The cookie's parameters 51 | * @see http://php.net/manual/en/function.session-get-cookie-params.php 52 | */ 53 | static public function cookieParams($args = []) { 54 | if (empty($args)) return session_get_cookie_params(); 55 | $args = array_merge(session_get_cookie_params(), $args); 56 | session_set_cookie_params($args["lifetime"], $args["path"], $args["domain"], $args["secure"], $args["httponly"]); 57 | return $args; 58 | } 59 | 60 | /** 61 | * Get/Set Session name 62 | * 63 | * @access public 64 | * @static 65 | * @param string $key The session name 66 | * @return string The session value 67 | */ 68 | static public function name($name=null){ 69 | return $name ? session_name($name) : session_name(); 70 | } 71 | 72 | /** 73 | * Get a session variable reference 74 | * 75 | * @access public 76 | * @static 77 | * @param mixed $key The variable name 78 | * @return mixed The variable value 79 | */ 80 | static public function get($key,$default=null){ 81 | if (($active = static::active()) && isset($_SESSION[$key])) { 82 | return $_SESSION[$key]; 83 | } else if ($active) { 84 | return $_SESSION[$key] = (is_callable($default)?call_user_func($default):$default); 85 | } else { 86 | return (is_callable($default)?call_user_func($default):$default); 87 | } 88 | } 89 | 90 | /** 91 | * Set a session variable 92 | * 93 | * @access public 94 | * @static 95 | * @param mixed $key The variable name 96 | * @param mixed $value The variable value 97 | * @return void 98 | */ 99 | static public function set($key,$value=null){ 100 | static::start(); 101 | if($value==null && is_array($key)){ 102 | foreach($key as $k=>$v) $_SESSION[$k]=$v; 103 | } else { 104 | $_SESSION[$key] = $value; 105 | } 106 | } 107 | 108 | /** 109 | * Delete a session variable 110 | * 111 | * @access public 112 | * @static 113 | * @param mixed $key The variable name 114 | * @return void 115 | */ 116 | static public function delete($key){ 117 | static::start(); 118 | unset($_SESSION[$key]); 119 | } 120 | 121 | 122 | /** 123 | * Delete all session variables 124 | * 125 | * @access public 126 | * @static 127 | * @return void 128 | */ 129 | static public function clear(){ 130 | static::start(); 131 | session_unset(); 132 | session_destroy(); 133 | static::trigger("end"); 134 | } 135 | 136 | /** 137 | * Check if session is active 138 | * 139 | * @access public 140 | * @static 141 | * @return void 142 | */ 143 | static public function active(){ 144 | return session_status() == PHP_SESSION_ACTIVE; 145 | } 146 | 147 | /** 148 | * Check if a session variable exists 149 | * 150 | * @access public 151 | * @static 152 | * @param mixed $key The variable name 153 | * @return bool 154 | */ 155 | static public function exists($key){ 156 | static::start(); 157 | return isset($_SESSION[$key]); 158 | } 159 | 160 | /** 161 | * Return a read-only accessor to session variables for in-view use. 162 | * @return SessionReadOnly 163 | */ 164 | static public function readOnly(){ 165 | return new SessionReadOnly; 166 | } 167 | 168 | } /* End of class */ 169 | 170 | 171 | 172 | /** 173 | * Read-only Session accessor class 174 | */ 175 | 176 | class SessionReadOnly { 177 | 178 | /** 179 | * Get a session variable reference 180 | * 181 | * @access public 182 | * @param mixed $key The variable name 183 | * @return mixed The variable value 184 | */ 185 | public function get($key){ 186 | return Session::get($key); 187 | } 188 | public function __get($key){ 189 | return Session::get($key); 190 | } 191 | 192 | public function name(){ 193 | return Session::name(); 194 | } 195 | 196 | /** 197 | * Check if a session variable exists 198 | * 199 | * @access public 200 | * @param mixed $key The variable name 201 | * @return bool 202 | */ 203 | public function exists($key){ 204 | return Session::exists($key); 205 | } 206 | public function __isset($key){ 207 | return Session::exists($key); 208 | } 209 | 210 | } /* End of class */ 211 | -------------------------------------------------------------------------------- /classes/Shell.php: -------------------------------------------------------------------------------- 1 | getShellCommand().')'; 30 | } else if (is_array($p)) foreach ($p as $key => $value) { 31 | if(is_numeric($key)){ 32 | $w[] = '--'.$value; 33 | } else { 34 | if(is_bool($value)){ 35 | if($value) $w[] = '--'.$key; 36 | } else { 37 | $w[] = '--'.$key.'='.escapeshellarg($value); 38 | } 39 | } 40 | } else { 41 | $s[] = $p; 42 | } 43 | } 44 | return trim( 45 | '/usr/bin/env '.$command.' '.implode(' ',array_merge($w,$s)) 46 | ); 47 | } 48 | 49 | /** 50 | * Returns the compiled shell command 51 | * @return string 52 | */ 53 | public function getShellCommand(){ 54 | return $this->command; 55 | } 56 | 57 | public static function __callStatic($command,$params){ 58 | $aliases = static::$aliases; 59 | // Check if is an alias 60 | if (isset($aliases[$command])){ 61 | if(!$results = $aliases[$command](...$params)) 62 | throw new Exception('Shell aliases must return a Shell class or a command string.'); 63 | return $results instanceof static? $results : new static($results); 64 | } else { 65 | return new static($command,$params); 66 | } 67 | } 68 | 69 | public function __construct($command,$params=null){ 70 | $this->command = $params?static::_compileCommand($command,$params):$command; 71 | } 72 | 73 | public function __toString(){ 74 | $output = []; 75 | exec($this->command,$output,$error_code); 76 | return empty($output)?'':implode(PHP_EOL,$output); 77 | } 78 | 79 | /** 80 | * Concatenate multiple shell commands via piping 81 | * @return Shell The piped shell command 82 | */ 83 | public static function pipe(/* ... */){ 84 | $cmd = []; 85 | foreach (func_get_args() as $item) { 86 | $cmd[] = ($item instanceof static)?$item->getShellCommand():$item; 87 | } 88 | return new static(implode(' | ',$cmd)); 89 | } 90 | 91 | /** 92 | * Concatenate multiple shell commands via logical implication ( && ) 93 | * @return Shell The concatenated shell command 94 | */ 95 | public static function sequence(...$items){ 96 | $cmd = []; 97 | foreach ($items as $item) { 98 | $cmd[] = ($item instanceof static)?$item->getShellCommand():$item; 99 | } 100 | return new static(implode(' && ',$cmd)); 101 | } 102 | 103 | public static function execCommand($command,$params = null){ 104 | return new static($command,$params); 105 | } 106 | 107 | public static function alias($command,callable $callback){ 108 | static::$aliases[$command] = $callback; 109 | } 110 | 111 | public static function escape($arg){ 112 | return escapeshellarg($arg); 113 | } 114 | 115 | public function run(){ 116 | return $this->__toString(); 117 | } 118 | 119 | } /* End of class */ 120 | 121 | -------------------------------------------------------------------------------- /classes/Structure.php: -------------------------------------------------------------------------------- 1 | &$value) { 29 | if (is_array($value) || is_a($value,'stdClass')){ 30 | $value = new self($value); 31 | } 32 | } 33 | } 34 | parent::__construct($data, static::ARRAY_AS_PROPS); 35 | } else { 36 | throw new InvalidArgumentException( 37 | 'Argument must be a string containing valid JSON, an array or an stdClass.' 38 | ); 39 | } 40 | } 41 | 42 | /** 43 | * ArrayObject::offsetSet 44 | */ 45 | public function offsetSet($key, $value){ 46 | if ( is_array($value) ) 47 | parent::offsetSet($key, new static($value)); 48 | else 49 | parent::offsetSet($key, $value); 50 | } 51 | 52 | /** 53 | * ArrayObject::offsetGet 54 | */ 55 | public function offsetGet($key){ 56 | $raw = parent::offsetGet($key); 57 | return is_callable($raw) ? call_user_func($raw) : $raw; 58 | } 59 | 60 | /** 61 | * Emulate object methods 62 | */ 63 | public function __call($method, $args){ 64 | $raw = parent::offsetGet($method); 65 | if (is_callable($raw)) { 66 | if ($raw instanceof \Closure) $raw->bindTo($this); 67 | return call_user_func_array($raw, $args); 68 | } 69 | } 70 | 71 | /** 72 | * If casted as a string, return a JSON rappresentation of the wrapped payload 73 | * @return string 74 | */ 75 | public function __toString(){ 76 | return json_encode($this,JSON_NUMERIC_CHECK); 77 | } 78 | 79 | /** 80 | * Dot-Notation Array Path Resolver 81 | * @param string $path The dot-notation path 82 | * @param array $root The array to navigate 83 | * @return mixed The pointed value 84 | */ 85 | 86 | public static function fetch($path, $root) { 87 | $_ = (array)$root; 88 | if (strpos($path,'.') === false) { 89 | return isset($_[$path]) ? $_[$path] : null; 90 | } else { 91 | list($frag,$rest) = explode('.', $path, 2); 92 | if ($rest) { 93 | return isset($_[$frag]) ? self::fetch($rest, $_[$frag]) : null; 94 | } elseif ($frag) { 95 | return (array)$_[$frag]; 96 | } else { 97 | return null; 98 | } 99 | } 100 | } 101 | 102 | public static function create($class, $args = null){ 103 | return is_array($args) ? (new ReflectionClass($class))->newInstanceArgs($args) : new $class; 104 | } 105 | 106 | public static function canBeString($var) { 107 | return $var === null || is_scalar($var) || is_callable([$var, '__toString']); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /classes/Text.php: -------------------------------------------------------------------------------- 1 | $_SERVER)); 22 | * 23 | * @author Stefano Azzolini 24 | * @access public 25 | * @static 26 | * @param mixed $t The text template 27 | * @param mixed $v (default: null) The array of values exposed in template. 28 | * @return string 29 | */ 30 | public static function render($t,$v=null){ 31 | if (Options::get('core.text.replace_empties', true)) { 32 | $replacer = function($c) use ($v){ 33 | return Structure::fetch(trim($c[1]), $v); 34 | }; 35 | } else { 36 | $replacer = function($c) use ($v){ 37 | return Structure::fetch(trim($c[1]), $v) ?: $c[0]; 38 | }; 39 | } 40 | 41 | return preg_replace_callback("(\{\{([^}]+)\}\})S",$replacer,$t); 42 | } 43 | 44 | /** 45 | * Create a "slug", an url-safe sanitized string. 46 | * 47 | * @example 48 | * echo Text::slugify("Thîs îs --- à vêry wrong séntènce!"); 49 | * // this-is-a-very-wrong-sentence 50 | * 51 | * @access public 52 | * @static 53 | * @param string $text The text to slugify 54 | * @return string The slug. 55 | */ 56 | public static function slugify($text){ 57 | return preg_replace( 58 | ['(\s+)','([^a-z0-9-])i','(-+)'],['-','','-'], 59 | strtolower(self::removeAccents($text))); 60 | } 61 | 62 | /** 63 | * Translit accented characters to neutral ones 64 | * 65 | * @example 66 | * echo Text::removeAccents("Thîs îs à vêry wrong séntènce!"); 67 | * // This is a very wrong sentence! 68 | * 69 | * @access public 70 | * @static 71 | * @param string $text The text to translit 72 | * @return string The translited text 73 | */ 74 | public static function removeAccents($text){ 75 | static $diac; 76 | return strtr( 77 | utf8_decode($text), 78 | $diac ? $diac : $diac = utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'), 79 | 'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY'); 80 | } 81 | 82 | /** 83 | * Cut a string from the end of a substring to the start of another 84 | * 85 | * @example 86 | * echo strcut("Name: Ethan Hunt; Role: Agent",'Name: ',';'); 87 | * // Ethan Hunt 88 | * 89 | * @param string $text The source text 90 | * @param string $start_tag The starting substring 91 | * @param string $end_tag Ending substring, if omitted all remaining string is returned 92 | * @return string The cutted string 93 | */ 94 | public static function cut($text, $start_tag, $end_tag=null){ 95 | $_s = strlen($start_tag) + strpos($text, $start_tag); 96 | return $end_tag ? substr($text, $_s, strpos($text,$end_tag,$_s)-$_s) : substr($text, $_s); 97 | } 98 | 99 | } /* End of class */ 100 | -------------------------------------------------------------------------------- /classes/Token.php: -------------------------------------------------------------------------------- 1 | 'JWT', 19 | 'alg' => $algo, 20 | ])), '+/', '-_'),'='), 21 | rtrim(strtr(base64_encode(json_encode($payload)), '+/', '-_'),'='), 22 | ]); 23 | return $encoded_payload . '.' . static::sign($encoded_payload, $secret, $algo); 24 | } 25 | 26 | public static function decode($jwt, $secret = null, $verify = true){ 27 | 28 | if (substr_count($jwt,'.') != 2) throw new \Exception('Token not valid'); 29 | 30 | list($encoded_header, $encoded_payload, $client_sig) = explode('.', $jwt); 31 | 32 | if (null === ($payload = json_decode(base64_decode(strtr($encoded_payload, '-_', '+/'))))) 33 | throw new \Exception('Invalid encoding'); 34 | 35 | 36 | if ($verify) { 37 | if (null === ($header = json_decode(base64_decode(strtr($encoded_header, '-_', '+/'))))) 38 | throw new \Exception('Invalid encoding'); 39 | 40 | if (empty($header->alg)) throw new \Exception('Invalid encoding'); 41 | 42 | if ($client_sig != static::sign("$encoded_header.$encoded_payload", $secret, $header->alg)) 43 | throw new \Exception('Token verification failed'); 44 | } 45 | 46 | return $payload; 47 | } 48 | 49 | protected static function sign($payload, $secret, $algo = 'HS256') { 50 | $algos = [ 51 | 'HS512' => 'sha512', 52 | 'HS384' => 'sha384', 53 | 'HS256' => 'sha256', 54 | ]; 55 | if (empty($algos[$algo])) throw new \Exception('Signing algorithm not supported'); 56 | return rtrim(strtr(base64_encode(hash_hmac($algos[$algo], $payload, $secret, true)), '+/', '-_'),'='); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /classes/URL.php: -------------------------------------------------------------------------------- 1 | _origin = $url; 29 | } 30 | 31 | private function parse(){ 32 | $url = $this->_origin; 33 | $tmp_url = (strpos($url, '://') === false) ? "..N..://$url" : $url; 34 | if (mb_detect_encoding($tmp_url, 'UTF-8', true) || ($parsed = parse_url($tmp_url)) === false) { 35 | preg_match('(^((?P[^:/?#]+):(//))?((\\3|//)?(?:(?P[^:]+):(?P[^@]+)@)?(?P[^/?:#]*))(:(?P\\d+))?(?P[^?#]*)(\\?(?P[^#]*))?(#(?P.*))?)u', $tmp_url, $parsed); 36 | } 37 | foreach($parsed as $k => $v) if(isset($this->$k)) $this->$k = $v; 38 | if ($this->scheme == '..N..') $this->scheme = null; 39 | if (!empty($this->query)) { 40 | parse_str($this->query, $this->query); 41 | } 42 | $this->_parsed = true; 43 | } 44 | 45 | public function & __get($name){ 46 | $this->_parsed || $this->parse(); 47 | if (isset($this->$name)) return $this->$name; 48 | else throw new Exception('Trying to read an unknown URL property'); 49 | } 50 | 51 | public function __set($name, $value){ 52 | $this->_parsed || $this->parse(); 53 | return $this->$name = $value; 54 | } 55 | 56 | public function __toString(){ 57 | if ($this->_parsed) { 58 | $d = []; 59 | if ($this->scheme) $d[] = "{$this->scheme}://"; 60 | if ($this->user) $d[] = "{$this->user}" . (empty($this->pass)?'':":{$this->pass}") . "@"; 61 | if ($this->host) $d[] = "{$this->host}"; 62 | if ($this->port) $d[] = ":{$this->port}"; 63 | if ($this->path) $d[] = "/" . ltrim($this->path,"/"); 64 | if (!empty($this->query)) $d[] = "?" . http_build_query($this->query); 65 | if ($this->fragment) $d[] = "#{$this->fragment}"; 66 | return implode('', $d); 67 | } else { 68 | return $this->_origin; 69 | } 70 | } 71 | 72 | } /* End of class */ 73 | -------------------------------------------------------------------------------- /classes/View.php: -------------------------------------------------------------------------------- 1 | '', 19 | 'data' => [], 20 | ]; 21 | 22 | /** 23 | * Construct a new view based on the passed template 24 | * @param mixed $template The template path or an array of them. 25 | */ 26 | public function __construct($template){ 27 | foreach ((array)$template as $templ){ 28 | if (static::$handler->exists($templ)) 29 | return $this->options['template'] = $templ; 30 | } 31 | throw new Exception("[Core.View] Template not found."); 32 | } 33 | 34 | /** 35 | * Load a Template Handler 36 | * @param class $handler The template handler class instance 37 | */ 38 | public static function using(View\Adapter $handler){ 39 | static::$handler = $handler; 40 | } 41 | 42 | /** 43 | * View factory method, can optionally pass data to pre-init view 44 | * @param string $template The template path 45 | * @param array $data The key-value map of data to pass to the view 46 | * @return View 47 | */ 48 | public static function from($template,$data=null){ 49 | $view = new self($template); 50 | return $data ? $view->with($data) : $view; 51 | } 52 | 53 | /** 54 | * Assigns data to the view 55 | * @param array $data The key-value map of data to pass to the view 56 | * @return View 57 | */ 58 | public function with($data){ 59 | if ($data){ 60 | $tmp = array_merge($data, (isset($this->options['data'])?$this->options['data']:[])); 61 | $this->options['data'] = $tmp; 62 | } 63 | return $this; 64 | } 65 | 66 | /** 67 | * Render view when casted to a string 68 | * @return string The rendered view 69 | */ 70 | public function __toString(){ 71 | return Filter::with('core.view',static::$handler->render($this->options['template'],$this->options['data'])); 72 | } 73 | 74 | /** 75 | * Returns the handler instance 76 | * @return mixed 77 | */ 78 | public static function & handler(){ 79 | return static::$handler; 80 | } 81 | 82 | /** 83 | * Check if a template exists 84 | * @return bool 85 | */ 86 | public static function exists($templatePath){ 87 | return static::$handler->exists($templatePath); 88 | } 89 | 90 | 91 | /** 92 | * Propagate the call to the handler 93 | */ 94 | public function __call($n,$p){ 95 | return call_user_func_array([static::$handler,$n],$p); 96 | } 97 | 98 | /** 99 | * Propagate the static call to the handler 100 | */ 101 | public static function __callStatic($n,$p){ 102 | return forward_static_call_array([static::$handler,$n],$p); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /classes/View/Adapter.php: -------------------------------------------------------------------------------- 1 | $val) { 36 | self::$globals[$key] = $val; 37 | } 38 | } 39 | 40 | public function render($template, $data=[]){ 41 | $template_path = self::$templatePath . trim($template,'/') . static::EXTENSION; 42 | $sandbox = function() use ($template_path){ 43 | ob_start(); 44 | include($template_path); 45 | $__buffer__ = ob_get_contents(); 46 | ob_end_clean(); 47 | return $__buffer__; 48 | }; 49 | return call_user_func($sandbox->bindTo(new PHPContext( 50 | array_merge(self::$globals, $data), 51 | self::$templatePath 52 | ))); 53 | } 54 | } 55 | 56 | class PHPContext { 57 | protected $data = []; 58 | 59 | public function __construct($data=[], $path=null){ 60 | $this->data = $data; 61 | } 62 | 63 | public function partial($template, $vars=[]){ 64 | return \View::from($template,array_merge($this->data,$vars)); 65 | } 66 | 67 | public function __isset($n){ return true; } 68 | 69 | public function __unset($n){} 70 | 71 | public function __get($n){ 72 | return empty($this->data[$n]) ? '' : $this->data[$n]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /classes/Work.php: -------------------------------------------------------------------------------- 1 | enqueue($task); 30 | return $task; 31 | } 32 | 33 | public static function send($id,$passValue) { 34 | isset(self::$workers[$id]) && self::$workers[$id]->pass($passValue); 35 | } 36 | 37 | public static function run(){ 38 | self::$pool or ( self::$pool = new \SplQueue() ); 39 | while (!self::$pool->isEmpty()) { 40 | $task = self::$pool->dequeue(); 41 | $task->run(); 42 | if ($task->complete()) { 43 | unset(self::$workers[$task->id()]); 44 | } else { 45 | self::$pool->enqueue($task); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Defer callback execution after script execution 52 | * @param callable $callback The deferred callback 53 | */ 54 | public static function after(callable $callback){ 55 | static::$inited_shutdown || static::install_shutdown(); 56 | Event::on('core.shutdown', $callback); 57 | } 58 | 59 | /** 60 | * Single shot defer handeler install 61 | */ 62 | protected static function install_shutdown(){ 63 | if (static::$inited_shutdown) return; 64 | 65 | // Disable time limit 66 | set_time_limit(0); 67 | 68 | // HHVM support 69 | if(function_exists('register_postsend_function')){ 70 | register_postsend_function(function(){ 71 | Event::trigger('core.shutdown'); 72 | }); 73 | } else if(function_exists('fastcgi_finish_request')) { 74 | register_shutdown_function(function(){ 75 | fastcgi_finish_request(); 76 | Event::trigger('core.shutdown'); 77 | }); 78 | } else { 79 | register_shutdown_function(function(){ 80 | Event::trigger('core.shutdown'); 81 | }); 82 | } 83 | 84 | static::$inited_shutdown = true; 85 | } 86 | } 87 | 88 | class TaskCoroutine { 89 | 90 | protected $id; 91 | protected $coroutine; 92 | protected $passValue = null; 93 | protected $beforeFirstYield = true; 94 | protected static $inited_shutdown = false; 95 | 96 | public function __construct($id, \Generator $coroutine) { 97 | $this->id = $id; 98 | $this->coroutine = $coroutine; 99 | } 100 | 101 | public function id() { 102 | return $this->id; 103 | } 104 | 105 | public function pass($passValue) { 106 | $this->passValue = $passValue; 107 | } 108 | 109 | public function run() { 110 | if ($this->beforeFirstYield) { 111 | $this->beforeFirstYield = false; 112 | return $this->coroutine->current(); 113 | } else { 114 | $retval = $this->coroutine->send($this->passValue); 115 | $this->passValue = null; 116 | return $retval; 117 | } 118 | } 119 | 120 | public function complete() { 121 | return ! $this->coroutine->valid(); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /classes/ZIP.php: -------------------------------------------------------------------------------- 1 | name = preg_replace('/\.zip$/','',($name?:tempnam(sys_get_temp_dir(), 'ZExp').'-archive')); 26 | $this->file = $this->name . '.zip'; 27 | if (!preg_match('~^/|\./|\.\./~',$this->file)) $this->file = './'.$this->file; 28 | $this->zip = new \ZipArchive; 29 | if ( true !== ($e = $this->zip->open($this->file, 30 | \ZipArchive::CREATE || \ZipArchive::OVERWRITE 31 | ))) { 32 | throw new Exception("Error opening temp ZIP file [".($this->file)."] Code $e", 1); 33 | } 34 | } 35 | 36 | public function __destruct(){ 37 | $this->close(); 38 | } 39 | 40 | public function path(){ 41 | return $this->file; 42 | } 43 | 44 | public function write($filename, $data){ 45 | $this->zip->addFromString($filename, $data); 46 | return $this; 47 | } 48 | 49 | public function close(){ 50 | if($this->zip) @$this->zip->close(); 51 | return $this; 52 | } 53 | 54 | public function addDirectory($folder, $root=null) { 55 | $folder = rtrim($folder,'/'); 56 | if (null === $root) { 57 | $root = dirname($folder); 58 | $folder = basename($folder); 59 | } 60 | $this->zip->addEmptyDir($folder); 61 | foreach (glob("$root/$folder/*") as $item) { 62 | if (is_dir($item)) { 63 | $this->addDirectory(str_replace($root,'',$item),$root); 64 | } else if (is_file($item)) { 65 | $this->zip->addFile($item, str_replace($root,'',$item)); 66 | } 67 | } 68 | 69 | return $this; 70 | } 71 | 72 | public function download(){ 73 | @$this->zip->close(); 74 | header('Content-Type: application/zip'); 75 | header('Content-Disposition: attachment;filename="'.$this->name.'"',true); 76 | header('Content-Transfer-Encoding: binary'); 77 | header('Expires: 0'); 78 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 79 | header('Pragma: public'); 80 | header('Content-Length: '.filesize($this->file)); 81 | while(ob_get_level()) ob_end_clean(); 82 | readfile($this->file); 83 | exit; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caffeina-core/core", 3 | "type": "library", 4 | "description": "Platform for rapid application development.", 5 | "keywords": ["framework","core","sdk","platform"], 6 | "homepage": "http://caffeina.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Stefano Azzolini", 11 | "email": "lastguest@gmail.com" 12 | }, 13 | { 14 | "name": "Gabriele Diener", 15 | "email": "gabriele.diener@caffeina.com" 16 | } 17 | ], 18 | "minimum-stability": "stable", 19 | "require": { 20 | "php": ">=5.6" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^5" 24 | }, 25 | "scripts": { 26 | "test-lint" : "find classes -type f -name '*.php' -exec php -l {} \\;", 27 | "test" : "vendor/bin/phpunit --colors=always --configuration=phpunit.xml", 28 | "changelog" : "docs/build-changelog.sh --skip-head > CHANGELOG.md && git add -A && git commit -am \"Updated CHANGELOG\"" 29 | }, 30 | "config": { 31 | "optimize-autoloader": true 32 | }, 33 | "autoload": { 34 | "classmap": [ "classes/" ] 35 | }, 36 | "autoload-dev": { 37 | "classmap": [ "classes/", "tests/" ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caffeina-core/core/819134644dfb3b6379a12fff08fa5ec45a2c614c/dist/.gitkeep -------------------------------------------------------------------------------- /stub.php: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------