initial
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/tmp/
|
||||
/.idea/
|
||||
/vendor/
|
||||
data/*
|
||||
!data/.keep
|
||||
.DS_Store
|
||||
16
.htaccess
Normal file
16
.htaccess
Normal file
@@ -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]
|
||||
20
composer.json
Normal file
20
composer.json
Normal file
@@ -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"]
|
||||
}
|
||||
}
|
||||
204
composer.lock
generated
Normal file
204
composer.lock
generated
Normal file
@@ -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"
|
||||
}
|
||||
4
config.ini
Normal file
4
config.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[globals]
|
||||
|
||||
DEBUG=3
|
||||
UI=ui/
|
||||
0
data/.keep
Normal file
0
data/.keep
Normal file
173
index.php
Normal file
173
index.php
Normal file
@@ -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();
|
||||
971
lib/CHANGELOG.md
Normal file
971
lib/CHANGELOG.md
Normal file
@@ -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
|
||||
621
lib/COPYING
Executable file
621
lib/COPYING
Executable file
@@ -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
|
||||
191
lib/audit.php
Normal file
191
lib/audit.php
Normal file
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
262
lib/auth.php
Normal file
262
lib/auth.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
3584
lib/base.php
Normal file
3584
lib/base.php
Normal file
File diff suppressed because it is too large
Load Diff
239
lib/basket.php
Normal file
239
lib/basket.php
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
96
lib/bcrypt.php
Normal file
96
lib/bcrypt.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
487
lib/cli/ws.php
Normal file
487
lib/cli/ws.php
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
1
lib/code.css
Executable file
1
lib/code.css
Executable file
@@ -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}
|
||||
388
lib/db/cursor.php
Normal file
388
lib/db/cursor.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
175
lib/db/jig.php
Normal file
175
lib/db/jig.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
541
lib/db/jig/mapper.php
Normal file
541
lib/db/jig/mapper.php
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
194
lib/db/jig/session.php
Normal file
194
lib/db/jig/session.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
145
lib/db/mongo.php
Normal file
145
lib/db/mongo.php
Normal file
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
405
lib/db/mongo/mapper.php
Normal file
405
lib/db/mongo/mapper.php
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
194
lib/db/mongo/session.php
Normal file
194
lib/db/mongo/session.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
523
lib/db/sql.php
Normal file
523
lib/db/sql.php
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
759
lib/db/sql/mapper.php
Normal file
759
lib/db/sql/mapper.php
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
222
lib/db/sql/session.php
Normal file
222
lib/db/sql/session.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
42
lib/f3.php
Normal file
42
lib/f3.php
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
616
lib/image.php
Normal file
616
lib/image.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
71
lib/log.php
Normal file
71
lib/log.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
139
lib/magic.php
Normal file
139
lib/magic.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
569
lib/markdown.php
Normal file
569
lib/markdown.php
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
139
lib/matrix.php
Normal file
139
lib/matrix.php
Normal 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/>.
|
||||
|
||||
*/
|
||||
|
||||
//! 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;
|
||||
}
|
||||
|
||||
}
|
||||
196
lib/session.php
Normal file
196
lib/session.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
360
lib/smtp.php
Normal file
360
lib/smtp.php
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
353
lib/template.php
Normal file
353
lib/template.php
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
98
lib/test.php
Normal file
98
lib/test.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
199
lib/utf.php
Normal file
199
lib/utf.php
Normal file
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
1017
lib/web.php
Normal file
1017
lib/web.php
Normal file
File diff suppressed because it is too large
Load Diff
111
lib/web/geo.php
Normal file
111
lib/web/geo.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
58
lib/web/google/recaptcha.php
Normal file
58
lib/web/google/recaptcha.php
Normal file
@@ -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'];
|
||||
}
|
||||
|
||||
}
|
||||
65
lib/web/google/staticmap.php
Normal file
65
lib/web/google/staticmap.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
163
lib/web/oauth2.php
Normal file
163
lib/web/oauth2.php
Normal file
@@ -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=[];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
248
lib/web/openid.php
Normal file
248
lib/web/openid.php
Normal file
@@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
176
lib/web/pingback.php
Normal file
176
lib/web/pingback.php
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
8
manifest.json
Normal file
8
manifest.json
Normal file
@@ -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"
|
||||
}
|
||||
2
readme.md
Normal file
2
readme.md
Normal file
@@ -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.
|
||||
6
ui/css/base.css
Normal file
6
ui/css/base.css
Normal file
@@ -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}}
|
||||
4
ui/css/font-awesome.min.css
vendored
Normal file
4
ui/css/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
96
ui/css/theme.css
Normal file
96
ui/css/theme.css
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
BIN
ui/fonts/FontAwesome.otf
Normal file
BIN
ui/fonts/FontAwesome.otf
Normal file
Binary file not shown.
BIN
ui/fonts/fontawesome-webfont.eot
Normal file
BIN
ui/fonts/fontawesome-webfont.eot
Normal file
Binary file not shown.
2671
ui/fonts/fontawesome-webfont.svg
Normal file
2671
ui/fonts/fontawesome-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
BIN
ui/fonts/fontawesome-webfont.ttf
Normal file
BIN
ui/fonts/fontawesome-webfont.ttf
Normal file
Binary file not shown.
BIN
ui/fonts/fontawesome-webfont.woff
Normal file
BIN
ui/fonts/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
ui/fonts/fontawesome-webfont.woff2
Normal file
BIN
ui/fonts/fontawesome-webfont.woff2
Normal file
Binary file not shown.
190
ui/home.htm
Normal file
190
ui/home.htm
Normal file
@@ -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>
|
||||
BIN
ui/images/btn.png
Normal file
BIN
ui/images/btn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
ui/images/homescreen.png
Normal file
BIN
ui/images/homescreen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
3
ui/js/axios.min.js
vendored
Normal file
3
ui/js/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11965
ui/js/vue.js
Normal file
11965
ui/js/vue.js
Normal file
File diff suppressed because it is too large
Load Diff
29
ui/layout.htm
Normal file
29
ui/layout.htm
Normal file
@@ -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>
|
||||
Reference in New Issue
Block a user