├── .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 | [](https://scrutinizer-ci.com/g/caffeina-core/core/?branch=master)
6 | [](https://travis-ci.org/caffeina-core/core)
7 | [](https://packagist.org/packages/caffeina-core/core)
8 | [](https://packagist.org/packages/caffeina-core/core)
9 | [](https://packagist.org/packages/caffeina-core/core)
10 | [](https://packagist.org/packages/caffeina-core/core)
11 | [](https://gitter.im/caffeina-core/core?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
12 | [](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 | [](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('~^(.*)<([^>]+)>(.+)\2>(.*)$~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 |
--------------------------------------------------------------------------------