Enzo Angiulli
4 years ago
commit
1af0dae049
61 changed files with 30015 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||
/tmp/ |
|||
/.idea/ |
|||
/vendor/ |
|||
data/* |
|||
!data/.keep |
|||
.DS_Store |
@ -0,0 +1,16 @@ |
|||
# Enable rewrite engine and route requests to framework |
|||
RewriteEngine On |
|||
|
|||
# Some servers require you to specify the `RewriteBase` directive |
|||
# In such cases, it should be the path (relative to the document root) |
|||
# containing this .htaccess file |
|||
# |
|||
# RewriteBase / |
|||
|
|||
RewriteRule ^(app|tmp)\/|\.ini$ - [R=404] |
|||
|
|||
RewriteCond %{REQUEST_FILENAME} !-l |
|||
RewriteCond %{REQUEST_FILENAME} !-f |
|||
RewriteCond %{REQUEST_FILENAME} !-d |
|||
RewriteRule .* index.php [L,QSA] |
|||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] |
@ -0,0 +1,20 @@ |
|||
{ |
|||
"name": "bcosca/fatfree", |
|||
"description": "A powerful yet easy-to-use PHP micro-framework designed to help you build dynamic and robust Web applications - fast!", |
|||
"homepage": "http://fatfreeframework.com/", |
|||
"license": "GPL-3.0", |
|||
"require": { |
|||
"php": ">=5.4", |
|||
"graze/telnet-client": "^2.2", |
|||
"tomnomnom/phpwol": "^0.1.1" |
|||
}, |
|||
"repositories": [ |
|||
{ |
|||
"type": "vcs", |
|||
"url": "https://github.com/bcosca/fatfree" |
|||
} |
|||
], |
|||
"autoload": { |
|||
"files": ["lib/base.php"] |
|||
} |
|||
} |
@ -0,0 +1,204 @@ |
|||
{ |
|||
"_readme": [ |
|||
"This file locks the dependencies of your project to a known state", |
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", |
|||
"This file is @generated automatically" |
|||
], |
|||
"content-hash": "f4784bd93a60f1cf96e2ee40541cf763", |
|||
"packages": [ |
|||
{ |
|||
"name": "clue/socket-raw", |
|||
"version": "v1.4.1", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/clue/php-socket-raw.git", |
|||
"reference": "00ab102d061f6cdb895e79dd4d69140c7bda31cc" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/clue/php-socket-raw/zipball/00ab102d061f6cdb895e79dd4d69140c7bda31cc", |
|||
"reference": "00ab102d061f6cdb895e79dd4d69140c7bda31cc", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"ext-sockets": "*", |
|||
"php": ">=5.3" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "^7.0 || ^6.0 || ^5.2 || ^4.8.35" |
|||
}, |
|||
"type": "library", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Socket\\Raw\\": "src" |
|||
} |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Christian Lück", |
|||
"email": "christian@clue.engineering" |
|||
} |
|||
], |
|||
"description": "Simple and lightweight OOP wrapper for PHP's low-level sockets extension (ext-sockets)", |
|||
"homepage": "https://github.com/clue/php-socket-raw", |
|||
"keywords": [ |
|||
"Socket", |
|||
"client", |
|||
"datagram", |
|||
"dgram", |
|||
"icmp", |
|||
"ipv6", |
|||
"server", |
|||
"stream", |
|||
"tcp", |
|||
"udg", |
|||
"udp", |
|||
"unix" |
|||
], |
|||
"support": { |
|||
"issues": "https://github.com/clue/php-socket-raw/issues", |
|||
"source": "https://github.com/clue/php-socket-raw/tree/v1.4.1" |
|||
}, |
|||
"time": "2019-10-28T12:32:07+00:00" |
|||
}, |
|||
{ |
|||
"name": "graze/telnet-client", |
|||
"version": "v2.2.2", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/graze/telnet-client.git", |
|||
"reference": "b8091d6fbf261aea2ff28cf47fb9b0d0853f63d3" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/graze/telnet-client/zipball/b8091d6fbf261aea2ff28cf47fb9b0d0853f63d3", |
|||
"reference": "b8091d6fbf261aea2ff28cf47fb9b0d0853f63d3", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"clue/socket-raw": "^1.3", |
|||
"php": ">5.5.0" |
|||
}, |
|||
"require-dev": { |
|||
"graze/standards": "^2.0", |
|||
"mockery/mockery": "^0.9.4", |
|||
"phpunit/phpunit": "^5.0", |
|||
"squizlabs/php_codesniffer": "^3.5.0", |
|||
"symfony/var-dumper": "^3.0" |
|||
}, |
|||
"type": "library", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Graze\\TelnetClient\\": "src/" |
|||
} |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "John Smith", |
|||
"email": "john@graze.com", |
|||
"role": "Developer" |
|||
}, |
|||
{ |
|||
"name": "Graze Developers", |
|||
"email": "developers@graze.com", |
|||
"homepage": "http://www.graze.com", |
|||
"role": "Development Team" |
|||
}, |
|||
{ |
|||
"name": "Bestnetwork", |
|||
"email": "reparto.sviluppo@bestnetwork.it" |
|||
}, |
|||
{ |
|||
"name": "Dalibor Andzakovic", |
|||
"email": "dali@swerve.co.nz" |
|||
}, |
|||
{ |
|||
"name": "Marc Ennaji" |
|||
}, |
|||
{ |
|||
"name": "Matthias Blaser", |
|||
"email": "mb@adfinis.ch" |
|||
}, |
|||
{ |
|||
"name": "Christian Hammers", |
|||
"email": "chammers@netcologne.de" |
|||
} |
|||
], |
|||
"description": "Telnet client written in PHP", |
|||
"homepage": "https://github.com/graze/telnet-client", |
|||
"keywords": [ |
|||
"client", |
|||
"graze", |
|||
"php", |
|||
"telnet", |
|||
"telnet-client" |
|||
], |
|||
"support": { |
|||
"issues": "https://github.com/graze/telnet-client/issues", |
|||
"source": "https://github.com/graze/telnet-client/tree/master" |
|||
}, |
|||
"time": "2020-06-30T00:25:30+00:00" |
|||
}, |
|||
{ |
|||
"name": "tomnomnom/phpwol", |
|||
"version": "0.1.1", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/tomnomnom/phpwol.git", |
|||
"reference": "678ab79a9a0effac290c2edf37c2a74c6bb6a46f" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/tomnomnom/phpwol/zipball/678ab79a9a0effac290c2edf37c2a74c6bb6a46f", |
|||
"reference": "678ab79a9a0effac290c2edf37c2a74c6bb6a46f", |
|||
"shasum": "" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "3.7.*" |
|||
}, |
|||
"type": "library", |
|||
"autoload": { |
|||
"psr-0": { |
|||
"Phpwol": "./" |
|||
} |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Tom Hudson", |
|||
"email": "mail@tomnomnom.com", |
|||
"homepage": "http://tomhudson.co.uk/" |
|||
} |
|||
], |
|||
"description": "Wake On LAN for PHP", |
|||
"homepage": "https://github.com/tomnomnom/phpwol/", |
|||
"support": { |
|||
"issues": "https://github.com/tomnomnom/phpwol/issues", |
|||
"source": "https://github.com/tomnomnom/phpwol/tree/route" |
|||
}, |
|||
"time": "2017-03-21T22:39:03+00:00" |
|||
} |
|||
], |
|||
"packages-dev": [], |
|||
"aliases": [], |
|||
"minimum-stability": "stable", |
|||
"stability-flags": [], |
|||
"prefer-stable": false, |
|||
"prefer-lowest": false, |
|||
"platform": { |
|||
"php": ">=5.4" |
|||
}, |
|||
"platform-dev": [], |
|||
"plugin-api-version": "2.0.0" |
|||
} |
@ -0,0 +1,4 @@ |
|||
[globals] |
|||
|
|||
DEBUG=3 |
|||
UI=ui/ |
@ -0,0 +1,173 @@ |
|||
<?php |
|||
|
|||
// Kickstart the framework |
|||
// $f3=require('lib/base.php'); |
|||
require_once './vendor/autoload.php'; |
|||
$f3 = \Base::instance(); |
|||
$f3->set('DB',new DB\Jig('data/')); |
|||
$db=new \DB\Jig('data/'); |
|||
class Item extends \DB\Jig\Mapper { |
|||
public function __construct() { |
|||
parent::__construct( \Base::instance()->get('DB'), 'items' ); |
|||
} |
|||
} |
|||
$f3->set('DEBUG',1); |
|||
if ((float)PCRE_VERSION<8.0) |
|||
trigger_error('PCRE version is out of date'); |
|||
|
|||
// Load configuration |
|||
$f3->config('config.ini'); |
|||
|
|||
$f3->route('GET /', |
|||
function($f3) { |
|||
$f3->set('template', 'home.htm'); |
|||
echo View::instance()->render('layout.htm'); |
|||
} |
|||
); |
|||
$f3->route('GET /admin', |
|||
function($f3) { |
|||
$f3->set('template', 'home.htm'); |
|||
$f3->set('admin', true); |
|||
echo View::instance()->render('layout.htm'); |
|||
} |
|||
); |
|||
$f3->route('GET /add', |
|||
function($f3) { |
|||
$f3->set('template', 'add.htm'); |
|||
echo View::instance()->render('layout.htm'); |
|||
} |
|||
); |
|||
$f3->route('POST /add', |
|||
function($f3) use ($db) { |
|||
$item =new Item; |
|||
$item->copyFrom(json_decode($f3->get('BODY'))); |
|||
$item->save(); |
|||
|
|||
} |
|||
); |
|||
|
|||
$f3->route('GET /api/items', |
|||
function($f3) { |
|||
$item = new Item; |
|||
$items = $item->find(); |
|||
$list = array_map([$item, 'cast'],$items); |
|||
echo json_encode($list); |
|||
} |
|||
); |
|||
|
|||
$f3->route('POST /sendsignal', function($f3) { |
|||
$post = json_decode($f3->get('BODY'), true); |
|||
// print_r($post); |
|||
// die; |
|||
$iparray = preg_split('/\r\n|\r|\n/', $post['item']['ip']); |
|||
$messages = []; |
|||
switch ($post['item']['channel']) { |
|||
case 'telnet': |
|||
foreach ($iparray as $ip) { |
|||
$result = telnet($ip, $post['item']['port'], $post['action']); |
|||
array_push($messages, $result); |
|||
} |
|||
break; |
|||
case 'udp': |
|||
foreach ($iparray as $ip) { |
|||
$result = udp($ip, $post['item']['port'], $post['action']); |
|||
array_push($messages, $result); |
|||
} |
|||
break; |
|||
case 'wol': |
|||
foreach ($iparray as $ip) { |
|||
$result = wol($post['item']['macAddress'], $post['item']['broadcastIP']); |
|||
array_push($messages, $result); |
|||
} |
|||
break; |
|||
default: |
|||
array_push($messages, ['success' => false, 'message' => 'No channel set for these equipments']); |
|||
break; |
|||
} |
|||
echo json_encode($messages); |
|||
}); |
|||
|
|||
$f3->route('POST /del', function ($f3) { |
|||
$item = new Item; |
|||
$target = json_decode($f3->get('BODY'), true); |
|||
$items = $item->load(['@name = ?', $target['name']]); |
|||
$items->erase(); |
|||
echo json_encode(['success' => true, 'message' => 'Deleted']); |
|||
|
|||
}); |
|||
|
|||
function telnet($ip, $port, $action) { |
|||
try { |
|||
$factory = new \Socket\Raw\Factory(); |
|||
$socket = $factory->createClient($ip . ':' . $port, 2); |
|||
$client = \Graze\TelnetClient\TelnetClient::factory(); |
|||
$client->setSocket($socket); |
|||
// $dsn = '192.168.0.100:23'; |
|||
$client->setLineEnding(null); |
|||
$client->setPrompt("Optoma_PJ>"); |
|||
$client->setPromptError("F"); |
|||
try { |
|||
$conn = $client->connect($ip . ':' . $port); |
|||
$client->setReadTimeout(1); |
|||
$response = $client->execute($action . "\r"); |
|||
return ['success' => true, 'message' => 'successfully sent command to ' . $ip]; |
|||
} catch (Exception $e) { |
|||
return ['success' => false, 'message' => $e->getMessage() . $ip]; |
|||
} |
|||
|
|||
} catch (Exception $e) { |
|||
return ['success' => false, 'message' => $e->getMessage() . ' ' . $ip]; |
|||
} |
|||
|
|||
} |
|||
|
|||
function udp($ip, $port, $action) { |
|||
if ($socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) { |
|||
socket_sendto($socket, $action, strlen($action), 0, $ip, $port); |
|||
return ['success' => true, 'message' => 'UDP successful']; |
|||
} else { |
|||
return ['success' => false, 'message' => 'UDP not sent...']; |
|||
} |
|||
|
|||
} |
|||
|
|||
function wol($macAddress, $broadcastIP) { |
|||
$f = new \Phpwol\Factory(); |
|||
$magicPacket = $f->magicPacket(); |
|||
if ($result = $magicPacket->send($macAddress, $broadcastIP)) { |
|||
return ['success' => true, 'message' => 'Successful Wake On Lan']; |
|||
} else { |
|||
return ['success' => false, 'message' => 'Unsuccessful WOL for IP ' . $macAddress]; |
|||
} |
|||
} |
|||
|
|||
$f3->route('GET /optomaon', |
|||
function($f3) { |
|||
$client = \Graze\TelnetClient\TelnetClient::factory(); |
|||
$dsn = '192.168.0.100:23'; |
|||
$client->setLineEnding(null); |
|||
$client->setPrompt("Optoma_PJ>"); |
|||
$client->setPromptError("F"); |
|||
$conn = $client->connect($dsn); |
|||
$client->setReadTimeout(1); |
|||
$response = $client->execute("~0000 1\r"); |
|||
print_r($response); |
|||
}); |
|||
|
|||
|
|||
function getItems($db) { |
|||
return $db->read('items'); |
|||
} |
|||
|
|||
function setItems($db) { |
|||
$items = $db->read('items'); |
|||
|
|||
} |
|||
$f3->route('GET /userref', |
|||
function($f3) { |
|||
$f3->set('content','userref.htm'); |
|||
echo View::instance()->render('layout.htm'); |
|||
} |
|||
); |
|||
|
|||
$f3->run(); |
@ -0,0 +1,971 @@ |
|||
CHANGELOG |
|||
|
|||
3.7.2 (28 May 2020) |
|||
* CHANGED, View->sandbox: disable escaping when rendering as text/plain, bcosca/fatfree#654 |
|||
* update HTTP protocol checks, #bcosca/fatfree#1190 |
|||
* Base->clear: close vulnerability on variable compilation, bcosca/fatfree#1191 |
|||
* DB\SQL\Mapper: fix empty ID after insert, bcosca/fatfree#1175 |
|||
* DB\SQL\Mapper: fix using correct key variable for grouped sql pagination sets |
|||
* Fix return type of 'count' in Cursor->paginate() (bcosca/fatfree#1187) |
|||
* Bug fix, Web->minify: fix minification of ES6 template literals, bcosca/fatfree#1178 |
|||
* Bug fix, config: refactoring custom section parser regex, bcosca/fatfree#1149 |
|||
* Bug fix: token resolve on non-alias reroute paths, ref. 221f0c930f8664565c9825faeb9ed9af0f7a01c8 |
|||
* Websocket: Improved event handler usage |
|||
* optimized internal get calls |
|||
* only use cached lexicon when a $ttl was given |
|||
* only use money_format up until php7.4, fixes bcosca/fatfree#1174 |
|||
|
|||
3.7.1 (30. December 2019) |
|||
* Base->build: Add support for brace-enclosed route tokens |
|||
* Base->reroute, fix duplicate fragment issue on non-alias routes |
|||
* DB\SQL\Mapper: fix empty check for pkey when reloading after insert |
|||
* Web->minify: fix minification with multiple files, [bcosca/fatfree#1152](https://github.com/bcosca/fatfree/issues/1152), [#bcosca/fatfree#1169](https://github.com/bcosca/fatfree/issues/1169) |
|||
|
|||
3.7.0 (26. November 2019) |
|||
* NEW: Matrix, added select and walk methods for array processing and validation tools |
|||
* NEW: Added configurable file locking via LOCK var |
|||
* NEW: json support for dictionary files |
|||
* NEW: $die parameter on ONREROUTE hook |
|||
* NEW: Added SameSite cookie support for php7.3+ (JAR.samesite), [bcosca/fatfree#1165](https://github.com/bcosca/fatfree/issues/1165) |
|||
* NEW, DB\SQL\Mapper: added updateAll method to batch-update multiple records at once |
|||
* CHANGED, DB\SQL\Mapper: Throw error on update/erase if the table has no primary key, [#285](https://github.com/bcosca/fatfree-core/issues/285) |
|||
* Cache, Redis: Added ability to set a Redis password, [#287](https://github.com/bcosca/fatfree-core/issues/287) |
|||
* DB\SQL\Session: make datatype of data column configurable, [bcosca/fatfree#1130](https://github.com/bcosca/fatfree/issues/1130) |
|||
* DB\SQL\Mapper: only add adhoc fields in count queries that are used for grouping |
|||
* DB\SQL\Mapper: fixed inserting an already loaded record again (duplicating), [bcosca/fatfree#1093](https://github.com/bcosca/fatfree/issues/1093) |
|||
* Magic (Mappers): fix isset check on existing properties |
|||
* SMTP: added support for Bounce mail recipient ("Sender" header) |
|||
* OAuth2: make query string encode type configurable, [#268](https://github.com/bcosca/fatfree-core/issues/268) [#269](https://github.com/bcosca/fatfree-core/issues/269) |
|||
* Web: Added more cyrillic letters to diacritics, [bcosca/fatfree#1158](https://github.com/bcosca/fatfree/issues/1158) |
|||
* Web: Fixed url string falsely detected as comment section [9ac8e615](https://github.com/bcosca/fatfree-core/commit/9ac8e615ccaf750b49497a3c86161331b24e637f) |
|||
* Web: added file inspection for mime-type detection, [#270](https://github.com/bcosca/fatfree-core/issues/270), [bcosca/fatfree#1138](https://github.com/bcosca/fatfree/issues/1138) |
|||
* WS: Fixed processing all queued data frames inside the buffer, [#277](https://github.com/bcosca/fatfree-core/issues/277) |
|||
* WS: Allow packet size override |
|||
* Markdown: Support mixed `strong` and `italic` elements, [#276](https://github.com/bcosca/fatfree-core/issues/276) |
|||
* Markdown: Keep spaces around `=` sign in ini code blocks |
|||
* Added route alias key name validation, [#243](https://github.com/bcosca/fatfree-core/issues/243) |
|||
* Added fragment argument to alias method, [#282](https://github.com/bcosca/fatfree-core/issues/282) |
|||
* Allow adding fragment to reroute, [#1156](https://github.com/bcosca/fatfree/issues/1156) |
|||
* Added additional HTTP status codes, [#283](https://github.com/bcosca/fatfree-core/issues/283) |
|||
* Added X-Forwarded-For IP to log entries, [bcosca/fatfree#1042](https://github.com/bcosca/fatfree/issues/1042) |
|||
* Bug fix: broken custom date/time formatting, [bcosca/fatfree#1147](https://github.com/bcosca/fatfree/issues/1147) |
|||
* Bug fix: duplicate UI path rendering edge-case in Views and minify, [bcosca/fatfree#1152](https://github.com/bcosca/fatfree/issues/1152) |
|||
* Bug fix: unicode chars in custom config section keys, [bcosca/fatfree#1149](https://github.com/bcosca/fatfree/issues/1149) |
|||
* Bug fix: ensure valid reroute path in location header, [bcosca/fatfree#1140](https://github.com/bcosca/fatfree/issues/1140) |
|||
* Bug fix: use dictionary path for lexicon caching-hash |
|||
* Bug fix, php7.3: number format ternary, [bcosca/fatfree#1142](https://github.com/bcosca/fatfree/issues/1142) |
|||
* fix PHPdoc and variable inspection, [bcosca/fatfree#865](https://github.com/bcosca/fatfree/issues/865), [bcosca/fatfree#1128](https://github.com/bcosca/fatfree/issues/1128) |
|||
|
|||
3.6.5 (24 December 2018) |
|||
* NEW: Log, added timestamp to each line |
|||
* NEW: Auth, added support for custom compare method, [#116](https://github.com/bcosca/fatfree-core/issues/116) |
|||
* NEW: cache tag support for mongo & jig mapper, ref [#166](https://github.com/bcosca/fatfree-core/issues/116) |
|||
* NEW: Allow PHP functions as template token filters |
|||
* Web: Fix double redirect bug when running cURL with open_basedir disabled |
|||
* Web: Cope with responses from HTTP/2 servers |
|||
* Web->filler: remove very first space, when $std is false |
|||
* Web\OAuth2: Cope with HTTP/2 responses |
|||
* Web\OAuth2: take Content-Type header into account for json decoding, [#250](https://github.com/bcosca/fatfree-core/issues/250) [#251](https://github.com/bcosca/fatfree-core/issues/251) |
|||
* Web\OAuth2: fixed empty results on some endpoints [#250](https://github.com/bcosca/fatfree-core/issues/250) |
|||
* DB\SQL\Mapper: optimize mapper->count memory usage |
|||
* DB\SQL\Mapper: New table alias operator |
|||
* DB\SQL\Mapper: fix count() performance on non-grouped result sets, [bcosca/fatfree#1114](https://github.com/bcosca/fatfree/issues/1114) |
|||
* DB\SQL: Support for CTE in postgreSQL, [bcosca/fatfree#1107](https://github.com/bcosca/fatfree/issues/1107), [bcosca/fatfree#1116](https://github.com/bcosca/fatfree/issues/1116), [bcosca/fatfree#1021](https://github.com/bcosca/fatfree/issues/1021) |
|||
* DB\SQL->log: Remove extraneous whitespace |
|||
* DB\SQL: Added ability to add inline comments per SQL query |
|||
* CLI\WS, Refactoring: Streamline socket server |
|||
* CLI\WS: Add option for dropping query in OAuth2 URI |
|||
* CLI\WS: Add URL-safe base64 encoding |
|||
* CLI\WS: Detect errors in returned JSON values |
|||
* CLI\WS: Added support for Sec-WebSocket-Protocol header |
|||
* Matrix->calendar: Allow unix timestamp as date argument |
|||
* Basket: Access basket item by _id [#260](https://github.com/bcosca/fatfree-core/issues/260) |
|||
* SMTP: Added TLS 1.2 support [bcosca/fatfree#1115](https://github.com/bcosca/fatfree/issues/1115) |
|||
* SMTP->send: Respect $log argument |
|||
* Base->cast: recognize binary and octal numbers in config |
|||
* Base->cast: add awareness of hexadecimal literals |
|||
* Base->abort: Remove unnecessary Content-Encoding header |
|||
* Base->abort: Ensure headers have not been flushed |
|||
* Base->format: Differentiate between long- and full-date (with localized weekday) formats |
|||
* Base->format: Conform with intl extension's number output |
|||
* Enable route handler to override Access-Control headers in response to OPTIONS request, [#257](https://github.com/bcosca/fatfree-core/issues/257) |
|||
* Augment filters with a var_export function |
|||
* Bug fix php7.3: Fix template parse regex to be compatible with strict PCRE2 rules for hyphen placement in a character class |
|||
* Bug fix, Cache->set: update creation time when updating existing cache entries |
|||
* Bug fix: incorrect ICU date/time formatting |
|||
* Bug fix, Jig: lazy write on empty data |
|||
* Bug fix: Method uppercase to avoid route failure [#252](https://github.com/bcosca/fatfree-core/issues/252) |
|||
* Fixed error description when (PSR-11) `CONTAINER` fails to resolve a class [#253](https://github.com/bcosca/fatfree-core/issues/253) |
|||
* Mitigate CSRF predictability/vulnerability |
|||
* Expose Mapper->factory() method |
|||
|
|||
3.6.4 (19 April 2018) |
|||
* NEW: Added Dependency Injection support with CONTAINER variable [#221](https://github.com/bcosca/fatfree-core/issues/221) |
|||
* NEW: configurable LOGGABLE error codes [#1091](https://github.com/bcosca/fatfree/issues/1091#issuecomment-364674701) |
|||
* NEW: JAR.lifetime option, [#178](https://github.com/bcosca/fatfree-core/issues/178) |
|||
* Template: reduced Prefab calls |
|||
* Template: optimized reflection for better derivative support, [bcosca/fatfree#1088](https://github.com/bcosca/fatfree/issues/1088) |
|||
* Template: optimized parsing for template attributes and tokens |
|||
* DB\Mongo: fixed logging with mongodb extention |
|||
* DB\Jig: added lazy-loading [#7e1cd9b9b89](https://github.com/bcosca/fatfree-core/commit/7e1cd9b9b89c4175d0f6b86ced9d9bd49c04ac39) |
|||
* DB\Jig\Mapper: Added group feature, bcosca/fatfree#616 |
|||
* DB\SQL\Mapper: fix PostgreSQL RETURNING ID when no pkey is available, [bcosca/fatfree#1069](https://github.com/bcosca/fatfree/issues/1069), [#230](https://github.com/bcosca/fatfree-core/issues/230) |
|||
* DB\SQL\Mapper: disable order clause auto-quoting when it's already been quoted |
|||
* Web->location: add failsafe for geoip_region_name_by_code() [#GB:Bxyn9xn9AgAJ](https://groups.google.com/d/msg/f3-framework/APau4wnwNzE/Bxyn9xn9AgAJ) |
|||
* Web->request: Added proxy support [#e936361b](https://github.com/bcosca/fatfree-core/commit/e936361bc03010c4c7c38a396562e5e96a8a100d) |
|||
* Web->mime: Added JFIF format |
|||
* Markdown: handle line breaks in paragraph blocks, [bcosca/fatfree#1100](https://github.com/bcosca/fatfree/issues/1100) |
|||
* config: reduced cast calls on parsing config sections |
|||
* Patch empty SERVER_NAME [bcosca/fatfree#1084](https://github.com/bcosca/fatfree/issues/1084) |
|||
* Bugfix: unreliable request headers in Web->request() response [bcosca/fatfree#1092](https://github.com/bcosca/fatfree/issues/1092) |
|||
* Fixed, View->render: utilizing multiple UI paths, [bcosca/fatfree#1083](https://github.com/bcosca/fatfree/issues/1083) |
|||
* Fixed URL parsing with PHP 5.4 [#247](https://github.com/bcosca/fatfree-core/issues/247) |
|||
* Fixed PHP 7.2 warnings when session is active prematurely, [#238](https://github.com/bcosca/fatfree-core/issues/238) |
|||
* Fixed setcookie $expire variable type [#240](https://github.com/bcosca/fatfree-core/issues/240) |
|||
* Fixed expiration time when updating an existing cookie |
|||
|
|||
3.6.3 (31 December 2017) |
|||
* PHP7 fix: remove deprecated (unset) cast |
|||
* Web->request: restricted follow_location to 3XX responses only |
|||
* CLI mode: refactored arguments parsing |
|||
* CLI mode: fixed query string encoding |
|||
* SMTP: Refactor parsing of attachments |
|||
* SMTP: clean-up mail headers for multipart messages, [#1065](https://github.com/bcosca/fatfree/issues/1065) |
|||
* config: fixed performance issues on parsing config files |
|||
* config: cast command parameters in config entries to php type & constant, [#1030](https://github.com/bcosca/fatfree/issues/1030) |
|||
* config: reduced registry calls |
|||
* config: skip hive escaping when resolving dynamic config vars, [#1030](https://github.com/bcosca/fatfree/issues/1030) |
|||
* Bug fix: Incorrect cookie lifetime computation, [#1070](https://github.com/bcosca/fatfree/issues/1070), [#1016](https://github.com/bcosca/fatfree/issues/1016) |
|||
* DB\SQL\Mapper: use RETURNING option instead of a sequence query to get lastInsertId in PostgreSQL, [#1069](https://github.com/bcosca/fatfree/issues/1069), [#230](https://github.com/bcosca/fatfree-core/issues/230) |
|||
* DB\SQL\Session: check if _agent is too long for SQL based sessions [#236](https://github.com/bcosca/fatfree-core/issues/236) |
|||
* DB\SQL\Session: fix Session handler table creation issue on SQL Server, [#899](https://github.com/bcosca/fatfree/issues/899) |
|||
* DB\SQL: fix oracle db issue with empty error variable, [#1072](https://github.com/bcosca/fatfree/issues/1072) |
|||
* DB\SQL\Mapper: fix sorting issues on SQL Server, [#1052](https://github.com/bcosca/fatfree/issues/1052) [#225](https://github.com/bcosca/fatfree-core/issues/225) |
|||
* Prevent directory traversal attacks on filesystem based cache [#1073](https://github.com/bcosca/fatfree/issues/1073) |
|||
* Bug fix, Template: PHP constants used in include with attribute, [#983](https://github.com/bcosca/fatfree/issues/983) |
|||
* Bug fix, Template: Numeric value in expression alters PHP_EOL context |
|||
* Template: use existing linefeed instead of PHP_EOL, [#1048](https://github.com/bcosca/fatfree/issues/1048) |
|||
* Template: make newline interpolation handling configurable [#223](https://github.com/bcosca/fatfree-core/issues/223) |
|||
* Template: add beforerender to Preview |
|||
* fix custom FORMATS without modifiers |
|||
* Cache: Refactor Cache->reset for XCache |
|||
* Cache: loosen reset cache key pattern, [#1041](https://github.com/bcosca/fatfree/issues/1041) |
|||
* XCache: suffix reset only works if xcache.admin.enable_auth is disabled |
|||
* Added HTTP 103 as recently approved by the IETF |
|||
* LDAP changes to for AD flexibility [#227](https://github.com/bcosca/fatfree-core/issues/227) |
|||
* Hide debug trace from ajax errors when DEBUG=0 [#1071](https://github.com/bcosca/fatfree/issues/1071) |
|||
* fix View->render using potentially wrong cache entry |
|||
|
|||
3.6.2 (26 June 2017) |
|||
* Return a status code > 0 when dying on error [#220](https://github.com/bcosca/fatfree-core/issues/220) |
|||
* fix SMTP line width [#215](https://github.com/bcosca/fatfree-core/issues/215) |
|||
* Allow using a custom field for ldap user id checking [#217](https://github.com/bcosca/fatfree-core/issues/217) |
|||
* NEW: DB\SQL->exists: generic method to check if SQL table exists |
|||
* Pass handler to route handler and hooks [#1035](https://github.com/bcosca/fatfree/issues/1035) |
|||
* pass carriage return of multiline dictionary keys |
|||
* Better Web->slug customization |
|||
* fix incorrect header issue [#211](https://github.com/bcosca/fatfree-core/issues/211) |
|||
* fix schema issue on databases with case-sensitive collation, fixes [#209](https://github.com/bcosca/fatfree-core/issues/209) |
|||
* Add filter for deriving C-locale equivalent of a number |
|||
* Bug fix: @LANGUAGE remains unchanged after override |
|||
* abort: added Header pre-check |
|||
* Assemble URL after ONREROUTE |
|||
* Add reroute argument to skip script termination |
|||
* Invoke ONREROUTE after headers are sent |
|||
* SQLite switch to backtick as quote |
|||
* Bug fix: Incorrect timing in SQL query logs |
|||
* DB\SQL\Mapper: Cast return value of count to integer |
|||
* Patched $_SERVER['REQUEST_URI'] to ensure it contains a relative URI |
|||
* Tweak debug verbosity |
|||
* fix php carriage return issue in preview->build [#205](https://github.com/bcosca/fatfree-core/pull/205) |
|||
* fixed template string resolution [#205](https://github.com/bcosca/fatfree-core/pull/205) |
|||
* Fixed unexpected default seed on CACHE set [#1028](https://github.com/bcosca/fatfree/issues/1028) |
|||
* DB\SQL\Mapper: Optimized field escaping on options |
|||
* Optimize template conversion to PHP file |
|||
|
|||
3.6.1 (2 April 2017) |
|||
* NEW: Recaptcha plugin [#194](https://github.com/bcosca/fatfree-core/pull/194) |
|||
* NEW: MB variable for detecting multibyte support |
|||
* NEW: DB\SQL: Cache parsed schema for the TTL duration |
|||
* NEW: quick erase flag on Jig/Mongo/SQL mappers [#193](https://github.com/bcosca/fatfree-core/pull/193) |
|||
* NEW: Allow OPTIONS method to return a response body [#171](https://github.com/bcosca/fatfree-core/pull/171) |
|||
* NEW: Add support for Memcached (bcosca/fatfree#997) |
|||
* NEW: Rudimentary preload resource (HTTP2 server) support via template push() |
|||
* NEW: Add support for new MongoDB driver [#177](https://github.com/bcosca/fatfree-core/pull/177) |
|||
* Changed: template filter are all lowercase now |
|||
* Changed: Fix template lookup inconsistency: removed base dir from UI on render |
|||
* Changed: count() method now has an options argument [#192](https://github.com/bcosca/fatfree-core/pull/192) |
|||
* Changed: SMTP, Spit out error message if any |
|||
* \DB\SQL\Mapper: refactored row count strategy |
|||
* DB\SQL\Mapper: Allow non-scalar values to be assigned as mapper property |
|||
* DB\SQL::PARAM_FLOAT: remove cast to float (#106 and bcosca/fatfree#984) (#191) |
|||
* DB\SQL\mapper->erase: allow empty string |
|||
* DB\SQL\mapper->insert: fields reset after successful INSERT |
|||
* Add option to debounce Cursor->paginate subset [#195](https://github.com/bcosca/fatfree-core/pull/195) |
|||
* View: Don't delete sandboxed variables (#198) |
|||
* Preview: Optimize compilation of template expressions |
|||
* Preview: Use shorthand tag for direct rendering |
|||
* Preview->resolve(): new tweak to allow template persistence as option |
|||
* Web: Expose diacritics translation table |
|||
* SMTP: Enable logging of message body only when $log argument is 'verbose' |
|||
* SMTP: Convert headers to camelcase for consistency |
|||
* make cache seed more flexible, #164 |
|||
* Improve trace details for DEBUG>2 |
|||
* Enable config() to read from an array of input files |
|||
* Improved alias and reroute regex |
|||
* Make camelCase and snakeCase Unicode-aware |
|||
* format: Provision for optional whitespaces |
|||
* Break APCu-BC dependence |
|||
* Old PHP 5.3 cleanup |
|||
* Debug log must include HTTP query |
|||
* Recognize X-Forwarded-Port header (bcosca/fatfree#1002) |
|||
* Avoid use of deprecated mcrypt module |
|||
* Return only the client's IP when using the `X-Forwarded-For` header to deduce an IP address |
|||
* Remove orphan mutex locks on termination (#157) |
|||
* Use 80 as default port number to avoid issues when `$_SERVER['SERVER_PORT']` is not existing |
|||
* fread replaced with readfile() for simple send() usecase |
|||
* Bug fix: request URI with multiple leading slashes, #203 |
|||
* Bug fix: Query generates wrong adhoc field value |
|||
* Bug fix: SMTP stream context issue #200 |
|||
* Bug fix: child pseudo class selector in minify, bcosca/fatfree#1008 |
|||
* Bug fix: "Undefined index: CLI" error (#197) |
|||
* Bug fix: cast Cache-Control expire time to int, bcosca/fatfree#1004 |
|||
* Bug fix: Avoid issuance of multiple Content-Type headers for nested templates |
|||
* Bug fix: wildcard token issue with digits (bcosca/fatfree#996) |
|||
* Bug fix: afterupdate ignored when row does not change |
|||
* Bug fix: session handler read() method for PHP7 (need strict string) #184 #185 |
|||
* Bug fix: reroute mocking in CLI mode (#183) |
|||
* Bug fix: Reroute authoritative relative references (#181) |
|||
* Bug fix: locales order and charset hyphen |
|||
* Bug fix: base stripped twice in router (#176) |
|||
|
|||
3.6.0 (19 November 2016) |
|||
* NEW: [cli] request type |
|||
* NEW: console-friendly CLI mode |
|||
* NEW: lexicon caching |
|||
* NEW: Silent operator skips startup error check (#125) |
|||
* NEW: DB\SQL->trans() |
|||
* NEW: custom config section parser, i.e. [conf > Foo::bar] |
|||
* NEW: support for cache tags in SQL |
|||
* NEW: custom FORMATS |
|||
* NEW: Mongo mapper fields whitelist |
|||
* NEW: WebSocket server |
|||
* NEW: Base->extend method (#158) |
|||
* NEW: Implement framework variable caching via config, i.e. FOO = "bar" | 3600 |
|||
* NEW: Lightweight OAuth2 client |
|||
* NEW: SEED variable, configurable app-specific hashing prefix (#149, bcosca/fatfree#951, bcosca/fatfree#884, bcosca/fatfree#629) |
|||
* NEW: CLI variable |
|||
* NEW: Web->send, specify custom filename (#124) |
|||
* NEW: Web->send, added flushing flag (#131) |
|||
* NEW: Indexed route wildcards, now exposed in PARAMS['*'] |
|||
* Changed: PHP 5.4 is now the minimum version requirement |
|||
* Changed: Prevent database wrappers from being cloned |
|||
* Changed: Router works on PATH instead of URI (#126) NB: PARAMS.0 no longer contains the query string |
|||
* Changed: Removed ALIASES autobuilding (#118) |
|||
* Changed: Route wildcards match empty strings (#119) |
|||
* Changed: Disable default debug highlighting, HIGHLIGHT is false now |
|||
* General PHP 5.4 optimizations |
|||
* Optimized config parsing |
|||
* Optimized Base->recursive |
|||
* Optimized header extraction |
|||
* Optimized cache/expire headers |
|||
* Optimized session_start behaviour (bcosca/fatfree#673) |
|||
* Optimized reroute regex |
|||
* Tweaked cookie removal |
|||
* Better route precedence order |
|||
* Performance tweak: reduced cache calls |
|||
* Refactored lexicon (LOCALES) build-up, much faster now |
|||
* Added turkish locale bug workaround |
|||
* Geo->tzinfo Update to UTC |
|||
* Added Xcache reset (bcosca/fatfree#928) |
|||
* Redis cache: allow db name in dsn |
|||
* SMTP: Improve server emulation responses |
|||
* SMTP: Optimize transmission envelope |
|||
* SMTP: Implement mock transmission |
|||
* SMTP: Various bug fixes and feature improvements |
|||
* SMTP: quit on failed authentication |
|||
* Geo->weather: force metric units |
|||
* Base->until: Implement CLI interoperability |
|||
* Base->format: looser plural syntax |
|||
* Base->format: Force decimal as default number format |
|||
* Base->merge: Added $keep flag to save result to the hive key |
|||
* Base->reroute: Allow array as URL argument for aliasing |
|||
* Base->alias: Allow query string (or array) to be appended to alias |
|||
* Permit reroute to named routes with URL query segment |
|||
* Sync COOKIE global on set() |
|||
* Permit non-hive variables to use JS dot notation |
|||
* RFC2616: Use absolute URIs for Location header |
|||
* Matrix->calendar: Check if calendar extension is loaded |
|||
* Markdown: require start of line/whitespace for text processing (#136) |
|||
* DB\[SQL|Jig|Mongo]->log(FALSE) disables logging |
|||
* DB\SQL->exec: Added timestamp toggle to db log |
|||
* DB\SQL->schema: Remove unnecessary line terminators |
|||
* DB\SQL\Mapper: allow array filter with empty string |
|||
* DB\SQL\Mapper: optimized handling for key-less tables |
|||
* DB\SQL\Mapper: added float support (#106) |
|||
* DB\SQL\Session: increased default column sizes (#148, bcosca/fatfree#931, bcosca/fatfree#950) |
|||
* Web: Catch cURL errors |
|||
* Optimize Web->receive (bcosca/fatfree#930) |
|||
* Web->minify: fix arbitrary file download vulnerability |
|||
* Web->request: fix cache control max-age detection (bcosca/fatfree#908) |
|||
* Web->request: Add request headers & error message to return value (bcosca/fatfree#737) |
|||
* Web->request: Refactored response to HTTP request |
|||
* Web->send flush while sending big files |
|||
* Image->rgb: allow hex strings |
|||
* Image->captcha: Check if GD module supports TrueType |
|||
* Image->load: Return FALSE on load failure |
|||
* Image->resize: keep aspect ratio when only width or height was given |
|||
* Updated OpenID lib (bcosca/fatfree#965) |
|||
* Audit->card: add new mastercard "2" BIN range (bcosca/fatfree#954) |
|||
* Deprecated: Bcrypt class |
|||
* Preview->render: optimized detection to remove short open PHP tags and allow xml tags (#133) |
|||
* Display file and line number in exception handler (bcosca/fatfree#967) |
|||
* Added error reporting level to Base->error and ERROR.level (bcosca/fatfree#957) |
|||
* Added optional custom cache instance to Session (#141) |
|||
* CLI-aware mock() |
|||
* XFRAME and PACKAGE can be switched off now (#128) |
|||
* Bug fix: wrong time calculation on memcache reset (#170) |
|||
* Bug fix: encode CLI parameters |
|||
* Bug fix: Close connection on abort explicitly (#162) |
|||
* Bug fix: Image->identicon, Avoid double-size sprite rotation (and possible segfault) |
|||
* Bug fix: Image->render and Image->dump, removed unnecessary 2nd argument (#146) |
|||
* Bug fix: Magic->offsetset, access property as array element (#147) |
|||
* Bug fix: multi-line custom template tag parsing (bcosca/fatfree#935) |
|||
* Bug fix: cache headers on errors (bcosca/fatfree#885) |
|||
* Bug fix: Web, deprecated CURLOPT_SSL_VERIFYHOST in curl |
|||
* Bug fix: Web, Invalid user error constant (bcosca/fatfree#962) |
|||
* Bug fix: Web->request, redirections for domain-less location (#135) |
|||
* Bug fix: DB\SQL\Mapper, reset changed flag after update (#142, #152) |
|||
* Bug fix: DB\SQL\Mapper, fix changed flag when using assignment operator #143 #150 #151 |
|||
* Bug fix: DB\SQL\Mapper, revival of the HAVING clause |
|||
* Bug fix: DB\SQL\Mapper, pgsql with non-integer primary keys (bcosca/fatfree#916) |
|||
* Bug fix: DB\SQL\Session, quote table name (bcosca/fatfree#977) |
|||
* Bug fix: snakeCase returns word starting with underscore (bcosca/fatfree#927) |
|||
* Bug fix: mock does not populate PATH variable |
|||
* Bug fix: Geo->weather API key (#129) |
|||
* Bug fix: Incorrect compilation of array element with zero index |
|||
* Bug fix: Compilation of array construct is incorrect |
|||
* Bug fix: Trailing slash redirection on UTF-8 paths (#121) |
|||
|
|||
3.5.1 (31 December 2015) |
|||
* NEW: ttl attribute in <include> template tag |
|||
* NEW: allow anonymous function for template filter |
|||
* NEW: format modifier for international and custom currency symbol |
|||
* NEW: Image->data() returns image resource |
|||
* NEW: extract() get prefixed array keys from an assoc array |
|||
* NEW: Optimized and faster Template parser with full support for HTML5 empty tags |
|||
* NEW: Added support for {@token} encapsulation syntax in routes definition |
|||
* NEW: DB\SQL->exec(), automatically shift to 1-based query arguments |
|||
* NEW: abort() flush output |
|||
* Added referenced value to devoid() |
|||
* Template token filters are now resolved within Preview->token() |
|||
* Web->_curl: restrict redirections to HTTP |
|||
* Web->minify(), skip importing of external files |
|||
* Improved session and error handling in until() |
|||
* Get the error trace array with the new $format parameter |
|||
* Better support for unicode URLs |
|||
* Optimized TZ detection with date_default_timezone_get() |
|||
* format() Provide default decimal places |
|||
* Optimize code: remove redundant TTL checks |
|||
* Optimized timeout handling in Web->request() |
|||
* Improved PHPDoc hints |
|||
* Added missing russian DIACRITICS letters |
|||
* DB\Cursor: allow child implementation of reset() |
|||
* DB\Cursor: Copyfrom now does an internal call to set() |
|||
* DB\SQL: Provide the ability to disable SQL logging |
|||
* DB\SQL: improved query analysis to trigger fetchAll |
|||
* DB\SQL\Mapper: added support for binary table columns |
|||
* SQL,JIG,MONGO,CACHE Session handlers refactored and optimized |
|||
* SMTP Refactoring and optimization |
|||
* Bug fix: SMTP, Align quoted_printable_encode() with SMTP specs (dot-stuffing) |
|||
* Bug fix: SMTP, Send buffered optional headers to output |
|||
* Bug fix: SMTP, Content-Transfer-Encoding for non-TLS connections |
|||
* Bug fix: SMTP, Single attachment error |
|||
* Bug fix: Cursor->load not always mapping to first record |
|||
* Bug fix: dry SQL mapper should not trigger 'load' |
|||
* Bug fix: Code highlighting on empty text |
|||
* Bug fix: Image->resize, round dimensions instead of cast |
|||
* Bug fix: whitespace handling in $f3->compile() |
|||
* Bug fix: TTL of `View` and `Preview` (`Template`) |
|||
* Bug fix: token filter regex |
|||
* Bug fix: Template, empty attributes |
|||
* Bug fix: Preview->build() greedy regex |
|||
* Bug fix: Web->minify() single-line comment on last line |
|||
* Bug fix: Web->request(), follow_location with cURL and open_basedir |
|||
* Bug fix: Web->send() Single quotes around filename not interpreted correctly by some browsers |
|||
|
|||
3.5.0 (2 June 2015) |
|||
* NEW: until() method for long polling |
|||
* NEW: abort() to disconnect HTTP client (and continue execution) |
|||
* NEW: SQL Mapper->required() returns TRUE if field is not nullable |
|||
* NEW: PREMAP variable for allowing prefixes to handlers named after HTTP verbs |
|||
* NEW: [configs] section to allow config includes |
|||
* NEW: Test->passed() returns TRUE if no test failed |
|||
* NEW: SQL mapper changed() function |
|||
* NEW: fatfree-core composer support |
|||
* NEW: constants() method to expose constants |
|||
* NEW: Preview->filter() for configurable token filters |
|||
* NEW: CORS variable for Cross-Origin Resource Sharing support, #731 |
|||
* Change in behavior: Switch to htmlspecialchars for escaping |
|||
* Change in behavior: No movement in cursor position after erase(), #797 |
|||
* Change in behavior: ERROR.trace is a multiline string now |
|||
* Change in behavior: Strict token recognition in <include> href attribute |
|||
* Router fix: loose method search |
|||
* Better route precedence order, #12 |
|||
* Preserve contents of ROUTES, #723 |
|||
* Alias: allow array of parameters |
|||
* Improvements on reroute method |
|||
* Fix for custom Jig session files |
|||
* Audit: better mobile detection |
|||
* Audit: add argument to test string as browser agent |
|||
* DB mappers: abort insert/update/erase from hooks, #684 |
|||
* DB mappers: Allow array inputs in copyfrom() |
|||
* Cache,SQL,Jig,Mongo Session: custom callback for suspect sessions |
|||
* Fix for unexpected HIVE values when defining an empty HIVE array |
|||
* SQL mapper: check for results from CALL and EXEC queries, #771 |
|||
* SQL mapper: consider SQL schema prefix, #820 |
|||
* SQL mapper: write to log before execution to |
|||
enable tracking of PDOStatement error |
|||
* Add SQL Mapper->table() to return table name |
|||
* Allow override of the schema in SQL Mapper->schema() |
|||
* Improvement: Keep JIG table as reference, #758 |
|||
* Expand regex to include whitespaces in SQL DB dsn, #817 |
|||
* View: Removed reserved variables $fw and $implicit |
|||
* Add missing newlines after template expansion |
|||
* Web->receive: fix for complex field names, #806 |
|||
* Web: Improvements in socket engine |
|||
* Web: customizable user_agent for all engines, #822 |
|||
* SMTP: Provision for Content-ID in attachments |
|||
* Image + minify: allow absolute paths |
|||
* Promote framework error to E_USER_ERROR |
|||
* Geo->weather switch to OpenWeather |
|||
* Expose mask() and grab() methods for routing |
|||
* Expose trace() method to expose the debug backtrace |
|||
* Implement recursion strategy using IteratorAggregate, #714 |
|||
* Exempt whitespace between % and succeeding operator from being minified, #773 |
|||
* Optimized error detection and ONERROR handler, fatfree-core#18 |
|||
* Tweak error log output |
|||
* Optimized If-Modified-Since cache header usage |
|||
* Improved APCu compatibility, #724 |
|||
* Bug fix: Web::send fails on filename with spaces, #810 |
|||
* Bug fix: overwrite limit in findone() |
|||
* Bug fix: locale-specific edge cases affecting SQL schema, #772 |
|||
* Bug fix: Newline stripping in config() |
|||
* Bug fix: bracket delimited identifier for sybase and dblib driver |
|||
* Bug fix: Mongo mapper collection->count driver compatibility |
|||
* Bug fix: SQL Mapper->set() forces adhoc value if already defined |
|||
* Bug fix: Mapper ignores HAVING clause |
|||
* Bug fix: Constructor invocation in call() |
|||
* Bug fix: Wrong element returned by ajax/sync request |
|||
* Bug fix: handling of non-consecutive compound key members |
|||
* Bug fix: Virtual fields not retrieved when group option is present, #757 |
|||
* Bug fix: group option generates incorrect SQL query, #757 |
|||
* Bug fix: ONERROR does not receive PARAMS on fatal error |
|||
|
|||
3.4.0 (1 January 2015) |
|||
* NEW: [redirects] section |
|||
* NEW: Custom config sections |
|||
* NEW: User-defined AUTOLOAD function |
|||
* NEW: ONREROUTE variable |
|||
* NEW: Provision for in-memory Jig database (#727) |
|||
* Return run() result (#687) |
|||
* Pass result of run() to mock() (#687) |
|||
* Add port suffix to REALM variable |
|||
* New attribute in <include> tag to extend hive |
|||
* Adjust unit tests and clean up templates |
|||
* Expose header-related methods |
|||
* Web->request: allow content array |
|||
* Preserve contents of ROUTES (#723) |
|||
* Smart detection of PHP functions in template expressions |
|||
* Add afterrender() hook to View class |
|||
* Implement ArrayAccess and magic properties on hive |
|||
* Improvement on mocking of superglobals and request body |
|||
* Fix table creation for pgsql handled sessions |
|||
* Add QUERY to hive |
|||
* Exempt E_NOTICE from default error_reporting() |
|||
* Add method to build alias routes from template, fixes #693 |
|||
* Fix dangerous caching of cookie values |
|||
* Fix multiple encoding in nested templates |
|||
* Fix node attribute parsing for empty/zero values |
|||
* Apply URL encoding on BASE to emulate v2 behavior (#123) |
|||
* Improve Base->map performance (#595) |
|||
* Add simple backtrace for fatal errors |
|||
* Count Cursor->load() results (#581) |
|||
* Add form field name to Web->receive() callback arguments |
|||
* Fix missing newlines after template expansion |
|||
* Fix overwrite of ENCODING variable |
|||
* limit & offset workaround for SQL Server, fixes #671 |
|||
* SQL Mapper->find: GROUP BY SQL compliant statement |
|||
* Bug fix: Missing abstract method fields() |
|||
* Bug fix: Auto escaping does not work with mapper objects (#710) |
|||
* Bug fix: 'with' attribute in <include> tag raise error when no token |
|||
inside |
|||
* View rendering: optional Content-Type header |
|||
* Bug fix: Undefined variable: cache (#705) |
|||
* Bug fix: Routing does not work if project base path includes valid |
|||
special URI character (#704) |
|||
* Bug fix: Template hash collision (#702) |
|||
* Bug fix: Property visibility is incorrect (#697) |
|||
* Bug fix: Missing Allow header on HTTP 405 response |
|||
* Bug fix: Double quotes in lexicon files (#681) |
|||
* Bug fix: Space should not be mandatory in ICU pluralization format string |
|||
* Bug fix: Incorrect log entry when SQL query contains a question mark |
|||
* Bug fix: Error stack trace |
|||
* Bug fix: Cookie expiration (#665) |
|||
* Bug fix: OR operator (||) parsed incorrectly |
|||
* Bug fix: Routing treatment of * wildcard character |
|||
* Bug fix: Mapper copyfrom() method doesn't allow class/object callbacks |
|||
(#590) |
|||
* Bug fix: exists() creates elements/properties (#591) |
|||
* Bug fix: Wildcard in routing pattern consumes entire query string (#592) |
|||
* Bug fix: Workaround bug in latest MongoDB driver |
|||
* Bug fix: Default error handler silently fails for AJAX request with |
|||
DEBUG>0 (#599) |
|||
* Bug fix: Mocked BODY overwritten (#601) |
|||
* Bug fix: Undefined pkey (#607) |
|||
|
|||
3.3.0 (8 August 2014) |
|||
* NEW: Attribute in <include> tag to extend hive |
|||
* NEW: Image overlay with transparency and alignment control |
|||
* NEW: Allow redirection of specified route patterns to a URL |
|||
* Bug fix: Missing AND operator in SQL Server schema query (Issue #576) |
|||
* Count Cursor->load() results (Feature request #581) |
|||
* Mapper copyfrom() method doesn't allow class/object callbacks (Issue #590) |
|||
* Bug fix: exists() creates elements/properties (Issue #591) |
|||
* Bug fix: Wildcard in routing pattern consumes entire query string |
|||
(Issue #592) |
|||
* Tweak Base->map performance (Issue #595) |
|||
* Bug fix: Default error handler silently fails for AJAX request with |
|||
DEBUG>0 (Issue #599) |
|||
* Bug fix: Mocked BODY overwritten (Issue #601) |
|||
* Bug fix: Undefined pkey (Issue #607) |
|||
* Bug fix: beforeupdate() position (Issue #633) |
|||
* Bug fix: exists() return value for cached keys |
|||
* Bug fix: Missing error code in UNLOAD handler |
|||
* Bug fix: OR operator (||) parsed incorrectly |
|||
* Add input name parameter to custom slug function |
|||
* Apply URL encoding on BASE to emulate v2 behavior (Issue #123) |
|||
* Reduce mapper update() iterations |
|||
* Bug fix: Routing treatment of * wildcard character |
|||
* SQL Mapper->find: GROUP BY SQL compliant statement |
|||
* Work around bug in latest MongoDB driver |
|||
* Work around probable race condition and optimize cache access |
|||
* View rendering: Optional Content-Type header |
|||
* Fix missing newlines after template expansion |
|||
* Add form field name to Web->receive() callback arguments |
|||
* Quick reference: add RAW variable |
|||
|
|||
3.2.2 (19 March 2014) |
|||
* NEW: Locales set automatically (Feature request #522) |
|||
* NEW: Mapper dbtype() |
|||
* NEW: before- and after- triggers for all mappers |
|||
* NEW: Decode HTML5 entities if PHP>5.3 detected (Feature request #552) |
|||
* NEW: Send credentials only if AUTH is present in the SMTP extension |
|||
response (Feature request #545) |
|||
* NEW: BITMASK variable to allow ENT_COMPAT override |
|||
* NEW: Redis support for caching |
|||
* Enable SMTP feature detection |
|||
* Enable extended ICU custom date format (Feature request #555) |
|||
* Enable custom time ICU format |
|||
* Add option to turn off session table creation (Feature request #557) |
|||
* Enhanced template token rendering and custom filters (Feature request |
|||
#550) |
|||
* Avert multiple loads in DB-managed sessions (Feature request #558) |
|||
* Add EXEC to associative fetch |
|||
* Bug fix: Building template tokens breaks on inline OR condition (Issue |
|||
#573) |
|||
* Bug fix: SMTP->send does not use the $log parameter (Issue #571) |
|||
* Bug fix: Allow setting sqlsrv primary keys on insert (Issue #570) |
|||
* Bug fix: Generated query for obtaining table schema in sqlsrv incorrect |
|||
(Bug #565) |
|||
* Bug fix: SQL mapper flag set even when value has not changed (Bug #562) |
|||
* Bug fix: Add XFRAME config option (Feature request #546) |
|||
* Bug fix: Incorrect parsing of comments (Issue #541) |
|||
* Bug fix: Multiple Set-Cookie headers (Issue #533) |
|||
* Bug fix: Mapper is dry after save() |
|||
* Bug fix: Prevent infinite loop when error handler is triggered |
|||
(Issue #361) |
|||
* Bug fix: Mapper tweaks not passing primary keys as arguments |
|||
* Bug fix: Zero indexes in dot-notated arrays fail to compile |
|||
* Bug fix: Prevent GROUP clause double-escaping |
|||
* Bug fix: Regression of zlib compression bug |
|||
* Bug fix: Method copyto() does not include ad hoc fields |
|||
* Check existence of OpenID mode (Issue #529) |
|||
* Generate a 404 when a tokenized class doesn't exist |
|||
* Fix SQLite quotes (Issue #521) |
|||
* Bug fix: BASE is incorrect on Windows |
|||
|
|||
3.2.1 (7 January 2014) |
|||
* NEW: EMOJI variable, UTF->translate(), UTF->emojify(), and UTF->strrev() |
|||
* Allow empty strings in config() |
|||
* Add support for turning off php://input buffering via RAW |
|||
(FALSE by default) |
|||
* Add Cursor->load() and Cursor->find() TTL support |
|||
* Support Web->receive() large file downloads via PUT |
|||
* ONERROR safety check |
|||
* Fix session CSRF cookie detection |
|||
* Framework object now passed to route handler contructors |
|||
* Allow override of DIACRITICS |
|||
* Various code optimizations |
|||
* Support log disabling (Issue #483) |
|||
* Implicit mapper load() on authentication |
|||
* Declare abstract methods for Cursor derivatives |
|||
* Support single-quoted HTML/XML attributes (Feature request #503) |
|||
* Relax property visibility of mappers and derivatives |
|||
* Deprecated: {{~ ~}} instructions and {{* *}} comments; Use {~ ~} and |
|||
{* *} instead |
|||
* Minor fix: Audit->ipv4() return value |
|||
* Bug fix: Backslashes in BASE not converted on Windows |
|||
* Bug fix: UTF->substr() with negative offset and specified length |
|||
* Bug fix: Replace named URL tokens on render() |
|||
* Bug fix: BASE is not empty when run from document root |
|||
* Bug fix: stringify() recursion |
|||
|
|||
3.2.0 (18 December 2013) |
|||
* NEW: Automatic CSRF protection (with IP and User-Agent checks) for |
|||
sessions mapped to SQL-, Jig-, Mongo- and Cache-based backends |
|||
* NEW: Named routes |
|||
* NEW: PATH variable; returns the URL relative to BASE |
|||
* NEW: Image->captcha() color parameters |
|||
* NEW: Ability to access MongoCuror thru the cursor() method |
|||
* NEW: Mapper->fields() method returns array of field names |
|||
* NEW: Mapper onload(), oninsert(), onupdate(), and onerase() event |
|||
listeners/triggers |
|||
* NEW: Preview class (a lightweight template engine) |
|||
* NEW: rel() method derives path from URL relative to BASE; useful for |
|||
rerouting |
|||
* NEW: PREFIX variable for prepending a string to a dictionary term; |
|||
Enable support for prefixed dictionary arrays and .ini files (Feature |
|||
request #440) |
|||
* NEW: Google static map plugin |
|||
* NEW: devoid() method |
|||
* Introduce clean(); similar to scrub(), except that arg is passed by |
|||
value |
|||
* Use $ttl for cookie expiration (Issue #457) |
|||
* Fix needs_rehash() cost comparison |
|||
* Add pass-by-reference argument to exists() so if method returns TRUE, |
|||
a subsequent get() is unnecessary |
|||
* Improve MySQL support |
|||
* Move esc(), raw(), and dupe() to View class where they more |
|||
appropriately belong |
|||
* Allow user-defined fields in SQL mapper constructor (Feature request |
|||
#450) |
|||
* Re-implement the pre-3.0 template resolve() feature |
|||
* Remove redundant instances of session_commit() |
|||
* Add support for input filtering in Mapper->copyfrom() |
|||
* Prevent intrusive behavior of Mapper->copyfrom() |
|||
* Support multiple SQL primary keys |
|||
* Support custom tag attributes/inline tokens defined at runtime |
|||
(Feature request #438) |
|||
* Broader support for HTTP basic auth |
|||
* Prohibit Jig _id clear() |
|||
* Add support for detailed stringify() output |
|||
* Add base directory to UI path as fallback |
|||
* Support Test->expect() chaining |
|||
* Support __tostring() in stringify() |
|||
* Trigger error on invalid CAPTCHA length (Issue #458) |
|||
* Bug fix: exists() pass-by-reference argument returns incorrect value |
|||
* Bug fix: DB Exec does not return affected row if query contains a |
|||
sub-SELECT (Issue #437) |
|||
* Improve seed generator and add code for detecting of acceptable |
|||
limits in Image->captcha() (Feature request #460) |
|||
* Add decimal format ICU extension |
|||
* Bug fix: 404-reported URI contains HTTP query |
|||
* Bug fix: Data type detection in DB->schema() |
|||
* Bug fix: TZ initialization |
|||
* Bug fix: paginate() passes incorrect argument to count() |
|||
* Bug fix: Incorrect query when reloading after insert() |
|||
* Bug fix: SQL preg_match error in pdo_type matching (Issue #447) |
|||
* Bug fix: Missing merge() function (Issue #444) |
|||
* Bug fix: BASE misdefined in command line mode |
|||
* Bug fix: Stringifying hive may run infinite (Issue #436) |
|||
* Bug fix: Incomplete stringify() when DEBUG<3 (Issue #432) |
|||
* Bug fix: Redirection of basic auth (Issue #430) |
|||
* Bug fix: Filter only PHP code (including short tags) in templates |
|||
* Bug fix: Markdown paragraph parser does not convert PHP code blocks |
|||
properly |
|||
* Bug fix: identicon() colors on same keys are randomized |
|||
* Bug fix: quotekey() fails on aliased keys |
|||
* Bug fix: Missing _id in Jig->find() return value |
|||
* Bug fix: LANGUAGE/LOCALES handling |
|||
* Bug fix: Loose comparison in stringify() |
|||
|
|||
3.1.2 (5 November 2013) |
|||
* Abandon .chm help format; Package API documentation in plain HTML; |
|||
(Launch lib/api/index.html in your browser) |
|||
* Deprecate BAIL in favor of HALT (default: TRUE) |
|||
* Revert to 3.1.0 autoload behavior; Add support for lowercase folder |
|||
names |
|||
* Allow Spring-style HTTP method overrides |
|||
* Add support for SQL Server-based sessions |
|||
* Capture full X-Forwarded-For header |
|||
* Add protection against malicious scripts; Extra check if file was really |
|||
uploaded |
|||
* Pass-thru page limit in return value of Cursor->paginate() |
|||
* Optimize code: Implement single-pass escaping |
|||
* Short circuit Jig->find() if source file is empty |
|||
* Bug fix: PHP globals passed by reference in hive() result (Issue #424) |
|||
* Bug fix: ZIP mime type incorrect behavior |
|||
* Bug fix: Jig->erase() filter malfunction |
|||
* Bug fix: Mongo->select() group |
|||
* Bug fix: Unknown bcrypt constant |
|||
|
|||
3.1.1 (13 October 2013) |
|||
* NEW: Support OpenID attribute exchange |
|||
* NEW: BAIL variable enables/disables continuance of execution on non-fatal |
|||
errors |
|||
* Deprecate BAIL in favor of HALT (default: FALSE) |
|||
* Add support for Oracle |
|||
* Mark cached queries in log (Feature Request #405) |
|||
* Implement Bcrypt->needs_reshash() |
|||
* Add entropy to SQL cache hash; Add uuid() method to DB backends |
|||
* Find real document root; Simplify debug paths |
|||
* Permit OpenID required fields to be declared as comma-separated string or |
|||
array |
|||
* Pass modified filename as argument to user-defined function in |
|||
Web->receive() |
|||
* Quote keys in optional SQL clauses (Issue #408) |
|||
* Allow UNLOAD to override fatal error detection (Issue #404) |
|||
* Mutex operator precedence error (Issue #406) |
|||
* Bug fix: exists() malfunction (Issue #401) |
|||
* Bug fix: Jig mapper triggers error when loading from CACHE (Issue #403) |
|||
* Bug fix: Array index check |
|||
* Bug fix: OpenID verified() return value |
|||
* Bug fix: Basket->find() should return a set of results (Issue #407); |
|||
Also implemented findone() for consistency with mappers |
|||
* Bug fix: PostgreSQL last insert ID (Issue #410) |
|||
* Bug fix: $port component URL overwritten by _socket() |
|||
* Bug fix: Calculation of elapsed time |
|||
|
|||
3.1.0 (20 August 2013) |
|||
* NEW: Web->filler() returns a chunk of text from the standard |
|||
Lorem Ipsum passage |
|||
* Change in behavior: Drop support for JSON serialization |
|||
* SQL->exec() now returns value of RETURNING clause |
|||
* Add support for $ttl argument in count() (Issue #393) |
|||
* Allow UI to be overridden by custom $path |
|||
* Return result of PDO primitives: begintransaction(), rollback(), and |
|||
commit() |
|||
* Full support for PHP 5.5 |
|||
* Flush buffers only when DEBUG=0 |
|||
* Support class->method, class::method, and lambda functions as |
|||
Web->basic() arguments |
|||
* Commit session on Basket->save() |
|||
* Optional enlargement in Image->resize() |
|||
* Support authentication on hosts running PHP-CGI |
|||
* Change visibility level of Cache properties |
|||
* Prevent ONERROR recursion |
|||
* Work around Apache pre-2.4 VirtualDocumentRoot bug |
|||
* Prioritize cURL in HTTP engine detection |
|||
* Bug fix: Minify tricky JS |
|||
* Bug fix: desktop() detection |
|||
* Bug fix: Double-slash on TEMP-relative path |
|||
* Bug fix: Cursor mapping of first() and last() records |
|||
* Bug fix: Premature end of Web->receive() on multiple files |
|||
* Bug fix: German umlaute to its corresponding grammatically-correct |
|||
equivalent |
|||
|
|||
3.0.9 (12 June 2013) |
|||
* NEW: Web->whois() |
|||
* NEW: Template <switch> <case> tags |
|||
* Improve CACHE consistency |
|||
* Case-insensitive MIME type detection |
|||
* Support pre-PHP 5.3.4 in Prefab->instance() |
|||
* Refactor isdesktop() and ismobile(); Add isbot() |
|||
* Add support for Markdown strike-through |
|||
* Work around ODBC's lack of quote() support |
|||
* Remove useless Prefab destructor |
|||
* Support multiple cache instances |
|||
* Bug fix: Underscores in OpenId keys mangled |
|||
* Refactor format() |
|||
* Numerous tweaks |
|||
* Bug fix: MongoId object not preserved |
|||
* Bug fix: Double-quotes included in lexicon() string (Issue #341) |
|||
* Bug fix: UTF-8 formatting mangled on Windows (Issue #342) |
|||
* Bug fix: Cache->load() error when CACHE is FALSE (Issue #344) |
|||
* Bug fix: send() ternary expression |
|||
* Bug fix: Country code constants |
|||
|
|||
3.0.8 (17 May 2013) |
|||
* NEW: Bcrypt lightweight hashing library\ |
|||
* Return total number of records in superset in Cursor->paginate() |
|||
* ONERROR short-circuit (Enhancement #334) |
|||
* Apply quotes/backticks on DB identifiers |
|||
* Allow enabling/disabling of SQL log |
|||
* Normalize glob() behavior (Issue #330) |
|||
* Bug fix: mbstring 2-byte text truncation (Issue #325) |
|||
* Bug fix: Unsupported operand types (Issue #324) |
|||
|
|||
3.0.7 (2 May 2013) |
|||
* NEW: route() now allows an array of routing patterns as first argument; |
|||
support array as first argument of map() |
|||
* NEW: entropy() for calculating password strength (NIST 800-63) |
|||
* NEW: AGENT variable containing auto-detected HTTP user agent string |
|||
* NEW: ismobile() and isdesktop() methods |
|||
* NEW: Prefab class and descendants now accept constructor arguments |
|||
* Change in behavior: Cache->exists() now returns timestamp and TTL of |
|||
cache entry or FALSE if not found (Feature request #315) |
|||
* Preserve timestamp and TTL when updating cache entry (Feature request |
|||
#316) |
|||
* Improved currency formatting with C99 compliance |
|||
* Suppress unnecessary program halt at startup caused by misconfigured |
|||
server |
|||
* Add support for dashes in custom attribute names in templates |
|||
* Bug fix: Routing precedene (Issue #313) |
|||
* Bug fix: Remove Jig _id element from document property |
|||
* Bug fix: Web->rss() error when not enough items in the feed (Issue #299) |
|||
* Bug fix: Web engine fallback (Issue #300) |
|||
* Bug fix: <strong> and <em> formatting |
|||
* Bug fix: Text rendering of text with trailing punctuation (Issue #303) |
|||
* Bug fix: Incorrect regex in SMTP |
|||
|
|||
3.0.6 (31 Mar 2013) |
|||
* NEW: Image->crop() |
|||
* Modify documentation blocks for PHPDoc interoperability |
|||
* Allow user to control whether Base->rerouet() uses a permanent or |
|||
temporary redirect |
|||
* Allow JAR elements to be set individually |
|||
* Refactor DB\SQL\Mapper->insert() to cope with autoincrement fields |
|||
* Trigger error when captcha() font is missing |
|||
* Remove unnecessary markdown regex recursion |
|||
* Check for scalars instead of DB\SQL strings |
|||
* Implement more comprehensive diacritics table |
|||
* Add option for disabling 401 errors when basic auth() fails |
|||
* Add markdown syntax highlighting for Apache configuration |
|||
* Markdown->render() deprecated to remove dependency on UI variable; |
|||
Feature replaced by Markdown->convert() to enable translation from |
|||
markdown string to HTML |
|||
* Optimize factory() code of all data mappers |
|||
* Apply backticks on MySQL table names |
|||
* Bug fix: Routing failure when directory path contains a tilde (Issue #291) |
|||
* Bug fix: Incorrect markdown parsing of strong/em sequences and inline HTML |
|||
* Bug fix: Cached page not echoed (Issue #278) |
|||
* Bug fix: Object properties not escaped when rendering |
|||
* Bug fix: OpenID error response ignored |
|||
* Bug fix: memcache_get_extended_stats() timeout |
|||
* Bug fix: Base->set() doesn't pass TTL to Cache->set() |
|||
* Bug fix: Base->scrub() ignores pass-thru * argument (Issue #274) |
|||
|
|||
3.0.5 (16 Feb 2013) |
|||
* NEW: Markdown class with PHP, HTML, and .ini syntax highlighting support |
|||
* NEW: Options for caching of select() and find() results |
|||
* NEW: Web->acceptable() |
|||
* Add send() argument for forcing downloads |
|||
* Provide read() option for applying Unix LF as standard line ending |
|||
* Bypass lexicon() call if LANGUAGE is undefined |
|||
* Load fallback language dictionary if LANGUAGE is undefined |
|||
* map() now checks existence of class/methods for non-tokenized URLs |
|||
* Improve error reporting of non-existent Template methods |
|||
* Address output buffer issues on some servers |
|||
* Bug fix: Setting DEBUG to 0 won't suppress the stack trace when the |
|||
content type is application/json (Issue #257) |
|||
* Bug fix: Image dump/render additional arguments shifted |
|||
* Bug fix: ob_clean() causes buffer issues with zlib compression |
|||
* Bug fix: minify() fails when commenting CSS @ rules (Issue #251) |
|||
* Bug fix: Handling of commas inside quoted strings |
|||
* Bug fix: Glitch in stringify() handling of closures |
|||
* Bug fix: dry() in mappers returns TRUE despite being hydrated by |
|||
factory() (Issue #265) |
|||
* Bug fix: expect() not handling flags correctly |
|||
* Bug fix: weather() fails when server is unreachable |
|||
|
|||
3.0.4 (29 Jan 2013) |
|||
* NEW: Support for ICU/CLDR pluralization |
|||
* NEW: User-defined FALLBACK language |
|||
* NEW: minify() now recognizes CSS @import directives |
|||
* NEW: UTF->bom() returns byte order mark for UTF-8 encoding |
|||
* Expose SQL\Mapper->schema() |
|||
* Change in behavior: Send error response as JSON string if AJAX request is |
|||
detected |
|||
* Deprecated: afind*() methods |
|||
* Discard output buffer in favor of debug output |
|||
* Make _id available to Jig queries |
|||
* Magic class now implements ArrayAccess |
|||
* Abort execution on startup errors |
|||
* Suppress stack trace on DEBUG level 0 |
|||
* Allow single = as equality operator in Jig query expressions |
|||
* Abort OpenID discovery if Web->request() fails |
|||
* Mimic PHP *RECURSION* in stringify() |
|||
* Modify Jig parser to allow wildcard-search using preg_match() |
|||
* Abort execution after error() execution |
|||
* Concatenate cached/uncached minify() iterations; Prevent spillover |
|||
caching of previous minify() result |
|||
* Work around obscure PHP session id regeneration bug |
|||
* Revise algorithm for Jig filter involving undefined fields (Issue #230) |
|||
* Use checkdnsrr() instead of gethostbyname() in DNSBL check |
|||
* Auto-adjust pagination to cursor boundaries |
|||
* Add Romanian diacritics |
|||
* Bug fix: Root namespace reference and sorting with undefined Jig fields |
|||
* Bug fix: Greedy receive() regex |
|||
* Bug fix: Default LANGUAGE always 'en' |
|||
* Bug fix: minify() hammers cache backend |
|||
* Bug fix: Previous values of primary keys not saved during factory() |
|||
instantiation |
|||
* Bug fix: Jig find() fails when search key is not present in all records |
|||
* Bug fix: Jig SORT_DESC (Issue #233) |
|||
* Bug fix: Error reporting (Issue #225) |
|||
* Bug fix: language() return value |
|||
|
|||
3.0.3 (29 Dec 2013) |
|||
* NEW: [ajax] and [sync] routing pattern modifiers |
|||
* NEW: Basket class (session-based pseudo-mapper, shopping cart, etc.) |
|||
* NEW: Test->message() method |
|||
* NEW: DB profiling via DB->log() |
|||
* NEW: Matrix->calendar() |
|||
* NEW: Audit->card() and Audit->mod10() for credit card verification |
|||
* NEW: Geo->weather() |
|||
* NEW: Base->relay() accepts comma-separated callbacks; but unlike |
|||
Base->chain(), result of previous callback becomes argument of the next |
|||
* Numerous performance tweaks |
|||
* Interoperability with new MongoClient class |
|||
* Web->request() now recognizes gzip and deflate encoding |
|||
* Differences in behavior of Web->request() engines rectified |
|||
* mutex() now uses an ID as argument (instead of filename to make it clear |
|||
that specified file is not the target being locked, but a primitive |
|||
cross-platform semaphore) |
|||
* DB\SQL\Mapper field _id now returned even in the absence of any |
|||
auto-increment field |
|||
* Magic class spinned off as a separate file |
|||
* ISO 3166-1 alpha-2 table updated |
|||
* Apache redirect emulation for PHP 5.4 CLI server mode |
|||
* Framework instance now passed as argument to any user-defined shutdown |
|||
function |
|||
* Cache engine now used as storage for Web->minify() output |
|||
* Flag added for enabling/disabling Image class filter history |
|||
* Bug fix: Trailing routing token consumes HTTP query |
|||
* Bug fix: LANGUAGE spills over to LOCALES setting |
|||
* Bug fix: Inconsistent dry() return value |
|||
* Bug fix: URL-decoding |
|||
|
|||
3.0.2 (23 Dec 2013) |
|||
* NEW: Syntax-highlighted stack traces via Base->highlight(); boolean |
|||
HIGHLIGHT global variable can be used to enable/disable this feature |
|||
* NEW: Template engine <ignore> tag |
|||
* NEW: Image->captcha() |
|||
* NEW: DNSBL-based spammer detection (ported from 2.x) |
|||
* NEW: paginate(), first(), and last() methods for data mappers |
|||
* NEW: X-HTTP-Method-Override header now recognized |
|||
* NEW: Base->chain() method for executing callbacks in succession |
|||
* NEW: HOST global variable; derived from either $_SERVER['SERVER_NAME'] or |
|||
gethostname() |
|||
* NEW: REALM global variable representing full canonical URI |
|||
* NEW: Auth plug-in |
|||
* NEW: Pingback plug-in (implements both Pingback 1.0 protocol client and |
|||
server) |
|||
* NEW: DEBUG verbosity can now reach up to level 3; Base->stringify() drills |
|||
down to object properties at this setting |
|||
* NEW: HTTP PATCH method added to recognized HTTP ReST methods |
|||
* Web->slug() now trims trailing dashes |
|||
* Web->request() now allows relative local URLs as argument |
|||
* Use of PARAMS in route handlers now unnecessary; framework now passes two |
|||
arguments to route handlers: the framework object instance and an array |
|||
containing the captured values of tokens in route patterns |
|||
* Standardized timeout settings among Web->request() backends |
|||
* Session IDs regenerated for additional security |
|||
* Automatic HTTP 404 responses by Base->call() now restricted to route |
|||
handlers |
|||
* Empty comments in ini-style files now parsed properly |
|||
* Use file_get_contents() in methods that don't involve high concurrency |
|||
|
|||
3.0.1 (14 Dec 2013) |
|||
* Major rewrite of much of the framework's core features |
@ -0,0 +1,621 @@ |
|||
GNU GENERAL PUBLIC LICENSE |
|||
Version 3, 29 June 2007 |
|||
|
|||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
|||
Everyone is permitted to copy and distribute verbatim copies |
|||
of this license document, but changing it is not allowed. |
|||
|
|||
Preamble |
|||
|
|||
The GNU General Public License is a free, copyleft license for |
|||
software and other kinds of works. |
|||
|
|||
The licenses for most software and other practical works are designed |
|||
to take away your freedom to share and change the works. By contrast, |
|||
the GNU General Public License is intended to guarantee your freedom to |
|||
share and change all versions of a program--to make sure it remains free |
|||
software for all its users. We, the Free Software Foundation, use the |
|||
GNU General Public License for most of our software; it applies also to |
|||
any other work released this way by its authors. You can apply it to |
|||
your programs, too. |
|||
|
|||
When we speak of free software, we are referring to freedom, not |
|||
price. Our General Public Licenses are designed to make sure that you |
|||
have the freedom to distribute copies of free software (and charge for |
|||
them if you wish), that you receive source code or can get it if you |
|||
want it, that you can change the software or use pieces of it in new |
|||
free programs, and that you know you can do these things. |
|||
|
|||
To protect your rights, we need to prevent others from denying you |
|||
these rights or asking you to surrender the rights. Therefore, you have |
|||
certain responsibilities if you distribute copies of the software, or if |
|||
you modify it: responsibilities to respect the freedom of others. |
|||
|
|||
For example, if you distribute copies of such a program, whether |
|||
gratis or for a fee, you must pass on to the recipients the same |
|||
freedoms that you received. You must make sure that they, too, receive |
|||
or can get the source code. And you must show them these terms so they |
|||
know their rights. |
|||
|
|||
Developers that use the GNU GPL protect your rights with two steps: |
|||
(1) assert copyright on the software, and (2) offer you this License |
|||
giving you legal permission to copy, distribute and/or modify it. |
|||
|
|||
For the developers' and authors' protection, the GPL clearly explains |
|||
that there is no warranty for this free software. For both users' and |
|||
authors' sake, the GPL requires that modified versions be marked as |
|||
changed, so that their problems will not be attributed erroneously to |
|||
authors of previous versions. |
|||
|
|||
Some devices are designed to deny users access to install or run |
|||
modified versions of the software inside them, although the manufacturer |
|||
can do so. This is fundamentally incompatible with the aim of |
|||
protecting users' freedom to change the software. The systematic |
|||
pattern of such abuse occurs in the area of products for individuals to |
|||
use, which is precisely where it is most unacceptable. Therefore, we |
|||
have designed this version of the GPL to prohibit the practice for those |
|||
products. If such problems arise substantially in other domains, we |
|||
stand ready to extend this provision to those domains in future versions |
|||
of the GPL, as needed to protect the freedom of users. |
|||
|
|||
Finally, every program is threatened constantly by software patents. |
|||
States should not allow patents to restrict development and use of |
|||
software on general-purpose computers, but in those that do, we wish to |
|||
avoid the special danger that patents applied to a free program could |
|||
make it effectively proprietary. To prevent this, the GPL assures that |
|||
patents cannot be used to render the program non-free. |
|||
|
|||
The precise terms and conditions for copying, distribution and |
|||
modification follow. |
|||
|
|||
TERMS AND CONDITIONS |
|||
|
|||
0. Definitions. |
|||
|
|||
"This License" refers to version 3 of the GNU General Public License. |
|||
|
|||
"Copyright" also means copyright-like laws that apply to other kinds of |
|||
works, such as semiconductor masks. |
|||
|
|||
"The Program" refers to any copyrightable work licensed under this |
|||
License. Each licensee is addressed as "you". "Licensees" and |
|||
"recipients" may be individuals or organizations. |
|||
|
|||
To "modify" a work means to copy from or adapt all or part of the work |
|||
in a fashion requiring copyright permission, other than the making of an |
|||
exact copy. The resulting work is called a "modified version" of the |
|||
earlier work or a work "based on" the earlier work. |
|||
|
|||
A "covered work" means either the unmodified Program or a work based |
|||
on the Program. |
|||
|
|||
To "propagate" a work means to do anything with it that, without |
|||
permission, would make you directly or secondarily liable for |
|||
infringement under applicable copyright law, except executing it on a |
|||
computer or modifying a private copy. Propagation includes copying, |
|||
distribution (with or without modification), making available to the |
|||
public, and in some countries other activities as well. |
|||
|
|||
To "convey" a work means any kind of propagation that enables other |
|||
parties to make or receive copies. Mere interaction with a user through |
|||
a computer network, with no transfer of a copy, is not conveying. |
|||
|
|||
An interactive user interface displays "Appropriate Legal Notices" |
|||
to the extent that it includes a convenient and prominently visible |
|||
feature that (1) displays an appropriate copyright notice, and (2) |
|||
tells the user that there is no warranty for the work (except to the |
|||
extent that warranties are provided), that licensees may convey the |
|||
work under this License, and how to view a copy of this License. If |
|||
the interface presents a list of user commands or options, such as a |
|||
menu, a prominent item in the list meets this criterion. |
|||
|
|||
1. Source Code. |
|||
|
|||
The "source code" for a work means the preferred form of the work |
|||
for making modifications to it. "Object code" means any non-source |
|||
form of a work. |
|||
|
|||
A "Standard Interface" means an interface that either is an official |
|||
standard defined by a recognized standards body, or, in the case of |
|||
interfaces specified for a particular programming language, one that |
|||
is widely used among developers working in that language. |
|||
|
|||
The "System Libraries" of an executable work include anything, other |
|||
than the work as a whole, that (a) is included in the normal form of |
|||
packaging a Major Component, but which is not part of that Major |
|||
Component, and (b) serves only to enable use of the work with that |
|||
Major Component, or to implement a Standard Interface for which an |
|||
implementation is available to the public in source code form. A |
|||
"Major Component", in this context, means a major essential component |
|||
(kernel, window system, and so on) of the specific operating system |
|||
(if any) on which the executable work runs, or a compiler used to |
|||
produce the work, or an object code interpreter used to run it. |
|||
|
|||
The "Corresponding Source" for a work in object code form means all |
|||
the source code needed to generate, install, and (for an executable |
|||
work) run the object code and to modify the work, including scripts to |
|||
control those activities. However, it does not include the work's |
|||
System Libraries, or general-purpose tools or generally available free |
|||
programs which are used unmodified in performing those activities but |
|||
which are not part of the work. For example, Corresponding Source |
|||
includes interface definition files associated with source files for |
|||
the work, and the source code for shared libraries and dynamically |
|||
linked subprograms that the work is specifically designed to require, |
|||
such as by intimate data communication or control flow between those |
|||
subprograms and other parts of the work. |
|||
|
|||
The Corresponding Source need not include anything that users |
|||
can regenerate automatically from other parts of the Corresponding |
|||
Source. |
|||
|
|||
The Corresponding Source for a work in source code form is that |
|||
same work. |
|||
|
|||
2. Basic Permissions. |
|||
|
|||
All rights granted under this License are granted for the term of |
|||
copyright on the Program, and are irrevocable provided the stated |
|||
conditions are met. This License explicitly affirms your unlimited |
|||
permission to run the unmodified Program. The output from running a |
|||
covered work is covered by this License only if the output, given its |
|||
content, constitutes a covered work. This License acknowledges your |
|||
rights of fair use or other equivalent, as provided by copyright law. |
|||
|
|||
You may make, run and propagate covered works that you do not |
|||
convey, without conditions so long as your license otherwise remains |
|||
in force. You may convey covered works to others for the sole purpose |
|||
of having them make modifications exclusively for you, or provide you |
|||
with facilities for running those works, provided that you comply with |
|||
the terms of this License in conveying all material for which you do |
|||
not control copyright. Those thus making or running the covered works |
|||
for you must do so exclusively on your behalf, under your direction |
|||
and control, on terms that prohibit them from making any copies of |
|||
your copyrighted material outside their relationship with you. |
|||
|
|||
Conveying under any other circumstances is permitted solely under |
|||
the conditions stated below. Sublicensing is not allowed; section 10 |
|||
makes it unnecessary. |
|||
|
|||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
|||
|
|||
No covered work shall be deemed part of an effective technological |
|||
measure under any applicable law fulfilling obligations under article |
|||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
|||
similar laws prohibiting or restricting circumvention of such |
|||
measures. |
|||
|
|||
When you convey a covered work, you waive any legal power to forbid |
|||
circumvention of technological measures to the extent such circumvention |
|||
is effected by exercising rights under this License with respect to |
|||
the covered work, and you disclaim any intention to limit operation or |
|||
modification of the work as a means of enforcing, against the work's |
|||
users, your or third parties' legal rights to forbid circumvention of |
|||
technological measures. |
|||
|
|||
4. Conveying Verbatim Copies. |
|||
|
|||
You may convey verbatim copies of the Program's source code as you |
|||
receive it, in any medium, provided that you conspicuously and |
|||
appropriately publish on each copy an appropriate copyright notice; |
|||
keep intact all notices stating that this License and any |
|||
non-permissive terms added in accord with section 7 apply to the code; |
|||
keep intact all notices of the absence of any warranty; and give all |
|||
recipients a copy of this License along with the Program. |
|||
|
|||
You may charge any price or no price for each copy that you convey, |
|||
and you may offer support or warranty protection for a fee. |
|||
|
|||
5. Conveying Modified Source Versions. |
|||
|
|||
You may convey a work based on the Program, or the modifications to |
|||
produce it from the Program, in the form of source code under the |
|||
terms of section 4, provided that you also meet all of these conditions: |
|||
|
|||
a) The work must carry prominent notices stating that you modified |
|||
it, and giving a relevant date. |
|||
|
|||
b) The work must carry prominent notices stating that it is |
|||
released under this License and any conditions added under section |
|||
7. This requirement modifies the requirement in section 4 to |
|||
"keep intact all notices". |
|||
|
|||
c) You must license the entire work, as a whole, under this |
|||
License to anyone who comes into possession of a copy. This |
|||
License will therefore apply, along with any applicable section 7 |
|||
additional terms, to the whole of the work, and all its parts, |
|||
regardless of how they are packaged. This License gives no |
|||
permission to license the work in any other way, but it does not |
|||
invalidate such permission if you have separately received it. |
|||
|
|||
d) If the work has interactive user interfaces, each must display |
|||
Appropriate Legal Notices; however, if the Program has interactive |
|||
interfaces that do not display Appropriate Legal Notices, your |
|||
work need not make them do so. |
|||
|
|||
A compilation of a covered work with other separate and independent |
|||
works, which are not by their nature extensions of the covered work, |
|||
and which are not combined with it such as to form a larger program, |
|||
in or on a volume of a storage or distribution medium, is called an |
|||
"aggregate" if the compilation and its resulting copyright are not |
|||
used to limit the access or legal rights of the compilation's users |
|||
beyond what the individual works permit. Inclusion of a covered work |
|||
in an aggregate does not cause this License to apply to the other |
|||
parts of the aggregate. |
|||
|
|||
6. Conveying Non-Source Forms. |
|||
|
|||
You may convey a covered work in object code form under the terms |
|||
of sections 4 and 5, provided that you also convey the |
|||
machine-readable Corresponding Source under the terms of this License, |
|||
in one of these ways: |
|||
|
|||
a) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by the |
|||
Corresponding Source fixed on a durable physical medium |
|||
customarily used for software interchange. |
|||
|
|||
b) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by a |
|||
written offer, valid for at least three years and valid for as |
|||
long as you offer spare parts or customer support for that product |
|||
model, to give anyone who possesses the object code either (1) a |
|||
copy of the Corresponding Source for all the software in the |
|||
product that is covered by this License, on a durable physical |
|||
medium customarily used for software interchange, for a price no |
|||
more than your reasonable cost of physically performing this |
|||
conveying of source, or (2) access to copy the |
|||
Corresponding Source from a network server at no charge. |
|||
|
|||
c) Convey individual copies of the object code with a copy of the |
|||
written offer to provide the Corresponding Source. This |
|||
alternative is allowed only occasionally and noncommercially, and |
|||
only if you received the object code with such an offer, in accord |
|||
with subsection 6b. |
|||
|
|||
d) Convey the object code by offering access from a designated |
|||
place (gratis or for a charge), and offer equivalent access to the |
|||
Corresponding Source in the same way through the same place at no |
|||
further charge. You need not require recipients to copy the |
|||
Corresponding Source along with the object code. If the place to |
|||
copy the object code is a network server, the Corresponding Source |
|||
may be on a different server (operated by you or a third party) |
|||
that supports equivalent copying facilities, provided you maintain |
|||
clear directions next to the object code saying where to find the |
|||
Corresponding Source. Regardless of what server hosts the |
|||
Corresponding Source, you remain obligated to ensure that it is |
|||
available for as long as needed to satisfy these requirements. |
|||
|
|||
e) Convey the object code using peer-to-peer transmission, provided |
|||
you inform other peers where the object code and Corresponding |
|||
Source of the work are being offered to the general public at no |
|||
charge under subsection 6d. |
|||
|
|||
A separable portion of the object code, whose source code is excluded |
|||
from the Corresponding Source as a System Library, need not be |
|||
included in conveying the object code work. |
|||
|
|||
A "User Product" is either (1) a "consumer product", which means any |
|||
tangible personal property which is normally used for personal, family, |
|||
or household purposes, or (2) anything designed or sold for incorporation |
|||
into a dwelling. In determining whether a product is a consumer product, |
|||
doubtful cases shall be resolved in favor of coverage. For a particular |
|||
product received by a particular user, "normally used" refers to a |
|||
typical or common use of that class of product, regardless of the status |
|||
of the particular user or of the way in which the particular user |
|||
actually uses, or expects or is expected to use, the product. A product |
|||
is a consumer product regardless of whether the product has substantial |
|||
commercial, industrial or non-consumer uses, unless such uses represent |
|||
the only significant mode of use of the product. |
|||
|
|||
"Installation Information" for a User Product means any methods, |
|||
procedures, authorization keys, or other information required to install |
|||
and execute modified versions of a covered work in that User Product from |
|||
a modified version of its Corresponding Source. The information must |
|||
suffice to ensure that the continued functioning of the modified object |
|||
code is in no case prevented or interfered with solely because |
|||
modification has been made. |
|||
|
|||
If you convey an object code work under this section in, or with, or |
|||
specifically for use in, a User Product, and the conveying occurs as |
|||
part of a transaction in which the right of possession and use of the |
|||
User Product is transferred to the recipient in perpetuity or for a |
|||
fixed term (regardless of how the transaction is characterized), the |
|||
Corresponding Source conveyed under this section must be accompanied |
|||
by the Installation Information. But this requirement does not apply |
|||
if neither you nor any third party retains the ability to install |
|||
modified object code on the User Product (for example, the work has |
|||
been installed in ROM). |
|||
|
|||
The requirement to provide Installation Information does not include a |
|||
requirement to continue to provide support service, warranty, or updates |
|||
for a work that has been modified or installed by the recipient, or for |
|||
the User Product in which it has been modified or installed. Access to a |
|||
network may be denied when the modification itself materially and |
|||
adversely affects the operation of the network or violates the rules and |
|||
protocols for communication across the network. |
|||
|
|||
Corresponding Source conveyed, and Installation Information provided, |
|||
in accord with this section must be in a format that is publicly |
|||
documented (and with an implementation available to the public in |
|||
source code form), and must require no special password or key for |
|||
unpacking, reading or copying. |
|||
|
|||
7. Additional Terms. |
|||
|
|||
"Additional permissions" are terms that supplement the terms of this |
|||
License by making exceptions from one or more of its conditions. |
|||
Additional permissions that are applicable to the entire Program shall |
|||
be treated as though they were included in this License, to the extent |
|||
that they are valid under applicable law. If additional permissions |
|||
apply only to part of the Program, that part may be used separately |
|||
under those permissions, but the entire Program remains governed by |
|||
this License without regard to the additional permissions. |
|||
|
|||
When you convey a copy of a covered work, you may at your option |
|||
remove any additional permissions from that copy, or from any part of |
|||
it. (Additional permissions may be written to require their own |
|||
removal in certain cases when you modify the work.) You may place |
|||
additional permissions on material, added by you to a covered work, |
|||
for which you have or can give appropriate copyright permission. |
|||
|
|||
Notwithstanding any other provision of this License, for material you |
|||
add to a covered work, you may (if authorized by the copyright holders of |
|||
that material) supplement the terms of this License with terms: |
|||
|
|||
a) Disclaiming warranty or limiting liability differently from the |
|||
terms of sections 15 and 16 of this License; or |
|||
|
|||
b) Requiring preservation of specified reasonable legal notices or |
|||
author attributions in that material or in the Appropriate Legal |
|||
Notices displayed by works containing it; or |
|||
|
|||
c) Prohibiting misrepresentation of the origin of that material, or |
|||
requiring that modified versions of such material be marked in |
|||
reasonable ways as different from the original version; or |
|||
|
|||
d) Limiting the use for publicity purposes of names of licensors or |
|||
authors of the material; or |
|||
|
|||
e) Declining to grant rights under trademark law for use of some |
|||
trade names, trademarks, or service marks; or |
|||
|
|||
f) Requiring indemnification of licensors and authors of that |
|||
material by anyone who conveys the material (or modified versions of |
|||
it) with contractual assumptions of liability to the recipient, for |
|||
any liability that these contractual assumptions directly impose on |
|||
those licensors and authors. |
|||
|
|||
All other non-permissive additional terms are considered "further |
|||
restrictions" within the meaning of section 10. If the Program as you |
|||
received it, or any part of it, contains a notice stating that it is |
|||
governed by this License along with a term that is a further |
|||
restriction, you may remove that term. If a license document contains |
|||
a further restriction but permits relicensing or conveying under this |
|||
License, you may add to a covered work material governed by the terms |
|||
of that license document, provided that the further restriction does |
|||
not survive such relicensing or conveying. |
|||
|
|||
If you add terms to a covered work in accord with this section, you |
|||
must place, in the relevant source files, a statement of the |
|||
additional terms that apply to those files, or a notice indicating |
|||
where to find the applicable terms. |
|||
|
|||
Additional terms, permissive or non-permissive, may be stated in the |
|||
form of a separately written license, or stated as exceptions; |
|||
the above requirements apply either way. |
|||
|
|||
8. Termination. |
|||
|
|||
You may not propagate or modify a covered work except as expressly |
|||
provided under this License. Any attempt otherwise to propagate or |
|||
modify it is void, and will automatically terminate your rights under |
|||
this License (including any patent licenses granted under the third |
|||
paragraph of section 11). |
|||
|
|||
However, if you cease all violation of this License, then your |
|||
license from a particular copyright holder is reinstated (a) |
|||
provisionally, unless and until the copyright holder explicitly and |
|||
finally terminates your license, and (b) permanently, if the copyright |
|||
holder fails to notify you of the violation by some reasonable means |
|||
prior to 60 days after the cessation. |
|||
|
|||
Moreover, your license from a particular copyright holder is |
|||
reinstated permanently if the copyright holder notifies you of the |
|||
violation by some reasonable means, this is the first time you have |
|||
received notice of violation of this License (for any work) from that |
|||
copyright holder, and you cure the violation prior to 30 days after |
|||
your receipt of the notice. |
|||
|
|||
Termination of your rights under this section does not terminate the |
|||
licenses of parties who have received copies or rights from you under |
|||
this License. If your rights have been terminated and not permanently |
|||
reinstated, you do not qualify to receive new licenses for the same |
|||
material under section 10. |
|||
|
|||
9. Acceptance Not Required for Having Copies. |
|||
|
|||
You are not required to accept this License in order to receive or |
|||
run a copy of the Program. Ancillary propagation of a covered work |
|||
occurring solely as a consequence of using peer-to-peer transmission |
|||
to receive a copy likewise does not require acceptance. However, |
|||
nothing other than this License grants you permission to propagate or |
|||
modify any covered work. These actions infringe copyright if you do |
|||
not accept this License. Therefore, by modifying or propagating a |
|||
covered work, you indicate your acceptance of this License to do so. |
|||
|
|||
10. Automatic Licensing of Downstream Recipients. |
|||
|
|||
Each time you convey a covered work, the recipient automatically |
|||
receives a license from the original licensors, to run, modify and |
|||
propagate that work, subject to this License. You are not responsible |
|||
for enforcing compliance by third parties with this License. |
|||
|
|||
An "entity transaction" is a transaction transferring control of an |
|||
organization, or substantially all assets of one, or subdividing an |
|||
organization, or merging organizations. If propagation of a covered |
|||
work results from an entity transaction, each party to that |
|||
transaction who receives a copy of the work also receives whatever |
|||
licenses to the work the party's predecessor in interest had or could |
|||
give under the previous paragraph, plus a right to possession of the |
|||
Corresponding Source of the work from the predecessor in interest, if |
|||
the predecessor has it or can get it with reasonable efforts. |
|||
|
|||
You may not impose any further restrictions on the exercise of the |
|||
rights granted or affirmed under this License. For example, you may |
|||
not impose a license fee, royalty, or other charge for exercise of |
|||
rights granted under this License, and you may not initiate litigation |
|||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
|||
any patent claim is infringed by making, using, selling, offering for |
|||
sale, or importing the Program or any portion of it. |
|||
|
|||
11. Patents. |
|||
|
|||
A "contributor" is a copyright holder who authorizes use under this |
|||
License of the Program or a work on which the Program is based. The |
|||
work thus licensed is called the contributor's "contributor version". |
|||
|
|||
A contributor's "essential patent claims" are all patent claims |
|||
owned or controlled by the contributor, whether already acquired or |
|||
hereafter acquired, that would be infringed by some manner, permitted |
|||
by this License, of making, using, or selling its contributor version, |
|||
but do not include claims that would be infringed only as a |
|||
consequence of further modification of the contributor version. For |
|||
purposes of this definition, "control" includes the right to grant |
|||
patent sublicenses in a manner consistent with the requirements of |
|||
this License. |
|||
|
|||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
|||
patent license under the contributor's essential patent claims, to |
|||
make, use, sell, offer for sale, import and otherwise run, modify and |
|||
propagate the contents of its contributor version. |
|||
|
|||
In the following three paragraphs, a "patent license" is any express |
|||
agreement or commitment, however denominated, not to enforce a patent |
|||
(such as an express permission to practice a patent or covenant not to |
|||
sue for patent infringement). To "grant" such a patent license to a |
|||
party means to make such an agreement or commitment not to enforce a |
|||
patent against the party. |
|||
|
|||
If you convey a covered work, knowingly relying on a patent license, |
|||
and the Corresponding Source of the work is not available for anyone |
|||
to copy, free of charge and under the terms of this License, through a |
|||
publicly available network server or other readily accessible means, |
|||
then you must either (1) cause the Corresponding Source to be so |
|||
available, or (2) arrange to deprive yourself of the benefit of the |
|||
patent license for this particular work, or (3) arrange, in a manner |
|||
consistent with the requirements of this License, to extend the patent |
|||
license to downstream recipients. "Knowingly relying" means you have |
|||
actual knowledge that, but for the patent license, your conveying the |
|||
covered work in a country, or your recipient's use of the covered work |
|||
in a country, would infringe one or more identifiable patents in that |
|||
country that you have reason to believe are valid. |
|||
|
|||
If, pursuant to or in connection with a single transaction or |
|||
arrangement, you convey, or propagate by procuring conveyance of, a |
|||
covered work, and grant a patent license to some of the parties |
|||
receiving the covered work authorizing them to use, propagate, modify |
|||
or convey a specific copy of the covered work, then the patent license |
|||
you grant is automatically extended to all recipients of the covered |
|||
work and works based on it. |
|||
|
|||
A patent license is "discriminatory" if it does not include within |
|||
the scope of its coverage, prohibits the exercise of, or is |
|||
conditioned on the non-exercise of one or more of the rights that are |
|||
specifically granted under this License. You may not convey a covered |
|||
work if you are a party to an arrangement with a third party that is |
|||
in the business of distributing software, under which you make payment |
|||
to the third party based on the extent of your activity of conveying |
|||
the work, and under which the third party grants, to any of the |
|||
parties who would receive the covered work from you, a discriminatory |
|||
patent license (a) in connection with copies of the covered work |
|||
conveyed by you (or copies made from those copies), or (b) primarily |
|||
for and in connection with specific products or compilations that |
|||
contain the covered work, unless you entered into that arrangement, |
|||
or that patent license was granted, prior to 28 March 2007. |
|||
|
|||
Nothing in this License shall be construed as excluding or limiting |
|||
any implied license or other defenses to infringement that may |
|||
otherwise be available to you under applicable patent law. |
|||
|
|||
12. No Surrender of Others' Freedom. |
|||
|
|||
If conditions are imposed on you (whether by court order, agreement or |
|||
otherwise) that contradict the conditions of this License, they do not |
|||
excuse you from the conditions of this License. If you cannot convey a |
|||
covered work so as to satisfy simultaneously your obligations under this |
|||
License and any other pertinent obligations, then as a consequence you may |
|||
not convey it at all. For example, if you agree to terms that obligate you |
|||
to collect a royalty for further conveying from those to whom you convey |
|||
the Program, the only way you could satisfy both those terms and this |
|||
License would be to refrain entirely from conveying the Program. |
|||
|
|||
13. Use with the GNU Affero General Public License. |
|||
|
|||
Notwithstanding any other provision of this License, you have |
|||
permission to link or combine any covered work with a work licensed |
|||
under version 3 of the GNU Affero General Public License into a single |
|||
combined work, and to convey the resulting work. The terms of this |
|||
License will continue to apply to the part which is the covered work, |
|||
but the special requirements of the GNU Affero General Public License, |
|||
section 13, concerning interaction through a network will apply to the |
|||
combination as such. |
|||
|
|||
14. Revised Versions of this License. |
|||
|
|||
The Free Software Foundation may publish revised and/or new versions of |
|||
the GNU General Public License from time to time. Such new versions will |
|||
be similar in spirit to the present version, but may differ in detail to |
|||
address new problems or concerns. |
|||
|
|||
Each version is given a distinguishing version number. If the |
|||
Program specifies that a certain numbered version of the GNU General |
|||
Public License "or any later version" applies to it, you have the |
|||
option of following the terms and conditions either of that numbered |
|||
version or of any later version published by the Free Software |
|||
Foundation. If the Program does not specify a version number of the |
|||
GNU General Public License, you may choose any version ever published |
|||
by the Free Software Foundation. |
|||
|
|||
If the Program specifies that a proxy can decide which future |
|||
versions of the GNU General Public License can be used, that proxy's |
|||
public statement of acceptance of a version permanently authorizes you |
|||
to choose that version for the Program. |
|||
|
|||
Later license versions may give you additional or different |
|||
permissions. However, no additional obligations are imposed on any |
|||
author or copyright holder as a result of your choosing to follow a |
|||
later version. |
|||
|
|||
15. Disclaimer of Warranty. |
|||
|
|||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
|||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
|||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
|||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
|||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
|||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
|||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
|||
|
|||
16. Limitation of Liability. |
|||
|
|||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
|||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
|||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
|||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
|||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
|||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
|||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
|||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
|||
SUCH DAMAGES. |
|||
|
|||
17. Interpretation of Sections 15 and 16. |
|||
|
|||
If the disclaimer of warranty and limitation of liability provided |
|||
above cannot be given local legal effect according to their terms, |
|||
reviewing courts shall apply local law that most closely approximates |
|||
an absolute waiver of all civil liability in connection with the |
|||
Program, unless a warranty or assumption of liability accompanies a |
|||
copy of the Program in return for a fee. |
|||
|
|||
END OF TERMS AND CONDITIONS |
@ -0,0 +1,191 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Data validator |
|||
class Audit extends Prefab { |
|||
|
|||
//@{ User agents |
|||
const |
|||
UA_Mobile='android|blackberry|phone|ipod|palm|windows\s+ce', |
|||
UA_Desktop='bsd|linux|os\s+[x9]|solaris|windows', |
|||
UA_Bot='bot|crawl|slurp|spider'; |
|||
//@} |
|||
|
|||
/** |
|||
* Return TRUE if string is a valid URL |
|||
* @return bool |
|||
* @param $str string |
|||
**/ |
|||
function url($str) { |
|||
return is_string(filter_var($str,FILTER_VALIDATE_URL)); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if string is a valid e-mail address; |
|||
* Check DNS MX records if specified |
|||
* @return bool |
|||
* @param $str string |
|||
* @param $mx boolean |
|||
**/ |
|||
function email($str,$mx=TRUE) { |
|||
$hosts=[]; |
|||
return is_string(filter_var($str,FILTER_VALIDATE_EMAIL)) && |
|||
(!$mx || getmxrr(substr($str,strrpos($str,'@')+1),$hosts)); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if string is a valid IPV4 address |
|||
* @return bool |
|||
* @param $addr string |
|||
**/ |
|||
function ipv4($addr) { |
|||
return (bool)filter_var($addr,FILTER_VALIDATE_IP,FILTER_FLAG_IPV4); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if string is a valid IPV6 address |
|||
* @return bool |
|||
* @param $addr string |
|||
**/ |
|||
function ipv6($addr) { |
|||
return (bool)filter_var($addr,FILTER_VALIDATE_IP,FILTER_FLAG_IPV6); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if IP address is within private range |
|||
* @return bool |
|||
* @param $addr string |
|||
**/ |
|||
function isprivate($addr) { |
|||
return !(bool)filter_var($addr,FILTER_VALIDATE_IP, |
|||
FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_PRIV_RANGE); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if IP address is within reserved range |
|||
* @return bool |
|||
* @param $addr string |
|||
**/ |
|||
function isreserved($addr) { |
|||
return !(bool)filter_var($addr,FILTER_VALIDATE_IP, |
|||
FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_RES_RANGE); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if IP address is neither private nor reserved |
|||
* @return bool |
|||
* @param $addr string |
|||
**/ |
|||
function ispublic($addr) { |
|||
return (bool)filter_var($addr,FILTER_VALIDATE_IP, |
|||
FILTER_FLAG_IPV4|FILTER_FLAG_IPV6| |
|||
FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if user agent is a desktop browser |
|||
* @return bool |
|||
* @param $agent string |
|||
**/ |
|||
function isdesktop($agent=NULL) { |
|||
if (!isset($agent)) |
|||
$agent=Base::instance()->AGENT; |
|||
return (bool)preg_match('/('.self::UA_Desktop.')/i',$agent) && |
|||
!$this->ismobile($agent); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if user agent is a mobile device |
|||
* @return bool |
|||
* @param $agent string |
|||
**/ |
|||
function ismobile($agent=NULL) { |
|||
if (!isset($agent)) |
|||
$agent=Base::instance()->AGENT; |
|||
return (bool)preg_match('/('.self::UA_Mobile.')/i',$agent); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if user agent is a Web bot |
|||
* @return bool |
|||
* @param $agent string |
|||
**/ |
|||
function isbot($agent=NULL) { |
|||
if (!isset($agent)) |
|||
$agent=Base::instance()->AGENT; |
|||
return (bool)preg_match('/('.self::UA_Bot.')/i',$agent); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if specified ID has a valid (Luhn) Mod-10 check digit |
|||
* @return bool |
|||
* @param $id string |
|||
**/ |
|||
function mod10($id) { |
|||
if (!ctype_digit($id)) |
|||
return FALSE; |
|||
$id=strrev($id); |
|||
$sum=0; |
|||
for ($i=0,$l=strlen($id);$i<$l;$i++) |
|||
$sum+=$id[$i]+$i%2*(($id[$i]>4)*-4+$id[$i]%5); |
|||
return !($sum%10); |
|||
} |
|||
|
|||
/** |
|||
* Return credit card type if number is valid |
|||
* @return string|FALSE |
|||
* @param $id string |
|||
**/ |
|||
function card($id) { |
|||
$id=preg_replace('/[^\d]/','',$id); |
|||
if ($this->mod10($id)) { |
|||
if (preg_match('/^3[47][0-9]{13}$/',$id)) |
|||
return 'American Express'; |
|||
if (preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/',$id)) |
|||
return 'Diners Club'; |
|||
if (preg_match('/^6(?:011|5[0-9][0-9])[0-9]{12}$/',$id)) |
|||
return 'Discover'; |
|||
if (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/',$id)) |
|||
return 'JCB'; |
|||
if (preg_match('/^5[1-5][0-9]{14}$|'. |
|||
'^(222[1-9]|2[3-6]\d{2}|27[0-1]\d|2720)\d{12}$/',$id)) |
|||
return 'MasterCard'; |
|||
if (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/',$id)) |
|||
return 'Visa'; |
|||
} |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return entropy estimate of a password (NIST 800-63) |
|||
* @return int|float |
|||
* @param $str string |
|||
**/ |
|||
function entropy($str) { |
|||
$len=strlen($str); |
|||
return 4*min($len,1)+($len>1?(2*(min($len,8)-1)):0)+ |
|||
($len>8?(1.5*(min($len,20)-8)):0)+($len>20?($len-20):0)+ |
|||
6*(bool)(preg_match( |
|||
'/[A-Z].*?[0-9[:punct:]]|[0-9[:punct:]].*?[A-Z]/',$str)); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,262 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Authorization/authentication plug-in |
|||
class Auth { |
|||
|
|||
//@{ Error messages |
|||
const |
|||
E_LDAP='LDAP connection failure', |
|||
E_SMTP='SMTP connection failure'; |
|||
//@} |
|||
|
|||
protected |
|||
//! Auth storage |
|||
$storage, |
|||
//! Mapper object |
|||
$mapper, |
|||
//! Storage options |
|||
$args, |
|||
//! Custom compare function |
|||
$func; |
|||
|
|||
/** |
|||
* Jig storage handler |
|||
* @return bool |
|||
* @param $id string |
|||
* @param $pw string |
|||
* @param $realm string |
|||
**/ |
|||
protected function _jig($id,$pw,$realm) { |
|||
$success = (bool) |
|||
call_user_func_array( |
|||
[$this->mapper,'load'], |
|||
[ |
|||
array_merge( |
|||
[ |
|||
'@'.$this->args['id'].'==?'. |
|||
($this->func?'':' AND @'.$this->args['pw'].'==?'). |
|||
(isset($this->args['realm'])? |
|||
(' AND @'.$this->args['realm'].'==?'):''), |
|||
$id |
|||
], |
|||
($this->func?[]:[$pw]), |
|||
(isset($this->args['realm'])?[$realm]:[]) |
|||
) |
|||
] |
|||
); |
|||
if ($success && $this->func) |
|||
$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw'])); |
|||
return $success; |
|||
} |
|||
|
|||
/** |
|||
* MongoDB storage handler |
|||
* @return bool |
|||
* @param $id string |
|||
* @param $pw string |
|||
* @param $realm string |
|||
**/ |
|||
protected function _mongo($id,$pw,$realm) { |
|||
$success = (bool) |
|||
$this->mapper->load( |
|||
[$this->args['id']=>$id]+ |
|||
($this->func?[]:[$this->args['pw']=>$pw])+ |
|||
(isset($this->args['realm'])? |
|||
[$this->args['realm']=>$realm]:[]) |
|||
); |
|||
if ($success && $this->func) |
|||
$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw'])); |
|||
return $success; |
|||
} |
|||
|
|||
/** |
|||
* SQL storage handler |
|||
* @return bool |
|||
* @param $id string |
|||
* @param $pw string |
|||
* @param $realm string |
|||
**/ |
|||
protected function _sql($id,$pw,$realm) { |
|||
$success = (bool) |
|||
call_user_func_array( |
|||
[$this->mapper,'load'], |
|||
[ |
|||
array_merge( |
|||
[ |
|||
$this->args['id'].'=?'. |
|||
($this->func?'':' AND '.$this->args['pw'].'=?'). |
|||
(isset($this->args['realm'])? |
|||
(' AND '.$this->args['realm'].'=?'):''), |
|||
$id |
|||
], |
|||
($this->func?[]:[$pw]), |
|||
(isset($this->args['realm'])?[$realm]:[]) |
|||
) |
|||
] |
|||
); |
|||
if ($success && $this->func) |
|||
$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw'])); |
|||
return $success; |
|||
} |
|||
|
|||
/** |
|||
* LDAP storage handler |
|||
* @return bool |
|||
* @param $id string |
|||
* @param $pw string |
|||
**/ |
|||
protected function _ldap($id,$pw) { |
|||
$port=(int)($this->args['port']?:389); |
|||
$filter=$this->args['filter']=$this->args['filter']?:"uid=".$id; |
|||
$this->args['attr']=$this->args['attr']?:["uid"]; |
|||
array_walk($this->args['attr'], |
|||
function($attr)use(&$filter,$id) { |
|||
$filter=str_ireplace($attr."=*",$attr."=".$id,$filter);}); |
|||
$dc=@ldap_connect($this->args['dc'],$port); |
|||
if ($dc && |
|||
ldap_set_option($dc,LDAP_OPT_PROTOCOL_VERSION,3) && |
|||
ldap_set_option($dc,LDAP_OPT_REFERRALS,0) && |
|||
ldap_bind($dc,$this->args['rdn'],$this->args['pw']) && |
|||
($result=ldap_search($dc,$this->args['base_dn'], |
|||
$filter,$this->args['attr'])) && |
|||
ldap_count_entries($dc,$result) && |
|||
($info=ldap_get_entries($dc,$result)) && |
|||
$info['count']==1 && |
|||
@ldap_bind($dc,$info[0]['dn'],$pw) && |
|||
@ldap_close($dc)) { |
|||
return in_array($id,(array_map(function($value){return $value[0];}, |
|||
array_intersect_key($info[0], |
|||
array_flip($this->args['attr'])))),TRUE); |
|||
} |
|||
user_error(self::E_LDAP,E_USER_ERROR); |
|||
} |
|||
|
|||
/** |
|||
* SMTP storage handler |
|||
* @return bool |
|||
* @param $id string |
|||
* @param $pw string |
|||
**/ |
|||
protected function _smtp($id,$pw) { |
|||
$socket=@fsockopen( |
|||
(strtolower($this->args['scheme'])=='ssl'? |
|||
'ssl://':'').$this->args['host'], |
|||
$this->args['port']); |
|||
$dialog=function($cmd=NULL) use($socket) { |
|||
if (!is_null($cmd)) |
|||
fputs($socket,$cmd."\r\n"); |
|||
$reply=''; |
|||
while (!feof($socket) && |
|||
($info=stream_get_meta_data($socket)) && |
|||
!$info['timed_out'] && $str=fgets($socket,4096)) { |
|||
$reply.=$str; |
|||
if (preg_match('/(?:^|\n)\d{3} .+\r\n/s', |
|||
$reply)) |
|||
break; |
|||
} |
|||
return $reply; |
|||
}; |
|||
if ($socket) { |
|||
stream_set_blocking($socket,TRUE); |
|||
$dialog(); |
|||
$fw=Base::instance(); |
|||
$dialog('EHLO '.$fw->HOST); |
|||
if (strtolower($this->args['scheme'])=='tls') { |
|||
$dialog('STARTTLS'); |
|||
stream_socket_enable_crypto( |
|||
$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT); |
|||
$dialog('EHLO '.$fw->HOST); |
|||
} |
|||
// Authenticate |
|||
$dialog('AUTH LOGIN'); |
|||
$dialog(base64_encode($id)); |
|||
$reply=$dialog(base64_encode($pw)); |
|||
$dialog('QUIT'); |
|||
fclose($socket); |
|||
return (bool)preg_match('/^235 /',$reply); |
|||
} |
|||
user_error(self::E_SMTP,E_USER_ERROR); |
|||
} |
|||
|
|||
/** |
|||
* Login auth mechanism |
|||
* @return bool |
|||
* @param $id string |
|||
* @param $pw string |
|||
* @param $realm string |
|||
**/ |
|||
function login($id,$pw,$realm=NULL) { |
|||
return $this->{'_'.$this->storage}($id,$pw,$realm); |
|||
} |
|||
|
|||
/** |
|||
* HTTP basic auth mechanism |
|||
* @return bool |
|||
* @param $func callback |
|||
**/ |
|||
function basic($func=NULL) { |
|||
$fw=Base::instance(); |
|||
$realm=$fw->REALM; |
|||
$hdr=NULL; |
|||
if (isset($_SERVER['HTTP_AUTHORIZATION'])) |
|||
$hdr=$_SERVER['HTTP_AUTHORIZATION']; |
|||
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) |
|||
$hdr=$_SERVER['REDIRECT_HTTP_AUTHORIZATION']; |
|||
if (!empty($hdr)) |
|||
list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])= |
|||
explode(':',base64_decode(substr($hdr,6))); |
|||
if (isset($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) && |
|||
$this->login( |
|||
$_SERVER['PHP_AUTH_USER'], |
|||
$func? |
|||
$fw->call($func,$_SERVER['PHP_AUTH_PW']): |
|||
$_SERVER['PHP_AUTH_PW'], |
|||
$realm |
|||
)) |
|||
return TRUE; |
|||
if (PHP_SAPI!='cli') |
|||
header('WWW-Authenticate: Basic realm="'.$realm.'"'); |
|||
$fw->status(401); |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @return object |
|||
* @param $storage string|object |
|||
* @param $args array |
|||
* @param $func callback |
|||
**/ |
|||
function __construct($storage,array $args=NULL,$func=NULL) { |
|||
if (is_object($storage) && is_a($storage,'DB\Cursor')) { |
|||
$this->storage=$storage->dbtype(); |
|||
$this->mapper=$storage; |
|||
unset($ref); |
|||
} |
|||
else |
|||
$this->storage=$storage; |
|||
$this->args=$args; |
|||
$this->func=$func; |
|||
} |
|||
|
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,239 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Session-based pseudo-mapper |
|||
class Basket extends Magic { |
|||
|
|||
//@{ Error messages |
|||
const |
|||
E_Field='Undefined field %s'; |
|||
//@} |
|||
|
|||
protected |
|||
//! Session key |
|||
$key, |
|||
//! Current item identifier |
|||
$id, |
|||
//! Current item contents |
|||
$item=[]; |
|||
|
|||
/** |
|||
* Return TRUE if field is defined |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
function exists($key) { |
|||
return array_key_exists($key,$this->item); |
|||
} |
|||
|
|||
/** |
|||
* Assign value to field |
|||
* @return scalar|FALSE |
|||
* @param $key string |
|||
* @param $val scalar |
|||
**/ |
|||
function set($key,$val) { |
|||
return ($key=='_id')?FALSE:($this->item[$key]=$val); |
|||
} |
|||
|
|||
/** |
|||
* Retrieve value of field |
|||
* @return scalar|FALSE |
|||
* @param $key string |
|||
**/ |
|||
function &get($key) { |
|||
if ($key=='_id') |
|||
return $this->id; |
|||
if (array_key_exists($key,$this->item)) |
|||
return $this->item[$key]; |
|||
user_error(sprintf(self::E_Field,$key),E_USER_ERROR); |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Delete field |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function clear($key) { |
|||
unset($this->item[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Return items that match key/value pair; |
|||
* If no key/value pair specified, return all items |
|||
* @return array |
|||
* @param $key string |
|||
* @param $val mixed |
|||
**/ |
|||
function find($key=NULL,$val=NULL) { |
|||
$out=[]; |
|||
if (isset($_SESSION[$this->key])) { |
|||
foreach ($_SESSION[$this->key] as $id=>$item) |
|||
if (!isset($key) || |
|||
array_key_exists($key,$item) && $item[$key]==$val || |
|||
$key=='_id' && $id==$val) { |
|||
$obj=clone($this); |
|||
$obj->id=$id; |
|||
$obj->item=$item; |
|||
$out[]=$obj; |
|||
} |
|||
} |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Return first item that matches key/value pair |
|||
* @return object|FALSE |
|||
* @param $key string |
|||
* @param $val mixed |
|||
**/ |
|||
function findone($key,$val) { |
|||
return ($data=$this->find($key,$val))?$data[0]:FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Map current item to matching key/value pair |
|||
* @return array |
|||
* @param $key string |
|||
* @param $val mixed |
|||
**/ |
|||
function load($key,$val) { |
|||
if ($found=$this->find($key,$val)) { |
|||
$this->id=$found[0]->id; |
|||
return $this->item=$found[0]->item; |
|||
} |
|||
$this->reset(); |
|||
return []; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if current item is empty/undefined |
|||
* @return bool |
|||
**/ |
|||
function dry() { |
|||
return !$this->item; |
|||
} |
|||
|
|||
/** |
|||
* Return number of items in basket |
|||
* @return int |
|||
**/ |
|||
function count() { |
|||
return isset($_SESSION[$this->key])?count($_SESSION[$this->key]):0; |
|||
} |
|||
|
|||
/** |
|||
* Save current item |
|||
* @return array |
|||
**/ |
|||
function save() { |
|||
if (!$this->id) |
|||
$this->id=uniqid(NULL,TRUE); |
|||
$_SESSION[$this->key][$this->id]=$this->item; |
|||
return $this->item; |
|||
} |
|||
|
|||
/** |
|||
* Erase item matching key/value pair |
|||
* @return bool |
|||
* @param $key string |
|||
* @param $val mixed |
|||
**/ |
|||
function erase($key,$val) { |
|||
$found=$this->find($key,$val); |
|||
if ($found && $id=$found[0]->id) { |
|||
unset($_SESSION[$this->key][$id]); |
|||
if ($id==$this->id) |
|||
$this->reset(); |
|||
return TRUE; |
|||
} |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Reset cursor |
|||
* @return NULL |
|||
**/ |
|||
function reset() { |
|||
$this->id=NULL; |
|||
$this->item=[]; |
|||
} |
|||
|
|||
/** |
|||
* Empty basket |
|||
* @return NULL |
|||
**/ |
|||
function drop() { |
|||
unset($_SESSION[$this->key]); |
|||
} |
|||
|
|||
/** |
|||
* Hydrate item using hive array variable |
|||
* @return NULL |
|||
* @param $var array|string |
|||
**/ |
|||
function copyfrom($var) { |
|||
if (is_string($var)) |
|||
$var=\Base::instance()->$var; |
|||
foreach ($var as $key=>$val) |
|||
$this->set($key,$val); |
|||
} |
|||
|
|||
/** |
|||
* Populate hive array variable with item contents |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function copyto($key) { |
|||
$var=&\Base::instance()->ref($key); |
|||
foreach ($this->item as $key=>$field) |
|||
$var[$key]=$field; |
|||
} |
|||
|
|||
/** |
|||
* Check out basket contents |
|||
* @return array |
|||
**/ |
|||
function checkout() { |
|||
if (isset($_SESSION[$this->key])) { |
|||
$out=$_SESSION[$this->key]; |
|||
unset($_SESSION[$this->key]); |
|||
return $out; |
|||
} |
|||
return []; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @return void |
|||
* @param $key string |
|||
**/ |
|||
function __construct($key='basket') { |
|||
$this->key=$key; |
|||
if (session_status()!=PHP_SESSION_ACTIVE) |
|||
session_start(); |
|||
Base::instance()->sync('SESSION'); |
|||
$this->reset(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,96 @@ |
|||
<?php |
|||
|
|||
/** |
|||
* Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
* |
|||
* This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
* |
|||
* This is free software: you can redistribute it and/or modify it under the |
|||
* terms of the GNU General Public License as published by the Free Software |
|||
* Foundation, either version 3 of the License, or later. |
|||
* |
|||
* Fat-Free Framework is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
* General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU General Public License along |
|||
* with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
**/ |
|||
|
|||
/** |
|||
* Lightweight password hashing library (PHP 5.5+ only) |
|||
* @deprecated Use http://php.net/manual/en/ref.password.php instead |
|||
**/ |
|||
class Bcrypt extends Prefab { |
|||
|
|||
//@{ Error messages |
|||
const |
|||
E_CostArg='Invalid cost parameter', |
|||
E_SaltArg='Salt must be at least 22 alphanumeric characters'; |
|||
//@} |
|||
|
|||
//! Default cost |
|||
const |
|||
COST=10; |
|||
|
|||
/** |
|||
* Generate bcrypt hash of string |
|||
* @return string|FALSE |
|||
* @param $pw string |
|||
* @param $salt string |
|||
* @param $cost int |
|||
**/ |
|||
function hash($pw,$salt=NULL,$cost=self::COST) { |
|||
if ($cost<4 || $cost>31) |
|||
user_error(self::E_CostArg,E_USER_ERROR); |
|||
$len=22; |
|||
if ($salt) { |
|||
if (!preg_match('/^[[:alnum:]\.\/]{'.$len.',}$/',$salt)) |
|||
user_error(self::E_SaltArg,E_USER_ERROR); |
|||
} |
|||
else { |
|||
$raw=16; |
|||
$iv=''; |
|||
if (!$iv && extension_loaded('openssl')) |
|||
$iv=openssl_random_pseudo_bytes($raw); |
|||
if (!$iv) |
|||
for ($i=0;$i<$raw;$i++) |
|||
$iv.=chr(mt_rand(0,255)); |
|||
$salt=str_replace('+','.',base64_encode($iv)); |
|||
} |
|||
$salt=substr($salt,0,$len); |
|||
$hash=crypt($pw,sprintf('$2y$%02d$',$cost).$salt); |
|||
return strlen($hash)>13?$hash:FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Check if password is still strong enough |
|||
* @return bool |
|||
* @param $hash string |
|||
* @param $cost int |
|||
**/ |
|||
function needs_rehash($hash,$cost=self::COST) { |
|||
list($pwcost)=sscanf($hash,"$2y$%d$"); |
|||
return $pwcost<$cost; |
|||
} |
|||
|
|||
/** |
|||
* Verify password against hash using timing attack resistant approach |
|||
* @return bool |
|||
* @param $pw string |
|||
* @param $hash string |
|||
**/ |
|||
function verify($pw,$hash) { |
|||
$val=crypt($pw,$hash); |
|||
$len=strlen($val); |
|||
if ($len!=strlen($hash) || $len<14) |
|||
return FALSE; |
|||
$out=0; |
|||
for ($i=0;$i<$len;$i++) |
|||
$out|=(ord($val[$i])^ord($hash[$i])); |
|||
return $out===0; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,487 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace CLI; |
|||
|
|||
//! RFC6455 WebSocket server |
|||
class WS { |
|||
|
|||
const |
|||
//! UUID magic string |
|||
Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11', |
|||
//! Max packet size |
|||
Packet=65536; |
|||
|
|||
//@{ Mask bits for first byte of header |
|||
const |
|||
Text=0x01, |
|||
Binary=0x02, |
|||
Close=0x08, |
|||
Ping=0x09, |
|||
Pong=0x0a, |
|||
OpCode=0x0f, |
|||
Finale=0x80; |
|||
//@} |
|||
|
|||
//@{ Mask bits for second byte of header |
|||
const |
|||
Length=0x7f; |
|||
//@} |
|||
|
|||
protected |
|||
$addr, |
|||
$ctx, |
|||
$wait, |
|||
$sockets, |
|||
$protocol, |
|||
$agents=[], |
|||
$events=[]; |
|||
|
|||
/** |
|||
* Allocate stream socket |
|||
* @return NULL |
|||
* @param $socket resource |
|||
**/ |
|||
function alloc($socket) { |
|||
if (is_bool($buf=$this->read($socket))) |
|||
return; |
|||
// Get WebSocket headers |
|||
$hdrs=[]; |
|||
$EOL="\r\n"; |
|||
$verb=NULL; |
|||
$uri=NULL; |
|||
foreach (explode($EOL,trim($buf)) as $line) |
|||
if (preg_match('/^(\w+)\s(.+)\sHTTP\/[\d.]{1,3}$/', |
|||
trim($line),$match)) { |
|||
$verb=$match[1]; |
|||
$uri=$match[2]; |
|||
} |
|||
else |
|||
if (preg_match('/^(.+): (.+)/',trim($line),$match)) |
|||
// Standardize header |
|||
$hdrs[ |
|||
strtr( |
|||
ucwords( |
|||
strtolower( |
|||
strtr($match[1],'-',' ') |
|||
) |
|||
),' ','-' |
|||
) |
|||
]=$match[2]; |
|||
else { |
|||
$this->close($socket); |
|||
return; |
|||
} |
|||
if (empty($hdrs['Upgrade']) && |
|||
empty($hdrs['Sec-Websocket-Key'])) { |
|||
// Not a WebSocket request |
|||
if ($verb && $uri) |
|||
$this->write( |
|||
$socket, |
|||
'HTTP/1.1 400 Bad Request'.$EOL. |
|||
'Connection: close'.$EOL.$EOL |
|||
); |
|||
$this->close($socket); |
|||
return; |
|||
} |
|||
// Handshake |
|||
$buf='HTTP/1.1 101 Switching Protocols'.$EOL. |
|||
'Upgrade: websocket'.$EOL. |
|||
'Connection: Upgrade'.$EOL; |
|||
if (isset($hdrs['Sec-Websocket-Protocol'])) |
|||
$buf.='Sec-WebSocket-Protocol: '. |
|||
$hdrs['Sec-Websocket-Protocol'].$EOL; |
|||
$buf.='Sec-WebSocket-Accept: '. |
|||
base64_encode( |
|||
sha1($hdrs['Sec-Websocket-Key'].WS::Magic,TRUE) |
|||
).$EOL.$EOL; |
|||
if ($this->write($socket,$buf)) { |
|||
// Connect agent to server |
|||
$this->sockets[(int)$socket]=$socket; |
|||
$this->agents[(int)$socket]= |
|||
new Agent($this,$socket,$verb,$uri,$hdrs); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Close stream socket |
|||
* @return NULL |
|||
* @param $socket resource |
|||
**/ |
|||
function close($socket) { |
|||
if (isset($this->agents[(int)$socket])) |
|||
unset($this->sockets[(int)$socket],$this->agents[(int)$socket]); |
|||
stream_socket_shutdown($socket,STREAM_SHUT_WR); |
|||
@fclose($socket); |
|||
} |
|||
|
|||
/** |
|||
* Read from stream socket |
|||
* @return string|FALSE |
|||
* @param $socket resource |
|||
* @param $len int |
|||
**/ |
|||
function read($socket,$len=0) { |
|||
if (!$len) |
|||
$len=WS::Packet; |
|||
if (is_string($buf=@fread($socket,$len)) && |
|||
strlen($buf) && strlen($buf)<$len) |
|||
return $buf; |
|||
if (isset($this->events['error']) && |
|||
is_callable($func=$this->events['error'])) |
|||
$func($this); |
|||
$this->close($socket); |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Write to stream socket |
|||
* @return int|FALSE |
|||
* @param $socket resource |
|||
* @param $buf string |
|||
**/ |
|||
function write($socket,$buf) { |
|||
for ($i=0,$bytes=0;$i<strlen($buf);$i+=$bytes) { |
|||
if (($bytes=@fwrite($socket,substr($buf,$i))) && |
|||
@fflush($socket)) |
|||
continue; |
|||
if (isset($this->events['error']) && |
|||
is_callable($func=$this->events['error'])) |
|||
$func($this); |
|||
$this->close($socket); |
|||
return FALSE; |
|||
} |
|||
return $bytes; |
|||
} |
|||
|
|||
/** |
|||
* Return socket agents |
|||
* @return array |
|||
* @param $uri string |
|||
***/ |
|||
function agents($uri=NULL) { |
|||
return array_filter( |
|||
$this->agents, |
|||
/** |
|||
* @var $val Agent |
|||
* @return bool |
|||
*/ |
|||
function($val) use($uri) { |
|||
return $uri?($val->uri()==$uri):TRUE; |
|||
} |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Return event handlers |
|||
* @return array |
|||
**/ |
|||
function events() { |
|||
return $this->events; |
|||
} |
|||
|
|||
/** |
|||
* Bind function to event handler |
|||
* @return object |
|||
* @param $event string |
|||
* @param $func callable |
|||
**/ |
|||
function on($event,$func) { |
|||
$this->events[$event]=$func; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Terminate server |
|||
**/ |
|||
function kill() { |
|||
die; |
|||
} |
|||
|
|||
/** |
|||
* Execute the server process |
|||
**/ |
|||
function run() { |
|||
// Assign signal handlers |
|||
declare(ticks=1); |
|||
pcntl_signal(SIGINT,[$this,'kill']); |
|||
pcntl_signal(SIGTERM,[$this,'kill']); |
|||
gc_enable(); |
|||
// Activate WebSocket listener |
|||
$listen=stream_socket_server( |
|||
$this->addr,$errno,$errstr, |
|||
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, |
|||
$this->ctx |
|||
); |
|||
$socket=socket_import_stream($listen); |
|||
register_shutdown_function(function() use($listen) { |
|||
foreach ($this->sockets as $socket) |
|||
if ($socket!=$listen) |
|||
$this->close($socket); |
|||
$this->close($listen); |
|||
if (isset($this->events['stop']) && |
|||
is_callable($func=$this->events['stop'])) |
|||
$func($this); |
|||
}); |
|||
if ($errstr) |
|||
user_error($errstr,E_USER_ERROR); |
|||
if (isset($this->events['start']) && |
|||
is_callable($func=$this->events['start'])) |
|||
$func($this); |
|||
$this->sockets=[(int)$listen=>$listen]; |
|||
$empty=[]; |
|||
$wait=$this->wait; |
|||
while (TRUE) { |
|||
$active=$this->sockets; |
|||
$mark=microtime(TRUE); |
|||
$count=@stream_select( |
|||
$active,$empty,$empty,(int)$wait,round(1e6*($wait-(int)$wait)) |
|||
); |
|||
if (is_bool($count) && $wait) { |
|||
if (isset($this->events['error']) && |
|||
is_callable($func=$this->events['error'])) |
|||
$func($this); |
|||
die; |
|||
} |
|||
if ($count) { |
|||
// Process active connections |
|||
foreach ($active as $socket) { |
|||
if (!is_resource($socket)) |
|||
continue; |
|||
if ($socket==$listen) { |
|||
if ($socket=@stream_socket_accept($listen,0)) |
|||
$this->alloc($socket); |
|||
else |
|||
if (isset($this->events['error']) && |
|||
is_callable($func=$this->events['error'])) |
|||
$func($this); |
|||
} |
|||
else { |
|||
$id=(int)$socket; |
|||
if (isset($this->agents[$id])) |
|||
$this->agents[$id]->fetch(); |
|||
} |
|||
} |
|||
$wait-=microtime(TRUE)-$mark; |
|||
while ($wait<1e-6) { |
|||
$wait+=$this->wait; |
|||
$count=0; |
|||
} |
|||
} |
|||
if (!$count) { |
|||
$mark=microtime(TRUE); |
|||
foreach ($this->sockets as $id=>$socket) { |
|||
if (!is_resource($socket)) |
|||
continue; |
|||
if ($socket!=$listen && |
|||
isset($this->agents[$id]) && |
|||
isset($this->events['idle']) && |
|||
is_callable($func=$this->events['idle'])) |
|||
$func($this->agents[$id]); |
|||
} |
|||
$wait=$this->wait-microtime(TRUE)+$mark; |
|||
} |
|||
gc_collect_cycles(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param $addr string |
|||
* @param $ctx resource |
|||
* @param $wait int |
|||
**/ |
|||
function __construct($addr,$ctx=NULL,$wait=60) { |
|||
$this->addr=$addr; |
|||
$this->ctx=$ctx?:stream_context_create(); |
|||
$this->wait=$wait; |
|||
$this->events=[]; |
|||
} |
|||
|
|||
} |
|||
|
|||
//! RFC6455 remote socket |
|||
class Agent { |
|||
|
|||
protected |
|||
$server, |
|||
$id, |
|||
$socket, |
|||
$flag, |
|||
$verb, |
|||
$uri, |
|||
$headers; |
|||
|
|||
/** |
|||
* Return server instance |
|||
* @return WS |
|||
**/ |
|||
function server() { |
|||
return $this->server; |
|||
} |
|||
|
|||
/** |
|||
* Return socket ID |
|||
* @return string |
|||
**/ |
|||
function id() { |
|||
return $this->id; |
|||
} |
|||
|
|||
/** |
|||
* Return socket |
|||
* @return resource |
|||
**/ |
|||
function socket() { |
|||
return $this->socket; |
|||
} |
|||
|
|||
/** |
|||
* Return request method |
|||
* @return string |
|||
**/ |
|||
function verb() { |
|||
return $this->verb; |
|||
} |
|||
|
|||
/** |
|||
* Return request URI |
|||
* @return string |
|||
**/ |
|||
function uri() { |
|||
return $this->uri; |
|||
} |
|||
|
|||
/** |
|||
* Return socket headers |
|||
* @return array |
|||
**/ |
|||
function headers() { |
|||
return $this->headers; |
|||
} |
|||
|
|||
/** |
|||
* Frame and transmit payload |
|||
* @return string|FALSE |
|||
* @param $op int |
|||
* @param $data string |
|||
**/ |
|||
function send($op,$data='') { |
|||
$server=$this->server; |
|||
$mask=WS::Finale | $op & WS::OpCode; |
|||
$len=strlen($data); |
|||
$buf=''; |
|||
if ($len>0xffff) |
|||
$buf=pack('CCNN',$mask,0x7f,$len); |
|||
elseif ($len>0x7d) |
|||
$buf=pack('CCn',$mask,0x7e,$len); |
|||
else |
|||
$buf=pack('CC',$mask,$len); |
|||
$buf.=$data; |
|||
if (is_bool($server->write($this->socket,$buf))) |
|||
return FALSE; |
|||
if (!in_array($op,[WS::Pong,WS::Close]) && |
|||
isset($this->server->events['send']) && |
|||
is_callable($func=$this->server->events['send'])) |
|||
$func($this,$op,$data); |
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* Retrieve and unmask payload |
|||
* @return bool|NULL |
|||
**/ |
|||
function fetch() { |
|||
// Unmask payload |
|||
$server=$this->server; |
|||
if (is_bool($buf=$server->read($this->socket))) |
|||
return FALSE; |
|||
while($buf) { |
|||
$op=ord($buf[0]) & WS::OpCode; |
|||
$len=ord($buf[1]) & WS::Length; |
|||
$pos=2; |
|||
if ($len==0x7e) { |
|||
$len=ord($buf[2])*256+ord($buf[3]); |
|||
$pos+=2; |
|||
} |
|||
else |
|||
if ($len==0x7f) { |
|||
for ($i=0,$len=0;$i<8;$i++) |
|||
$len=$len*256+ord($buf[$i+2]); |
|||
$pos+=8; |
|||
} |
|||
for ($i=0,$mask=[];$i<4;$i++) |
|||
$mask[$i]=ord($buf[$pos+$i]); |
|||
$pos+=4; |
|||
if (strlen($buf)<$len+$pos) |
|||
return FALSE; |
|||
for ($i=0,$data='';$i<$len;$i++) |
|||
$data.=chr(ord($buf[$pos+$i])^$mask[$i%4]); |
|||
// Dispatch |
|||
switch ($op & WS::OpCode) { |
|||
case WS::Ping: |
|||
$this->send(WS::Pong); |
|||
break; |
|||
case WS::Close: |
|||
$server->close($this->socket); |
|||
break; |
|||
case WS::Text: |
|||
$data=trim($data); |
|||
case WS::Binary: |
|||
if (isset($this->server->events['receive']) && |
|||
is_callable($func=$this->server->events['receive'])) |
|||
$func($this,$op,$data); |
|||
break; |
|||
} |
|||
$buf = substr($buf, $len+$pos); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Destroy object |
|||
**/ |
|||
function __destruct() { |
|||
if (isset($this->server->events['disconnect']) && |
|||
is_callable($func=$this->server->events['disconnect'])) |
|||
$func($this); |
|||
} |
|||
|
|||
/** |
|||
* @param $server WS |
|||
* @param $socket resource |
|||
* @param $verb string |
|||
* @param $uri string |
|||
* @param $hdrs array |
|||
**/ |
|||
function __construct($server,$socket,$verb,$uri,array $hdrs) { |
|||
$this->server=$server; |
|||
$this->id=stream_socket_get_name($socket,TRUE); |
|||
$this->socket=$socket; |
|||
$this->verb=$verb; |
|||
$this->uri=$uri; |
|||
$this->headers=$hdrs; |
|||
|
|||
if (isset($server->events['connect']) && |
|||
is_callable($func=$server->events['connect'])) |
|||
$func($this); |
|||
} |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
code{word-wrap:break-word;color:black}.comment,.doc_comment,.ml_comment{color:dimgray;font-style:italic}.variable{color:blueviolet}.const,.constant_encapsed_string,.class_c,.dir,.file,.func_c,.halt_compiler,.line,.method_c,.lnumber,.dnumber{color:crimson}.string,.and_equal,.boolean_and,.boolean_or,.concat_equal,.dec,.div_equal,.inc,.is_equal,.is_greater_or_equal,.is_identical,.is_not_equal,.is_not_identical,.is_smaller_or_equal,.logical_and,.logical_or,.logical_xor,.minus_equal,.mod_equal,.mul_equal,.ns_c,.ns_separator,.or_equal,.plus_equal,.sl,.sl_equal,.sr,.sr_equal,.xor_equal,.start_heredoc,.end_heredoc,.object_operator,.paamayim_nekudotayim{color:black}.abstract,.array,.array_cast,.as,.break,.case,.catch,.class,.clone,.continue,.declare,.default,.do,.echo,.else,.elseif,.empty.enddeclare,.endfor,.endforach,.endif,.endswitch,.endwhile,.eval,.exit,.extends,.final,.for,.foreach,.function,.global,.goto,.if,.implements,.include,.include_once,.instanceof,.interface,.isset,.list,.namespace,.new,.print,.private,.public,.protected,.require,.require_once,.return,.static,.switch,.throw,.try,.unset,.use,.var,.while{color:royalblue}.open_tag,.open_tag_with_echo,.close_tag{color:orange}.ini_section{color:black}.ini_key{color:royalblue}.ini_value{color:crimson}.xml_tag{color:dodgerblue}.xml_attr{color:blueviolet}.xml_data{color:red}.section{color:black}.directive{color:blue}.data{color:dimgray} |
@ -0,0 +1,388 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB; |
|||
|
|||
//! Simple cursor implementation |
|||
abstract class Cursor extends \Magic implements \IteratorAggregate { |
|||
|
|||
//@{ Error messages |
|||
const |
|||
E_Field='Undefined field %s'; |
|||
//@} |
|||
|
|||
protected |
|||
//! Query results |
|||
$query=[], |
|||
//! Current position |
|||
$ptr=0, |
|||
//! Event listeners |
|||
$trigger=[]; |
|||
|
|||
/** |
|||
* Return database type |
|||
* @return string |
|||
**/ |
|||
abstract function dbtype(); |
|||
|
|||
/** |
|||
* Return field names |
|||
* @return array |
|||
**/ |
|||
abstract function fields(); |
|||
|
|||
/** |
|||
* Return fields of mapper object as an associative array |
|||
* @return array |
|||
* @param $obj object |
|||
**/ |
|||
abstract function cast($obj=NULL); |
|||
|
|||
/** |
|||
* Return records (array of mapper objects) that match criteria |
|||
* @return array |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
* @param $ttl int |
|||
**/ |
|||
abstract function find($filter=NULL,array $options=NULL,$ttl=0); |
|||
|
|||
/** |
|||
* Count records that match criteria |
|||
* @return int |
|||
* @param $filter array |
|||
* @param $options array |
|||
* @param $ttl int |
|||
**/ |
|||
abstract function count($filter=NULL,array $options=NULL,$ttl=0); |
|||
|
|||
/** |
|||
* Insert new record |
|||
* @return array |
|||
**/ |
|||
abstract function insert(); |
|||
|
|||
/** |
|||
* Update current record |
|||
* @return array |
|||
**/ |
|||
abstract function update(); |
|||
|
|||
/** |
|||
* Hydrate mapper object using hive array variable |
|||
* @return NULL |
|||
* @param $var array|string |
|||
* @param $func callback |
|||
**/ |
|||
abstract function copyfrom($var,$func=NULL); |
|||
|
|||
/** |
|||
* Populate hive array variable with mapper fields |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
abstract function copyto($key); |
|||
|
|||
/** |
|||
* Get cursor's equivalent external iterator |
|||
* Causes a fatal error in PHP 5.3.5 if uncommented |
|||
* return ArrayIterator |
|||
**/ |
|||
abstract function getiterator(); |
|||
|
|||
|
|||
/** |
|||
* Return TRUE if current cursor position is not mapped to any record |
|||
* @return bool |
|||
**/ |
|||
function dry() { |
|||
return empty($this->query[$this->ptr]); |
|||
} |
|||
|
|||
/** |
|||
* Return first record (mapper object) that matches criteria |
|||
* @return static|FALSE |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
* @param $ttl int |
|||
**/ |
|||
function findone($filter=NULL,array $options=NULL,$ttl=0) { |
|||
if (!$options) |
|||
$options=[]; |
|||
// Override limit |
|||
$options['limit']=1; |
|||
return ($data=$this->find($filter,$options,$ttl))?$data[0]:FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return array containing subset of records matching criteria, |
|||
* total number of records in superset, specified limit, number of |
|||
* subsets available, and actual subset position |
|||
* @return array |
|||
* @param $pos int |
|||
* @param $size int |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
* @param $ttl int |
|||
* @param $bounce bool |
|||
**/ |
|||
function paginate( |
|||
$pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0,$bounce=TRUE) { |
|||
$total=$this->count($filter,$options,$ttl); |
|||
$count=(int)ceil($total/$size); |
|||
if ($bounce) |
|||
$pos=max(0,min($pos,$count-1)); |
|||
return [ |
|||
'subset'=>($bounce || $pos<$count)?$this->find($filter, |
|||
array_merge( |
|||
$options?:[], |
|||
['limit'=>$size,'offset'=>$pos*$size] |
|||
), |
|||
$ttl |
|||
):[], |
|||
'total'=>$total, |
|||
'limit'=>$size, |
|||
'count'=>$count, |
|||
'pos'=>$bounce?($pos<$count?$pos:0):$pos |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* Map to first record that matches criteria |
|||
* @return array|FALSE |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
* @param $ttl int |
|||
**/ |
|||
function load($filter=NULL,array $options=NULL,$ttl=0) { |
|||
$this->reset(); |
|||
return ($this->query=$this->find($filter,$options,$ttl)) && |
|||
$this->skip(0)?$this->query[$this->ptr]:FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return the count of records loaded |
|||
* @return int |
|||
**/ |
|||
function loaded() { |
|||
return count($this->query); |
|||
} |
|||
|
|||
/** |
|||
* Map to first record in cursor |
|||
* @return mixed |
|||
**/ |
|||
function first() { |
|||
return $this->skip(-$this->ptr); |
|||
} |
|||
|
|||
/** |
|||
* Map to last record in cursor |
|||
* @return mixed |
|||
**/ |
|||
function last() { |
|||
return $this->skip(($ofs=count($this->query)-$this->ptr)?$ofs-1:0); |
|||
} |
|||
|
|||
/** |
|||
* Map to nth record relative to current cursor position |
|||
* @return mixed |
|||
* @param $ofs int |
|||
**/ |
|||
function skip($ofs=1) { |
|||
$this->ptr+=$ofs; |
|||
return $this->ptr>-1 && $this->ptr<count($this->query)? |
|||
$this->query[$this->ptr]:FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Map next record |
|||
* @return mixed |
|||
**/ |
|||
function next() { |
|||
return $this->skip(); |
|||
} |
|||
|
|||
/** |
|||
* Map previous record |
|||
* @return mixed |
|||
**/ |
|||
function prev() { |
|||
return $this->skip(-1); |
|||
} |
|||
|
|||
/** |
|||
* Return whether current iterator position is valid. |
|||
*/ |
|||
function valid() { |
|||
return !$this->dry(); |
|||
} |
|||
|
|||
/** |
|||
* Save mapped record |
|||
* @return mixed |
|||
**/ |
|||
function save() { |
|||
return $this->query?$this->update():$this->insert(); |
|||
} |
|||
|
|||
/** |
|||
* Delete current record |
|||
* @return int|bool |
|||
**/ |
|||
function erase() { |
|||
$this->query=array_slice($this->query,0,$this->ptr,TRUE)+ |
|||
array_slice($this->query,$this->ptr,NULL,TRUE); |
|||
$this->skip(0); |
|||
} |
|||
|
|||
/** |
|||
* Define onload trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function onload($func) { |
|||
return $this->trigger['load']=$func; |
|||
} |
|||
|
|||
/** |
|||
* Define beforeinsert trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function beforeinsert($func) { |
|||
return $this->trigger['beforeinsert']=$func; |
|||
} |
|||
|
|||
/** |
|||
* Define afterinsert trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function afterinsert($func) { |
|||
return $this->trigger['afterinsert']=$func; |
|||
} |
|||
|
|||
/** |
|||
* Define oninsert trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function oninsert($func) { |
|||
return $this->afterinsert($func); |
|||
} |
|||
|
|||
/** |
|||
* Define beforeupdate trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function beforeupdate($func) { |
|||
return $this->trigger['beforeupdate']=$func; |
|||
} |
|||
|
|||
/** |
|||
* Define afterupdate trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function afterupdate($func) { |
|||
return $this->trigger['afterupdate']=$func; |
|||
} |
|||
|
|||
/** |
|||
* Define onupdate trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function onupdate($func) { |
|||
return $this->afterupdate($func); |
|||
} |
|||
|
|||
/** |
|||
* Define beforesave trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function beforesave($func) { |
|||
$this->trigger['beforeinsert']=$func; |
|||
$this->trigger['beforeupdate']=$func; |
|||
return $func; |
|||
} |
|||
|
|||
/** |
|||
* Define aftersave trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function aftersave($func) { |
|||
$this->trigger['afterinsert']=$func; |
|||
$this->trigger['afterupdate']=$func; |
|||
return $func; |
|||
} |
|||
|
|||
/** |
|||
* Define onsave trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function onsave($func) { |
|||
return $this->aftersave($func); |
|||
} |
|||
|
|||
/** |
|||
* Define beforeerase trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function beforeerase($func) { |
|||
return $this->trigger['beforeerase']=$func; |
|||
} |
|||
|
|||
/** |
|||
* Define aftererase trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function aftererase($func) { |
|||
return $this->trigger['aftererase']=$func; |
|||
} |
|||
|
|||
/** |
|||
* Define onerase trigger |
|||
* @return callback |
|||
* @param $func callback |
|||
**/ |
|||
function onerase($func) { |
|||
return $this->aftererase($func); |
|||
} |
|||
|
|||
/** |
|||
* Reset cursor |
|||
* @return NULL |
|||
**/ |
|||
function reset() { |
|||
$this->query=[]; |
|||
$this->ptr=0; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,175 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB; |
|||
|
|||
//! In-memory/flat-file DB wrapper |
|||
class Jig { |
|||
|
|||
//@{ Storage formats |
|||
const |
|||
FORMAT_JSON=0, |
|||
FORMAT_Serialized=1; |
|||
//@} |
|||
|
|||
protected |
|||
//! UUID |
|||
$uuid, |
|||
//! Storage location |
|||
$dir, |
|||
//! Current storage format |
|||
$format, |
|||
//! Jig log |
|||
$log, |
|||
//! Memory-held data |
|||
$data, |
|||
//! lazy load/save files |
|||
$lazy; |
|||
|
|||
/** |
|||
* Read data from memory/file |
|||
* @return array |
|||
* @param $file string |
|||
**/ |
|||
function &read($file) { |
|||
if (!$this->dir || !is_file($dst=$this->dir.$file)) { |
|||
if (!isset($this->data[$file])) |
|||
$this->data[$file]=[]; |
|||
return $this->data[$file]; |
|||
} |
|||
if ($this->lazy && isset($this->data[$file])) |
|||
return $this->data[$file]; |
|||
$fw=\Base::instance(); |
|||
$raw=$fw->read($dst); |
|||
switch ($this->format) { |
|||
case self::FORMAT_JSON: |
|||
$data=json_decode($raw,TRUE); |
|||
break; |
|||
case self::FORMAT_Serialized: |
|||
$data=$fw->unserialize($raw); |
|||
break; |
|||
} |
|||
$this->data[$file] = $data; |
|||
return $this->data[$file]; |
|||
} |
|||
|
|||
/** |
|||
* Write data to memory/file |
|||
* @return int |
|||
* @param $file string |
|||
* @param $data array |
|||
**/ |
|||
function write($file,array $data=NULL) { |
|||
if (!$this->dir || $this->lazy) |
|||
return count($this->data[$file]=$data); |
|||
$fw=\Base::instance(); |
|||
switch ($this->format) { |
|||
case self::FORMAT_JSON: |
|||
$out=json_encode($data,JSON_PRETTY_PRINT); |
|||
break; |
|||
case self::FORMAT_Serialized: |
|||
$out=$fw->serialize($data); |
|||
break; |
|||
} |
|||
return $fw->write($this->dir.$file,$out); |
|||
} |
|||
|
|||
/** |
|||
* Return directory |
|||
* @return string |
|||
**/ |
|||
function dir() { |
|||
return $this->dir; |
|||
} |
|||
|
|||
/** |
|||
* Return UUID |
|||
* @return string |
|||
**/ |
|||
function uuid() { |
|||
return $this->uuid; |
|||
} |
|||
|
|||
/** |
|||
* Return profiler results (or disable logging) |
|||
* @param $flag bool |
|||
* @return string |
|||
**/ |
|||
function log($flag=TRUE) { |
|||
if ($flag) |
|||
return $this->log; |
|||
$this->log=FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Jot down log entry |
|||
* @return NULL |
|||
* @param $frame string |
|||
**/ |
|||
function jot($frame) { |
|||
if ($frame) |
|||
$this->log.=date('r').' '.$frame.PHP_EOL; |
|||
} |
|||
|
|||
/** |
|||
* Clean storage |
|||
* @return NULL |
|||
**/ |
|||
function drop() { |
|||
if ($this->lazy) // intentional |
|||
$this->data=[]; |
|||
if (!$this->dir) |
|||
$this->data=[]; |
|||
elseif ($glob=@glob($this->dir.'/*',GLOB_NOSORT)) |
|||
foreach ($glob as $file) |
|||
@unlink($file); |
|||
} |
|||
|
|||
//! Prohibit cloning |
|||
private function __clone() { |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $dir string |
|||
* @param $format int |
|||
**/ |
|||
function __construct($dir=NULL,$format=self::FORMAT_JSON,$lazy=FALSE) { |
|||
if ($dir && !is_dir($dir)) |
|||
mkdir($dir,\Base::MODE,TRUE); |
|||
$this->uuid=\Base::instance()->hash($this->dir=$dir); |
|||
$this->format=$format; |
|||
$this->lazy=$lazy; |
|||
} |
|||
|
|||
/** |
|||
* save file on destruction |
|||
**/ |
|||
function __destruct() { |
|||
if ($this->lazy) { |
|||
$this->lazy = FALSE; |
|||
foreach ($this->data?:[] as $file => $data) |
|||
$this->write($file,$data); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,541 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB\Jig; |
|||
|
|||
//! Flat-file DB mapper |
|||
class Mapper extends \DB\Cursor { |
|||
|
|||
protected |
|||
//! Flat-file DB wrapper |
|||
$db, |
|||
//! Data file |
|||
$file, |
|||
//! Document identifier |
|||
$id, |
|||
//! Document contents |
|||
$document=[], |
|||
//! field map-reduce handlers |
|||
$_reduce; |
|||
|
|||
/** |
|||
* Return database type |
|||
* @return string |
|||
**/ |
|||
function dbtype() { |
|||
return 'Jig'; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if field is defined |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
function exists($key) { |
|||
return array_key_exists($key,$this->document); |
|||
} |
|||
|
|||
/** |
|||
* Assign value to field |
|||
* @return scalar|FALSE |
|||
* @param $key string |
|||
* @param $val scalar |
|||
**/ |
|||
function set($key,$val) { |
|||
return ($key=='_id')?FALSE:($this->document[$key]=$val); |
|||
} |
|||
|
|||
/** |
|||
* Retrieve value of field |
|||
* @return scalar|FALSE |
|||
* @param $key string |
|||
**/ |
|||
function &get($key) { |
|||
if ($key=='_id') |
|||
return $this->id; |
|||
if (array_key_exists($key,$this->document)) |
|||
return $this->document[$key]; |
|||
user_error(sprintf(self::E_Field,$key),E_USER_ERROR); |
|||
} |
|||
|
|||
/** |
|||
* Delete field |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function clear($key) { |
|||
if ($key!='_id') |
|||
unset($this->document[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Convert array to mapper object |
|||
* @return object |
|||
* @param $id string |
|||
* @param $row array |
|||
**/ |
|||
function factory($id,$row) { |
|||
$mapper=clone($this); |
|||
$mapper->reset(); |
|||
$mapper->id=$id; |
|||
foreach ($row as $field=>$val) |
|||
$mapper->document[$field]=$val; |
|||
$mapper->query=[clone($mapper)]; |
|||
if (isset($mapper->trigger['load'])) |
|||
\Base::instance()->call($mapper->trigger['load'],$mapper); |
|||
return $mapper; |
|||
} |
|||
|
|||
/** |
|||
* Return fields of mapper object as an associative array |
|||
* @return array |
|||
* @param $obj object |
|||
**/ |
|||
function cast($obj=NULL) { |
|||
if (!$obj) |
|||
$obj=$this; |
|||
return $obj->document+['_id'=>$this->id]; |
|||
} |
|||
|
|||
/** |
|||
* Convert tokens in string expression to variable names |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function token($str) { |
|||
$str=preg_replace_callback( |
|||
'/(?<!\w)@(\w(?:[\w\.\[\]])*)/', |
|||
function($token) { |
|||
// Convert from JS dot notation to PHP array notation |
|||
return '$'.preg_replace_callback( |
|||
'/(\.\w+)|\[((?:[^\[\]]*|(?R))*)\]/', |
|||
function($expr) { |
|||
$fw=\Base::instance(); |
|||
return |
|||
'['. |
|||
($expr[1]? |
|||
$fw->stringify(substr($expr[1],1)): |
|||
(preg_match('/^\w+/', |
|||
$mix=$this->token($expr[2]))? |
|||
$fw->stringify($mix): |
|||
$mix)). |
|||
']'; |
|||
}, |
|||
$token[1] |
|||
); |
|||
}, |
|||
$str |
|||
); |
|||
return trim($str); |
|||
} |
|||
|
|||
/** |
|||
* Return records that match criteria |
|||
* @return static[]|FALSE |
|||
* @param $filter array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
* @param $log bool |
|||
**/ |
|||
function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) { |
|||
if (!$options) |
|||
$options=[]; |
|||
$options+=[ |
|||
'order'=>NULL, |
|||
'limit'=>0, |
|||
'offset'=>0, |
|||
'group'=>NULL, |
|||
]; |
|||
$fw=\Base::instance(); |
|||
$cache=\Cache::instance(); |
|||
$db=$this->db; |
|||
$now=microtime(TRUE); |
|||
$data=[]; |
|||
$tag=''; |
|||
if (is_array($ttl)) |
|||
list($ttl,$tag)=$ttl; |
|||
if (!$fw->CACHE || !$ttl || !($cached=$cache->exists( |
|||
$hash=$fw->hash($this->db->dir(). |
|||
$fw->stringify([$filter,$options])).($tag?'.'.$tag:'').'.jig',$data)) || |
|||
$cached[0]+$ttl<microtime(TRUE)) { |
|||
$data=$db->read($this->file); |
|||
if (is_null($data)) |
|||
return FALSE; |
|||
foreach ($data as $id=>&$doc) { |
|||
$doc['_id']=$id; |
|||
unset($doc); |
|||
} |
|||
if ($filter) { |
|||
if (!is_array($filter)) |
|||
return FALSE; |
|||
// Normalize equality operator |
|||
$expr=preg_replace('/(?<=[^<>!=])=(?!=)/','==',$filter[0]); |
|||
// Prepare query arguments |
|||
$args=isset($filter[1]) && is_array($filter[1])? |
|||
$filter[1]: |
|||
array_slice($filter,1,NULL,TRUE); |
|||
$args=is_array($args)?$args:[1=>$args]; |
|||
$keys=$vals=[]; |
|||
$tokens=array_slice( |
|||
token_get_all('<?php '.$this->token($expr)),1); |
|||
$data=array_filter($data, |
|||
function($_row) use($fw,$args,$tokens) { |
|||
$_expr=''; |
|||
$ctr=0; |
|||
$named=FALSE; |
|||
foreach ($tokens as $token) { |
|||
if (is_string($token)) |
|||
if ($token=='?') { |
|||
// Positional |
|||
$ctr++; |
|||
$key=$ctr; |
|||
} |
|||
else { |
|||
if ($token==':') |
|||
$named=TRUE; |
|||
else |
|||
$_expr.=$token; |
|||
continue; |
|||
} |
|||
elseif ($named && |
|||
token_name($token[0])=='T_STRING') { |
|||
$key=':'.$token[1]; |
|||
$named=FALSE; |
|||
} |
|||
else { |
|||
$_expr.=$token[1]; |
|||
continue; |
|||
} |
|||
$_expr.=$fw->stringify( |
|||
is_string($args[$key])? |
|||
addcslashes($args[$key],'\''): |
|||
$args[$key]); |
|||
} |
|||
// Avoid conflict with user code |
|||
unset($fw,$tokens,$args,$ctr,$token,$key,$named); |
|||
extract($_row); |
|||
// Evaluate pseudo-SQL expression |
|||
return eval('return '.$_expr.';'); |
|||
} |
|||
); |
|||
} |
|||
if (isset($options['group'])) { |
|||
$cols=array_reverse($fw->split($options['group'])); |
|||
// sort into groups |
|||
$data=$this->sort($data,$options['group']); |
|||
foreach($data as $i=>&$row) { |
|||
if (!isset($prev)) { |
|||
$prev=$row; |
|||
$prev_i=$i; |
|||
} |
|||
$drop=false; |
|||
foreach ($cols as $col) |
|||
if ($prev_i!=$i && array_key_exists($col,$row) && |
|||
array_key_exists($col,$prev) && $row[$col]==$prev[$col]) |
|||
// reduce/modify |
|||
$drop=!isset($this->_reduce[$col]) || call_user_func_array( |
|||
$this->_reduce[$col][0],[&$prev,&$row])!==FALSE; |
|||
elseif (isset($this->_reduce[$col])) { |
|||
$null=null; |
|||
// initial |
|||
call_user_func_array($this->_reduce[$col][0],[&$row,&$null]); |
|||
} |
|||
if ($drop) |
|||
unset($data[$i]); |
|||
else { |
|||
$prev=&$row; |
|||
$prev_i=$i; |
|||
} |
|||
unset($row); |
|||
} |
|||
// finalize |
|||
if ($this->_reduce[$col][1]) |
|||
foreach($data as $i=>&$row) { |
|||
$row=call_user_func($this->_reduce[$col][1],$row); |
|||
if (!$row) |
|||
unset($data[$i]); |
|||
unset($row); |
|||
} |
|||
} |
|||
if (isset($options['order'])) |
|||
$data=$this->sort($data,$options['order']); |
|||
$data=array_slice($data, |
|||
$options['offset'],$options['limit']?:NULL,TRUE); |
|||
if ($fw->CACHE && $ttl) |
|||
// Save to cache backend |
|||
$cache->set($hash,$data,$ttl); |
|||
} |
|||
$out=[]; |
|||
foreach ($data as $id=>&$doc) { |
|||
unset($doc['_id']); |
|||
$out[]=$this->factory($id,$doc); |
|||
unset($doc); |
|||
} |
|||
if ($log && isset($args)) { |
|||
if ($filter) |
|||
foreach ($args as $key=>$val) { |
|||
$vals[]=$fw->stringify(is_array($val)?$val[0]:$val); |
|||
$keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/'; |
|||
} |
|||
$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. |
|||
$this->file.' [find] '. |
|||
($filter?preg_replace($keys,$vals,$filter[0],1):'')); |
|||
} |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Sort a collection |
|||
* @param $data |
|||
* @param $cond |
|||
* @return mixed |
|||
*/ |
|||
protected function sort($data,$cond) { |
|||
$cols=\Base::instance()->split($cond); |
|||
uasort( |
|||
$data, |
|||
function($val1,$val2) use($cols) { |
|||
foreach ($cols as $col) { |
|||
$parts=explode(' ',$col,2); |
|||
$order=empty($parts[1])? |
|||
SORT_ASC: |
|||
constant($parts[1]); |
|||
$col=$parts[0]; |
|||
if (!array_key_exists($col,$val1)) |
|||
$val1[$col]=NULL; |
|||
if (!array_key_exists($col,$val2)) |
|||
$val2[$col]=NULL; |
|||
list($v1,$v2)=[$val1[$col],$val2[$col]]; |
|||
if ($out=strnatcmp($v1,$v2)* |
|||
(($order==SORT_ASC)*2-1)) |
|||
return $out; |
|||
} |
|||
return 0; |
|||
} |
|||
); |
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* Add reduce handler for grouped fields |
|||
* @param $key string |
|||
* @param $handler callback |
|||
* @param $finalize callback |
|||
*/ |
|||
function reduce($key,$handler,$finalize=null){ |
|||
$this->_reduce[$key]=[$handler,$finalize]; |
|||
} |
|||
|
|||
/** |
|||
* Count records that match criteria |
|||
* @return int |
|||
* @param $filter array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
**/ |
|||
function count($filter=NULL,array $options=NULL,$ttl=0) { |
|||
$now=microtime(TRUE); |
|||
$out=count($this->find($filter,$options,$ttl,FALSE)); |
|||
$this->db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. |
|||
$this->file.' [count] '.($filter?json_encode($filter):'')); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Return record at specified offset using criteria of previous |
|||
* load() call and make it active |
|||
* @return array |
|||
* @param $ofs int |
|||
**/ |
|||
function skip($ofs=1) { |
|||
$this->document=($out=parent::skip($ofs))?$out->document:[]; |
|||
$this->id=$out?$out->id:NULL; |
|||
if ($this->document && isset($this->trigger['load'])) |
|||
\Base::instance()->call($this->trigger['load'],$this); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Insert new record |
|||
* @return array |
|||
**/ |
|||
function insert() { |
|||
if ($this->id) |
|||
return $this->update(); |
|||
$db=$this->db; |
|||
$now=microtime(TRUE); |
|||
while (($id=uniqid(NULL,TRUE)) && |
|||
($data=&$db->read($this->file)) && isset($data[$id]) && |
|||
!connection_aborted()) |
|||
usleep(mt_rand(0,100)); |
|||
$this->id=$id; |
|||
$pkey=['_id'=>$this->id]; |
|||
if (isset($this->trigger['beforeinsert']) && |
|||
\Base::instance()->call($this->trigger['beforeinsert'], |
|||
[$this,$pkey])===FALSE) |
|||
return $this->document; |
|||
$data[$id]=$this->document; |
|||
$db->write($this->file,$data); |
|||
$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. |
|||
$this->file.' [insert] '.json_encode($this->document)); |
|||
if (isset($this->trigger['afterinsert'])) |
|||
\Base::instance()->call($this->trigger['afterinsert'], |
|||
[$this,$pkey]); |
|||
$this->load(['@_id=?',$this->id]); |
|||
return $this->document; |
|||
} |
|||
|
|||
/** |
|||
* Update current record |
|||
* @return array |
|||
**/ |
|||
function update() { |
|||
$db=$this->db; |
|||
$now=microtime(TRUE); |
|||
$data=&$db->read($this->file); |
|||
if (isset($this->trigger['beforeupdate']) && |
|||
\Base::instance()->call($this->trigger['beforeupdate'], |
|||
[$this,['_id'=>$this->id]])===FALSE) |
|||
return $this->document; |
|||
$data[$this->id]=$this->document; |
|||
$db->write($this->file,$data); |
|||
$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. |
|||
$this->file.' [update] '.json_encode($this->document)); |
|||
if (isset($this->trigger['afterupdate'])) |
|||
\Base::instance()->call($this->trigger['afterupdate'], |
|||
[$this,['_id'=>$this->id]]); |
|||
return $this->document; |
|||
} |
|||
|
|||
/** |
|||
* Delete current record |
|||
* @return bool |
|||
* @param $filter array |
|||
* @param $quick bool |
|||
**/ |
|||
function erase($filter=NULL,$quick=FALSE) { |
|||
$db=$this->db; |
|||
$now=microtime(TRUE); |
|||
$data=&$db->read($this->file); |
|||
$pkey=['_id'=>$this->id]; |
|||
if ($filter) { |
|||
foreach ($this->find($filter,NULL,FALSE) as $mapper) |
|||
if (!$mapper->erase(null,$quick)) |
|||
return FALSE; |
|||
return TRUE; |
|||
} |
|||
elseif (isset($this->id)) { |
|||
unset($data[$this->id]); |
|||
parent::erase(); |
|||
} |
|||
else |
|||
return FALSE; |
|||
if (!$quick && isset($this->trigger['beforeerase']) && |
|||
\Base::instance()->call($this->trigger['beforeerase'], |
|||
[$this,$pkey])===FALSE) |
|||
return FALSE; |
|||
$db->write($this->file,$data); |
|||
if ($filter) { |
|||
$args=isset($filter[1]) && is_array($filter[1])? |
|||
$filter[1]: |
|||
array_slice($filter,1,NULL,TRUE); |
|||
$args=is_array($args)?$args:[1=>$args]; |
|||
foreach ($args as $key=>$val) { |
|||
$vals[]=\Base::instance()-> |
|||
stringify(is_array($val)?$val[0]:$val); |
|||
$keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/'; |
|||
} |
|||
} |
|||
$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. |
|||
$this->file.' [erase] '. |
|||
($filter?preg_replace($keys,$vals,$filter[0],1):'')); |
|||
if (!$quick && isset($this->trigger['aftererase'])) |
|||
\Base::instance()->call($this->trigger['aftererase'], |
|||
[$this,$pkey]); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Reset cursor |
|||
* @return NULL |
|||
**/ |
|||
function reset() { |
|||
$this->id=NULL; |
|||
$this->document=[]; |
|||
parent::reset(); |
|||
} |
|||
|
|||
/** |
|||
* Hydrate mapper object using hive array variable |
|||
* @return NULL |
|||
* @param $var array|string |
|||
* @param $func callback |
|||
**/ |
|||
function copyfrom($var,$func=NULL) { |
|||
if (is_string($var)) |
|||
$var=\Base::instance()->$var; |
|||
if ($func) |
|||
$var=call_user_func($func,$var); |
|||
foreach ($var as $key=>$val) |
|||
$this->set($key,$val); |
|||
} |
|||
|
|||
/** |
|||
* Populate hive array variable with mapper fields |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function copyto($key) { |
|||
$var=&\Base::instance()->ref($key); |
|||
foreach ($this->document as $key=>$field) |
|||
$var[$key]=$field; |
|||
} |
|||
|
|||
/** |
|||
* Return field names |
|||
* @return array |
|||
**/ |
|||
function fields() { |
|||
return array_keys($this->document); |
|||
} |
|||
|
|||
/** |
|||
* Retrieve external iterator for fields |
|||
* @return object |
|||
**/ |
|||
function getiterator() { |
|||
return new \ArrayIterator($this->cast()); |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @return void |
|||
* @param $db object |
|||
* @param $file string |
|||
**/ |
|||
function __construct(\DB\Jig $db,$file) { |
|||
$this->db=$db; |
|||
$this->file=$file; |
|||
$this->reset(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,194 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB\Jig; |
|||
|
|||
//! Jig-managed session handler |
|||
class Session extends Mapper { |
|||
|
|||
protected |
|||
//! Session ID |
|||
$sid, |
|||
//! Anti-CSRF token |
|||
$_csrf, |
|||
//! User agent |
|||
$_agent, |
|||
//! IP, |
|||
$_ip, |
|||
//! Suspect callback |
|||
$onsuspect; |
|||
|
|||
/** |
|||
* Open session |
|||
* @return TRUE |
|||
* @param $path string |
|||
* @param $name string |
|||
**/ |
|||
function open($path,$name) { |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Close session |
|||
* @return TRUE |
|||
**/ |
|||
function close() { |
|||
$this->reset(); |
|||
$this->sid=NULL; |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session data in serialized format |
|||
* @return string |
|||
* @param $id string |
|||
**/ |
|||
function read($id) { |
|||
$this->load(['@session_id=?',$this->sid=$id]); |
|||
if ($this->dry()) |
|||
return ''; |
|||
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) { |
|||
$fw=\Base::instance(); |
|||
if (!isset($this->onsuspect) || |
|||
$fw->call($this->onsuspect,[$this,$id])===FALSE) { |
|||
// NB: `session_destroy` can't be called at that stage; |
|||
// `session_start` not completed |
|||
$this->destroy($id); |
|||
$this->close(); |
|||
unset($fw->{'COOKIE.'.session_name()}); |
|||
$fw->error(403); |
|||
} |
|||
} |
|||
return $this->get('data'); |
|||
} |
|||
|
|||
/** |
|||
* Write session data |
|||
* @return TRUE |
|||
* @param $id string |
|||
* @param $data string |
|||
**/ |
|||
function write($id,$data) { |
|||
$this->set('session_id',$id); |
|||
$this->set('data',$data); |
|||
$this->set('ip',$this->_ip); |
|||
$this->set('agent',$this->_agent); |
|||
$this->set('stamp',time()); |
|||
$this->save(); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Destroy session |
|||
* @return TRUE |
|||
* @param $id string |
|||
**/ |
|||
function destroy($id) { |
|||
$this->erase(['@session_id=?',$id]); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Garbage collector |
|||
* @return TRUE |
|||
* @param $max int |
|||
**/ |
|||
function cleanup($max) { |
|||
$this->erase(['@stamp+?<?',$max,time()]); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session id (if session has started) |
|||
* @return string|NULL |
|||
**/ |
|||
function sid() { |
|||
return $this->sid; |
|||
} |
|||
|
|||
/** |
|||
* Return anti-CSRF token |
|||
* @return string |
|||
**/ |
|||
function csrf() { |
|||
return $this->_csrf; |
|||
} |
|||
|
|||
/** |
|||
* Return IP address |
|||
* @return string |
|||
**/ |
|||
function ip() { |
|||
return $this->_ip; |
|||
} |
|||
|
|||
/** |
|||
* Return Unix timestamp |
|||
* @return string|FALSE |
|||
**/ |
|||
function stamp() { |
|||
if (!$this->sid) |
|||
session_start(); |
|||
return $this->dry()?FALSE:$this->get('stamp'); |
|||
} |
|||
|
|||
/** |
|||
* Return HTTP user agent |
|||
* @return string|FALSE |
|||
**/ |
|||
function agent() { |
|||
return $this->_agent; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $db \DB\Jig |
|||
* @param $file string |
|||
* @param $onsuspect callback |
|||
* @param $key string |
|||
**/ |
|||
function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL,$key=NULL) { |
|||
parent::__construct($db,$file); |
|||
$this->onsuspect=$onsuspect; |
|||
session_set_save_handler( |
|||
[$this,'open'], |
|||
[$this,'close'], |
|||
[$this,'read'], |
|||
[$this,'write'], |
|||
[$this,'destroy'], |
|||
[$this,'cleanup'] |
|||
); |
|||
register_shutdown_function('session_commit'); |
|||
$fw=\Base::instance(); |
|||
$headers=$fw->HEADERS; |
|||
$this->_csrf=$fw->hash($fw->SEED. |
|||
extension_loaded('openssl')? |
|||
implode(unpack('L',openssl_random_pseudo_bytes(4))): |
|||
mt_rand() |
|||
); |
|||
if ($key) |
|||
$fw->$key=$this->_csrf; |
|||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; |
|||
$this->_ip=$fw->IP; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,145 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB; |
|||
|
|||
//! MongoDB wrapper |
|||
class Mongo { |
|||
|
|||
//@{ |
|||
const |
|||
E_Profiler='MongoDB profiler is disabled'; |
|||
//@} |
|||
|
|||
protected |
|||
//! UUID |
|||
$uuid, |
|||
//! Data source name |
|||
$dsn, |
|||
//! MongoDB object |
|||
$db, |
|||
//! Legacy flag |
|||
$legacy, |
|||
//! MongoDB log |
|||
$log; |
|||
|
|||
/** |
|||
* Return data source name |
|||
* @return string |
|||
**/ |
|||
function dsn() { |
|||
return $this->dsn; |
|||
} |
|||
|
|||
/** |
|||
* Return UUID |
|||
* @return string |
|||
**/ |
|||
function uuid() { |
|||
return $this->uuid; |
|||
} |
|||
|
|||
/** |
|||
* Return MongoDB profiler results (or disable logging) |
|||
* @param $flag bool |
|||
* @return string |
|||
**/ |
|||
function log($flag=TRUE) { |
|||
if ($flag) { |
|||
$cursor=$this->db->selectcollection('system.profile')->find(); |
|||
foreach (iterator_to_array($cursor) as $frame) |
|||
if (!preg_match('/\.system\..+$/',$frame['ns'])) |
|||
$this->log.=date('r',$this->legacy() ? |
|||
$frame['ts']->sec : (round((string)$frame['ts'])/1000)). |
|||
' ('.sprintf('%.1f',$frame['millis']).'ms) '. |
|||
$frame['ns'].' ['.$frame['op'].'] '. |
|||
(empty($frame['query'])? |
|||
'':json_encode($frame['query'])). |
|||
(empty($frame['command'])? |
|||
'':json_encode($frame['command'])). |
|||
PHP_EOL; |
|||
} else { |
|||
$this->log=FALSE; |
|||
if ($this->legacy) |
|||
$this->db->setprofilinglevel(-1); |
|||
else |
|||
$this->db->command(['profile'=>-1]); |
|||
} |
|||
return $this->log; |
|||
} |
|||
|
|||
/** |
|||
* Intercept native call to re-enable profiler |
|||
* @return int |
|||
**/ |
|||
function drop() { |
|||
$out=$this->db->drop(); |
|||
if ($this->log!==FALSE) { |
|||
if ($this->legacy) |
|||
$this->db->setprofilinglevel(2); |
|||
else |
|||
$this->db->command(['profile'=>2]); |
|||
} |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Redirect call to MongoDB object |
|||
* @return mixed |
|||
* @param $func string |
|||
* @param $args array |
|||
**/ |
|||
function __call($func,array $args) { |
|||
return call_user_func_array([$this->db,$func],$args); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if legacy driver is loaded |
|||
* @return bool |
|||
**/ |
|||
function legacy() { |
|||
return $this->legacy; |
|||
} |
|||
|
|||
//! Prohibit cloning |
|||
private function __clone() { |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $dsn string |
|||
* @param $dbname string |
|||
* @param $options array |
|||
**/ |
|||
function __construct($dsn,$dbname,array $options=NULL) { |
|||
$this->uuid=\Base::instance()->hash($this->dsn=$dsn); |
|||
if ($this->legacy=class_exists('\MongoClient')) { |
|||
$this->db=new \MongoDB(new \MongoClient($dsn,$options?:[]),$dbname); |
|||
$this->db->setprofilinglevel(2); |
|||
} |
|||
else { |
|||
$this->db=(new \MongoDB\Client($dsn,$options?:[]))->$dbname; |
|||
$this->db->command(['profile'=>2]); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,405 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB\Mongo; |
|||
|
|||
//! MongoDB mapper |
|||
class Mapper extends \DB\Cursor { |
|||
|
|||
protected |
|||
//! MongoDB wrapper |
|||
$db, |
|||
//! Legacy flag |
|||
$legacy, |
|||
//! Mongo collection |
|||
$collection, |
|||
//! Mongo document |
|||
$document=[], |
|||
//! Mongo cursor |
|||
$cursor, |
|||
//! Defined fields |
|||
$fields; |
|||
|
|||
/** |
|||
* Return database type |
|||
* @return string |
|||
**/ |
|||
function dbtype() { |
|||
return 'Mongo'; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if field is defined |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
function exists($key) { |
|||
return array_key_exists($key,$this->document); |
|||
} |
|||
|
|||
/** |
|||
* Assign value to field |
|||
* @return scalar|FALSE |
|||
* @param $key string |
|||
* @param $val scalar |
|||
**/ |
|||
function set($key,$val) { |
|||
return $this->document[$key]=$val; |
|||
} |
|||
|
|||
/** |
|||
* Retrieve value of field |
|||
* @return scalar|FALSE |
|||
* @param $key string |
|||
**/ |
|||
function &get($key) { |
|||
if ($this->exists($key)) |
|||
return $this->document[$key]; |
|||
user_error(sprintf(self::E_Field,$key),E_USER_ERROR); |
|||
} |
|||
|
|||
/** |
|||
* Delete field |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function clear($key) { |
|||
unset($this->document[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Convert array to mapper object |
|||
* @return static |
|||
* @param $row array |
|||
**/ |
|||
function factory($row) { |
|||
$mapper=clone($this); |
|||
$mapper->reset(); |
|||
foreach ($row as $key=>$val) |
|||
$mapper->document[$key]=$val; |
|||
$mapper->query=[clone($mapper)]; |
|||
if (isset($mapper->trigger['load'])) |
|||
\Base::instance()->call($mapper->trigger['load'],$mapper); |
|||
return $mapper; |
|||
} |
|||
|
|||
/** |
|||
* Return fields of mapper object as an associative array |
|||
* @return array |
|||
* @param $obj object |
|||
**/ |
|||
function cast($obj=NULL) { |
|||
if (!$obj) |
|||
$obj=$this; |
|||
return $obj->document; |
|||
} |
|||
|
|||
/** |
|||
* Build query and execute |
|||
* @return static[] |
|||
* @param $fields string |
|||
* @param $filter array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
**/ |
|||
function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) { |
|||
if (!$options) |
|||
$options=[]; |
|||
$options+=[ |
|||
'group'=>NULL, |
|||
'order'=>NULL, |
|||
'limit'=>0, |
|||
'offset'=>0 |
|||
]; |
|||
$tag=''; |
|||
if (is_array($ttl)) |
|||
list($ttl,$tag)=$ttl; |
|||
$fw=\Base::instance(); |
|||
$cache=\Cache::instance(); |
|||
if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn(). |
|||
$fw->stringify([$fields,$filter,$options])).($tag?'.'.$tag:'').'.mongo', |
|||
$result)) || !$ttl || $cached[0]+$ttl<microtime(TRUE)) { |
|||
if ($options['group']) { |
|||
$grp=$this->collection->group( |
|||
$options['group']['keys'], |
|||
$options['group']['initial'], |
|||
$options['group']['reduce'], |
|||
[ |
|||
'condition'=>$filter, |
|||
'finalize'=>$options['group']['finalize'] |
|||
] |
|||
); |
|||
$tmp=$this->db->selectcollection( |
|||
$fw->HOST.'.'.$fw->BASE.'.'. |
|||
uniqid(NULL,TRUE).'.tmp' |
|||
); |
|||
$tmp->batchinsert($grp['retval'],['w'=>1]); |
|||
$filter=[]; |
|||
$collection=$tmp; |
|||
} |
|||
else { |
|||
$filter=$filter?:[]; |
|||
$collection=$this->collection; |
|||
} |
|||
if ($this->legacy) { |
|||
$this->cursor=$collection->find($filter,$fields?:[]); |
|||
if ($options['order']) |
|||
$this->cursor=$this->cursor->sort($options['order']); |
|||
if ($options['limit']) |
|||
$this->cursor=$this->cursor->limit($options['limit']); |
|||
if ($options['offset']) |
|||
$this->cursor=$this->cursor->skip($options['offset']); |
|||
$result=[]; |
|||
while ($this->cursor->hasnext()) |
|||
$result[]=$this->cursor->getnext(); |
|||
} |
|||
else { |
|||
$this->cursor=$collection->find($filter,[ |
|||
'sort'=>$options['order'], |
|||
'limit'=>$options['limit'], |
|||
'skip'=>$options['offset'] |
|||
]); |
|||
$result=$this->cursor->toarray(); |
|||
} |
|||
if ($options['group']) |
|||
$tmp->drop(); |
|||
if ($fw->CACHE && $ttl) |
|||
// Save to cache backend |
|||
$cache->set($hash,$result,$ttl); |
|||
} |
|||
$out=[]; |
|||
foreach ($result as $doc) |
|||
$out[]=$this->factory($doc); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Return records that match criteria |
|||
* @return static[] |
|||
* @param $filter array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
**/ |
|||
function find($filter=NULL,array $options=NULL,$ttl=0) { |
|||
if (!$options) |
|||
$options=[]; |
|||
$options+=[ |
|||
'group'=>NULL, |
|||
'order'=>NULL, |
|||
'limit'=>0, |
|||
'offset'=>0 |
|||
]; |
|||
return $this->select($this->fields,$filter,$options,$ttl); |
|||
} |
|||
|
|||
/** |
|||
* Count records that match criteria |
|||
* @return int |
|||
* @param $filter array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
**/ |
|||
function count($filter=NULL,array $options=NULL,$ttl=0) { |
|||
$fw=\Base::instance(); |
|||
$cache=\Cache::instance(); |
|||
$tag=''; |
|||
if (is_array($ttl)) |
|||
list($ttl,$tag)=$ttl; |
|||
if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify( |
|||
[$filter])).($tag?'.'.$tag:'').'.mongo',$result)) || !$ttl || |
|||
$cached[0]+$ttl<microtime(TRUE)) { |
|||
$result=$this->collection->count($filter?:[]); |
|||
if ($fw->CACHE && $ttl) |
|||
// Save to cache backend |
|||
$cache->set($hash,$result,$ttl); |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Return record at specified offset using criteria of previous |
|||
* load() call and make it active |
|||
* @return array |
|||
* @param $ofs int |
|||
**/ |
|||
function skip($ofs=1) { |
|||
$this->document=($out=parent::skip($ofs))?$out->document:[]; |
|||
if ($this->document && isset($this->trigger['load'])) |
|||
\Base::instance()->call($this->trigger['load'],$this); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Insert new record |
|||
* @return array |
|||
**/ |
|||
function insert() { |
|||
if (isset($this->document['_id'])) |
|||
return $this->update(); |
|||
if (isset($this->trigger['beforeinsert']) && |
|||
\Base::instance()->call($this->trigger['beforeinsert'], |
|||
[$this,['_id'=>$this->document['_id']]])===FALSE) |
|||
return $this->document; |
|||
if ($this->legacy) { |
|||
$this->collection->insert($this->document); |
|||
$pkey=['_id'=>$this->document['_id']]; |
|||
} |
|||
else { |
|||
$result=$this->collection->insertone($this->document); |
|||
$pkey=['_id'=>$result->getinsertedid()]; |
|||
} |
|||
if (isset($this->trigger['afterinsert'])) |
|||
\Base::instance()->call($this->trigger['afterinsert'], |
|||
[$this,$pkey]); |
|||
$this->load($pkey); |
|||
return $this->document; |
|||
} |
|||
|
|||
/** |
|||
* Update current record |
|||
* @return array |
|||
**/ |
|||
function update() { |
|||
$pkey=['_id'=>$this->document['_id']]; |
|||
if (isset($this->trigger['beforeupdate']) && |
|||
\Base::instance()->call($this->trigger['beforeupdate'], |
|||
[$this,$pkey])===FALSE) |
|||
return $this->document; |
|||
$upsert=['upsert'=>TRUE]; |
|||
if ($this->legacy) |
|||
$this->collection->update($pkey,$this->document,$upsert); |
|||
else |
|||
$this->collection->replaceone($pkey,$this->document,$upsert); |
|||
if (isset($this->trigger['afterupdate'])) |
|||
\Base::instance()->call($this->trigger['afterupdate'], |
|||
[$this,$pkey]); |
|||
return $this->document; |
|||
} |
|||
|
|||
/** |
|||
* Delete current record |
|||
* @return bool |
|||
* @param $quick bool |
|||
* @param $filter array |
|||
**/ |
|||
function erase($filter=NULL,$quick=TRUE) { |
|||
if ($filter) { |
|||
if (!$quick) { |
|||
foreach ($this->find($filter) as $mapper) |
|||
if (!$mapper->erase()) |
|||
return FALSE; |
|||
return TRUE; |
|||
} |
|||
return $this->legacy? |
|||
$this->collection->remove($filter): |
|||
$this->collection->deletemany($filter); |
|||
} |
|||
$pkey=['_id'=>$this->document['_id']]; |
|||
if (isset($this->trigger['beforeerase']) && |
|||
\Base::instance()->call($this->trigger['beforeerase'], |
|||
[$this,$pkey])===FALSE) |
|||
return FALSE; |
|||
$result=$this->legacy? |
|||
$this->collection->remove(['_id'=>$this->document['_id']]): |
|||
$this->collection->deleteone(['_id'=>$this->document['_id']]); |
|||
parent::erase(); |
|||
if (isset($this->trigger['aftererase'])) |
|||
\Base::instance()->call($this->trigger['aftererase'], |
|||
[$this,$pkey]); |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Reset cursor |
|||
* @return NULL |
|||
**/ |
|||
function reset() { |
|||
$this->document=[]; |
|||
parent::reset(); |
|||
} |
|||
|
|||
/** |
|||
* Hydrate mapper object using hive array variable |
|||
* @return NULL |
|||
* @param $var array|string |
|||
* @param $func callback |
|||
**/ |
|||
function copyfrom($var,$func=NULL) { |
|||
if (is_string($var)) |
|||
$var=\Base::instance()->$var; |
|||
if ($func) |
|||
$var=call_user_func($func,$var); |
|||
foreach ($var as $key=>$val) |
|||
$this->set($key,$val); |
|||
} |
|||
|
|||
/** |
|||
* Populate hive array variable with mapper fields |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function copyto($key) { |
|||
$var=&\Base::instance()->ref($key); |
|||
foreach ($this->document as $key=>$field) |
|||
$var[$key]=$field; |
|||
} |
|||
|
|||
/** |
|||
* Return field names |
|||
* @return array |
|||
**/ |
|||
function fields() { |
|||
return array_keys($this->document); |
|||
} |
|||
|
|||
/** |
|||
* Return the cursor from last query |
|||
* @return object|NULL |
|||
**/ |
|||
function cursor() { |
|||
return $this->cursor; |
|||
} |
|||
|
|||
/** |
|||
* Retrieve external iterator for fields |
|||
* @return object |
|||
**/ |
|||
function getiterator() { |
|||
return new \ArrayIterator($this->cast()); |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @return void |
|||
* @param $db object |
|||
* @param $collection string |
|||
* @param $fields array |
|||
**/ |
|||
function __construct(\DB\Mongo $db,$collection,$fields=NULL) { |
|||
$this->db=$db; |
|||
$this->legacy=$db->legacy(); |
|||
$this->collection=$db->selectcollection($collection); |
|||
$this->fields=$fields; |
|||
$this->reset(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,194 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB\Mongo; |
|||
|
|||
//! MongoDB-managed session handler |
|||
class Session extends Mapper { |
|||
|
|||
protected |
|||
//! Session ID |
|||
$sid, |
|||
//! Anti-CSRF token |
|||
$_csrf, |
|||
//! User agent |
|||
$_agent, |
|||
//! IP, |
|||
$_ip, |
|||
//! Suspect callback |
|||
$onsuspect; |
|||
|
|||
/** |
|||
* Open session |
|||
* @return TRUE |
|||
* @param $path string |
|||
* @param $name string |
|||
**/ |
|||
function open($path,$name) { |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Close session |
|||
* @return TRUE |
|||
**/ |
|||
function close() { |
|||
$this->reset(); |
|||
$this->sid=NULL; |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session data in serialized format |
|||
* @return string |
|||
* @param $id string |
|||
**/ |
|||
function read($id) { |
|||
$this->load(['session_id'=>$this->sid=$id]); |
|||
if ($this->dry()) |
|||
return ''; |
|||
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) { |
|||
$fw=\Base::instance(); |
|||
if (!isset($this->onsuspect) || |
|||
$fw->call($this->onsuspect,[$this,$id])===FALSE) { |
|||
// NB: `session_destroy` can't be called at that stage; |
|||
// `session_start` not completed |
|||
$this->destroy($id); |
|||
$this->close(); |
|||
unset($fw->{'COOKIE.'.session_name()}); |
|||
$fw->error(403); |
|||
} |
|||
} |
|||
return $this->get('data'); |
|||
} |
|||
|
|||
/** |
|||
* Write session data |
|||
* @return TRUE |
|||
* @param $id string |
|||
* @param $data string |
|||
**/ |
|||
function write($id,$data) { |
|||
$this->set('session_id',$id); |
|||
$this->set('data',$data); |
|||
$this->set('ip',$this->_ip); |
|||
$this->set('agent',$this->_agent); |
|||
$this->set('stamp',time()); |
|||
$this->save(); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Destroy session |
|||
* @return TRUE |
|||
* @param $id string |
|||
**/ |
|||
function destroy($id) { |
|||
$this->erase(['session_id'=>$id]); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Garbage collector |
|||
* @return TRUE |
|||
* @param $max int |
|||
**/ |
|||
function cleanup($max) { |
|||
$this->erase(['$where'=>'this.stamp+'.$max.'<'.time()]); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session id (if session has started) |
|||
* @return string|NULL |
|||
**/ |
|||
function sid() { |
|||
return $this->sid; |
|||
} |
|||
|
|||
/** |
|||
* Return anti-CSRF token |
|||
* @return string |
|||
**/ |
|||
function csrf() { |
|||
return $this->_csrf; |
|||
} |
|||
|
|||
/** |
|||
* Return IP address |
|||
* @return string |
|||
**/ |
|||
function ip() { |
|||
return $this->_ip; |
|||
} |
|||
|
|||
/** |
|||
* Return Unix timestamp |
|||
* @return string|FALSE |
|||
**/ |
|||
function stamp() { |
|||
if (!$this->sid) |
|||
session_start(); |
|||
return $this->dry()?FALSE:$this->get('stamp'); |
|||
} |
|||
|
|||
/** |
|||
* Return HTTP user agent |
|||
* @return string |
|||
**/ |
|||
function agent() { |
|||
return $this->_agent; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $db \DB\Mongo |
|||
* @param $table string |
|||
* @param $onsuspect callback |
|||
* @param $key string |
|||
**/ |
|||
function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL,$key=NULL) { |
|||
parent::__construct($db,$table); |
|||
$this->onsuspect=$onsuspect; |
|||
session_set_save_handler( |
|||
[$this,'open'], |
|||
[$this,'close'], |
|||
[$this,'read'], |
|||
[$this,'write'], |
|||
[$this,'destroy'], |
|||
[$this,'cleanup'] |
|||
); |
|||
register_shutdown_function('session_commit'); |
|||
$fw=\Base::instance(); |
|||
$headers=$fw->HEADERS; |
|||
$this->_csrf=$fw->hash($fw->SEED. |
|||
extension_loaded('openssl')? |
|||
implode(unpack('L',openssl_random_pseudo_bytes(4))): |
|||
mt_rand() |
|||
); |
|||
if ($key) |
|||
$fw->$key=$this->_csrf; |
|||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; |
|||
$this->_ip=$fw->IP; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,523 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB; |
|||
|
|||
//! PDO wrapper |
|||
class SQL { |
|||
|
|||
//@{ Error messages |
|||
const |
|||
E_PKey='Table %s does not have a primary key'; |
|||
//@} |
|||
|
|||
const |
|||
PARAM_FLOAT='float'; |
|||
|
|||
protected |
|||
//! UUID |
|||
$uuid, |
|||
//! Raw PDO |
|||
$pdo, |
|||
//! Data source name |
|||
$dsn, |
|||
//! Database engine |
|||
$engine, |
|||
//! Database name |
|||
$dbname, |
|||
//! Transaction flag |
|||
$trans=FALSE, |
|||
//! Number of rows affected by query |
|||
$rows=0, |
|||
//! SQL log |
|||
$log; |
|||
|
|||
/** |
|||
* Begin SQL transaction |
|||
* @return bool |
|||
**/ |
|||
function begin() { |
|||
$out=$this->pdo->begintransaction(); |
|||
$this->trans=TRUE; |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Rollback SQL transaction |
|||
* @return bool |
|||
**/ |
|||
function rollback() { |
|||
$out=$this->pdo->rollback(); |
|||
$this->trans=FALSE; |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Commit SQL transaction |
|||
* @return bool |
|||
**/ |
|||
function commit() { |
|||
$out=$this->pdo->commit(); |
|||
$this->trans=FALSE; |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Return transaction flag |
|||
* @return bool |
|||
**/ |
|||
function trans() { |
|||
return $this->trans; |
|||
} |
|||
|
|||
/** |
|||
* Map data type of argument to a PDO constant |
|||
* @return int |
|||
* @param $val scalar |
|||
**/ |
|||
function type($val) { |
|||
switch (gettype($val)) { |
|||
case 'NULL': |
|||
return \PDO::PARAM_NULL; |
|||
case 'boolean': |
|||
return \PDO::PARAM_BOOL; |
|||
case 'integer': |
|||
return \PDO::PARAM_INT; |
|||
case 'resource': |
|||
return \PDO::PARAM_LOB; |
|||
case 'float': |
|||
return self::PARAM_FLOAT; |
|||
default: |
|||
return \PDO::PARAM_STR; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Cast value to PHP type |
|||
* @return mixed |
|||
* @param $type string |
|||
* @param $val mixed |
|||
**/ |
|||
function value($type,$val) { |
|||
switch ($type) { |
|||
case self::PARAM_FLOAT: |
|||
if (!is_string($val)) |
|||
$val=str_replace(',','.',$val); |
|||
return $val; |
|||
case \PDO::PARAM_NULL: |
|||
return NULL; |
|||
case \PDO::PARAM_INT: |
|||
return (int)$val; |
|||
case \PDO::PARAM_BOOL: |
|||
return (bool)$val; |
|||
case \PDO::PARAM_STR: |
|||
return (string)$val; |
|||
case \PDO::PARAM_LOB: |
|||
return (binary)$val; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Execute SQL statement(s) |
|||
* @return array|int|FALSE |
|||
* @param $cmds string|array |
|||
* @param $args string|array |
|||
* @param $ttl int|array |
|||
* @param $log bool |
|||
* @param $stamp bool |
|||
**/ |
|||
function exec($cmds,$args=NULL,$ttl=0,$log=TRUE,$stamp=FALSE) { |
|||
$tag=''; |
|||
if (is_array($ttl)) |
|||
list($ttl,$tag)=$ttl; |
|||
$auto=FALSE; |
|||
if (is_null($args)) |
|||
$args=[]; |
|||
elseif (is_scalar($args)) |
|||
$args=[1=>$args]; |
|||
if (is_array($cmds)) { |
|||
if (count($args)<($count=count($cmds))) |
|||
// Apply arguments to SQL commands |
|||
$args=array_fill(0,$count,$args); |
|||
if (!$this->trans) { |
|||
$this->begin(); |
|||
$auto=TRUE; |
|||
} |
|||
} |
|||
else { |
|||
$count=1; |
|||
$cmds=[$cmds]; |
|||
$args=[$args]; |
|||
} |
|||
if ($this->log===FALSE) |
|||
$log=FALSE; |
|||
$fw=\Base::instance(); |
|||
$cache=\Cache::instance(); |
|||
$result=FALSE; |
|||
for ($i=0;$i<$count;$i++) { |
|||
$cmd=$cmds[$i]; |
|||
$arg=$args[$i]; |
|||
// ensure 1-based arguments |
|||
if (array_key_exists(0,$arg)) { |
|||
array_unshift($arg,''); |
|||
unset($arg[0]); |
|||
} |
|||
if (!preg_replace('/(^\s+|[\s;]+$)/','',$cmd)) |
|||
continue; |
|||
$now=microtime(TRUE); |
|||
$keys=$vals=[]; |
|||
if ($fw->CACHE && $ttl && ($cached=$cache->exists( |
|||
$hash=$fw->hash($this->dsn.$cmd. |
|||
$fw->stringify($arg)).($tag?'.'.$tag:'').'.sql',$result)) && |
|||
$cached[0]+$ttl>microtime(TRUE)) { |
|||
foreach ($arg as $key=>$val) { |
|||
$vals[]=$fw->stringify(is_array($val)?$val[0]:$val); |
|||
$keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key). |
|||
'/'; |
|||
} |
|||
if ($log) |
|||
$this->log.=($stamp?(date('r').' '):'').'('. |
|||
sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. |
|||
'[CACHED] '. |
|||
preg_replace($keys,$vals, |
|||
str_replace('?',chr(0).'?',$cmd),1).PHP_EOL; |
|||
} |
|||
elseif (is_object($query=$this->pdo->prepare($cmd))) { |
|||
foreach ($arg as $key=>$val) { |
|||
if (is_array($val)) { |
|||
// User-specified data type |
|||
$query->bindvalue($key,$val[0], |
|||
$val[1]==self::PARAM_FLOAT?\PDO::PARAM_STR:$val[1]); |
|||
$vals[]=$fw->stringify($this->value($val[1],$val[0])); |
|||
} |
|||
else { |
|||
// Convert to PDO data type |
|||
$query->bindvalue($key,$val, |
|||
($type=$this->type($val))==self::PARAM_FLOAT? |
|||
\PDO::PARAM_STR:$type); |
|||
$vals[]=$fw->stringify($this->value($type,$val)); |
|||
} |
|||
$keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key). |
|||
'/'; |
|||
} |
|||
if ($log) |
|||
$this->log.=($stamp?(date('r').' '):'').'(-0ms) '. |
|||
preg_replace($keys,$vals, |
|||
str_replace('?',chr(0).'?',$cmd),1).PHP_EOL; |
|||
$query->execute(); |
|||
if ($log) |
|||
$this->log=str_replace('(-0ms)', |
|||
'('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms)', |
|||
$this->log); |
|||
if (($error=$query->errorinfo()) && $error[0]!=\PDO::ERR_NONE) { |
|||
// Statement-level error occurred |
|||
if ($this->trans) |
|||
$this->rollback(); |
|||
user_error('PDOStatement: '.$error[2],E_USER_ERROR); |
|||
} |
|||
if (preg_match('/(?:^[\s\(]*'. |
|||
'(?:WITH|EXPLAIN|SELECT|PRAGMA|SHOW)|RETURNING)\b/is',$cmd) || |
|||
(preg_match('/^\s*(?:CALL|EXEC)\b/is',$cmd) && |
|||
$query->columnCount())) { |
|||
$result=$query->fetchall(\PDO::FETCH_ASSOC); |
|||
// Work around SQLite quote bug |
|||
if (preg_match('/sqlite2?/',$this->engine)) |
|||
foreach ($result as $pos=>$rec) { |
|||
unset($result[$pos]); |
|||
$result[$pos]=[]; |
|||
foreach ($rec as $key=>$val) |
|||
$result[$pos][trim($key,'\'"[]`')]=$val; |
|||
} |
|||
$this->rows=count($result); |
|||
if ($fw->CACHE && $ttl) |
|||
// Save to cache backend |
|||
$cache->set($hash,$result,$ttl); |
|||
} |
|||
else |
|||
$this->rows=$result=$query->rowcount(); |
|||
$query->closecursor(); |
|||
unset($query); |
|||
} |
|||
elseif (($error=$this->errorinfo()) && $error[0]!=\PDO::ERR_NONE) { |
|||
// PDO-level error occurred |
|||
if ($this->trans) |
|||
$this->rollback(); |
|||
user_error('PDO: '.$error[2],E_USER_ERROR); |
|||
} |
|||
|
|||
} |
|||
if ($this->trans && $auto) |
|||
$this->commit(); |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Return number of rows affected by last query |
|||
* @return int |
|||
**/ |
|||
function count() { |
|||
return $this->rows; |
|||
} |
|||
|
|||
/** |
|||
* Return SQL profiler results (or disable logging) |
|||
* @return string |
|||
* @param $flag bool |
|||
**/ |
|||
function log($flag=TRUE) { |
|||
if ($flag) |
|||
return $this->log; |
|||
$this->log=FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if table exists |
|||
* @return bool |
|||
* @param $table string |
|||
**/ |
|||
function exists($table) { |
|||
$mode=$this->pdo->getAttribute(\PDO::ATTR_ERRMODE); |
|||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_SILENT); |
|||
$out=$this->pdo-> |
|||
query('SELECT 1 FROM '.$this->quotekey($table).' LIMIT 1'); |
|||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE,$mode); |
|||
return is_object($out); |
|||
} |
|||
|
|||
/** |
|||
* Retrieve schema of SQL table |
|||
* @return array|FALSE |
|||
* @param $table string |
|||
* @param $fields array|string |
|||
* @param $ttl int|array |
|||
**/ |
|||
function schema($table,$fields=NULL,$ttl=0) { |
|||
$fw=\Base::instance(); |
|||
$cache=\Cache::instance(); |
|||
if ($fw->CACHE && $ttl && |
|||
($cached=$cache->exists( |
|||
$hash=$fw->hash($this->dsn.$table).'.schema',$result)) && |
|||
$cached[0]+$ttl>microtime(TRUE)) |
|||
return $result; |
|||
if (strpos($table,'.')) |
|||
list($schema,$table)=explode('.',$table); |
|||
// Supported engines |
|||
$cmd=[ |
|||
'sqlite2?'=>[ |
|||
'PRAGMA table_info(`'.$table.'`)', |
|||
'name','type','dflt_value','notnull',0,'pk',TRUE], |
|||
'mysql'=>[ |
|||
'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`', |
|||
'Field','Type','Default','Null','YES','Key','PRI'], |
|||
'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>[ |
|||
'SELECT '. |
|||
'C.COLUMN_NAME AS field,'. |
|||
'C.DATA_TYPE AS type,'. |
|||
'C.COLUMN_DEFAULT AS defval,'. |
|||
'C.IS_NULLABLE AS nullable,'. |
|||
'T.CONSTRAINT_TYPE AS pkey '. |
|||
'FROM INFORMATION_SCHEMA.COLUMNS AS C '. |
|||
'LEFT OUTER JOIN '. |
|||
'INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K '. |
|||
'ON '. |
|||
'C.TABLE_NAME=K.TABLE_NAME AND '. |
|||
'C.COLUMN_NAME=K.COLUMN_NAME AND '. |
|||
'C.TABLE_SCHEMA=K.TABLE_SCHEMA '. |
|||
($this->dbname? |
|||
('AND C.TABLE_CATALOG=K.TABLE_CATALOG '):''). |
|||
'LEFT OUTER JOIN '. |
|||
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS T ON '. |
|||
'K.TABLE_NAME=T.TABLE_NAME AND '. |
|||
'K.CONSTRAINT_NAME=T.CONSTRAINT_NAME AND '. |
|||
'K.TABLE_SCHEMA=T.TABLE_SCHEMA '. |
|||
($this->dbname? |
|||
('AND K.TABLE_CATALOG=T.TABLE_CATALOG '):''). |
|||
'WHERE '. |
|||
'C.TABLE_NAME='.$this->quote($table). |
|||
($this->dbname? |
|||
(' AND C.TABLE_CATALOG='. |
|||
$this->quote($this->dbname)):''), |
|||
'field','type','defval','nullable','YES','pkey','PRIMARY KEY'], |
|||
'oci'=>[ |
|||
'SELECT c.column_name AS field, '. |
|||
'c.data_type AS type, '. |
|||
'c.data_default AS defval, '. |
|||
'c.nullable AS nullable, '. |
|||
'(SELECT t.constraint_type '. |
|||
'FROM all_cons_columns acc '. |
|||
'LEFT OUTER JOIN all_constraints t '. |
|||
'ON acc.constraint_name=t.constraint_name '. |
|||
'WHERE acc.table_name='.$this->quote($table).' '. |
|||
'AND acc.column_name=c.column_name '. |
|||
'AND constraint_type='.$this->quote('P').') AS pkey '. |
|||
'FROM all_tab_cols c '. |
|||
'WHERE c.table_name='.$this->quote($table), |
|||
'FIELD','TYPE','DEFVAL','NULLABLE','Y','PKEY','P'] |
|||
]; |
|||
if (is_string($fields)) |
|||
$fields=\Base::instance()->split($fields); |
|||
$conv=[ |
|||
'int\b|integer'=>\PDO::PARAM_INT, |
|||
'bool'=>\PDO::PARAM_BOOL, |
|||
'blob|bytea|image|binary'=>\PDO::PARAM_LOB, |
|||
'float|real|double|decimal|numeric'=>self::PARAM_FLOAT, |
|||
'.+'=>\PDO::PARAM_STR |
|||
]; |
|||
foreach ($cmd as $key=>$val) |
|||
if (preg_match('/'.$key.'/',$this->engine)) { |
|||
$rows=[]; |
|||
foreach ($this->exec($val[0],NULL) as $row) |
|||
if (!$fields || in_array($row[$val[1]],$fields)) { |
|||
foreach ($conv as $regex=>$type) |
|||
if (preg_match('/'.$regex.'/i',$row[$val[2]])) |
|||
break; |
|||
$rows[$row[$val[1]]]=[ |
|||
'type'=>$row[$val[2]], |
|||
'pdo_type'=>$type, |
|||
'default'=>is_string($row[$val[3]])? |
|||
preg_replace('/^\s*([\'"])(.*)\1\s*/','\2', |
|||
$row[$val[3]]):$row[$val[3]], |
|||
'nullable'=>$row[$val[4]]==$val[5], |
|||
'pkey'=>$row[$val[6]]==$val[7] |
|||
]; |
|||
} |
|||
if ($fw->CACHE && $ttl) |
|||
// Save to cache backend |
|||
$cache->set($hash,$rows,$ttl); |
|||
return $rows; |
|||
} |
|||
user_error(sprintf(self::E_PKey,$table),E_USER_ERROR); |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Quote string |
|||
* @return string |
|||
* @param $val mixed |
|||
* @param $type int |
|||
**/ |
|||
function quote($val,$type=\PDO::PARAM_STR) { |
|||
return $this->engine=='odbc'? |
|||
(is_string($val)? |
|||
\Base::instance()->stringify(str_replace('\'','\'\'',$val)): |
|||
$val): |
|||
$this->pdo->quote($val,$type); |
|||
} |
|||
|
|||
/** |
|||
* Return UUID |
|||
* @return string |
|||
**/ |
|||
function uuid() { |
|||
return $this->uuid; |
|||
} |
|||
|
|||
/** |
|||
* Return parent object |
|||
* @return \PDO |
|||
**/ |
|||
function pdo() { |
|||
return $this->pdo; |
|||
} |
|||
|
|||
/** |
|||
* Return database engine |
|||
* @return string |
|||
**/ |
|||
function driver() { |
|||
return $this->engine; |
|||
} |
|||
|
|||
/** |
|||
* Return server version |
|||
* @return string |
|||
**/ |
|||
function version() { |
|||
return $this->pdo->getattribute(\PDO::ATTR_SERVER_VERSION); |
|||
} |
|||
|
|||
/** |
|||
* Return database name |
|||
* @return string |
|||
**/ |
|||
function name() { |
|||
return $this->dbname; |
|||
} |
|||
|
|||
/** |
|||
* Return quoted identifier name |
|||
* @return string |
|||
* @param $key |
|||
* @param bool $split |
|||
**/ |
|||
function quotekey($key, $split=TRUE) { |
|||
$delims=[ |
|||
'sqlite2?|mysql'=>'``', |
|||
'pgsql|oci'=>'""', |
|||
'mssql|sqlsrv|odbc|sybase|dblib'=>'[]' |
|||
]; |
|||
$use=''; |
|||
foreach ($delims as $engine=>$delim) |
|||
if (preg_match('/'.$engine.'/',$this->engine)) { |
|||
$use=$delim; |
|||
break; |
|||
} |
|||
return $use[0].($split ? implode($use[1].'.'.$use[0],explode('.',$key)) |
|||
: $key).$use[1]; |
|||
} |
|||
|
|||
/** |
|||
* Redirect call to PDO object |
|||
* @return mixed |
|||
* @param $func string |
|||
* @param $args array |
|||
**/ |
|||
function __call($func,array $args) { |
|||
return call_user_func_array([$this->pdo,$func],$args); |
|||
} |
|||
|
|||
//! Prohibit cloning |
|||
private function __clone() { |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $dsn string |
|||
* @param $user string |
|||
* @param $pw string |
|||
* @param $options array |
|||
**/ |
|||
function __construct($dsn,$user=NULL,$pw=NULL,array $options=NULL) { |
|||
$fw=\Base::instance(); |
|||
$this->uuid=$fw->hash($this->dsn=$dsn); |
|||
if (preg_match('/^.+?(?:dbname|database)=(.+?)(?=;|$)/is',$dsn,$parts)) |
|||
$this->dbname=$parts[1]; |
|||
if (!$options) |
|||
$options=[]; |
|||
if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql') |
|||
$options+=[\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '. |
|||
strtolower(str_replace('-','',$fw->ENCODING)).';']; |
|||
$this->pdo=new \PDO($dsn,$user,$pw,$options); |
|||
$this->engine=$this->pdo->getattribute(\PDO::ATTR_DRIVER_NAME); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,759 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB\SQL; |
|||
|
|||
//! SQL data mapper |
|||
class Mapper extends \DB\Cursor { |
|||
|
|||
//@{ Error messages |
|||
const |
|||
E_PKey='Table %s does not have a primary key'; |
|||
//@} |
|||
|
|||
protected |
|||
//! PDO wrapper |
|||
$db, |
|||
//! Database engine |
|||
$engine, |
|||
//! SQL table |
|||
$source, |
|||
//! SQL table (quoted) |
|||
$table, |
|||
//! Alias for SQL table |
|||
$as, |
|||
//! Last insert ID |
|||
$_id, |
|||
//! Defined fields |
|||
$fields, |
|||
//! Adhoc fields |
|||
$adhoc=[], |
|||
//! Dynamic properties |
|||
$props=[]; |
|||
|
|||
/** |
|||
* Return database type |
|||
* @return string |
|||
**/ |
|||
function dbtype() { |
|||
return 'SQL'; |
|||
} |
|||
|
|||
/** |
|||
* Return mapped table |
|||
* @return string |
|||
**/ |
|||
function table() { |
|||
return $this->source; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if any/specified field value has changed |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
function changed($key=NULL) { |
|||
if (isset($key)) |
|||
return $this->fields[$key]['changed']; |
|||
foreach($this->fields as $key=>$field) |
|||
if ($field['changed']) |
|||
return TRUE; |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if field is defined |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
function exists($key) { |
|||
return array_key_exists($key,$this->fields+$this->adhoc); |
|||
} |
|||
|
|||
/** |
|||
* Assign value to field |
|||
* @return scalar |
|||
* @param $key string |
|||
* @param $val scalar |
|||
**/ |
|||
function set($key,$val) { |
|||
if (array_key_exists($key,$this->fields)) { |
|||
$val=is_null($val) && $this->fields[$key]['nullable']? |
|||
NULL:$this->db->value($this->fields[$key]['pdo_type'],$val); |
|||
if ($this->fields[$key]['initial']!==$val || |
|||
$this->fields[$key]['default']!==$val && is_null($val)) |
|||
$this->fields[$key]['changed']=TRUE; |
|||
return $this->fields[$key]['value']=$val; |
|||
} |
|||
// Adjust result on existing expressions |
|||
if (isset($this->adhoc[$key])) |
|||
$this->adhoc[$key]['value']=$val; |
|||
elseif (is_string($val)) |
|||
// Parenthesize expression in case it's a subquery |
|||
$this->adhoc[$key]=['expr'=>'('.$val.')','value'=>NULL]; |
|||
else |
|||
$this->props[$key]=$val; |
|||
return $val; |
|||
} |
|||
|
|||
/** |
|||
* Retrieve value of field |
|||
* @return scalar |
|||
* @param $key string |
|||
**/ |
|||
function &get($key) { |
|||
if ($key=='_id') |
|||
return $this->_id; |
|||
elseif (array_key_exists($key,$this->fields)) |
|||
return $this->fields[$key]['value']; |
|||
elseif (array_key_exists($key,$this->adhoc)) |
|||
return $this->adhoc[$key]['value']; |
|||
elseif (array_key_exists($key,$this->props)) |
|||
return $this->props[$key]; |
|||
user_error(sprintf(self::E_Field,$key),E_USER_ERROR); |
|||
} |
|||
|
|||
/** |
|||
* Clear value of field |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function clear($key) { |
|||
if (array_key_exists($key,$this->adhoc)) |
|||
unset($this->adhoc[$key]); |
|||
else |
|||
unset($this->props[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Invoke dynamic method |
|||
* @return mixed |
|||
* @param $func string |
|||
* @param $args array |
|||
**/ |
|||
function __call($func,$args) { |
|||
return call_user_func_array( |
|||
(array_key_exists($func,$this->props)? |
|||
$this->props[$func]: |
|||
$this->$func),$args |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Convert array to mapper object |
|||
* @return static |
|||
* @param $row array |
|||
**/ |
|||
function factory($row) { |
|||
$mapper=clone($this); |
|||
$mapper->reset(); |
|||
foreach ($row as $key=>$val) { |
|||
if (array_key_exists($key,$this->fields)) |
|||
$var='fields'; |
|||
elseif (array_key_exists($key,$this->adhoc)) |
|||
$var='adhoc'; |
|||
else |
|||
continue; |
|||
$mapper->{$var}[$key]['value']=$val; |
|||
$mapper->{$var}[$key]['initial']=$val; |
|||
if ($var=='fields' && $mapper->{$var}[$key]['pkey']) |
|||
$mapper->{$var}[$key]['previous']=$val; |
|||
} |
|||
$mapper->query=[clone($mapper)]; |
|||
if (isset($mapper->trigger['load'])) |
|||
\Base::instance()->call($mapper->trigger['load'],$mapper); |
|||
return $mapper; |
|||
} |
|||
|
|||
/** |
|||
* Return fields of mapper object as an associative array |
|||
* @return array |
|||
* @param $obj object |
|||
**/ |
|||
function cast($obj=NULL) { |
|||
if (!$obj) |
|||
$obj=$this; |
|||
return array_map( |
|||
function($row) { |
|||
return $row['value']; |
|||
}, |
|||
$obj->fields+$obj->adhoc |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Build query string and arguments |
|||
* @return array |
|||
* @param $fields string |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
**/ |
|||
function stringify($fields,$filter=NULL,array $options=NULL) { |
|||
if (!$options) |
|||
$options=[]; |
|||
$options+=[ |
|||
'group'=>NULL, |
|||
'order'=>NULL, |
|||
'limit'=>0, |
|||
'offset'=>0, |
|||
'comment'=>NULL |
|||
]; |
|||
$db=$this->db; |
|||
$sql='SELECT '.$fields.' FROM '.$this->table; |
|||
if (isset($this->as)) |
|||
$sql.=' AS '.$this->db->quotekey($this->as); |
|||
$args=[]; |
|||
if (is_array($filter)) { |
|||
$args=isset($filter[1]) && is_array($filter[1])? |
|||
$filter[1]: |
|||
array_slice($filter,1,NULL,TRUE); |
|||
$args=is_array($args)?$args:[1=>$args]; |
|||
list($filter)=$filter; |
|||
} |
|||
if ($filter) |
|||
$sql.=' WHERE '.$filter; |
|||
if ($options['group']) { |
|||
$sql.=' GROUP BY '.implode(',',array_map( |
|||
function($str) use($db) { |
|||
return preg_replace_callback( |
|||
'/\b(\w+[._\-\w]*)\h*(HAVING.+|$)/i', |
|||
function($parts) use($db) { |
|||
return $db->quotekey($parts[1]). |
|||
(isset($parts[2])?(' '.$parts[2]):''); |
|||
}, |
|||
$str |
|||
); |
|||
}, |
|||
explode(',',$options['group']))); |
|||
} |
|||
if ($options['order']) { |
|||
$char=substr($db->quotekey(''),0,1);// quoting char |
|||
$order=' ORDER BY '.(is_bool(strpos($options['order'],$char))? |
|||
implode(',',array_map(function($str) use($db) { |
|||
return preg_match('/^\h*(\w+[._\-\w]*)'. |
|||
'(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i', |
|||
$str,$parts)? |
|||
($db->quotekey($parts[1]). |
|||
(isset($parts[2])?(' '.$parts[2]):'')):$str; |
|||
},explode(',',$options['order']))): |
|||
$options['order']); |
|||
} |
|||
// SQL Server fixes |
|||
if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) && |
|||
($options['limit'] || $options['offset'])) { |
|||
// order by pkey when no ordering option was given |
|||
if (!$options['order']) |
|||
foreach ($this->fields as $key=>$field) |
|||
if ($field['pkey']) { |
|||
$order=' ORDER BY '.$db->quotekey($key); |
|||
break; |
|||
} |
|||
$ofs=$options['offset']?(int)$options['offset']:0; |
|||
$lmt=$options['limit']?(int)$options['limit']:0; |
|||
if (strncmp($db->version(),'11',2)>=0) { |
|||
// SQL Server >= 2012 |
|||
$sql.=$order.' OFFSET '.$ofs.' ROWS'; |
|||
if ($lmt) |
|||
$sql.=' FETCH NEXT '.$lmt.' ROWS ONLY'; |
|||
} |
|||
else { |
|||
// SQL Server 2008 |
|||
$sql=preg_replace('/SELECT/', |
|||
'SELECT '. |
|||
($lmt>0?'TOP '.($ofs+$lmt):'').' ROW_NUMBER() '. |
|||
'OVER ('.$order.') AS rnum,',$sql.$order,1); |
|||
$sql='SELECT * FROM ('.$sql.') x WHERE rnum > '.($ofs); |
|||
} |
|||
} |
|||
else { |
|||
if (isset($order)) |
|||
$sql.=$order; |
|||
if ($options['limit']) |
|||
$sql.=' LIMIT '.(int)$options['limit']; |
|||
if ($options['offset']) |
|||
$sql.=' OFFSET '.(int)$options['offset']; |
|||
} |
|||
if ($options['comment']) |
|||
$sql.="\n".' /* '.$options['comment'].' */'; |
|||
return [$sql,$args]; |
|||
} |
|||
|
|||
/** |
|||
* Build query string and execute |
|||
* @return static[] |
|||
* @param $fields string |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
**/ |
|||
function select($fields,$filter=NULL,array $options=NULL,$ttl=0) { |
|||
list($sql,$args)=$this->stringify($fields,$filter,$options); |
|||
$result=$this->db->exec($sql,$args,$ttl); |
|||
$out=[]; |
|||
foreach ($result as &$row) { |
|||
foreach ($row as $field=>&$val) { |
|||
if (array_key_exists($field,$this->fields)) { |
|||
if (!is_null($val) || !$this->fields[$field]['nullable']) |
|||
$val=$this->db->value( |
|||
$this->fields[$field]['pdo_type'],$val); |
|||
} |
|||
unset($val); |
|||
} |
|||
$out[]=$this->factory($row); |
|||
unset($row); |
|||
} |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Return records that match criteria |
|||
* @return static[] |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
**/ |
|||
function find($filter=NULL,array $options=NULL,$ttl=0) { |
|||
if (!$options) |
|||
$options=[]; |
|||
$options+=[ |
|||
'group'=>NULL, |
|||
'order'=>NULL, |
|||
'limit'=>0, |
|||
'offset'=>0 |
|||
]; |
|||
$adhoc=''; |
|||
foreach ($this->adhoc as $key=>$field) |
|||
$adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key); |
|||
return $this->select( |
|||
($options['group'] && !preg_match('/mysql|sqlite/',$this->engine)? |
|||
$options['group']: |
|||
implode(',',array_map([$this->db,'quotekey'], |
|||
array_keys($this->fields)))).$adhoc,$filter,$options,$ttl); |
|||
} |
|||
|
|||
/** |
|||
* Count records that match criteria |
|||
* @return int |
|||
* @param $filter string|array |
|||
* @param $options array |
|||
* @param $ttl int|array |
|||
**/ |
|||
function count($filter=NULL,array $options=NULL,$ttl=0) { |
|||
$adhoc=[]; |
|||
// with grouping involved, we need to wrap the actualy query and count the results |
|||
if ($subquery_mode=($options && !empty($options['group']))) { |
|||
$group_string=preg_replace('/HAVING.+$/i','',$options['group']); |
|||
$group_fields=array_flip(array_map('trim',explode(',',$group_string))); |
|||
foreach ($this->adhoc as $key=>$field) |
|||
// add adhoc fields that are used for grouping |
|||
if (isset($group_fields[$key])) |
|||
$adhoc[]=$field['expr'].' AS '.$this->db->quotekey($key); |
|||
$fields=implode(',',$adhoc); |
|||
if (empty($fields)) |
|||
// Select at least one field, ideally the grouping fields |
|||
// or sqlsrv fails |
|||
$fields=$group_string; |
|||
if (preg_match('/mssql|dblib|sqlsrv/',$this->engine)) |
|||
$fields='TOP 100 PERCENT '.$fields; |
|||
} else { |
|||
// for simple count just add a new adhoc counter |
|||
$fields='COUNT(*) AS '.$this->db->quotekey('_rows'); |
|||
} |
|||
list($sql,$args)=$this->stringify($fields,$filter,$options); |
|||
if ($subquery_mode) |
|||
$sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '. |
|||
'FROM ('.$sql.') AS '.$this->db->quotekey('_temp'); |
|||
$result=$this->db->exec($sql,$args,$ttl); |
|||
unset($this->adhoc['_rows']); |
|||
return (int)$result[0]['_rows']; |
|||
} |
|||
/** |
|||
* Return record at specified offset using same criteria as |
|||
* previous load() call and make it active |
|||
* @return static |
|||
* @param $ofs int |
|||
**/ |
|||
function skip($ofs=1) { |
|||
$out=parent::skip($ofs); |
|||
$dry=$this->dry(); |
|||
foreach ($this->fields as $key=>&$field) { |
|||
$field['value']=$dry?NULL:$out->fields[$key]['value']; |
|||
$field['initial']=$field['value']; |
|||
$field['changed']=FALSE; |
|||
if ($field['pkey']) |
|||
$field['previous']=$dry?NULL:$out->fields[$key]['value']; |
|||
unset($field); |
|||
} |
|||
foreach ($this->adhoc as $key=>&$field) { |
|||
$field['value']=$dry?NULL:$out->adhoc[$key]['value']; |
|||
unset($field); |
|||
} |
|||
if (!$dry && isset($this->trigger['load'])) |
|||
\Base::instance()->call($this->trigger['load'],$this); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Insert new record |
|||
* @return static |
|||
**/ |
|||
function insert() { |
|||
$args=[]; |
|||
$actr=0; |
|||
$nctr=0; |
|||
$fields=''; |
|||
$values=''; |
|||
$filter=''; |
|||
$pkeys=[]; |
|||
$nkeys=[]; |
|||
$ckeys=[]; |
|||
$inc=NULL; |
|||
foreach ($this->fields as $key=>$field) |
|||
if ($field['pkey']) |
|||
$pkeys[$key]=$field['previous']; |
|||
if (isset($this->trigger['beforeinsert']) && |
|||
\Base::instance()->call($this->trigger['beforeinsert'], |
|||
[$this,$pkeys])===FALSE) |
|||
return $this; |
|||
if ($this->valid()) |
|||
// duplicate record |
|||
foreach ($this->fields as $key=>&$field) { |
|||
$field['changed']=true; |
|||
if ($field['pkey'] && !$inc && $field['pdo_type']==\PDO::PARAM_INT |
|||
&& !$field['nullable']) |
|||
$inc=$key; |
|||
unset($field); |
|||
} |
|||
foreach ($this->fields as $key=>&$field) { |
|||
if ($field['pkey']) { |
|||
$field['previous']=$field['value']; |
|||
if (!$inc && $field['pdo_type']==\PDO::PARAM_INT && |
|||
empty($field['value']) && !$field['nullable'] && |
|||
is_null($field['default'])) |
|||
$inc=$key; |
|||
$filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?'; |
|||
$nkeys[$nctr+1]=[$field['value'],$field['pdo_type']]; |
|||
$nctr++; |
|||
} |
|||
if ($field['changed'] && $key!=$inc) { |
|||
$fields.=($actr?',':'').$this->db->quotekey($key); |
|||
$values.=($actr?',':'').'?'; |
|||
$args[$actr+1]=[$field['value'],$field['pdo_type']]; |
|||
$actr++; |
|||
$ckeys[]=$key; |
|||
} |
|||
unset($field); |
|||
} |
|||
if ($fields) { |
|||
$add=$aik=''; |
|||
if ($this->engine=='pgsql' && !empty($pkeys)) { |
|||
$names=array_keys($pkeys); |
|||
$aik=end($names); |
|||
$add=' RETURNING '.$this->db->quotekey($aik); |
|||
} |
|||
$lID=$this->db->exec( |
|||
(preg_match('/mssql|dblib|sqlsrv/',$this->engine) && |
|||
array_intersect(array_keys($pkeys),$ckeys)? |
|||
'SET IDENTITY_INSERT '.$this->table.' ON;':''). |
|||
'INSERT INTO '.$this->table.' ('.$fields.') '. |
|||
'VALUES ('.$values.')'.$add,$args |
|||
); |
|||
if ($this->engine=='pgsql' && $lID && $aik) |
|||
$this->_id=$lID[0][$aik]; |
|||
elseif ($this->engine!='oci') |
|||
$this->_id=$this->db->lastinsertid(); |
|||
// Reload to obtain default and auto-increment field values |
|||
if ($reload=(($inc && $this->_id) || $filter)) |
|||
$this->load($inc? |
|||
[$inc.'=?',$this->db->value( |
|||
$this->fields[$inc]['pdo_type'],$this->_id)]: |
|||
[$filter,$nkeys]); |
|||
if (isset($this->trigger['afterinsert'])) |
|||
\Base::instance()->call($this->trigger['afterinsert'], |
|||
[$this,$pkeys]); |
|||
// reset changed flag after calling afterinsert |
|||
if (!$reload) |
|||
foreach ($this->fields as $key=>&$field) { |
|||
$field['changed']=FALSE; |
|||
$field['initial']=$field['value']; |
|||
unset($field); |
|||
} |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Update current record |
|||
* @return static |
|||
**/ |
|||
function update() { |
|||
$args=[]; |
|||
$ctr=0; |
|||
$pairs=''; |
|||
$pkeys=[]; |
|||
foreach ($this->fields as $key=>$field) |
|||
if ($field['pkey']) |
|||
$pkeys[$key]=$field['previous']; |
|||
if (isset($this->trigger['beforeupdate']) && |
|||
\Base::instance()->call($this->trigger['beforeupdate'], |
|||
[$this,$pkeys])===FALSE) |
|||
return $this; |
|||
foreach ($this->fields as $key=>$field) |
|||
if ($field['changed']) { |
|||
$pairs.=($pairs?',':'').$this->db->quotekey($key).'=?'; |
|||
$args[++$ctr]=[$field['value'],$field['pdo_type']]; |
|||
} |
|||
if ($pairs) { |
|||
$filter=''; |
|||
foreach ($this->fields as $key=>$field) |
|||
if ($field['pkey']) { |
|||
$filter.=($filter?' AND ':' WHERE '). |
|||
$this->db->quotekey($key).'=?'; |
|||
$args[++$ctr]=[$field['previous'],$field['pdo_type']]; |
|||
} |
|||
if (!$filter) |
|||
user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR); |
|||
$sql='UPDATE '.$this->table.' SET '.$pairs.$filter; |
|||
$this->db->exec($sql,$args); |
|||
} |
|||
if (isset($this->trigger['afterupdate'])) |
|||
\Base::instance()->call($this->trigger['afterupdate'], |
|||
[$this,$pkeys]); |
|||
// reset changed flag after calling afterupdate |
|||
foreach ($this->fields as $key=>&$field) { |
|||
$field['changed']=FALSE; |
|||
$field['initial']=$field['value']; |
|||
unset($field); |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* batch-update multiple records at once |
|||
* @param string|array $filter |
|||
* @return int |
|||
*/ |
|||
function updateAll($filter=NULL) { |
|||
$args=[]; |
|||
$ctr=$out=0; |
|||
$pairs=''; |
|||
foreach ($this->fields as $key=>$field) |
|||
if ($field['changed']) { |
|||
$pairs.=($pairs?',':'').$this->db->quotekey($key).'=?'; |
|||
$args[++$ctr]=[$field['value'],$field['pdo_type']]; |
|||
} |
|||
if ($filter) |
|||
if (is_array($filter)) { |
|||
$cond=array_shift($filter); |
|||
$args=array_merge($args,$filter); |
|||
$filter=' WHERE '.$cond; |
|||
} else |
|||
$filter=' WHERE '.$filter; |
|||
if ($pairs) { |
|||
$sql='UPDATE '.$this->table.' SET '.$pairs.$filter; |
|||
$out = $this->db->exec($sql,$args); |
|||
} |
|||
// reset changed flag after calling afterupdate |
|||
foreach ($this->fields as $key=>&$field) { |
|||
$field['changed']=FALSE; |
|||
$field['initial']=$field['value']; |
|||
unset($field); |
|||
} |
|||
return $out; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Delete current record |
|||
* @return int |
|||
* @param $quick bool |
|||
* @param $filter string|array |
|||
**/ |
|||
function erase($filter=NULL,$quick=TRUE) { |
|||
if (isset($filter)) { |
|||
if (!$quick) { |
|||
$out=0; |
|||
foreach ($this->find($filter) as $mapper) |
|||
$out+=$mapper->erase(); |
|||
return $out; |
|||
} |
|||
$args=[]; |
|||
if (is_array($filter)) { |
|||
$args=isset($filter[1]) && is_array($filter[1])? |
|||
$filter[1]: |
|||
array_slice($filter,1,NULL,TRUE); |
|||
$args=is_array($args)?$args:[1=>$args]; |
|||
list($filter)=$filter; |
|||
} |
|||
return $this->db-> |
|||
exec('DELETE FROM '.$this->table. |
|||
($filter?' WHERE '.$filter:'').';',$args); |
|||
} |
|||
$args=[]; |
|||
$ctr=0; |
|||
$filter=''; |
|||
$pkeys=[]; |
|||
foreach ($this->fields as $key=>&$field) { |
|||
if ($field['pkey']) { |
|||
$filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?'; |
|||
$args[$ctr+1]=[$field['previous'],$field['pdo_type']]; |
|||
$pkeys[$key]=$field['previous']; |
|||
$ctr++; |
|||
} |
|||
$field['value']=NULL; |
|||
$field['changed']=(bool)$field['default']; |
|||
if ($field['pkey']) |
|||
$field['previous']=NULL; |
|||
unset($field); |
|||
} |
|||
if (!$filter) |
|||
user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR); |
|||
foreach ($this->adhoc as &$field) { |
|||
$field['value']=NULL; |
|||
unset($field); |
|||
} |
|||
parent::erase(); |
|||
if (isset($this->trigger['beforeerase']) && |
|||
\Base::instance()->call($this->trigger['beforeerase'], |
|||
[$this,$pkeys])===FALSE) |
|||
return 0; |
|||
$out=$this->db-> |
|||
exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args); |
|||
if (isset($this->trigger['aftererase'])) |
|||
\Base::instance()->call($this->trigger['aftererase'], |
|||
[$this,$pkeys]); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Reset cursor |
|||
* @return NULL |
|||
**/ |
|||
function reset() { |
|||
foreach ($this->fields as &$field) { |
|||
$field['value']=NULL; |
|||
$field['initial']=NULL; |
|||
$field['changed']=FALSE; |
|||
if ($field['pkey']) |
|||
$field['previous']=NULL; |
|||
unset($field); |
|||
} |
|||
foreach ($this->adhoc as &$field) { |
|||
$field['value']=NULL; |
|||
unset($field); |
|||
} |
|||
parent::reset(); |
|||
} |
|||
|
|||
/** |
|||
* Hydrate mapper object using hive array variable |
|||
* @return NULL |
|||
* @param $var array|string |
|||
* @param $func callback |
|||
**/ |
|||
function copyfrom($var,$func=NULL) { |
|||
if (is_string($var)) |
|||
$var=\Base::instance()->$var; |
|||
if ($func) |
|||
$var=call_user_func($func,$var); |
|||
foreach ($var as $key=>$val) |
|||
if (in_array($key,array_keys($this->fields))) |
|||
$this->set($key,$val); |
|||
} |
|||
|
|||
/** |
|||
* Populate hive array variable with mapper fields |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function copyto($key) { |
|||
$var=&\Base::instance()->ref($key); |
|||
foreach ($this->fields+$this->adhoc as $key=>$field) |
|||
$var[$key]=$field['value']; |
|||
} |
|||
|
|||
/** |
|||
* Return schema and, if the first argument is provided, update it |
|||
* @return array |
|||
* @param $fields NULL|array |
|||
**/ |
|||
function schema($fields=null) { |
|||
if ($fields) |
|||
$this->fields = $fields; |
|||
return $this->fields; |
|||
} |
|||
|
|||
/** |
|||
* Return field names |
|||
* @return array |
|||
* @param $adhoc bool |
|||
**/ |
|||
function fields($adhoc=TRUE) { |
|||
return array_keys($this->fields+($adhoc?$this->adhoc:[])); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if field is not nullable |
|||
* @return bool |
|||
* @param $field string |
|||
**/ |
|||
function required($field) { |
|||
return isset($this->fields[$field]) && |
|||
!$this->fields[$field]['nullable']; |
|||
} |
|||
|
|||
/** |
|||
* Retrieve external iterator for fields |
|||
* @return object |
|||
**/ |
|||
function getiterator() { |
|||
return new \ArrayIterator($this->cast()); |
|||
} |
|||
|
|||
/** |
|||
* Assign alias for table |
|||
* @param $alias string |
|||
**/ |
|||
function alias($alias) { |
|||
$this->as=$alias; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $db \DB\SQL |
|||
* @param $table string |
|||
* @param $fields array|string |
|||
* @param $ttl int|array |
|||
**/ |
|||
function __construct(\DB\SQL $db,$table,$fields=NULL,$ttl=60) { |
|||
$this->db=$db; |
|||
$this->engine=$db->driver(); |
|||
if ($this->engine=='oci') |
|||
$table=strtoupper($table); |
|||
$this->source=$table; |
|||
$this->table=$this->db->quotekey($table); |
|||
$this->fields=$db->schema($table,$fields,$ttl); |
|||
$this->reset(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,222 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace DB\SQL; |
|||
|
|||
//! SQL-managed session handler |
|||
class Session extends Mapper { |
|||
|
|||
protected |
|||
//! Session ID |
|||
$sid, |
|||
//! Anti-CSRF token |
|||
$_csrf, |
|||
//! User agent |
|||
$_agent, |
|||
//! IP, |
|||
$_ip, |
|||
//! Suspect callback |
|||
$onsuspect; |
|||
|
|||
/** |
|||
* Open session |
|||
* @return TRUE |
|||
* @param $path string |
|||
* @param $name string |
|||
**/ |
|||
function open($path,$name) { |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Close session |
|||
* @return TRUE |
|||
**/ |
|||
function close() { |
|||
$this->reset(); |
|||
$this->sid=NULL; |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session data in serialized format |
|||
* @return string |
|||
* @param $id string |
|||
**/ |
|||
function read($id) { |
|||
$this->load(['session_id=?',$this->sid=$id]); |
|||
if ($this->dry()) |
|||
return ''; |
|||
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) { |
|||
$fw=\Base::instance(); |
|||
if (!isset($this->onsuspect) || |
|||
$fw->call($this->onsuspect,[$this,$id])===FALSE) { |
|||
//NB: `session_destroy` can't be called at that stage (`session_start` not completed) |
|||
$this->destroy($id); |
|||
$this->close(); |
|||
unset($fw->{'COOKIE.'.session_name()}); |
|||
$fw->error(403); |
|||
} |
|||
} |
|||
return $this->get('data'); |
|||
} |
|||
|
|||
/** |
|||
* Write session data |
|||
* @return TRUE |
|||
* @param $id string |
|||
* @param $data string |
|||
**/ |
|||
function write($id,$data) { |
|||
$this->set('session_id',$id); |
|||
$this->set('data',$data); |
|||
$this->set('ip',$this->_ip); |
|||
$this->set('agent',$this->_agent); |
|||
$this->set('stamp',time()); |
|||
$this->save(); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Destroy session |
|||
* @return TRUE |
|||
* @param $id string |
|||
**/ |
|||
function destroy($id) { |
|||
$this->erase(['session_id=?',$id]); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Garbage collector |
|||
* @return TRUE |
|||
* @param $max int |
|||
**/ |
|||
function cleanup($max) { |
|||
$this->erase(['stamp+?<?',$max,time()]); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session id (if session has started) |
|||
* @return string|NULL |
|||
**/ |
|||
function sid() { |
|||
return $this->sid; |
|||
} |
|||
|
|||
/** |
|||
* Return anti-CSRF token |
|||
* @return string |
|||
**/ |
|||
function csrf() { |
|||
return $this->_csrf; |
|||
} |
|||
|
|||
/** |
|||
* Return IP address |
|||
* @return string |
|||
**/ |
|||
function ip() { |
|||
return $this->_ip; |
|||
} |
|||
|
|||
/** |
|||
* Return Unix timestamp |
|||
* @return string|FALSE |
|||
**/ |
|||
function stamp() { |
|||
if (!$this->sid) |
|||
session_start(); |
|||
return $this->dry()?FALSE:$this->get('stamp'); |
|||
} |
|||
|
|||
/** |
|||
* Return HTTP user agent |
|||
* @return string |
|||
**/ |
|||
function agent() { |
|||
return $this->_agent; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $db \DB\SQL |
|||
* @param $table string |
|||
* @param $force bool |
|||
* @param $onsuspect callback |
|||
* @param $key string |
|||
* @param $type string, column type for data field |
|||
**/ |
|||
function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$key=NULL,$type='TEXT') { |
|||
if ($force) { |
|||
$eol="\n"; |
|||
$tab="\t"; |
|||
$sqlsrv=preg_match('/mssql|sqlsrv|sybase/',$db->driver()); |
|||
$db->exec( |
|||
($sqlsrv? |
|||
('IF NOT EXISTS (SELECT * FROM sysobjects WHERE '. |
|||
'name='.$db->quote($table).' AND xtype=\'U\') '. |
|||
'CREATE TABLE dbo.'): |
|||
('CREATE TABLE IF NOT EXISTS '. |
|||
((($name=$db->name())&&$db->driver()!='pgsql')? |
|||
($db->quotekey($name,FALSE).'.'):''))). |
|||
$db->quotekey($table,FALSE).' ('.$eol. |
|||
($sqlsrv?$tab.$db->quotekey('id').' INT IDENTITY,'.$eol:''). |
|||
$tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol. |
|||
$tab.$db->quotekey('data').' '.$type.','.$eol. |
|||
$tab.$db->quotekey('ip').' VARCHAR(45),'.$eol. |
|||
$tab.$db->quotekey('agent').' VARCHAR(300),'.$eol. |
|||
$tab.$db->quotekey('stamp').' INTEGER,'.$eol. |
|||
$tab.'PRIMARY KEY ('.$db->quotekey($sqlsrv?'id':'session_id').')'.$eol. |
|||
($sqlsrv?',CONSTRAINT [UK_session_id] UNIQUE(session_id)':''). |
|||
');' |
|||
); |
|||
} |
|||
parent::__construct($db,$table); |
|||
$this->onsuspect=$onsuspect; |
|||
session_set_save_handler( |
|||
[$this,'open'], |
|||
[$this,'close'], |
|||
[$this,'read'], |
|||
[$this,'write'], |
|||
[$this,'destroy'], |
|||
[$this,'cleanup'] |
|||
); |
|||
register_shutdown_function('session_commit'); |
|||
$fw=\Base::instance(); |
|||
$headers=$fw->HEADERS; |
|||
$this->_csrf=$fw->hash($fw->SEED. |
|||
extension_loaded('openssl')? |
|||
implode(unpack('L',openssl_random_pseudo_bytes(4))): |
|||
mt_rand() |
|||
); |
|||
if ($key) |
|||
$fw->$key=$this->_csrf; |
|||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; |
|||
if (strlen($this->_agent) > 300) { |
|||
$this->_agent = substr($this->_agent, 0, 300); |
|||
} |
|||
$this->_ip=$fw->IP; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,42 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Legacy mode enabler |
|||
class F3 { |
|||
|
|||
static |
|||
//! Framework instance |
|||
$fw; |
|||
|
|||
/** |
|||
* Forward function calls to framework |
|||
* @return mixed |
|||
* @param $func callback |
|||
* @param $args array |
|||
**/ |
|||
static function __callstatic($func,array $args) { |
|||
if (!self::$fw) |
|||
self::$fw=Base::instance(); |
|||
return call_user_func_array([self::$fw,$func],$args); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,616 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Image manipulation tools |
|||
class Image { |
|||
|
|||
//@{ Messages |
|||
const |
|||
E_Color='Invalid color specified: %s', |
|||
E_File='File not found', |
|||
E_Font='CAPTCHA font not found', |
|||
E_TTF='No TrueType support in GD module', |
|||
E_Length='Invalid CAPTCHA length: %s'; |
|||
//@} |
|||
|
|||
//@{ Positional cues |
|||
const |
|||
POS_Left=1, |
|||
POS_Center=2, |
|||
POS_Right=4, |
|||
POS_Top=8, |
|||
POS_Middle=16, |
|||
POS_Bottom=32; |
|||
//@} |
|||
|
|||
protected |
|||
//! Source filename |
|||
$file, |
|||
//! Image resource |
|||
$data, |
|||
//! Enable/disable history |
|||
$flag=FALSE, |
|||
//! Filter count |
|||
$count=0; |
|||
|
|||
/** |
|||
* Convert RGB hex triad to array |
|||
* @return array|FALSE |
|||
* @param $color int|string |
|||
**/ |
|||
function rgb($color) { |
|||
if (is_string($color)) |
|||
$color=hexdec($color); |
|||
$hex=str_pad($hex=dechex($color),$color<4096?3:6,'0',STR_PAD_LEFT); |
|||
if (($len=strlen($hex))>6) |
|||
user_error(sprintf(self::E_Color,'0x'.$hex),E_USER_ERROR); |
|||
$color=str_split($hex,$len/3); |
|||
foreach ($color as &$hue) { |
|||
$hue=hexdec(str_repeat($hue,6/$len)); |
|||
unset($hue); |
|||
} |
|||
return $color; |
|||
} |
|||
|
|||
/** |
|||
* Invert image |
|||
* @return object |
|||
**/ |
|||
function invert() { |
|||
imagefilter($this->data,IMG_FILTER_NEGATE); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Adjust brightness (range:-255 to 255) |
|||
* @return object |
|||
* @param $level int |
|||
**/ |
|||
function brightness($level) { |
|||
imagefilter($this->data,IMG_FILTER_BRIGHTNESS,$level); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Adjust contrast (range:-100 to 100) |
|||
* @return object |
|||
* @param $level int |
|||
**/ |
|||
function contrast($level) { |
|||
imagefilter($this->data,IMG_FILTER_CONTRAST,$level); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Convert to grayscale |
|||
* @return object |
|||
**/ |
|||
function grayscale() { |
|||
imagefilter($this->data,IMG_FILTER_GRAYSCALE); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Adjust smoothness |
|||
* @return object |
|||
* @param $level int |
|||
**/ |
|||
function smooth($level) { |
|||
imagefilter($this->data,IMG_FILTER_SMOOTH,$level); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Emboss the image |
|||
* @return object |
|||
**/ |
|||
function emboss() { |
|||
imagefilter($this->data,IMG_FILTER_EMBOSS); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Apply sepia effect |
|||
* @return object |
|||
**/ |
|||
function sepia() { |
|||
imagefilter($this->data,IMG_FILTER_GRAYSCALE); |
|||
imagefilter($this->data,IMG_FILTER_COLORIZE,90,60,45); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Pixelate the image |
|||
* @return object |
|||
* @param $size int |
|||
**/ |
|||
function pixelate($size) { |
|||
imagefilter($this->data,IMG_FILTER_PIXELATE,$size,TRUE); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Blur the image using Gaussian filter |
|||
* @return object |
|||
* @param $selective bool |
|||
**/ |
|||
function blur($selective=FALSE) { |
|||
imagefilter($this->data, |
|||
$selective?IMG_FILTER_SELECTIVE_BLUR:IMG_FILTER_GAUSSIAN_BLUR); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Apply sketch effect |
|||
* @return object |
|||
**/ |
|||
function sketch() { |
|||
imagefilter($this->data,IMG_FILTER_MEAN_REMOVAL); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Flip on horizontal axis |
|||
* @return object |
|||
**/ |
|||
function hflip() { |
|||
$tmp=imagecreatetruecolor( |
|||
$width=$this->width(),$height=$this->height()); |
|||
imagesavealpha($tmp,TRUE); |
|||
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT); |
|||
imagecopyresampled($tmp,$this->data, |
|||
0,0,$width-1,0,$width,$height,-$width,$height); |
|||
imagedestroy($this->data); |
|||
$this->data=$tmp; |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Flip on vertical axis |
|||
* @return object |
|||
**/ |
|||
function vflip() { |
|||
$tmp=imagecreatetruecolor( |
|||
$width=$this->width(),$height=$this->height()); |
|||
imagesavealpha($tmp,TRUE); |
|||
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT); |
|||
imagecopyresampled($tmp,$this->data, |
|||
0,0,0,$height-1,$width,$height,$width,-$height); |
|||
imagedestroy($this->data); |
|||
$this->data=$tmp; |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Crop the image |
|||
* @return object |
|||
* @param $x1 int |
|||
* @param $y1 int |
|||
* @param $x2 int |
|||
* @param $y2 int |
|||
**/ |
|||
function crop($x1,$y1,$x2,$y2) { |
|||
$tmp=imagecreatetruecolor($width=$x2-$x1+1,$height=$y2-$y1+1); |
|||
imagesavealpha($tmp,TRUE); |
|||
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT); |
|||
imagecopyresampled($tmp,$this->data, |
|||
0,0,$x1,$y1,$width,$height,$width,$height); |
|||
imagedestroy($this->data); |
|||
$this->data=$tmp; |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Resize image (Maintain aspect ratio); Crop relative to center |
|||
* if flag is enabled; Enlargement allowed if flag is enabled |
|||
* @return object |
|||
* @param $width int |
|||
* @param $height int |
|||
* @param $crop bool |
|||
* @param $enlarge bool |
|||
**/ |
|||
function resize($width=NULL,$height=NULL,$crop=TRUE,$enlarge=TRUE) { |
|||
if (is_null($width) && is_null($height)) |
|||
return $this; |
|||
$origw=$this->width(); |
|||
$origh=$this->height(); |
|||
if (is_null($width)) |
|||
$width=round(($height/$origh)*$origw); |
|||
if (is_null($height)) |
|||
$height=round(($width/$origw)*$origh); |
|||
// Adjust dimensions; retain aspect ratio |
|||
$ratio=$origw/$origh; |
|||
if (!$crop) { |
|||
if ($width/$ratio<=$height) |
|||
$height=round($width/$ratio); |
|||
else |
|||
$width=round($height*$ratio); |
|||
} |
|||
if (!$enlarge) { |
|||
$width=min($origw,$width); |
|||
$height=min($origh,$height); |
|||
} |
|||
// Create blank image |
|||
$tmp=imagecreatetruecolor($width,$height); |
|||
imagesavealpha($tmp,TRUE); |
|||
imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT); |
|||
// Resize |
|||
if ($crop) { |
|||
if ($width/$ratio<=$height) { |
|||
$cropw=round($origh*$width/$height); |
|||
imagecopyresampled($tmp,$this->data, |
|||
0,0,($origw-$cropw)/2,0,$width,$height,$cropw,$origh); |
|||
} |
|||
else { |
|||
$croph=round($origw*$height/$width); |
|||
imagecopyresampled($tmp,$this->data, |
|||
0,0,0,($origh-$croph)/2,$width,$height,$origw,$croph); |
|||
} |
|||
} |
|||
else |
|||
imagecopyresampled($tmp,$this->data, |
|||
0,0,0,0,$width,$height,$origw,$origh); |
|||
imagedestroy($this->data); |
|||
$this->data=$tmp; |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Rotate image |
|||
* @return object |
|||
* @param $angle int |
|||
**/ |
|||
function rotate($angle) { |
|||
$this->data=imagerotate($this->data,$angle, |
|||
imagecolorallocatealpha($this->data,0,0,0,127)); |
|||
imagesavealpha($this->data,TRUE); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Apply an image overlay |
|||
* @return object |
|||
* @param $img object |
|||
* @param $align int|array |
|||
* @param $alpha int |
|||
**/ |
|||
function overlay(Image $img,$align=NULL,$alpha=100) { |
|||
if (is_null($align)) |
|||
$align=self::POS_Right|self::POS_Bottom; |
|||
if (is_array($align)) { |
|||
list($posx,$posy)=$align; |
|||
$align = 0; |
|||
} |
|||
$ovr=imagecreatefromstring($img->dump()); |
|||
imagesavealpha($ovr,TRUE); |
|||
$imgw=$this->width(); |
|||
$imgh=$this->height(); |
|||
$ovrw=imagesx($ovr); |
|||
$ovrh=imagesy($ovr); |
|||
if ($align & self::POS_Left) |
|||
$posx=0; |
|||
if ($align & self::POS_Center) |
|||
$posx=($imgw-$ovrw)/2; |
|||
if ($align & self::POS_Right) |
|||
$posx=$imgw-$ovrw; |
|||
if ($align & self::POS_Top) |
|||
$posy=0; |
|||
if ($align & self::POS_Middle) |
|||
$posy=($imgh-$ovrh)/2; |
|||
if ($align & self::POS_Bottom) |
|||
$posy=$imgh-$ovrh; |
|||
if (empty($posx)) |
|||
$posx=0; |
|||
if (empty($posy)) |
|||
$posy=0; |
|||
if ($alpha==100) |
|||
imagecopy($this->data,$ovr,$posx,$posy,0,0,$ovrw,$ovrh); |
|||
else { |
|||
$cut=imagecreatetruecolor($ovrw,$ovrh); |
|||
imagecopy($cut,$this->data,0,0,$posx,$posy,$ovrw,$ovrh); |
|||
imagecopy($cut,$ovr,0,0,0,0,$ovrw,$ovrh); |
|||
imagecopymerge($this->data, |
|||
$cut,$posx,$posy,0,0,$ovrw,$ovrh,$alpha); |
|||
} |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Generate identicon |
|||
* @return object |
|||
* @param $str string |
|||
* @param $size int |
|||
* @param $blocks int |
|||
**/ |
|||
function identicon($str,$size=64,$blocks=4) { |
|||
$sprites=[ |
|||
[.5,1,1,0,1,1], |
|||
[.5,0,1,0,.5,1,0,1], |
|||
[.5,0,1,0,1,1,.5,1,1,.5], |
|||
[0,.5,.5,0,1,.5,.5,1,.5,.5], |
|||
[0,.5,1,0,1,1,0,1,1,.5], |
|||
[1,0,1,1,.5,1,1,.5,.5,.5], |
|||
[0,0,1,0,1,.5,0,0,.5,1,0,1], |
|||
[0,0,.5,0,1,.5,.5,1,0,1,.5,.5], |
|||
[.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5], |
|||
[0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1], |
|||
[0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1], |
|||
[.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25], |
|||
[0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1], |
|||
[0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25], |
|||
[0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1], |
|||
[0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1] |
|||
]; |
|||
$hash=sha1($str); |
|||
$this->data=imagecreatetruecolor($size,$size); |
|||
list($r,$g,$b)=$this->rgb(hexdec(substr($hash,-3))); |
|||
$fg=imagecolorallocate($this->data,$r,$g,$b); |
|||
imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT); |
|||
$ctr=count($sprites); |
|||
$dim=$blocks*floor($size/$blocks)*2/$blocks; |
|||
for ($j=0,$y=ceil($blocks/2);$j<$y;$j++) |
|||
for ($i=$j,$x=$blocks-1-$j;$i<$x;$i++) { |
|||
$sprite=imagecreatetruecolor($dim,$dim); |
|||
imagefill($sprite,0,0,IMG_COLOR_TRANSPARENT); |
|||
$block=$sprites[hexdec($hash[($j*$blocks+$i)*2])%$ctr]; |
|||
for ($k=0,$pts=count($block);$k<$pts;$k++) |
|||
$block[$k]*=$dim; |
|||
imagefilledpolygon($sprite,$block,$pts/2,$fg); |
|||
for ($k=0;$k<4;$k++) { |
|||
imagecopyresampled($this->data,$sprite, |
|||
$i*$dim/2,$j*$dim/2,0,0,$dim/2,$dim/2,$dim,$dim); |
|||
$this->data=imagerotate($this->data,90, |
|||
imagecolorallocatealpha($this->data,0,0,0,127)); |
|||
} |
|||
imagedestroy($sprite); |
|||
} |
|||
imagesavealpha($this->data,TRUE); |
|||
return $this->save(); |
|||
} |
|||
|
|||
/** |
|||
* Generate CAPTCHA image |
|||
* @return object|FALSE |
|||
* @param $font string |
|||
* @param $size int |
|||
* @param $len int |
|||
* @param $key string |
|||
* @param $path string |
|||
* @param $fg int |
|||
* @param $bg int |
|||
**/ |
|||
function captcha($font,$size=24,$len=5, |
|||
$key=NULL,$path='',$fg=0xFFFFFF,$bg=0x000000) { |
|||
if ((!$ssl=extension_loaded('openssl')) && ($len<4 || $len>13)) { |
|||
user_error(sprintf(self::E_Length,$len),E_USER_ERROR); |
|||
return FALSE; |
|||
} |
|||
if (!function_exists('imagettftext')) { |
|||
user_error(self::E_TTF,E_USER_ERROR); |
|||
return FALSE; |
|||
} |
|||
$fw=Base::instance(); |
|||
foreach ($fw->split($path?:$fw->UI.';./') as $dir) |
|||
if (is_file($path=$dir.$font)) { |
|||
$seed=strtoupper(substr( |
|||
$ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(), |
|||
-$len)); |
|||
$block=$size*3; |
|||
$tmp=[]; |
|||
for ($i=0,$width=0,$height=0;$i<$len;$i++) { |
|||
// Process at 2x magnification |
|||
$box=imagettfbbox($size*2,0,$path,$seed[$i]); |
|||
$w=$box[2]-$box[0]; |
|||
$h=$box[1]-$box[5]; |
|||
$char=imagecreatetruecolor($block,$block); |
|||
imagefill($char,0,0,$bg); |
|||
imagettftext($char,$size*2,0, |
|||
($block-$w)/2,$block-($block-$h)/2, |
|||
$fg,$path,$seed[$i]); |
|||
$char=imagerotate($char,mt_rand(-30,30), |
|||
imagecolorallocatealpha($char,0,0,0,127)); |
|||
// Reduce to normal size |
|||
$tmp[$i]=imagecreatetruecolor( |
|||
($w=imagesx($char))/2,($h=imagesy($char))/2); |
|||
imagefill($tmp[$i],0,0,IMG_COLOR_TRANSPARENT); |
|||
imagecopyresampled($tmp[$i], |
|||
$char,0,0,0,0,$w/2,$h/2,$w,$h); |
|||
imagedestroy($char); |
|||
$width+=$i+1<$len?$block/2:$w/2; |
|||
$height=max($height,$h/2); |
|||
} |
|||
$this->data=imagecreatetruecolor($width,$height); |
|||
imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT); |
|||
for ($i=0;$i<$len;$i++) { |
|||
imagecopy($this->data,$tmp[$i], |
|||
$i*$block/2,($height-imagesy($tmp[$i]))/2,0,0, |
|||
imagesx($tmp[$i]),imagesy($tmp[$i])); |
|||
imagedestroy($tmp[$i]); |
|||
} |
|||
imagesavealpha($this->data,TRUE); |
|||
if ($key) |
|||
$fw->$key=$seed; |
|||
return $this->save(); |
|||
} |
|||
user_error(self::E_Font,E_USER_ERROR); |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return image width |
|||
* @return int |
|||
**/ |
|||
function width() { |
|||
return imagesx($this->data); |
|||
} |
|||
|
|||
/** |
|||
* Return image height |
|||
* @return int |
|||
**/ |
|||
function height() { |
|||
return imagesy($this->data); |
|||
} |
|||
|
|||
/** |
|||
* Send image to HTTP client |
|||
* @return NULL |
|||
**/ |
|||
function render() { |
|||
$args=func_get_args(); |
|||
$format=$args?array_shift($args):'png'; |
|||
if (PHP_SAPI!='cli') { |
|||
header('Content-Type: image/'.$format); |
|||
header('X-Powered-By: '.Base::instance()->PACKAGE); |
|||
} |
|||
call_user_func_array( |
|||
'image'.$format, |
|||
array_merge([$this->data,NULL],$args) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Return image as a string |
|||
* @return string |
|||
**/ |
|||
function dump() { |
|||
$args=func_get_args(); |
|||
$format=$args?array_shift($args):'png'; |
|||
ob_start(); |
|||
call_user_func_array( |
|||
'image'.$format, |
|||
array_merge([$this->data,NULL],$args) |
|||
); |
|||
return ob_get_clean(); |
|||
} |
|||
|
|||
/** |
|||
* Return image resource |
|||
* @return resource |
|||
**/ |
|||
function data() { |
|||
return $this->data; |
|||
} |
|||
|
|||
/** |
|||
* Save current state |
|||
* @return object |
|||
**/ |
|||
function save() { |
|||
$fw=Base::instance(); |
|||
if ($this->flag) { |
|||
if (!is_dir($dir=$fw->TEMP)) |
|||
mkdir($dir,Base::MODE,TRUE); |
|||
$this->count++; |
|||
$fw->write($dir.'/'.$fw->SEED.'.'. |
|||
$fw->hash($this->file).'-'.$this->count.'.png', |
|||
$this->dump()); |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Revert to specified state |
|||
* @return object |
|||
* @param $state int |
|||
**/ |
|||
function restore($state=1) { |
|||
$fw=Base::instance(); |
|||
if ($this->flag && is_file($file=($path=$fw->TEMP. |
|||
$fw->SEED.'.'.$fw->hash($this->file).'-').$state.'.png')) { |
|||
if (is_resource($this->data)) |
|||
imagedestroy($this->data); |
|||
$this->data=imagecreatefromstring($fw->read($file)); |
|||
imagesavealpha($this->data,TRUE); |
|||
foreach (glob($path.'*.png',GLOB_NOSORT) as $match) |
|||
if (preg_match('/-(\d+)\.png/',$match,$parts) && |
|||
$parts[1]>$state) |
|||
@unlink($match); |
|||
$this->count=$state; |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Undo most recently applied filter |
|||
* @return object |
|||
**/ |
|||
function undo() { |
|||
if ($this->flag) { |
|||
if ($this->count) |
|||
$this->count--; |
|||
return $this->restore($this->count); |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Load string |
|||
* @return object|FALSE |
|||
* @param $str string |
|||
**/ |
|||
function load($str) { |
|||
if (!$this->data=@imagecreatefromstring($str)) |
|||
return FALSE; |
|||
imagesavealpha($this->data,TRUE); |
|||
$this->save(); |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate image |
|||
* @param $file string |
|||
* @param $flag bool |
|||
* @param $path string |
|||
**/ |
|||
function __construct($file=NULL,$flag=FALSE,$path=NULL) { |
|||
$this->flag=$flag; |
|||
if ($file) { |
|||
$fw=Base::instance(); |
|||
// Create image from file |
|||
$this->file=$file; |
|||
if (!isset($path)) |
|||
$path=$fw->UI.';./'; |
|||
foreach ($fw->split($path,FALSE) as $dir) |
|||
if (is_file($dir.$file)) |
|||
return $this->load($fw->read($dir.$file)); |
|||
user_error(self::E_File,E_USER_ERROR); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Wrap-up |
|||
* @return NULL |
|||
**/ |
|||
function __destruct() { |
|||
if (is_resource($this->data)) { |
|||
imagedestroy($this->data); |
|||
$fw=Base::instance(); |
|||
$path=$fw->TEMP.$fw->SEED.'.'.$fw->hash($this->file); |
|||
if ($glob=@glob($path.'*.png',GLOB_NOSORT)) |
|||
foreach ($glob as $match) |
|||
if (preg_match('/-(\d+)\.png/',$match)) |
|||
@unlink($match); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,71 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Custom logger |
|||
class Log { |
|||
|
|||
protected |
|||
//! File name |
|||
$file; |
|||
|
|||
/** |
|||
* Write specified text to log file |
|||
* @return string |
|||
* @param $text string |
|||
* @param $format string |
|||
**/ |
|||
function write($text,$format='r') { |
|||
$fw=Base::instance(); |
|||
foreach (preg_split('/\r?\n|\r/',trim($text)) as $line) |
|||
$fw->write( |
|||
$this->file, |
|||
date($format). |
|||
(isset($_SERVER['REMOTE_ADDR'])? |
|||
(' ['.$_SERVER['REMOTE_ADDR']. |
|||
(($fwd=filter_var($fw->get('HEADERS.X-Forwarded-For'), |
|||
FILTER_VALIDATE_IP))?(' ('.$fwd.')'):'') |
|||
.']'):'').' '. |
|||
trim($line).PHP_EOL, |
|||
TRUE |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Erase log |
|||
* @return NULL |
|||
**/ |
|||
function erase() { |
|||
@unlink($this->file); |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $file string |
|||
**/ |
|||
function __construct($file) { |
|||
$fw=Base::instance(); |
|||
if (!is_dir($dir=$fw->LOGS)) |
|||
mkdir($dir,Base::MODE,TRUE); |
|||
$this->file=$dir.$file; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,139 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! PHP magic wrapper |
|||
abstract class Magic implements ArrayAccess { |
|||
|
|||
/** |
|||
* Return TRUE if key is not empty |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
abstract function exists($key); |
|||
|
|||
/** |
|||
* Bind value to key |
|||
* @return mixed |
|||
* @param $key string |
|||
* @param $val mixed |
|||
**/ |
|||
abstract function set($key,$val); |
|||
|
|||
/** |
|||
* Retrieve contents of key |
|||
* @return mixed |
|||
* @param $key string |
|||
**/ |
|||
abstract function &get($key); |
|||
|
|||
/** |
|||
* Unset key |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
abstract function clear($key); |
|||
|
|||
/** |
|||
* Convenience method for checking property value |
|||
* @return mixed |
|||
* @param $key string |
|||
**/ |
|||
function offsetexists($key) { |
|||
return Base::instance()->visible($this,$key)? |
|||
isset($this->$key): |
|||
($this->exists($key) && $this->get($key)!==NULL); |
|||
} |
|||
|
|||
/** |
|||
* Convenience method for assigning property value |
|||
* @return mixed |
|||
* @param $key string |
|||
* @param $val mixed |
|||
**/ |
|||
function offsetset($key,$val) { |
|||
return Base::instance()->visible($this,$key)? |
|||
($this->$key=$val):$this->set($key,$val); |
|||
} |
|||
|
|||
/** |
|||
* Convenience method for retrieving property value |
|||
* @return mixed |
|||
* @param $key string |
|||
**/ |
|||
function &offsetget($key) { |
|||
if (Base::instance()->visible($this,$key)) |
|||
$val=&$this->$key; |
|||
else |
|||
$val=&$this->get($key); |
|||
return $val; |
|||
} |
|||
|
|||
/** |
|||
* Convenience method for removing property value |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function offsetunset($key) { |
|||
if (Base::instance()->visible($this,$key)) |
|||
unset($this->$key); |
|||
else |
|||
$this->clear($key); |
|||
} |
|||
|
|||
/** |
|||
* Alias for offsetexists() |
|||
* @return mixed |
|||
* @param $key string |
|||
**/ |
|||
function __isset($key) { |
|||
return $this->offsetexists($key); |
|||
} |
|||
|
|||
/** |
|||
* Alias for offsetset() |
|||
* @return mixed |
|||
* @param $key string |
|||
* @param $val mixed |
|||
**/ |
|||
function __set($key,$val) { |
|||
return $this->offsetset($key,$val); |
|||
} |
|||
|
|||
/** |
|||
* Alias for offsetget() |
|||
* @return mixed |
|||
* @param $key string |
|||
**/ |
|||
function &__get($key) { |
|||
$val=&$this->offsetget($key); |
|||
return $val; |
|||
} |
|||
|
|||
/** |
|||
* Alias for offsetunset() |
|||
* @param $key string |
|||
**/ |
|||
function __unset($key) { |
|||
$this->offsetunset($key); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,569 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Markdown-to-HTML converter |
|||
class Markdown extends Prefab { |
|||
|
|||
protected |
|||
//! Parsing rules |
|||
$blocks, |
|||
//! Special characters |
|||
$special; |
|||
|
|||
/** |
|||
* Process blockquote |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _blockquote($str) { |
|||
$str=preg_replace('/(?<=^|\n)\h?>\h?(.*?(?:\n+|$))/','\1',$str); |
|||
return strlen($str)? |
|||
('<blockquote>'.$this->build($str).'</blockquote>'."\n\n"):''; |
|||
} |
|||
|
|||
/** |
|||
* Process whitespace-prefixed code block |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _pre($str) { |
|||
$str=preg_replace('/(?<=^|\n)(?: {4}|\t)(.+?(?:\n+|$))/','\1', |
|||
$this->esc($str)); |
|||
return strlen($str)? |
|||
('<pre><code>'. |
|||
$this->esc($this->snip($str)). |
|||
'</code></pre>'."\n\n"): |
|||
''; |
|||
} |
|||
|
|||
/** |
|||
* Process fenced code block |
|||
* @return string |
|||
* @param $hint string |
|||
* @param $str string |
|||
**/ |
|||
protected function _fence($hint,$str) { |
|||
$str=$this->snip($str); |
|||
$fw=Base::instance(); |
|||
if ($fw->HIGHLIGHT) { |
|||
switch (strtolower($hint)) { |
|||
case 'php': |
|||
$str=$fw->highlight($str); |
|||
break; |
|||
case 'apache': |
|||
preg_match_all('/(?<=^|\n)(\h*)'. |
|||
'(?:(<\/?)(\w+)((?:\h+[^>]+)*)(>)|'. |
|||
'(?:(\w+)(\h.+?)))(\h*(?:\n+|$))/', |
|||
$str,$matches,PREG_SET_ORDER); |
|||
$out=''; |
|||
foreach ($matches as $match) |
|||
$out.=$match[1]. |
|||
($match[3]? |
|||
('<span class="section">'. |
|||
$this->esc($match[2]).$match[3]. |
|||
'</span>'. |
|||
($match[4]? |
|||
('<span class="data">'. |
|||
$this->esc($match[4]). |
|||
'</span>'): |
|||
''). |
|||
'<span class="section">'. |
|||
$this->esc($match[5]). |
|||
'</span>'): |
|||
('<span class="directive">'. |
|||
$match[6]. |
|||
'</span>'. |
|||
'<span class="data">'. |
|||
$this->esc($match[7]). |
|||
'</span>')). |
|||
$match[8]; |
|||
$str='<code>'.$out.'</code>'; |
|||
break; |
|||
case 'html': |
|||
preg_match_all( |
|||
'/(?:(?:<(\/?)(\w+)'. |
|||
'((?:\h+(?:\w+\h*=\h*)?".+?"|[^>]+)*|'. |
|||
'\h+.+?)(\h*\/?)>)|(.+?))/s', |
|||
$str,$matches,PREG_SET_ORDER |
|||
); |
|||
$out=''; |
|||
foreach ($matches as $match) { |
|||
if ($match[2]) { |
|||
$out.='<span class="xml_tag"><'. |
|||
$match[1].$match[2].'</span>'; |
|||
if ($match[3]) { |
|||
preg_match_all( |
|||
'/(?:\h+(?:(?:(\w+)\h*=\h*)?'. |
|||
'(".+?")|(.+)))/', |
|||
$match[3],$parts,PREG_SET_ORDER |
|||
); |
|||
foreach ($parts as $part) |
|||
$out.=' '. |
|||
(empty($part[3])? |
|||
((empty($part[1])? |
|||
'': |
|||
('<span class="xml_attr">'. |
|||
$part[1].'</span>=')). |
|||
'<span class="xml_data">'. |
|||
$part[2].'</span>'): |
|||
('<span class="xml_tag">'. |
|||
$part[3].'</span>')); |
|||
} |
|||
$out.='<span class="xml_tag">'. |
|||
$match[4].'></span>'; |
|||
} |
|||
else |
|||
$out.=$this->esc($match[5]); |
|||
} |
|||
$str='<code>'.$out.'</code>'; |
|||
break; |
|||
case 'ini': |
|||
preg_match_all( |
|||
'/(?<=^|\n)(?:'. |
|||
'(;[^\n]*)|(?:<\?php.+?\?>?)|'. |
|||
'(?:\[(.+?)\])|'. |
|||
'(.+?)(\h*=\h*)'. |
|||
'((?:\\\\\h*\r?\n|.+?)*)'. |
|||
')((?:\r?\n)+|$)/', |
|||
$str,$matches,PREG_SET_ORDER |
|||
); |
|||
$out=''; |
|||
foreach ($matches as $match) { |
|||
if ($match[1]) |
|||
$out.='<span class="comment">'.$match[1]. |
|||
'</span>'; |
|||
elseif ($match[2]) |
|||
$out.='<span class="ini_section">['.$match[2].']'. |
|||
'</span>'; |
|||
elseif ($match[3]) |
|||
$out.='<span class="ini_key">'.$match[3]. |
|||
'</span>'.$match[4]. |
|||
($match[5]? |
|||
('<span class="ini_value">'. |
|||
$match[5].'</span>'):''); |
|||
else |
|||
$out.=$match[0]; |
|||
if (isset($match[6])) |
|||
$out.=$match[6]; |
|||
} |
|||
$str='<code>'.$out.'</code>'; |
|||
break; |
|||
default: |
|||
$str='<code>'.$this->esc($str).'</code>'; |
|||
break; |
|||
} |
|||
} |
|||
else |
|||
$str='<code>'.$this->esc($str).'</code>'; |
|||
return '<pre>'.$str.'</pre>'."\n\n"; |
|||
} |
|||
|
|||
/** |
|||
* Process horizontal rule |
|||
* @return string |
|||
**/ |
|||
protected function _hr() { |
|||
return '<hr />'."\n\n"; |
|||
} |
|||
|
|||
/** |
|||
* Process atx-style heading |
|||
* @return string |
|||
* @param $type string |
|||
* @param $str string |
|||
**/ |
|||
protected function _atx($type,$str) { |
|||
$level=strlen($type); |
|||
return '<h'.$level.' id="'.Web::instance()->slug($str).'">'. |
|||
$this->scan($str).'</h'.$level.'>'."\n\n"; |
|||
} |
|||
|
|||
/** |
|||
* Process setext-style heading |
|||
* @return string |
|||
* @param $str string |
|||
* @param $type string |
|||
**/ |
|||
protected function _setext($str,$type) { |
|||
$level=strpos('=-',$type)+1; |
|||
return '<h'.$level.' id="'.Web::instance()->slug($str).'">'. |
|||
$this->scan($str).'</h'.$level.'>'."\n\n"; |
|||
} |
|||
|
|||
/** |
|||
* Process ordered/unordered list |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _li($str) { |
|||
// Initialize list parser |
|||
$len=strlen($str); |
|||
$ptr=0; |
|||
$dst=''; |
|||
$first=TRUE; |
|||
$tight=TRUE; |
|||
$type='ul'; |
|||
// Main loop |
|||
while ($ptr<$len) { |
|||
if (preg_match('/^\h*[*-](?:\h?[*-]){2,}(?:\n+|$)/', |
|||
substr($str,$ptr),$match)) { |
|||
$ptr+=strlen($match[0]); |
|||
// Embedded horizontal rule |
|||
return (strlen($dst)? |
|||
('<'.$type.'>'."\n".$dst.'</'.$type.'>'."\n\n"):''). |
|||
'<hr />'."\n\n".$this->build(substr($str,$ptr)); |
|||
} |
|||
elseif (preg_match('/(?<=^|\n)([*+-]|\d+\.)\h'. |
|||
'(.+?(?:\n+|$))((?:(?: {4}|\t)+.+?(?:\n+|$))*)/s', |
|||
substr($str,$ptr),$match)) { |
|||
$match[3]=preg_replace('/(?<=^|\n)(?: {4}|\t)/','',$match[3]); |
|||
$found=FALSE; |
|||
foreach (array_slice($this->blocks,0,-1) as $regex) |
|||
if (preg_match($regex,$match[3])) { |
|||
$found=TRUE; |
|||
break; |
|||
} |
|||
// List |
|||
if ($first) { |
|||
// First pass |
|||
if (is_numeric($match[1])) |
|||
$type='ol'; |
|||
if (preg_match('/\n{2,}$/',$match[2]. |
|||
($found?'':$match[3]))) |
|||
// Loose structure; Use paragraphs |
|||
$tight=FALSE; |
|||
$first=FALSE; |
|||
} |
|||
// Strip leading whitespaces |
|||
$ptr+=strlen($match[0]); |
|||
$tmp=$this->snip($match[2].$match[3]); |
|||
if ($tight) { |
|||
if ($found) |
|||
$tmp=$match[2].$this->build($this->snip($match[3])); |
|||
} |
|||
else |
|||
$tmp=$this->build($tmp); |
|||
$dst.='<li>'.$this->scan(trim($tmp)).'</li>'."\n"; |
|||
} |
|||
} |
|||
return strlen($dst)? |
|||
('<'.$type.'>'."\n".$dst.'</'.$type.'>'."\n\n"):''; |
|||
} |
|||
|
|||
/** |
|||
* Ignore raw HTML |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _raw($str) { |
|||
return $str; |
|||
} |
|||
|
|||
/** |
|||
* Process paragraph |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _p($str) { |
|||
$str=trim($str); |
|||
if (strlen($str)) { |
|||
if (preg_match('/^(.+?\n)([>#].+)$/s',$str,$parts)) |
|||
return $this->_p($parts[1]).$this->build($parts[2]); |
|||
$str=preg_replace_callback( |
|||
'/([^<>\[]+)?(<[\?%].+?[\?%]>|<.+?>|\[.+?\]\s*\(.+?\))|'. |
|||
'(.+)/s', |
|||
function($expr) { |
|||
$tmp=''; |
|||
if (isset($expr[4])) |
|||
$tmp.=$this->esc($expr[4]); |
|||
else { |
|||
if (isset($expr[1])) |
|||
$tmp.=$this->esc($expr[1]); |
|||
$tmp.=$expr[2]; |
|||
if (isset($expr[3])) |
|||
$tmp.=$this->esc($expr[3]); |
|||
} |
|||
return $tmp; |
|||
}, |
|||
$str |
|||
); |
|||
$str=preg_replace('/\s{2}\r?\n/','<br />',$str); |
|||
return '<p>'.$this->scan($str).'</p>'."\n\n"; |
|||
} |
|||
return ''; |
|||
} |
|||
|
|||
/** |
|||
* Process strong/em/strikethrough spans |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _text($str) { |
|||
$tmp=''; |
|||
while ($str!=$tmp) |
|||
$str=preg_replace_callback( |
|||
'/(?<=\s|^)(?<!\\\\)([*_])([*_]?)([*_]?)(.*?)(?!\\\\)\3\2\1(?=[\s[:punct:]]|$)/', |
|||
function($expr) { |
|||
if ($expr[3]) |
|||
return '<strong><em>'.$expr[4].'</em></strong>'; |
|||
if ($expr[2]) |
|||
return '<strong>'.$expr[4].'</strong>'; |
|||
return '<em>'.$expr[4].'</em>'; |
|||
}, |
|||
preg_replace( |
|||
'/(?<!\\\\)~~(.*?)(?!\\\\)~~(?=[\s[:punct:]]|$)/', |
|||
'<del>\1</del>', |
|||
$tmp=$str |
|||
) |
|||
); |
|||
return $str; |
|||
} |
|||
|
|||
/** |
|||
* Process image span |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _img($str) { |
|||
return preg_replace_callback( |
|||
'/!(?:\[(.+?)\])?\h*\(<?(.*?)>?(?:\h*"(.*?)"\h*)?\)/', |
|||
function($expr) { |
|||
return '<img src="'.$expr[2].'"'. |
|||
(empty($expr[1])? |
|||
'': |
|||
(' alt="'.$this->esc($expr[1]).'"')). |
|||
(empty($expr[3])? |
|||
'': |
|||
(' title="'.$this->esc($expr[3]).'"')).' />'; |
|||
}, |
|||
$str |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Process anchor span |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _a($str) { |
|||
return preg_replace_callback( |
|||
'/(?<!\\\\)\[(.+?)(?!\\\\)\]\h*\(<?(.*?)>?(?:\h*"(.*?)"\h*)?\)/', |
|||
function($expr) { |
|||
return '<a href="'.$this->esc($expr[2]).'"'. |
|||
(empty($expr[3])? |
|||
'': |
|||
(' title="'.$this->esc($expr[3]).'"')). |
|||
'>'.$this->scan($expr[1]).'</a>'; |
|||
}, |
|||
$str |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Auto-convert links |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _auto($str) { |
|||
return preg_replace_callback( |
|||
'/`.*?<(.+?)>.*?`|<(.+?)>/', |
|||
function($expr) { |
|||
if (empty($expr[1]) && parse_url($expr[2],PHP_URL_SCHEME)) { |
|||
$expr[2]=$this->esc($expr[2]); |
|||
return '<a href="'.$expr[2].'">'.$expr[2].'</a>'; |
|||
} |
|||
return $expr[0]; |
|||
}, |
|||
$str |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Process code span |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function _code($str) { |
|||
return preg_replace_callback( |
|||
'/`` (.+?) ``|(?<!\\\\)`(.+?)(?!\\\\)`/', |
|||
function($expr) { |
|||
return '<code>'. |
|||
$this->esc(empty($expr[1])?$expr[2]:$expr[1]).'</code>'; |
|||
}, |
|||
$str |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Convert characters to HTML entities |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function esc($str) { |
|||
if (!$this->special) |
|||
$this->special=[ |
|||
'...'=>'…', |
|||
'(tm)'=>'™', |
|||
'(r)'=>'®', |
|||
'(c)'=>'©' |
|||
]; |
|||
foreach ($this->special as $key=>$val) |
|||
$str=preg_replace('/'.preg_quote($key,'/').'/i',$val,$str); |
|||
return htmlspecialchars($str,ENT_COMPAT, |
|||
Base::instance()->ENCODING,FALSE); |
|||
} |
|||
|
|||
/** |
|||
* Reduce multiple line feeds |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function snip($str) { |
|||
return preg_replace('/(?:(?<=\n)\n+)|\n+$/',"\n",$str); |
|||
} |
|||
|
|||
/** |
|||
* Scan line for convertible spans |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function scan($str) { |
|||
$inline=['img','a','text','auto','code']; |
|||
foreach ($inline as $func) |
|||
$str=$this->{'_'.$func}($str); |
|||
return $str; |
|||
} |
|||
|
|||
/** |
|||
* Assemble blocks |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
protected function build($str) { |
|||
if (!$this->blocks) { |
|||
// Regexes for capturing entire blocks |
|||
$this->blocks=[ |
|||
'blockquote'=>'/^(?:\h?>\h?.*?(?:\n+|$))+/', |
|||
'pre'=>'/^(?:(?: {4}|\t).+?(?:\n+|$))+/', |
|||
'fence'=>'/^`{3}\h*(\w+)?.*?[^\n]*\n+(.+?)`{3}[^\n]*'. |
|||
'(?:\n+|$)/s', |
|||
'hr'=>'/^\h*[*_-](?:\h?[\*_-]){2,}\h*(?:\n+|$)/', |
|||
'atx'=>'/^\h*(#{1,6})\h?(.+?)\h*(?:#.*)?(?:\n+|$)/', |
|||
'setext'=>'/^\h*(.+?)\h*\n([=-])+\h*(?:\n+|$)/', |
|||
'li'=>'/^(?:(?:[*+-]|\d+\.)\h.+?(?:\n+|$)'. |
|||
'(?:(?: {4}|\t)+.+?(?:\n+|$))*)+/s', |
|||
'raw'=>'/^((?:<!--.+?-->|'. |
|||
'<(address|article|aside|audio|blockquote|canvas|dd|'. |
|||
'div|dl|fieldset|figcaption|figure|footer|form|h\d|'. |
|||
'header|hgroup|hr|noscript|object|ol|output|p|pre|'. |
|||
'section|table|tfoot|ul|video).*?'. |
|||
'(?:\/>|>(?:(?>[^><]+)|(?R))*<\/\2>))'. |
|||
'\h*(?:\n{2,}|\n*$)|<[\?%].+?[\?%]>\h*(?:\n?$|\n*))/s', |
|||
'p'=>'/^(.+?(?:\n{2,}|\n*$))/s' |
|||
]; |
|||
} |
|||
// Treat lines with nothing but whitespaces as empty lines |
|||
$str=preg_replace('/\n\h+(?=\n)/',"\n",$str); |
|||
// Initialize block parser |
|||
$len=strlen($str); |
|||
$ptr=0; |
|||
$dst=''; |
|||
// Main loop |
|||
while ($ptr<$len) { |
|||
if (preg_match('/^ {0,3}\[([^\[\]]+)\]:\s*<?(.*?)>?\s*'. |
|||
'(?:"([^\n]*)")?(?:\n+|$)/s',substr($str,$ptr),$match)) { |
|||
// Reference-style link; Backtrack |
|||
$ptr+=strlen($match[0]); |
|||
$tmp=''; |
|||
// Catch line breaks in title attribute |
|||
$ref=preg_replace('/\h/','\s',preg_quote($match[1],'/')); |
|||
while ($dst!=$tmp) { |
|||
$dst=preg_replace_callback( |
|||
'/(?<!\\\\)\[('.$ref.')(?!\\\\)\]\s*\[\]|'. |
|||
'(!?)(?:\[([^\[\]]+)\]\s*)?'. |
|||
'(?<!\\\\)\[('.$ref.')(?!\\\\)\]/', |
|||
function($expr) use($match) { |
|||
return (empty($expr[2]))? |
|||
// Anchor |
|||
('<a href="'.$this->esc($match[2]).'"'. |
|||
(empty($match[3])? |
|||
'': |
|||
(' title="'. |
|||
$this->esc($match[3]).'"')).'>'. |
|||
// Link |
|||
$this->scan( |
|||
empty($expr[3])? |
|||
(empty($expr[1])? |
|||
$expr[4]: |
|||
$expr[1]): |
|||
$expr[3] |
|||
).'</a>'): |
|||
// Image |
|||
('<img src="'.$match[2].'"'. |
|||
(empty($expr[2])? |
|||
'': |
|||
(' alt="'. |
|||
$this->esc($expr[3]).'"')). |
|||
(empty($match[3])? |
|||
'': |
|||
(' title="'. |
|||
$this->esc($match[3]).'"')). |
|||
' />'); |
|||
}, |
|||
$tmp=$dst |
|||
); |
|||
} |
|||
} |
|||
else |
|||
foreach ($this->blocks as $func=>$regex) |
|||
if (preg_match($regex,substr($str,$ptr),$match)) { |
|||
$ptr+=strlen($match[0]); |
|||
$dst.=call_user_func_array( |
|||
[$this,'_'.$func], |
|||
count($match)>1?array_slice($match,1):$match |
|||
); |
|||
break; |
|||
} |
|||
} |
|||
return $dst; |
|||
} |
|||
|
|||
/** |
|||
* Render HTML equivalent of markdown |
|||
* @return string |
|||
* @param $txt string |
|||
**/ |
|||
function convert($txt) { |
|||
$txt=preg_replace_callback( |
|||
'/(<code.*?>.+?<\/code>|'. |
|||
'<[^>\n]+>|\([^\n\)]+\)|"[^"\n]+")|'. |
|||
'\\\\(.)/s', |
|||
function($expr) { |
|||
// Process escaped characters |
|||
return empty($expr[1])?$expr[2]:$expr[1]; |
|||
}, |
|||
$this->build(preg_replace('/\r\n|\r/',"\n",$txt)) |
|||
); |
|||
return $this->snip($txt); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,139 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Generic array utilities |
|||
class Matrix extends Prefab { |
|||
|
|||
/** |
|||
* Retrieve values from a specified column of a multi-dimensional |
|||
* array variable |
|||
* @return array |
|||
* @param $var array |
|||
* @param $col mixed |
|||
**/ |
|||
function pick(array $var,$col) { |
|||
return array_map( |
|||
function($row) use($col) { |
|||
return $row[$col]; |
|||
}, |
|||
$var |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* select a subset of fields from an input array |
|||
* @param string|array $fields splittable string or array |
|||
* @param string|array $data hive key or array |
|||
* @return array |
|||
*/ |
|||
function select($fields, $data) { |
|||
return array_intersect_key(is_array($data) ? $data : \Base::instance()->get($data), |
|||
array_flip(is_array($fields) ? $fields : \Base::instance()->split($fields))); |
|||
} |
|||
|
|||
/** |
|||
* walk with a callback function through a subset of fields from an input array |
|||
* the callback receives the value, index-key and the full input array as parameters |
|||
* set value parameter as reference and you're able to modify the data as well |
|||
* @param string|array $fields splittable string or array of fields |
|||
* @param string|array $data hive key or input array |
|||
* @param callable $callback (mixed &$value, string $key, array $data) |
|||
* @return array modified subset data |
|||
*/ |
|||
function walk($fields, $data, $callback) { |
|||
$subset=$this->select($fields, $data); |
|||
array_walk($subset, $callback, $data); |
|||
return $subset; |
|||
} |
|||
|
|||
/** |
|||
* Rotate a two-dimensional array variable |
|||
* @return NULL |
|||
* @param $var array |
|||
**/ |
|||
function transpose(array &$var) { |
|||
$out=[]; |
|||
foreach ($var as $keyx=>$cols) |
|||
foreach ($cols as $keyy=>$valy) |
|||
$out[$keyy][$keyx]=$valy; |
|||
$var=$out; |
|||
} |
|||
|
|||
/** |
|||
* Sort a multi-dimensional array variable on a specified column |
|||
* @return bool |
|||
* @param $var array |
|||
* @param $col mixed |
|||
* @param $order int |
|||
**/ |
|||
function sort(array &$var,$col,$order=SORT_ASC) { |
|||
uasort( |
|||
$var, |
|||
function($val1,$val2) use($col,$order) { |
|||
list($v1,$v2)=[$val1[$col],$val2[$col]]; |
|||
$out=is_numeric($v1) && is_numeric($v2)? |
|||
Base::instance()->sign($v1-$v2):strcmp($v1,$v2); |
|||
if ($order==SORT_DESC) |
|||
$out=-$out; |
|||
return $out; |
|||
} |
|||
); |
|||
$var=array_values($var); |
|||
} |
|||
|
|||
/** |
|||
* Change the key of a two-dimensional array element |
|||
* @return NULL |
|||
* @param $var array |
|||
* @param $old string |
|||
* @param $new string |
|||
**/ |
|||
function changekey(array &$var,$old,$new) { |
|||
$keys=array_keys($var); |
|||
$vals=array_values($var); |
|||
$keys[array_search($old,$keys)]=$new; |
|||
$var=array_combine($keys,$vals); |
|||
} |
|||
|
|||
/** |
|||
* Return month calendar of specified date, with optional setting for |
|||
* first day of week (0 for Sunday) |
|||
* @return array |
|||
* @param $date string|int |
|||
* @param $first int |
|||
**/ |
|||
function calendar($date='now',$first=0) { |
|||
$out=FALSE; |
|||
if (extension_loaded('calendar')) { |
|||
if (is_string($date)) |
|||
$date=strtotime($date); |
|||
$parts=getdate($date); |
|||
$days=cal_days_in_month(CAL_GREGORIAN,$parts['mon'],$parts['year']); |
|||
$ref=date('w',strtotime(date('Y-m',$parts[0]).'-01'))+(7-$first)%7; |
|||
$out=[]; |
|||
for ($i=0;$i<$days;$i++) |
|||
$out[floor(($ref+$i)/7)][($ref+$i)%7]=$i+1; |
|||
} |
|||
return $out; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,196 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Cache-based session handler |
|||
class Session { |
|||
|
|||
protected |
|||
//! Session ID |
|||
$sid, |
|||
//! Anti-CSRF token |
|||
$_csrf, |
|||
//! User agent |
|||
$_agent, |
|||
//! IP, |
|||
$_ip, |
|||
//! Suspect callback |
|||
$onsuspect, |
|||
//! Cache instance |
|||
$_cache; |
|||
|
|||
/** |
|||
* Open session |
|||
* @return TRUE |
|||
* @param $path string |
|||
* @param $name string |
|||
**/ |
|||
function open($path,$name) { |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Close session |
|||
* @return TRUE |
|||
**/ |
|||
function close() { |
|||
$this->sid=NULL; |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session data in serialized format |
|||
* @return string |
|||
* @param $id string |
|||
**/ |
|||
function read($id) { |
|||
$this->sid=$id; |
|||
if (!$data=$this->_cache->get($id.'.@')) |
|||
return ''; |
|||
if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) { |
|||
$fw=Base::instance(); |
|||
if (!isset($this->onsuspect) || |
|||
$fw->call($this->onsuspect,[$this,$id])===FALSE) { |
|||
//NB: `session_destroy` can't be called at that stage (`session_start` not completed) |
|||
$this->destroy($id); |
|||
$this->close(); |
|||
unset($fw->{'COOKIE.'.session_name()}); |
|||
$fw->error(403); |
|||
} |
|||
} |
|||
return $data['data']; |
|||
} |
|||
|
|||
/** |
|||
* Write session data |
|||
* @return TRUE |
|||
* @param $id string |
|||
* @param $data string |
|||
**/ |
|||
function write($id,$data) { |
|||
$fw=Base::instance(); |
|||
$jar=$fw->JAR; |
|||
$this->_cache->set($id.'.@', |
|||
[ |
|||
'data'=>$data, |
|||
'ip'=>$this->_ip, |
|||
'agent'=>$this->_agent, |
|||
'stamp'=>time() |
|||
], |
|||
$jar['expire'] |
|||
); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Destroy session |
|||
* @return TRUE |
|||
* @param $id string |
|||
**/ |
|||
function destroy($id) { |
|||
$this->_cache->clear($id.'.@'); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Garbage collector |
|||
* @return TRUE |
|||
* @param $max int |
|||
**/ |
|||
function cleanup($max) { |
|||
$this->_cache->reset('.@',$max); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Return session id (if session has started) |
|||
* @return string|NULL |
|||
**/ |
|||
function sid() { |
|||
return $this->sid; |
|||
} |
|||
|
|||
/** |
|||
* Return anti-CSRF token |
|||
* @return string |
|||
**/ |
|||
function csrf() { |
|||
return $this->_csrf; |
|||
} |
|||
|
|||
/** |
|||
* Return IP address |
|||
* @return string |
|||
**/ |
|||
function ip() { |
|||
return $this->_ip; |
|||
} |
|||
|
|||
/** |
|||
* Return Unix timestamp |
|||
* @return string|FALSE |
|||
**/ |
|||
function stamp() { |
|||
if (!$this->sid) |
|||
session_start(); |
|||
return $this->_cache->exists($this->sid.'.@',$data)? |
|||
$data['stamp']:FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return HTTP user agent |
|||
* @return string |
|||
**/ |
|||
function agent() { |
|||
return $this->_agent; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $onsuspect callback |
|||
* @param $key string |
|||
**/ |
|||
function __construct($onsuspect=NULL,$key=NULL,$cache=null) { |
|||
$this->onsuspect=$onsuspect; |
|||
$this->_cache=$cache?:Cache::instance(); |
|||
session_set_save_handler( |
|||
[$this,'open'], |
|||
[$this,'close'], |
|||
[$this,'read'], |
|||
[$this,'write'], |
|||
[$this,'destroy'], |
|||
[$this,'cleanup'] |
|||
); |
|||
register_shutdown_function('session_commit'); |
|||
$fw=\Base::instance(); |
|||
$headers=$fw->HEADERS; |
|||
$this->_csrf=$fw->hash($fw->SEED. |
|||
extension_loaded('openssl')? |
|||
implode(unpack('L',openssl_random_pseudo_bytes(4))): |
|||
mt_rand() |
|||
); |
|||
if ($key) |
|||
$fw->$key=$this->_csrf; |
|||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; |
|||
$this->_ip=$fw->IP; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,360 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! SMTP plug-in |
|||
class SMTP extends Magic { |
|||
|
|||
//@{ Locale-specific error/exception messages |
|||
const |
|||
E_Header='%s: header is required', |
|||
E_Blank='Message must not be blank', |
|||
E_Attach='Attachment %s not found'; |
|||
//@} |
|||
|
|||
protected |
|||
//! Message properties |
|||
$headers, |
|||
//! E-mail attachments |
|||
$attachments, |
|||
//! SMTP host |
|||
$host, |
|||
//! SMTP port |
|||
$port, |
|||
//! TLS/SSL |
|||
$scheme, |
|||
//! User ID |
|||
$user, |
|||
//! Password |
|||
$pw, |
|||
//! TLS/SSL stream context |
|||
$context, |
|||
//! TCP/IP socket |
|||
$socket, |
|||
//! Server-client conversation |
|||
$log; |
|||
|
|||
/** |
|||
* Fix header |
|||
* @return string |
|||
* @param $key string |
|||
**/ |
|||
protected function fixheader($key) { |
|||
return str_replace(' ','-', |
|||
ucwords(preg_replace('/[_-]/',' ',strtolower($key)))); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if header exists |
|||
* @return bool |
|||
* @param $key |
|||
**/ |
|||
function exists($key) { |
|||
$key=$this->fixheader($key); |
|||
return isset($this->headers[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Bind value to e-mail header |
|||
* @return string |
|||
* @param $key string |
|||
* @param $val string |
|||
**/ |
|||
function set($key,$val) { |
|||
$key=$this->fixheader($key); |
|||
return $this->headers[$key]=$val; |
|||
} |
|||
|
|||
/** |
|||
* Return value of e-mail header |
|||
* @return string|NULL |
|||
* @param $key string |
|||
**/ |
|||
function &get($key) { |
|||
$key=$this->fixheader($key); |
|||
if (isset($this->headers[$key])) |
|||
$val=&$this->headers[$key]; |
|||
else |
|||
$val=NULL; |
|||
return $val; |
|||
} |
|||
|
|||
/** |
|||
* Remove header |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function clear($key) { |
|||
$key=$this->fixheader($key); |
|||
unset($this->headers[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Return client-server conversation history |
|||
* @return string |
|||
**/ |
|||
function log() { |
|||
return str_replace("\n",PHP_EOL,$this->log); |
|||
} |
|||
|
|||
/** |
|||
* Send SMTP command and record server response |
|||
* @return string |
|||
* @param $cmd string |
|||
* @param $log bool|string |
|||
* @param $mock bool |
|||
**/ |
|||
protected function dialog($cmd=NULL,$log=TRUE,$mock=FALSE) { |
|||
$reply=''; |
|||
if ($mock) { |
|||
$host=str_replace('ssl://','',$this->host); |
|||
switch ($cmd) { |
|||
case NULL: |
|||
$reply='220 '.$host.' ESMTP ready'."\n"; |
|||
break; |
|||
case 'DATA': |
|||
$reply='354 Go ahead'."\n"; |
|||
break; |
|||
case 'QUIT': |
|||
$reply='221 '.$host.' closing connection'."\n"; |
|||
break; |
|||
default: |
|||
$reply='250 OK'."\n"; |
|||
break; |
|||
} |
|||
} |
|||
else { |
|||
$socket=&$this->socket; |
|||
if ($cmd) |
|||
fputs($socket,$cmd."\r\n"); |
|||
while (!feof($socket) && ($info=stream_get_meta_data($socket)) && |
|||
!$info['timed_out'] && $str=fgets($socket,4096)) { |
|||
$reply.=$str; |
|||
if (preg_match('/(?:^|\n)\d{3} .+?\r\n/s',$reply)) |
|||
break; |
|||
} |
|||
} |
|||
if ($log) { |
|||
if ($cmd) |
|||
$this->log.=$cmd."\n"; |
|||
$this->log.=str_replace("\r",'',$reply); |
|||
} |
|||
return $reply; |
|||
} |
|||
|
|||
/** |
|||
* Add e-mail attachment |
|||
* @return NULL |
|||
* @param $file string |
|||
* @param $alias string |
|||
* @param $cid string |
|||
**/ |
|||
function attach($file,$alias=NULL,$cid=NULL) { |
|||
if (!is_file($file)) |
|||
user_error(sprintf(self::E_Attach,$file),E_USER_ERROR); |
|||
if ($alias) |
|||
$file=[$alias,$file]; |
|||
$this->attachments[]=['filename'=>$file,'cid'=>$cid]; |
|||
} |
|||
|
|||
/** |
|||
* Transmit message |
|||
* @return bool |
|||
* @param $message string |
|||
* @param $log bool|string |
|||
* @param $mock bool |
|||
**/ |
|||
function send($message,$log=TRUE,$mock=FALSE) { |
|||
if ($this->scheme=='ssl' && !extension_loaded('openssl')) |
|||
return FALSE; |
|||
// Message should not be blank |
|||
if (!$message) |
|||
user_error(self::E_Blank,E_USER_ERROR); |
|||
$fw=Base::instance(); |
|||
// Retrieve headers |
|||
$headers=$this->headers; |
|||
// Connect to the server |
|||
if (!$mock) { |
|||
$socket=&$this->socket; |
|||
$socket=@stream_socket_client($this->host.':'.$this->port, |
|||
$errno,$errstr,ini_get('default_socket_timeout'), |
|||
STREAM_CLIENT_CONNECT,$this->context); |
|||
if (!$socket) { |
|||
$fw->error(500,$errstr); |
|||
return FALSE; |
|||
} |
|||
stream_set_blocking($socket,TRUE); |
|||
} |
|||
// Get server's initial response |
|||
$this->dialog(NULL,$log,$mock); |
|||
// Announce presence |
|||
$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock); |
|||
if (strtolower($this->scheme)=='tls') { |
|||
$this->dialog('STARTTLS',$log,$mock); |
|||
if (!$mock) { |
|||
$method=STREAM_CRYPTO_METHOD_TLS_CLIENT; |
|||
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { |
|||
$method|=STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; |
|||
$method|=STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; |
|||
} |
|||
stream_socket_enable_crypto($socket,TRUE,$method); |
|||
} |
|||
$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock); |
|||
} |
|||
$message=wordwrap($message,998); |
|||
if (preg_match('/8BITMIME/',$reply)) |
|||
$headers['Content-Transfer-Encoding']='8bit'; |
|||
else { |
|||
$headers['Content-Transfer-Encoding']='quoted-printable'; |
|||
$message=preg_replace('/^\.(.+)/m', |
|||
'..$1',quoted_printable_encode($message)); |
|||
} |
|||
if ($this->user && $this->pw && preg_match('/AUTH/',$reply)) { |
|||
// Authenticate |
|||
$this->dialog('AUTH LOGIN',$log,$mock); |
|||
$this->dialog(base64_encode($this->user),$log,$mock); |
|||
$reply=$this->dialog(base64_encode($this->pw),$log,$mock); |
|||
if (!preg_match('/^235\s.*/',$reply)) { |
|||
$this->dialog('QUIT',$log,$mock); |
|||
if (!$mock && $socket) |
|||
fclose($socket); |
|||
return FALSE; |
|||
} |
|||
} |
|||
if (empty($headers['Message-Id'])) |
|||
$headers['Message-Id']='<'.uniqid('',TRUE).'@'.$this->host.'>'; |
|||
if (empty($headers['Date'])) |
|||
$headers['Date']=date('r'); |
|||
// Required headers |
|||
$reqd=['From','To','Subject']; |
|||
foreach ($reqd as $id) |
|||
if (empty($headers[$id])) |
|||
user_error(sprintf(self::E_Header,$id),E_USER_ERROR); |
|||
$eol="\r\n"; |
|||
// Stringify headers |
|||
foreach ($headers as $key=>&$val) { |
|||
if (in_array($key,['From','To','Cc','Bcc'])) { |
|||
$email=''; |
|||
preg_match_all('/(?:".+?" )?(?:<.+?>|[^ ,]+)/', |
|||
$val,$matches,PREG_SET_ORDER); |
|||
foreach ($matches as $raw) |
|||
$email.=($email?', ':''). |
|||
(preg_match('/<.+?>/',$raw[0])? |
|||
$raw[0]: |
|||
('<'.$raw[0].'>')); |
|||
$val=$email; |
|||
} |
|||
unset($val); |
|||
} |
|||
$from=isset($headers['Sender'])?$headers['Sender']:strstr($headers['From'],'<'); |
|||
unset($headers['Sender']); |
|||
// Start message dialog |
|||
$this->dialog('MAIL FROM: '.$from,$log,$mock); |
|||
foreach ($fw->split($headers['To']. |
|||
(isset($headers['Cc'])?(';'.$headers['Cc']):''). |
|||
(isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst) { |
|||
$this->dialog('RCPT TO: '.strstr($dst,'<'),$log,$mock); |
|||
} |
|||
$this->dialog('DATA',$log,$mock); |
|||
if ($this->attachments) { |
|||
// Replace Content-Type |
|||
$type=$headers['Content-Type']; |
|||
unset($headers['Content-Type']); |
|||
$enc=$headers['Content-Transfer-Encoding']; |
|||
unset($headers['Content-Transfer-Encoding']); |
|||
$hash=uniqid(NULL,TRUE); |
|||
// Send mail headers |
|||
$out='Content-Type: multipart/mixed; boundary="'.$hash.'"'.$eol; |
|||
foreach ($headers as $key=>$val) |
|||
if ($key!='Bcc') |
|||
$out.=$key.': '.$val.$eol; |
|||
$out.=$eol; |
|||
$out.='This is a multi-part message in MIME format'.$eol; |
|||
$out.=$eol; |
|||
$out.='--'.$hash.$eol; |
|||
$out.='Content-Type: '.$type.$eol; |
|||
$out.='Content-Transfer-Encoding: '.$enc.$eol; |
|||
$out.=$eol; |
|||
$out.=$message.$eol; |
|||
foreach ($this->attachments as $attachment) { |
|||
if (is_array($attachment['filename'])) |
|||
list($alias,$file)=$attachment['filename']; |
|||
else |
|||
$alias=basename($file=$attachment['filename']); |
|||
$out.='--'.$hash.$eol; |
|||
$out.='Content-Type: application/octet-stream'.$eol; |
|||
$out.='Content-Transfer-Encoding: base64'.$eol; |
|||
if ($attachment['cid']) |
|||
$out.='Content-Id: '.$attachment['cid'].$eol; |
|||
$out.='Content-Disposition: attachment; '. |
|||
'filename="'.$alias.'"'.$eol; |
|||
$out.=$eol; |
|||
$out.=chunk_split(base64_encode( |
|||
file_get_contents($file))).$eol; |
|||
} |
|||
$out.=$eol; |
|||
$out.='--'.$hash.'--'.$eol; |
|||
$out.='.'; |
|||
$this->dialog($out,preg_match('/verbose/i',$log),$mock); |
|||
} |
|||
else { |
|||
// Send mail headers |
|||
$out=''; |
|||
foreach ($headers as $key=>$val) |
|||
if ($key!='Bcc') |
|||
$out.=$key.': '.$val.$eol; |
|||
$out.=$eol; |
|||
$out.=$message.$eol; |
|||
$out.='.'; |
|||
// Send message |
|||
$this->dialog($out,preg_match('/verbose/i',$log),$mock); |
|||
} |
|||
$this->dialog('QUIT',$log,$mock); |
|||
if (!$mock && $socket) |
|||
fclose($socket); |
|||
return TRUE; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @param $host string |
|||
* @param $port int |
|||
* @param $scheme string |
|||
* @param $user string |
|||
* @param $pw string |
|||
* @param $ctx resource |
|||
**/ |
|||
function __construct( |
|||
$host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL,$ctx=NULL) { |
|||
$this->headers=[ |
|||
'MIME-Version'=>'1.0', |
|||
'Content-Type'=>'text/plain; '. |
|||
'charset='.Base::instance()->ENCODING |
|||
]; |
|||
$this->host=strtolower((($this->scheme=strtolower($scheme))=='ssl'? |
|||
'ssl':'tcp').'://'.$host); |
|||
$this->port=$port; |
|||
$this->user=$user; |
|||
$this->pw=$pw; |
|||
$this->context=stream_context_create($ctx); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,353 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! XML-style template engine |
|||
class Template extends Preview { |
|||
|
|||
//@{ Error messages |
|||
const |
|||
E_Method='Call to undefined method %s()'; |
|||
//@} |
|||
|
|||
protected |
|||
//! Template tags |
|||
$tags, |
|||
//! Custom tag handlers |
|||
$custom=[]; |
|||
|
|||
/** |
|||
* Template -set- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _set(array $node) { |
|||
$out=''; |
|||
foreach ($node['@attrib'] as $key=>$val) |
|||
$out.='$'.$key.'='. |
|||
(preg_match('/\{\{(.+?)\}\}/',$val)? |
|||
$this->token($val): |
|||
Base::instance()->stringify($val)).'; '; |
|||
return '<?php '.$out.'?>'; |
|||
} |
|||
|
|||
/** |
|||
* Template -include- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _include(array $node) { |
|||
$attrib=$node['@attrib']; |
|||
$hive=isset($attrib['with']) && |
|||
($attrib['with']=$this->token($attrib['with'])) && |
|||
preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/', |
|||
$attrib['with'],$pairs,PREG_SET_ORDER)? |
|||
('['.implode(',', |
|||
array_map(function($pair) { |
|||
return '\''.$pair[1].'\'=>'. |
|||
(preg_match('/^\'.*\'$/',$pair[2]) || |
|||
preg_match('/\$/',$pair[2])? |
|||
$pair[2]:Base::instance()->stringify( |
|||
Base::instance()->cast($pair[2]))); |
|||
},$pairs)).']+get_defined_vars()'): |
|||
'get_defined_vars()'; |
|||
$ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0; |
|||
return |
|||
'<?php '.(isset($attrib['if'])? |
|||
('if ('.$this->token($attrib['if']).') '):''). |
|||
('echo $this->render('. |
|||
(preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])? |
|||
$this->token($attrib['href']): |
|||
Base::instance()->stringify($attrib['href'])).','. |
|||
'NULL,'.$hive.','.$ttl.'); ?>'); |
|||
} |
|||
|
|||
/** |
|||
* Template -exclude- tag handler |
|||
* @return string |
|||
**/ |
|||
protected function _exclude() { |
|||
return ''; |
|||
} |
|||
|
|||
/** |
|||
* Template -ignore- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _ignore(array $node) { |
|||
return $node[0]; |
|||
} |
|||
|
|||
/** |
|||
* Template -loop- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _loop(array $node) { |
|||
$attrib=$node['@attrib']; |
|||
unset($node['@attrib']); |
|||
return |
|||
'<?php for ('. |
|||
$this->token($attrib['from']).';'. |
|||
$this->token($attrib['to']).';'. |
|||
$this->token($attrib['step']).'): ?>'. |
|||
$this->build($node). |
|||
'<?php endfor; ?>'; |
|||
} |
|||
|
|||
/** |
|||
* Template -repeat- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _repeat(array $node) { |
|||
$attrib=$node['@attrib']; |
|||
unset($node['@attrib']); |
|||
return |
|||
'<?php '. |
|||
(isset($attrib['counter'])? |
|||
(($ctr=$this->token($attrib['counter'])).'=0; '):''). |
|||
'foreach (('. |
|||
$this->token($attrib['group']).'?:[]) as '. |
|||
(isset($attrib['key'])? |
|||
($this->token($attrib['key']).'=>'):''). |
|||
$this->token($attrib['value']).'):'. |
|||
(isset($ctr)?(' '.$ctr.'++;'):'').' ?>'. |
|||
$this->build($node). |
|||
'<?php endforeach; ?>'; |
|||
} |
|||
|
|||
/** |
|||
* Template -check- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _check(array $node) { |
|||
$attrib=$node['@attrib']; |
|||
unset($node['@attrib']); |
|||
// Grab <true> and <false> blocks |
|||
foreach ($node as $pos=>$block) |
|||
if (isset($block['true'])) |
|||
$true=[$pos,$block]; |
|||
elseif (isset($block['false'])) |
|||
$false=[$pos,$block]; |
|||
if (isset($true,$false) && $true[0]>$false[0]) |
|||
// Reverse <true> and <false> blocks |
|||
list($node[$true[0]],$node[$false[0]])=[$false[1],$true[1]]; |
|||
return |
|||
'<?php if ('.$this->token($attrib['if']).'): ?>'. |
|||
$this->build($node). |
|||
'<?php endif; ?>'; |
|||
} |
|||
|
|||
/** |
|||
* Template -true- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _true(array $node) { |
|||
return $this->build($node); |
|||
} |
|||
|
|||
/** |
|||
* Template -false- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _false(array $node) { |
|||
return '<?php else: ?>'.$this->build($node); |
|||
} |
|||
|
|||
/** |
|||
* Template -switch- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _switch(array $node) { |
|||
$attrib=$node['@attrib']; |
|||
unset($node['@attrib']); |
|||
foreach ($node as $pos=>$block) |
|||
if (is_string($block) && !preg_replace('/\s+/','',$block)) |
|||
unset($node[$pos]); |
|||
return |
|||
'<?php switch ('.$this->token($attrib['expr']).'): ?>'. |
|||
$this->build($node). |
|||
'<?php endswitch; ?>'; |
|||
} |
|||
|
|||
/** |
|||
* Template -case- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _case(array $node) { |
|||
$attrib=$node['@attrib']; |
|||
unset($node['@attrib']); |
|||
return |
|||
'<?php case '.(preg_match('/\{\{(.+?)\}\}/',$attrib['value'])? |
|||
$this->token($attrib['value']): |
|||
Base::instance()->stringify($attrib['value'])).': ?>'. |
|||
$this->build($node). |
|||
'<?php '.(isset($attrib['break'])? |
|||
'if ('.$this->token($attrib['break']).') ':''). |
|||
'break; ?>'; |
|||
} |
|||
|
|||
/** |
|||
* Template -default- tag handler |
|||
* @return string |
|||
* @param $node array |
|||
**/ |
|||
protected function _default(array $node) { |
|||
return |
|||
'<?php default: ?>'. |
|||
$this->build($node). |
|||
'<?php break; ?>'; |
|||
} |
|||
|
|||
/** |
|||
* Assemble markup |
|||
* @return string |
|||
* @param $node array|string |
|||
**/ |
|||
function build($node) { |
|||
if (is_string($node)) |
|||
return parent::build($node); |
|||
$out=''; |
|||
foreach ($node as $key=>$val) |
|||
$out.=is_int($key)?$this->build($val):$this->{'_'.$key}($val); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Extend template with custom tag |
|||
* @return NULL |
|||
* @param $tag string |
|||
* @param $func callback |
|||
**/ |
|||
function extend($tag,$func) { |
|||
$this->tags.='|'.$tag; |
|||
$this->custom['_'.$tag]=$func; |
|||
} |
|||
|
|||
/** |
|||
* Call custom tag handler |
|||
* @return string|FALSE |
|||
* @param $func string |
|||
* @param $args array |
|||
**/ |
|||
function __call($func,array $args) { |
|||
if ($func[0]=='_') |
|||
return call_user_func_array($this->custom[$func],$args); |
|||
if (method_exists($this,$func)) |
|||
return call_user_func_array([$this,$func],$args); |
|||
user_error(sprintf(self::E_Method,$func),E_USER_ERROR); |
|||
} |
|||
|
|||
/** |
|||
* Parse string for template directives and tokens |
|||
* @return array |
|||
* @param $text string |
|||
**/ |
|||
function parse($text) { |
|||
$text=parent::parse($text); |
|||
// Build tree structure |
|||
for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;) |
|||
if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'. |
|||
'('.$this->tags.')\b((?:\s+[\w.:@!-]+'. |
|||
'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'. |
|||
'\h*\{\{.+?\}\})*)\h*(\/?)>/is', |
|||
substr($text,$ptr),$match)) { |
|||
if (strlen($tmp) || $match[1]) |
|||
$tree[]=$tmp.$match[1]; |
|||
// Element node |
|||
if ($match[2]) { |
|||
// Find matching start tag |
|||
$stack=[]; |
|||
for($i=count($tree)-1;$i>=0;$i--) { |
|||
$item=$tree[$i]; |
|||
if (is_array($item) && |
|||
array_key_exists($match[3],$item) && |
|||
!isset($item[$match[3]][0])) { |
|||
// Start tag found |
|||
$tree[$i][$match[3]]+=array_reverse($stack); |
|||
$tree=array_slice($tree,0,$i+1); |
|||
break; |
|||
} |
|||
else $stack[]=$item; |
|||
} |
|||
} |
|||
else { |
|||
// Start tag |
|||
$node=&$tree[][$match[3]]; |
|||
$node=[]; |
|||
if ($match[4]) { |
|||
// Process attributes |
|||
preg_match_all( |
|||
'/(?:(\{\{.+?\}\})|([^\s\/"\'=]+))'. |
|||
'\h*(?:=\h*(?:"(.*?)"|\'(.*?)\'))?/s', |
|||
$match[4],$attr,PREG_SET_ORDER); |
|||
foreach ($attr as $kv) |
|||
if (!empty($kv[1]) && !isset($kv[3]) && !isset($kv[4])) |
|||
$node['@attrib'][]=$kv[1]; |
|||
else |
|||
$node['@attrib'][$kv[1]?:$kv[2]]= |
|||
(isset($kv[3]) && $kv[3]!==''? |
|||
$kv[3]: |
|||
(isset($kv[4]) && $kv[4]!==''? |
|||
$kv[4]:NULL)); |
|||
} |
|||
} |
|||
$tmp=''; |
|||
$ptr+=strlen($match[0]); |
|||
$w=5; |
|||
} |
|||
else { |
|||
// Text node |
|||
$tmp.=substr($text,$ptr,$w); |
|||
$ptr+=$w; |
|||
if ($w<50) |
|||
$w++; |
|||
} |
|||
if (strlen($tmp)) |
|||
// Append trailing text |
|||
$tree[]=$tmp; |
|||
// Break references |
|||
unset($node); |
|||
return $tree; |
|||
} |
|||
|
|||
/** |
|||
* Class constructor |
|||
* return object |
|||
**/ |
|||
function __construct() { |
|||
$ref=new ReflectionClass(get_called_class()); |
|||
$this->tags=''; |
|||
foreach ($ref->getmethods() as $method) |
|||
if (preg_match('/^_(?=[[:alpha:]])/',$method->name)) |
|||
$this->tags.=(strlen($this->tags)?'|':''). |
|||
substr($method->name,1); |
|||
parent::__construct(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,98 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Unit test kit |
|||
class Test { |
|||
|
|||
//@{ Reporting level |
|||
const |
|||
FLAG_False=0, |
|||
FLAG_True=1, |
|||
FLAG_Both=2; |
|||
//@} |
|||
|
|||
protected |
|||
//! Test results |
|||
$data=[], |
|||
//! Success indicator |
|||
$passed=TRUE, |
|||
//! Reporting level |
|||
$level; |
|||
|
|||
/** |
|||
* Return test results |
|||
* @return array |
|||
**/ |
|||
function results() { |
|||
return $this->data; |
|||
} |
|||
|
|||
/** |
|||
* Return FALSE if at least one test case fails |
|||
* @return bool |
|||
**/ |
|||
function passed() { |
|||
return $this->passed; |
|||
} |
|||
|
|||
/** |
|||
* Evaluate condition and save test result |
|||
* @return object |
|||
* @param $cond bool |
|||
* @param $text string |
|||
**/ |
|||
function expect($cond,$text=NULL) { |
|||
$out=(bool)$cond; |
|||
if ($this->level==$out || $this->level==self::FLAG_Both) { |
|||
$data=['status'=>$out,'text'=>$text,'source'=>NULL]; |
|||
foreach (debug_backtrace() as $frame) |
|||
if (isset($frame['file'])) { |
|||
$data['source']=Base::instance()-> |
|||
fixslashes($frame['file']).':'.$frame['line']; |
|||
break; |
|||
} |
|||
$this->data[]=$data; |
|||
} |
|||
if (!$out && $this->passed) |
|||
$this->passed=FALSE; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Append message to test results |
|||
* @return NULL |
|||
* @param $text string |
|||
**/ |
|||
function message($text) { |
|||
$this->expect(TRUE,$text); |
|||
} |
|||
|
|||
/** |
|||
* Class constructor |
|||
* @return NULL |
|||
* @param $level int |
|||
**/ |
|||
function __construct($level=self::FLAG_Both) { |
|||
$this->level=$level; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,199 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
//! Unicode string manager |
|||
class UTF extends Prefab { |
|||
|
|||
/** |
|||
* Get string length |
|||
* @return int |
|||
* @param $str string |
|||
**/ |
|||
function strlen($str) { |
|||
preg_match_all('/./us',$str,$parts); |
|||
return count($parts[0]); |
|||
} |
|||
|
|||
/** |
|||
* Reverse a string |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function strrev($str) { |
|||
preg_match_all('/./us',$str,$parts); |
|||
return implode('',array_reverse($parts[0])); |
|||
} |
|||
|
|||
/** |
|||
* Find position of first occurrence of a string (case-insensitive) |
|||
* @return int|FALSE |
|||
* @param $stack string |
|||
* @param $needle string |
|||
* @param $ofs int |
|||
**/ |
|||
function stripos($stack,$needle,$ofs=0) { |
|||
return $this->strpos($stack,$needle,$ofs,TRUE); |
|||
} |
|||
|
|||
/** |
|||
* Find position of first occurrence of a string |
|||
* @return int|FALSE |
|||
* @param $stack string |
|||
* @param $needle string |
|||
* @param $ofs int |
|||
* @param $case bool |
|||
**/ |
|||
function strpos($stack,$needle,$ofs=0,$case=FALSE) { |
|||
return preg_match('/^(.{'.$ofs.'}.*?)'. |
|||
preg_quote($needle,'/').'/us'.($case?'i':''),$stack,$match)? |
|||
$this->strlen($match[1]):FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Returns part of haystack string from the first occurrence of |
|||
* needle to the end of haystack (case-insensitive) |
|||
* @return string|FALSE |
|||
* @param $stack string |
|||
* @param $needle string |
|||
* @param $before bool |
|||
**/ |
|||
function stristr($stack,$needle,$before=FALSE) { |
|||
return $this->strstr($stack,$needle,$before,TRUE); |
|||
} |
|||
|
|||
/** |
|||
* Returns part of haystack string from the first occurrence of |
|||
* needle to the end of haystack |
|||
* @return string|FALSE |
|||
* @param $stack string |
|||
* @param $needle string |
|||
* @param $before bool |
|||
* @param $case bool |
|||
**/ |
|||
function strstr($stack,$needle,$before=FALSE,$case=FALSE) { |
|||
if (!$needle) |
|||
return FALSE; |
|||
preg_match('/^(.*?)'.preg_quote($needle,'/').'/us'.($case?'i':''), |
|||
$stack,$match); |
|||
return isset($match[1])? |
|||
($before? |
|||
$match[1]: |
|||
$this->substr($stack,$this->strlen($match[1]))): |
|||
FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return part of a string |
|||
* @return string|FALSE |
|||
* @param $str string |
|||
* @param $start int |
|||
* @param $len int |
|||
**/ |
|||
function substr($str,$start,$len=0) { |
|||
if ($start<0) |
|||
$start=$this->strlen($str)+$start; |
|||
if (!$len) |
|||
$len=$this->strlen($str)-$start; |
|||
return preg_match('/^.{'.$start.'}(.{0,'.$len.'})/us',$str,$match)? |
|||
$match[1]:FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Count the number of substring occurrences |
|||
* @return int |
|||
* @param $stack string |
|||
* @param $needle string |
|||
**/ |
|||
function substr_count($stack,$needle) { |
|||
preg_match_all('/'.preg_quote($needle,'/').'/us',$stack, |
|||
$matches,PREG_SET_ORDER); |
|||
return count($matches); |
|||
} |
|||
|
|||
/** |
|||
* Strip whitespaces from the beginning of a string |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function ltrim($str) { |
|||
return preg_replace('/^[\pZ\pC]+/u','',$str); |
|||
} |
|||
|
|||
/** |
|||
* Strip whitespaces from the end of a string |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function rtrim($str) { |
|||
return preg_replace('/[\pZ\pC]+$/u','',$str); |
|||
} |
|||
|
|||
/** |
|||
* Strip whitespaces from the beginning and end of a string |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function trim($str) { |
|||
return preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u','',$str); |
|||
} |
|||
|
|||
/** |
|||
* Return UTF-8 byte order mark |
|||
* @return string |
|||
**/ |
|||
function bom() { |
|||
return chr(0xef).chr(0xbb).chr(0xbf); |
|||
} |
|||
|
|||
/** |
|||
* Convert code points to Unicode symbols |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function translate($str) { |
|||
return html_entity_decode( |
|||
preg_replace('/\\\\u([[:xdigit:]]+)/i','&#x\1;',$str)); |
|||
} |
|||
|
|||
/** |
|||
* Translate emoji tokens to Unicode font-supported symbols |
|||
* @return string |
|||
* @param $str string |
|||
**/ |
|||
function emojify($str) { |
|||
$map=[ |
|||
':('=>'\u2639', // frown |
|||
':)'=>'\u263a', // smile |
|||
'<3'=>'\u2665', // heart |
|||
':D'=>'\u1f603', // grin |
|||
'XD'=>'\u1f606', // laugh |
|||
';)'=>'\u1f609', // wink |
|||
':P'=>'\u1f60b', // tongue |
|||
':,'=>'\u1f60f', // think |
|||
':/'=>'\u1f623', // skeptic |
|||
'8O'=>'\u1f632', // oops |
|||
]+Base::instance()->EMOJI; |
|||
return $this->translate(str_replace(array_keys($map), |
|||
array_values($map),$str)); |
|||
} |
|||
|
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,111 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace Web; |
|||
|
|||
//! Geo plug-in |
|||
class Geo extends \Prefab { |
|||
|
|||
/** |
|||
* Return information about specified Unix time zone |
|||
* @return array |
|||
* @param $zone string |
|||
**/ |
|||
function tzinfo($zone) { |
|||
$ref=new \DateTimeZone($zone); |
|||
$loc=$ref->getLocation(); |
|||
$trn=$ref->getTransitions($now=time(),$now); |
|||
$out=[ |
|||
'offset'=>$ref-> |
|||
getOffset(new \DateTime('now',new \DateTimeZone('UTC')))/3600, |
|||
'country'=>$loc['country_code'], |
|||
'latitude'=>$loc['latitude'], |
|||
'longitude'=>$loc['longitude'], |
|||
'dst'=>$trn[0]['isdst'] |
|||
]; |
|||
unset($ref); |
|||
return $out; |
|||
} |
|||
|
|||
/** |
|||
* Return geolocation data based on specified/auto-detected IP address |
|||
* @return array|FALSE |
|||
* @param $ip string |
|||
**/ |
|||
function location($ip=NULL) { |
|||
$fw=\Base::instance(); |
|||
$web=\Web::instance(); |
|||
if (!$ip) |
|||
$ip=$fw->IP; |
|||
$public=filter_var($ip,FILTER_VALIDATE_IP, |
|||
FILTER_FLAG_IPV4|FILTER_FLAG_IPV6| |
|||
FILTER_FLAG_NO_RES_RANGE|FILTER_FLAG_NO_PRIV_RANGE); |
|||
if (function_exists('geoip_db_avail') && |
|||
geoip_db_avail(GEOIP_CITY_EDITION_REV1) && |
|||
$out=@geoip_record_by_name($ip)) { |
|||
$out['request']=$ip; |
|||
$out['region_code']=$out['region']; |
|||
$out['region_name']=''; |
|||
if (!empty($out['country_code']) && !empty($out['region'])) |
|||
$out['region_name']=geoip_region_name_by_code( |
|||
$out['country_code'],$out['region'] |
|||
); |
|||
unset($out['country_code3'],$out['region'],$out['postal_code']); |
|||
return $out; |
|||
} |
|||
if (($req=$web->request('http://www.geoplugin.net/json.gp'. |
|||
($public?('?ip='.$ip):''))) && |
|||
$data=json_decode($req['body'],TRUE)) { |
|||
$out=[]; |
|||
foreach ($data as $key=>$val) |
|||
if (!strpos($key,'currency') && $key!=='geoplugin_status' |
|||
&& $key!=='geoplugin_region') |
|||
$out[$fw->snakecase(substr($key, 10))]=$val; |
|||
return $out; |
|||
} |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return weather data based on specified latitude/longitude |
|||
* @return array|FALSE |
|||
* @param $latitude float |
|||
* @param $longitude float |
|||
* @param $key string |
|||
**/ |
|||
function weather($latitude,$longitude,$key) { |
|||
$fw=\Base::instance(); |
|||
$web=\Web::instance(); |
|||
$query=[ |
|||
'lat'=>$latitude, |
|||
'lon'=>$longitude, |
|||
'APPID'=>$key, |
|||
'units'=>'metric' |
|||
]; |
|||
return ($req=$web->request( |
|||
'http://api.openweathermap.org/data/2.5/weather?'. |
|||
http_build_query($query)))? |
|||
json_decode($req['body'],TRUE): |
|||
FALSE; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,58 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace Web\Google; |
|||
|
|||
//! Google ReCAPTCHA v2 plug-in |
|||
class Recaptcha { |
|||
|
|||
const |
|||
//! API URL |
|||
URL_Recaptcha='https://www.google.com/recaptcha/api/siteverify'; |
|||
|
|||
/** |
|||
* Verify reCAPTCHA response |
|||
* @param string $secret |
|||
* @param string $response |
|||
* @return bool |
|||
**/ |
|||
static function verify($secret,$response=NULL) { |
|||
$fw=\Base::instance(); |
|||
if (!isset($response)) |
|||
$response=$fw->{'POST.g-recaptcha-response'}; |
|||
if (!$response) |
|||
return FALSE; |
|||
$web=\Web::instance(); |
|||
$out=$web->request(self::URL_Recaptcha,[ |
|||
'method'=>'POST', |
|||
'content'=>http_build_query([ |
|||
'secret'=>$secret, |
|||
'response'=>$response, |
|||
'remoteip'=>$fw->IP |
|||
]), |
|||
]); |
|||
return isset($out['body']) && |
|||
($json=json_decode($out['body'],TRUE)) && |
|||
isset($json['success']) && $json['success']; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,65 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace Web\Google; |
|||
|
|||
//! Google Static Maps API v2 plug-in |
|||
class StaticMap { |
|||
|
|||
const |
|||
//! API URL |
|||
URL_Static='http://maps.googleapis.com/maps/api/staticmap'; |
|||
|
|||
protected |
|||
//! Query arguments |
|||
$query=array(); |
|||
|
|||
/** |
|||
* Specify API key-value pair via magic call |
|||
* @return object |
|||
* @param $func string |
|||
* @param $args array |
|||
**/ |
|||
function __call($func,array $args) { |
|||
$this->query[]=array($func,$args[0]); |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Generate map |
|||
* @return string |
|||
**/ |
|||
function dump() { |
|||
$fw=\Base::instance(); |
|||
$web=\Web::instance(); |
|||
$out=''; |
|||
return ($req=$web->request( |
|||
self::URL_Static.'?'.array_reduce( |
|||
$this->query, |
|||
function($out,$item) { |
|||
return ($out.=($out?'&':''). |
|||
urlencode($item[0]).'='.urlencode($item[1])); |
|||
} |
|||
))) && $req['body']?$req['body']:FALSE; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,163 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace Web; |
|||
|
|||
//! Lightweight OAuth2 client |
|||
class OAuth2 extends \Magic { |
|||
|
|||
protected |
|||
//! Scopes and claims |
|||
$args=[], |
|||
//! Encoding |
|||
$enc_type = PHP_QUERY_RFC1738; |
|||
|
|||
/** |
|||
* Return OAuth2 authentication URI |
|||
* @return string |
|||
* @param $endpoint string |
|||
* @param $query bool |
|||
**/ |
|||
function uri($endpoint,$query=TRUE) { |
|||
return $endpoint.($query?('?'. |
|||
http_build_query($this->args,null,'&',$this->enc_type)):''); |
|||
} |
|||
|
|||
/** |
|||
* Send request to API/token endpoint |
|||
* @return string|FALSE |
|||
* @param $uri string |
|||
* @param $method string |
|||
* @param $token array |
|||
**/ |
|||
function request($uri,$method,$token=NULL) { |
|||
$web=\Web::instance(); |
|||
$options=[ |
|||
'method'=>$method, |
|||
'content'=>http_build_query($this->args,null,'&',$this->enc_type), |
|||
'header'=>['Accept: application/json'] |
|||
]; |
|||
if ($token) |
|||
array_push($options['header'],'Authorization: Bearer '.$token); |
|||
elseif ($method=='POST' && isset($this->args['client_id'])) |
|||
array_push($options['header'],'Authorization: Basic '. |
|||
base64_encode( |
|||
$this->args['client_id'].':'. |
|||
$this->args['client_secret'] |
|||
) |
|||
); |
|||
$response=$web->request($uri,$options); |
|||
if ($response['error']) |
|||
user_error($response['error'],E_USER_ERROR); |
|||
if (isset($response['body'])) { |
|||
if (preg_grep('/^Content-Type:.*application\/json/i', |
|||
$response['headers'])) { |
|||
$token=json_decode($response['body'],TRUE); |
|||
if (isset($token['error_description'])) |
|||
user_error($token['error_description'],E_USER_ERROR); |
|||
if (isset($token['error'])) |
|||
user_error($token['error'],E_USER_ERROR); |
|||
return $token; |
|||
} |
|||
else |
|||
return $response['body']; |
|||
} |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Parse JSON Web token |
|||
* @return array |
|||
* @param $token string |
|||
**/ |
|||
function jwt($token) { |
|||
return json_decode( |
|||
base64_decode( |
|||
str_replace(['-','_'],['+','/'],explode('.',$token)[1]) |
|||
), |
|||
TRUE |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* change default url encoding type, i.E. PHP_QUERY_RFC3986 |
|||
* @param $type |
|||
*/ |
|||
function setEncoding($type) { |
|||
$this->enc_type = $type; |
|||
} |
|||
|
|||
/** |
|||
* URL-safe base64 encoding |
|||
* @return array |
|||
* @param $data string |
|||
**/ |
|||
function b64url($data) { |
|||
return trim(strtr(base64_encode($data),'+/','-_'),'='); |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if scope/claim exists |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
function exists($key) { |
|||
return isset($this->args[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Bind value to scope/claim |
|||
* @return string |
|||
* @param $key string |
|||
* @param $val string |
|||
**/ |
|||
function set($key,$val) { |
|||
return $this->args[$key]=$val; |
|||
} |
|||
|
|||
/** |
|||
* Return value of scope/claim |
|||
* @return mixed |
|||
* @param $key string |
|||
**/ |
|||
function &get($key) { |
|||
if (isset($this->args[$key])) |
|||
$val=&$this->args[$key]; |
|||
else |
|||
$val=NULL; |
|||
return $val; |
|||
} |
|||
|
|||
/** |
|||
* Remove scope/claim |
|||
* @return NULL |
|||
* @param $key string |
|||
**/ |
|||
function clear($key=NULL) { |
|||
if ($key) |
|||
unset($this->args[$key]); |
|||
else |
|||
$this->args=[]; |
|||
} |
|||
|
|||
} |
|||
|
@ -0,0 +1,248 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace Web; |
|||
|
|||
//! OpenID consumer |
|||
class OpenID extends \Magic { |
|||
|
|||
protected |
|||
//! OpenID provider endpoint URL |
|||
$url, |
|||
//! HTTP request parameters |
|||
$args=[]; |
|||
|
|||
/** |
|||
* Determine OpenID provider |
|||
* @return string|FALSE |
|||
* @param $proxy string |
|||
**/ |
|||
protected function discover($proxy) { |
|||
// Normalize |
|||
if (!preg_match('/https?:\/\//i',$this->args['endpoint'])) |
|||
$this->args['endpoint']='http://'.$this->args['endpoint']; |
|||
$url=parse_url($this->args['endpoint']); |
|||
// Remove fragment; reconnect parts |
|||
$this->args['endpoint']=$url['scheme'].'://'. |
|||
(isset($url['user'])? |
|||
($url['user']. |
|||
(isset($url['pass'])?(':'.$url['pass']):'').'@'):''). |
|||
strtolower($url['host']).(isset($url['path'])?$url['path']:'/'). |
|||
(isset($url['query'])?('?'.$url['query']):''); |
|||
// HTML-based discovery of OpenID provider |
|||
$req=\Web::instance()-> |
|||
request($this->args['endpoint'],['proxy'=>$proxy]); |
|||
if (!$req) |
|||
return FALSE; |
|||
$type=array_values(preg_grep('/Content-Type:/',$req['headers'])); |
|||
if ($type && |
|||
preg_match('/application\/xrds\+xml|text\/xml/',$type[0]) && |
|||
($sxml=simplexml_load_string($req['body'])) && |
|||
($xrds=json_decode(json_encode($sxml),TRUE)) && |
|||
isset($xrds['XRD'])) { |
|||
// XRDS document |
|||
$svc=$xrds['XRD']['Service']; |
|||
if (isset($svc[0])) |
|||
$svc=$svc[0]; |
|||
$svc_type=is_array($svc['Type'])?$svc['Type']:array($svc['Type']); |
|||
if (preg_grep('/http:\/\/specs\.openid\.net\/auth\/2.0\/'. |
|||
'(?:server|signon)/',$svc_type)) { |
|||
$this->args['provider']=$svc['URI']; |
|||
if (isset($svc['LocalID'])) |
|||
$this->args['localidentity']=$svc['LocalID']; |
|||
elseif (isset($svc['CanonicalID'])) |
|||
$this->args['localidentity']=$svc['CanonicalID']; |
|||
} |
|||
$this->args['server']=$svc['URI']; |
|||
if (isset($svc['Delegate'])) |
|||
$this->args['delegate']=$svc['Delegate']; |
|||
} |
|||
else { |
|||
$len=strlen($req['body']); |
|||
$ptr=0; |
|||
// Parse document |
|||
while ($ptr<$len) |
|||
if (preg_match( |
|||
'/^<link\b((?:\h+\w+\h*=\h*'. |
|||
'(?:"(?:.+?)"|\'(?:.+?)\'))*)\h*\/?>/is', |
|||
substr($req['body'],$ptr),$parts)) { |
|||
if ($parts[1] && |
|||
// Process attributes |
|||
preg_match_all('/\b(rel|href)\h*=\h*'. |
|||
'(?:"(.+?)"|\'(.+?)\')/s',$parts[1],$attr, |
|||
PREG_SET_ORDER)) { |
|||
$node=[]; |
|||
foreach ($attr as $kv) |
|||
$node[$kv[1]]=isset($kv[2])?$kv[2]:$kv[3]; |
|||
if (isset($node['rel']) && |
|||
preg_match('/openid2?\.(\w+)/', |
|||
$node['rel'],$var) && |
|||
isset($node['href'])) |
|||
$this->args[$var[1]]=$node['href']; |
|||
|
|||
} |
|||
$ptr+=strlen($parts[0]); |
|||
} |
|||
else |
|||
$ptr++; |
|||
} |
|||
// Get OpenID provider's endpoint URL |
|||
if (isset($this->args['provider'])) { |
|||
// OpenID 2.0 |
|||
$this->args['ns']='http://specs.openid.net/auth/2.0'; |
|||
if (isset($this->args['localidentity'])) |
|||
$this->args['identity']=$this->args['localidentity']; |
|||
if (isset($this->args['trust_root'])) |
|||
$this->args['realm']=$this->args['trust_root']; |
|||
} |
|||
elseif (isset($this->args['server'])) { |
|||
// OpenID 1.1 |
|||
$this->args['ns']='http://openid.net/signon/1.1'; |
|||
if (isset($this->args['delegate'])) |
|||
$this->args['identity']=$this->args['delegate']; |
|||
} |
|||
if (isset($this->args['provider'])) { |
|||
// OpenID 2.0 |
|||
if (empty($this->args['claimed_id'])) |
|||
$this->args['claimed_id']=$this->args['identity']; |
|||
return $this->args['provider']; |
|||
} |
|||
elseif (isset($this->args['server'])) |
|||
// OpenID 1.1 |
|||
return $this->args['server']; |
|||
else |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Initiate OpenID authentication sequence; Return FALSE on failure |
|||
* or redirect to OpenID provider URL |
|||
* @return bool |
|||
* @param $proxy string |
|||
* @param $attr array |
|||
* @param $reqd string|array |
|||
**/ |
|||
function auth($proxy=NULL,$attr=[],array $reqd=NULL) { |
|||
$fw=\Base::instance(); |
|||
$root=$fw->SCHEME.'://'.$fw->HOST; |
|||
if (empty($this->args['trust_root'])) |
|||
$this->args['trust_root']=$root.$fw->BASE.'/'; |
|||
if (empty($this->args['return_to'])) |
|||
$this->args['return_to']=$root.$_SERVER['REQUEST_URI']; |
|||
$this->args['mode']='checkid_setup'; |
|||
if ($this->url=$this->discover($proxy)) { |
|||
if ($attr) { |
|||
$this->args['ns.ax']='http://openid.net/srv/ax/1.0'; |
|||
$this->args['ax.mode']='fetch_request'; |
|||
foreach ($attr as $key=>$val) |
|||
$this->args['ax.type.'.$key]=$val; |
|||
$this->args['ax.required']=is_string($reqd)? |
|||
$reqd:implode(',',$reqd); |
|||
} |
|||
$var=[]; |
|||
foreach ($this->args as $key=>$val) |
|||
$var['openid.'.$key]=$val; |
|||
$fw->reroute($this->url.'?'.http_build_query($var)); |
|||
} |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if OpenID verification was successful |
|||
* @return bool |
|||
* @param $proxy string |
|||
**/ |
|||
function verified($proxy=NULL) { |
|||
preg_match_all('/(?<=^|&)openid\.([^=]+)=([^&]+)/', |
|||
$_SERVER['QUERY_STRING'],$matches,PREG_SET_ORDER); |
|||
foreach ($matches as $match) |
|||
$this->args[$match[1]]=urldecode($match[2]); |
|||
if (isset($this->args['mode']) && |
|||
$this->args['mode']!='error' && |
|||
$this->url=$this->discover($proxy)) { |
|||
$this->args['mode']='check_authentication'; |
|||
$var=[]; |
|||
foreach ($this->args as $key=>$val) |
|||
$var['openid.'.$key]=$val; |
|||
$req=\Web::instance()->request( |
|||
$this->url, |
|||
[ |
|||
'method'=>'POST', |
|||
'content'=>http_build_query($var), |
|||
'proxy'=>$proxy |
|||
] |
|||
); |
|||
return (bool)preg_match('/is_valid:true/i',$req['body']); |
|||
} |
|||
return FALSE; |
|||
} |
|||
|
|||
/** |
|||
* Return OpenID response fields |
|||
* @return array |
|||
**/ |
|||
function response() { |
|||
return $this->args; |
|||
} |
|||
|
|||
/** |
|||
* Return TRUE if OpenID request parameter exists |
|||
* @return bool |
|||
* @param $key string |
|||
**/ |
|||
function exists($key) { |
|||
return isset($this->args[$key]); |
|||
} |
|||
|
|||
/** |
|||
* Bind value to OpenID request parameter |
|||
* @return string |
|||
* @param $key string |
|||
* @param $val string |
|||
**/ |
|||
function set($key,$val) { |
|||
return $this->args[$key]=$val; |
|||
} |
|||
|
|||
/** |
|||
* Return value of OpenID request parameter |
|||
* @return mixed |
|||
* @param $key string |
|||
**/ |
|||
function &get($key) { |
|||
if (isset($this->args[$key])) |
|||
$val=&$this->args[$key]; |
|||
else |
|||
$val=NULL; |
|||
return $val; |
|||
} |
|||
|
|||
/** |
|||
* Remove OpenID request parameter |
|||
* @return NULL |
|||
* @param $key |
|||
**/ |
|||
function clear($key) { |
|||
unset($this->args[$key]); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,176 @@ |
|||
<?php |
|||
|
|||
/* |
|||
|
|||
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved. |
|||
|
|||
This file is part of the Fat-Free Framework (http://fatfreeframework.com). |
|||
|
|||
This is free software: you can redistribute it and/or modify it under the |
|||
terms of the GNU General Public License as published by the Free Software |
|||
Foundation, either version 3 of the License, or later. |
|||
|
|||
Fat-Free Framework is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License along |
|||
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
*/ |
|||
|
|||
namespace Web; |
|||
|
|||
//! Pingback 1.0 protocol (client and server) implementation |
|||
class Pingback extends \Prefab { |
|||
|
|||
protected |
|||
//! Transaction history |
|||
$log; |
|||
|
|||
/** |
|||
* Return TRUE if URL points to a pingback-enabled resource |
|||
* @return bool |
|||
* @param $url |
|||
**/ |
|||
protected function enabled($url) { |
|||
$web=\Web::instance(); |
|||
$req=$web->request($url); |
|||
$found=FALSE; |
|||
if ($req['body']) { |
|||
// Look for pingback header |
|||
foreach ($req['headers'] as $header) |
|||
if (preg_match('/^X-Pingback:\h*(.+)/',$header,$href)) { |
|||
$found=$href[1]; |
|||
break; |
|||
} |
|||
if (!$found && |
|||
// Scan page for pingback link tag |
|||
preg_match('/<link\h+(.+?)\h*\/?>/i',$req['body'],$parts) && |
|||
preg_match('/rel\h*=\h*"pingback"/i',$parts[1]) && |
|||
preg_match('/href\h*=\h*"\h*(.+?)\h*"/i',$parts[1],$href)) |
|||
$found=$href[1]; |
|||
} |
|||
return $found; |
|||
} |
|||
|
|||
/** |
|||
* Load local page contents, parse HTML anchor tags, find permalinks, |
|||
* and send XML-RPC calls to corresponding pingback servers |
|||
* @return NULL |
|||
* @param $source string |
|||
**/ |
|||
function inspect($source) { |
|||
$fw=\Base::instance(); |
|||
$web=\Web::instance(); |
|||
$parts=parse_url($source); |
|||
if (empty($parts['scheme']) || empty($parts['host']) || |
|||
$parts['host']==$fw->HOST) { |
|||
$req=$web->request($source); |
|||
$doc=new \DOMDocument('1.0',$fw->ENCODING); |
|||
$doc->stricterrorchecking=FALSE; |
|||
$doc->recover=TRUE; |
|||
if (@$doc->loadhtml($req['body'])) { |
|||
// Parse anchor tags |
|||
$links=$doc->getelementsbytagname('a'); |
|||
foreach ($links as $link) { |
|||
$permalink=$link->getattribute('href'); |
|||
// Find pingback-enabled resources |
|||
if ($permalink && $found=$this->enabled($permalink)) { |
|||
$req=$web->request($found, |
|||
[ |
|||
'method'=>'POST', |
|||
'header'=>'Content-Type: application/xml', |
|||
'content'=>xmlrpc_encode_request( |
|||
'pingback.ping', |
|||
[$source,$permalink], |
|||
['encoding'=>$fw->ENCODING] |
|||
) |
|||
] |
|||
); |
|||
if ($req['body']) |
|||
$this->log.=date('r').' '. |
|||
$permalink.' [permalink:'.$found.']'.PHP_EOL. |
|||
$req['body'].PHP_EOL; |
|||
} |
|||
} |
|||
} |
|||
unset($doc); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Receive ping, check if local page is pingback-enabled, verify |
|||
* source contents, and return XML-RPC response |
|||
* @return string |
|||
* @param $func callback |
|||
* @param $path string |
|||
**/ |
|||
function listen($func,$path=NULL) { |
|||
$fw=\Base::instance(); |
|||
if (PHP_SAPI!='cli') { |
|||
header('X-Powered-By: '.$fw->PACKAGE); |
|||
header('Content-Type: application/xml; '. |
|||
'charset='.$charset=$fw->ENCODING); |
|||
} |
|||
if (!$path) |
|||
$path=$fw->BASE; |
|||
$web=\Web::instance(); |
|||
$args=xmlrpc_decode_request($fw->BODY,$method,$charset); |
|||
$options=['encoding'=>$charset]; |
|||
if ($method=='pingback.ping' && isset($args[0],$args[1])) { |
|||
list($source,$permalink)=$args; |
|||
$doc=new \DOMDocument('1.0',$fw->ENCODING); |
|||
// Check local page if pingback-enabled |
|||
$parts=parse_url($permalink); |
|||
if ((empty($parts['scheme']) || |
|||
$parts['host']==$fw->HOST) && |
|||
preg_match('/^'.preg_quote($path,'/').'/'. |
|||
($fw->CASELESS?'i':''),$parts['path']) && |
|||
$this->enabled($permalink)) { |
|||
// Check source |
|||
$parts=parse_url($source); |
|||
if ((empty($parts['scheme']) || |
|||
$parts['host']==$fw->HOST) && |
|||
($req=$web->request($source)) && |
|||
$doc->loadhtml($req['body'])) { |
|||
$links=$doc->getelementsbytagname('a'); |
|||
foreach ($links as $link) { |
|||
if ($link->getattribute('href')==$permalink) { |
|||
call_user_func_array($func,[$source,$req['body']]); |
|||
// Success |
|||
die(xmlrpc_encode_request(NULL,$source,$options)); |
|||
} |
|||
} |
|||
// No link to local page |
|||
die(xmlrpc_encode_request(NULL,0x11,$options)); |
|||
} |
|||
// Source failure |
|||
die(xmlrpc_encode_request(NULL,0x10,$options)); |
|||
} |
|||
// Doesn't exist (or not pingback-enabled) |
|||
die(xmlrpc_encode_request(NULL,0x21,$options)); |
|||
} |
|||
// Access denied |
|||
die(xmlrpc_encode_request(NULL,0x31,$options)); |
|||
} |
|||
|
|||
/** |
|||
* Return transaction history |
|||
* @return string |
|||
**/ |
|||
function log() { |
|||
return $this->log; |
|||
} |
|||
|
|||
/** |
|||
* Instantiate class |
|||
* @return object |
|||
**/ |
|||
function __construct() { |
|||
// Suppress errors caused by invalid HTML structures |
|||
libxml_use_internal_errors(TRUE); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"name": "Glowing Bulbs Power Manager Interface", |
|||
"short_name": "IZZ Control", |
|||
"lang": "hu", |
|||
"start_url": "/192.168.0.200", |
|||
"display": "standalone", |
|||
"theme_color": "#979ea5" |
|||
} |
@ -0,0 +1,2 @@ |
|||
#IzControl# |
|||
IzControl is an application that makes is possible to easily control appliances over the network using UDP, telnet and other protocols. |
@ -0,0 +1,6 @@ |
|||
/* Reset */ |
|||
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,dir,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}ol{list-style:decimal}ul{list-style:disc}ul ul{list-style:circle}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}ins{text-decoration:underline}mark{background:none}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}input,select,a img{vertical-align:middle} |
|||
/* Typography */ |
|||
*{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;max-width:100%}html{height:100%;font-size:100%;font-family:Verdana, sans-serif;overflow-y:scroll;-webkit-text-size-adjust:100%}body{margin:0;min-height:100%;overflow:hidden}body,pre,label,input,button,select,textarea{font:normal 100%/1.25 Verdana, sans-serif;vertical-align:top}a{display:inline-block}p,ul,ol{margin:1.25em 0}h1{font-size:2em;line-height:1.25em;margin:0.625em 0}h2{font-size:1.5em;line-height:1.6667em;margin:0.8333em 0}h3{font-size:1.25em;line-height:1em;margin:1em 0}h4{font-size:1em;line-height:1.25em;margin:1.25em 0}h5{font-size:0.8125em;line-height:1.5385em;margin:1.5385em 0}h6{font-size:0.6875em;line-height:1.8182em;margin:1.8182em 0}blockquote{margin:0 3em}caption{font-weight:bold}ul,ol,dir,menu,dd{margin-left:3em}ul,dir,menu{list-style:disc}ol{list-style:decimal}sub,sup{font-size:75%;line-height:0;vertical-align:baseline;position:relative}sub{top:0.5em}sup{top:-0.5em}label{display:inline-block}input[type="text"],input[type="password"],input[type="file"]{padding:1px;border:1px solid #999;margin:-4px 0 0 0}select,textarea{padding:0;border:1px solid #999;margin:-4px 0 0 0}fieldset{padding:0.625em;border:1px solid #ccc;margin-bottom:0.625em}input[type="radio"],input[type="checkbox"]{height:1em;vertical-align:top;margin:0.125em}div,table{overflow:hidden} |
|||
/* Fluid Fonts */ |
|||
@media screen and (max-width:960px){body{font-size:0.81255em}} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,96 @@ |
|||
body { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-family: Verdana, sans-serif; |
|||
font-size: 14px; |
|||
background-color: #979ea5; |
|||
} |
|||
|
|||
div.buttons { |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
label { |
|||
padding: 10px; |
|||
border: 1px solid grey; |
|||
margin-bottom: 10px; |
|||
background: #a7a7a7; |
|||
} |
|||
input:focus { |
|||
outline: none !important; |
|||
border-color: #719ECE; |
|||
box-shadow: 0 0 10px #719ECE; |
|||
} |
|||
textarea:focus { |
|||
outline: none !important; |
|||
border-color: #719ECE; |
|||
box-shadow: 0 0 10px #719ECE; |
|||
} |
|||
|
|||
.addbtn { |
|||
border: 1px solid grey; |
|||
background-color: #a7a7a7; |
|||
padding: 5px; |
|||
margin: 3px; |
|||
text-decoration: none; |
|||
color: #000; |
|||
} |
|||
.darker { |
|||
background-image: linear-gradient(#b1b1b1, #868686); |
|||
padding: 4px 7px; |
|||
} |
|||
input, textarea { |
|||
background-color: #979ea5; |
|||
margin: 10px !important; |
|||
border: 1px solid grey !important; |
|||
|
|||
} |
|||
|
|||
#app {text-align: center;} |
|||
|
|||
.messages { |
|||
color: green; |
|||
padding: 5px; |
|||
border: 1px solid green; |
|||
margin-bottom: 10px; |
|||
} |
|||
.messages.error { |
|||
color: #c72f2f; |
|||
border: 1px solid #c72f2f; |
|||
} |
|||
.button { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-family: Verdana, sans-serif; |
|||
font-size: 14px; |
|||
/*text-transform: capitalize;*/ |
|||
width: 120px; |
|||
height: 120px; |
|||
margin: 20px; |
|||
background-image: url(/ui/images/btn.png); |
|||
background-position: center center; |
|||
background-size: contain; |
|||
text-decoration: none; |
|||
color: #111; |
|||
|
|||
} |
|||
|
|||
p { |
|||
margin: 0; |
|||
} |
|||
|
|||
.button div { |
|||
display: flex; |
|||
} |
|||
|
|||
@media screen and (max-width:48em) { |
|||
|
|||
body { |
|||
font-size:1em; |
|||
} |
|||
|
|||
} |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,190 @@ |
|||
<div id="app"> |
|||
<div v-for="message in messages" :class="{'error': !message.success}" class="messages">{{message.message}}</div> |
|||
<p v-show="sending" style="position:absolute;top: 10px; right: 10px;">Parancs küldése, kérem várjon.</p> |
|||
<p v-if="loading">No equipments, please add one.</p> |
|||
<div class="buttons" v-else> |
|||
<div class="button" v-for="item in items"> |
|||
<div> |
|||
<p>{{item.name}}</p> |
|||
</div> |
|||
<div> |
|||
<a v-on:click="sendsignal(item, item.oncommand)" href="javascript:void(0)" class="addbtn darker"><i class="fa fa-play" aria-hidden="true"></i></a> |
|||
<a v-on:click="sendsignal(item, item.offcommand)" href="javascript:void(0)" class="addbtn darker"><i class="fa fa-stop" aria-hidden="true"></i></a> |
|||
</div> |
|||
<?php if ($admin): ?> |
|||
<div> |
|||
<a v-on:click="delitem(item)" href="javascript:void(0)" class="addbtn darker"><i class="fa fa-trash" aria-hidden="true"></i></a> |
|||
|
|||
</div> |
|||
<?php endif ?> |
|||
|
|||
|
|||
</div> |
|||
|
|||
|
|||
</div> |
|||
<?php if ($admin): ?> |
|||
<div> |
|||
<a href="javascript:void(0)" class="addbtn" v-on:click="add">{{addtext}}</a> |
|||
<div class="add" v-show="adding"> |
|||
<form> |
|||
<div> |
|||
<div> |
|||
<label for="channel"> |
|||
Channel<br> |
|||
<label>UDP<br><input type="radio" name="channel" value="udp" v-model="newitem.channel"></label> |
|||
<label>TELNET<br><input type="radio" name="channel" value="telnet" v-model="newitem.channel"> </label> |
|||
<label>WOL<br><input type="radio" name="channel" value="wol" v-model="newitem.channel"> </label> |
|||
</label> |
|||
</div> |
|||
<label for="name" v-show="newitem.channel != ''"> |
|||
Add a name for this resource<br> |
|||
<input type="text" name="name" v-model="newitem.name"> <br> |
|||
</label> |
|||
<label for="oncommand" v-show="['udp', 'telnet'].indexOf(newitem.channel) >= 0"> |
|||
What is the on command?<br> |
|||
<input type="text" name="oncommand" v-model="newitem.oncommand"> <br> |
|||
</label> |
|||
<label for="ontime" v-show="newitem.channel != ''"> |
|||
When to send the on command?<br> |
|||
<input type="time" name="ontime" v-model="newitem.ontime"> <br> |
|||
</label> |
|||
<label for="offcommand" v-show="['udp', 'telnet'].indexOf(newitem.channel) >= 0"> |
|||
What is the off command?<br> |
|||
<input type="text" name="offcommand" v-model="newitem.offcommand"> <br> |
|||
</label> |
|||
<label for="offtime" v-show="['udp', 'telnet'].indexOf(newitem.channel) >= 0"> |
|||
When to send the off command?<br> |
|||
<input type="time" name="offtime" v-model="newitem.offtime"> <br> |
|||
</label> |
|||
|
|||
<div v-show="newitem.channel != ''"> |
|||
<label for="days"> |
|||
Days<br> |
|||
<label>monday<br><input type="checkbox" v-model="newitem.days" value="monday"></label> |
|||
<label>tuesday<br><input type="checkbox" v-model="newitem.days" value="tuesday"></label> |
|||
<label>wednesday<br><input type="checkbox" v-model="newitem.days" value="wednesday"></label> |
|||
<label>thursday<br><input type="checkbox" v-model="newitem.days" value="thursday"></label> |
|||
<label>friday<br><input type="checkbox" v-model="newitem.days" value="friday"></label> |
|||
<label>saturday<br><input type="checkbox" v-model="newitem.days" value="saturday"></label> |
|||
<label>sunday<br><input type="checkbox" v-model="newitem.days" value="sunday"></label> |
|||
</label> |
|||
</div> |
|||
<div v-show="['udp', 'telnet'].indexOf(newitem.channel) >= 0"> |
|||
<label for="ip"> |
|||
What ip-s to send the signal to? (Each IP: new line!)<br> |
|||
<textarea name="ip" v-model="newitem.ip" rows="10"> |
|||
|
|||
</textarea> |
|||
</label> |
|||
<label for="port" v-show="newitem.channel != ''"> |
|||
What is the network port?<br> |
|||
<input type="text" name="port" v-model="newitem.port"> <br> |
|||
</label> |
|||
</div> |
|||
<div v-show="newitem.channel === 'wol'"> |
|||
<label for="macAdress"> |
|||
What MAC Addresses to send the signal to? (Each MAC Address: new line!)<br> |
|||
<textarea name="macAdress" v-model="newitem.macAddress" rows="10"> |
|||
|
|||
</textarea> |
|||
|
|||
</label> |
|||
<label for="broadcastIP"> |
|||
What is the network Broadcast IP? (usually 255.255.255.255)<br> |
|||
<input type="text" name="broadcastIP" v-model="newitem.broadcastIP"> <br> |
|||
</label> |
|||
</div> |
|||
<a class="addbtn" v-show="submittable" href="javascript:void(0)" v-on:click="plug"><i class="fa fa-plug" aria-hidden="true"></i>Plug it in!</a> |
|||
|
|||
|
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
|
|||
<?php endif ?> |
|||
|
|||
</div> |
|||
|
|||
|
|||
|
|||
<script> |
|||
var app = new Vue({ |
|||
el: '#app', |
|||
data: { |
|||
items: [], |
|||
sending: false, |
|||
adding: false, |
|||
messages: [], |
|||
newitem: { |
|||
name: '', |
|||
oncommand: '', |
|||
ontime: '', |
|||
offcommand: '', |
|||
offtime: '', |
|||
days: [], |
|||
channel: '', |
|||
port: '', |
|||
ip: '', |
|||
broadcastIP: '', |
|||
macAddress: '' |
|||
|
|||
}, |
|||
}, |
|||
mounted () { |
|||
this.getitems() |
|||
}, |
|||
computed: { |
|||
loading() {return this.items.length === 0}, |
|||
addtext: function() { |
|||
if (this.adding == false) { return 'Add new group or element'} else {return 'Back'} |
|||
}, |
|||
submittable: function() { |
|||
if ( |
|||
this.newitem.name != '' && |
|||
this.newitem.channel != '' |
|||
) {return true} else {return false} |
|||
} |
|||
}, |
|||
methods: { |
|||
add: function() { |
|||
this.adding = !this.adding |
|||
}, |
|||
sendsignal: function(item, action) { |
|||
if (this.sending) { return alert('Please wait, the signal is sending')} |
|||
this.sending = true |
|||
console.log(`sending signal ${action} to ${item.name}`) |
|||
|
|||
axios |
|||
.post('sendsignal', {'item': item, 'action': action}).then(response => { |
|||
this.messages = response.data |
|||
this.sending = false |
|||
}, error => { |
|||
alert(error) |
|||
this.sending = false |
|||
}) |
|||
}, |
|||
delitem: function(item) { |
|||
if (confirm('Are you sure?')) { |
|||
axios |
|||
.post('del', item).then(response => { |
|||
this.getitems() |
|||
}) |
|||
} |
|||
}, |
|||
getitems: function() { |
|||
axios |
|||
.get('api/items') |
|||
.then(response => (this.items = response.data)) |
|||
}, |
|||
plug: function() { |
|||
axios |
|||
.post('add', this.newitem).then(response => { |
|||
this.items.push(this.newitem) |
|||
}) |
|||
this.adding = false |
|||
} |
|||
} |
|||
}) |
|||
</script> |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 6.1 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
@ -0,0 +1,29 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="<?php echo $ENCODING; ?>" /> |
|||
<link rel="manifest" href="manifest.json"> |
|||
<meta name="mobile-web-app-capable" content="yes"> |
|||
<meta name="apple-mobile-web-app-capable" content="yes"> |
|||
<meta name="application-name" content="IZZ Control"> |
|||
<meta name="apple-mobile-web-app-title" content="IZZ Control"> |
|||
<meta name="theme-color" content="#979ea5"> |
|||
<meta name="msapplication-navbutton-color" content="#979ea5"> |
|||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> |
|||
<meta name="msapplication-starturl" content="/192.168.0.200"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
|||
<title>IzzoControl</title> |
|||
<base href="<?php echo $SCHEME.'://'.$HOST.':'.$PORT.$BASE.'/'; ?>" /> |
|||
<link rel="stylesheet" href="lib/code.css" type="text/css" /> |
|||
<link rel="stylesheet" href="ui/css/base.css" type="text/css" /> |
|||
<link rel="stylesheet" href="ui/css/theme.css" type="text/css" /> |
|||
<link rel="stylesheet" href="ui/css/font-awesome.min.css" type="text/css" /> |
|||
<script src="ui/js/vue.js" type="text/javascript" charset="utf-8"></script> |
|||
<script src="ui/js/axios.min.js" type="text/javascript" charset="utf-8"></script> |
|||
|
|||
|
|||
</head> |
|||
<body> |
|||
<?php echo $this->render(Base::instance()->get('template')); ?> |
|||
</body> |
|||
</html> |
Loading…
Reference in new issue