diff --git a/app/helper/dashboard.php b/app/helper/dashboard.php index 02a467bf..b4fd2058 100644 --- a/app/helper/dashboard.php +++ b/app/helper/dashboard.php @@ -31,13 +31,16 @@ public function getOwnerIds() { public function projects() { $f3 = \Base::instance(); - $ownerString = implode(",", $this->getOwnerIds()); $typeIds = []; foreach ($f3->get('issue_types') as $t) { if ($t->role == 'project') { $typeIds[] = $t->id; } } + if (!$typeIds) { + return []; + } + $ownerString = implode(",", $this->getOwnerIds()); $typeIdStr = implode(",", $typeIds); $this->_projects = $this->getIssue()->find( array( @@ -67,13 +70,16 @@ public function subprojects() { public function bugs() { $f3 = \Base::instance(); - $ownerString = implode(",", $this->getOwnerIds()); $typeIds = []; foreach ($f3->get('issue_types') as $t) { if ($t->role == 'bug') { $typeIds[] = $t->id; } } + if (!$typeIds) { + return []; + } + $ownerString = implode(",", $this->getOwnerIds()); $typeIdStr = implode(",", $typeIds); return $this->getIssue()->find( array( @@ -99,13 +105,16 @@ public function watchlist() { public function tasks() { $f3 = \Base::instance(); - $ownerString = implode(",", $this->getOwnerIds()); $typeIds = []; foreach ($f3->get('issue_types') as $t) { if ($t->role == 'task') { $typeIds[] = $t->id; } } + if (!$typeIds) { + return []; + } + $ownerString = implode(",", $this->getOwnerIds()); $typeIdStr = implode(",", $typeIds); return $this->getIssue()->find( array( @@ -136,8 +145,11 @@ public function recent_comments() { $ids[] = $item->id; } - $comment = new \Model\Issue\Comment\Detail; + if (!$ids) { + return []; + } $issueIds = implode(",", $ids); + $comment = new \Model\Issue\Comment\Detail; return $comment->find(array("issue_id IN ($issueIds) AND user_id != ?", $f3->get("user.id")), array("order" => "created_date DESC", "limit" => 15)); } @@ -156,8 +168,11 @@ public function open_comments() { $ids[] = $item->id; } - $comment = new \Model\Issue\Comment\Detail; + if (!$ids) { + return []; + } $issueIds = implode(",", $ids); + $comment = new \Model\Issue\Comment\Detail; return $comment->find(array("issue_id IN ($issueIds) AND user_id != ?", $f3->get("user.id")), array("order" => "created_date DESC", "limit" => 15)); } diff --git a/app/view/backlog/item.html b/app/view/backlog/item.html index 384470c1..beca7af1 100644 --- a/app/view/backlog/item.html +++ b/app/view/backlog/item.html @@ -1,4 +1,4 @@ -
  • +
  • {{ @project.priority_name }} {{ @project.type_name }} diff --git a/app/view/taskboard/index.html b/app/view/taskboard/index.html index 7860a90a..8093d6fb 100644 --- a/app/view/taskboard/index.html +++ b/app/view/taskboard/index.html @@ -144,7 +144,11 @@ {{ empty(@row.project.due_date) ? "" : date('n/j', strtotime(@row.project.due_date)) }} -
    {{ @row.project.name | esc }}
    +
    {{ @row.project.name | esc }} + + - {{ @row.project.size_estimate }} + +
    @@ -241,7 +245,7 @@
    - +
    @@ -372,6 +376,10 @@ $(window).scroll(); }); + // Every time a modal is shown, if it has an autofocus element, focus on it. + $('.modal').on('shown.bs.modal', function() { + $(this).find('[autofocus]').focus(); + }); diff --git a/db/17.03.17.sql b/db/17.03.17.sql index c7d6de93..da9d6cc8 100644 --- a/db/17.03.17.sql +++ b/db/17.03.17.sql @@ -1,58 +1,54 @@ -ALTER TABLE issue - ADD size_estimate VARCHAR(20) AFTER name; - - ALTER ALGORITHM = UNDEFINED DEFINER = `root` @`localhost` SQL SECURITY DEFINER VIEW `issue_detail` -AS - (SELECT `issue`.`id` AS `id`, - `issue`.`status` AS `status`, - `issue`.`type_id` AS `type_id`, - `issue`.`name` AS `name`, - `issue`.`size_estimate` AS `size_estimate`, - `issue`.`description` AS `description`, - `issue`.`parent_id` AS `parent_id`, - `issue`.`author_id` AS `author_id`, - `issue`.`owner_id` AS `owner_id`, - `issue`.`priority` AS `priority`, - `issue`.`hours_total` AS `hours_total`, - `issue`.`hours_remaining` AS `hours_remaining`, - `issue`.`hours_spent` AS `hours_spent`, - `issue`.`created_date` AS `created_date`, - `issue`.`closed_date` AS `closed_date`, - `issue`.`deleted_date` AS `deleted_date`, - `issue`.`start_date` AS `start_date`, - `issue`.`due_date` AS `due_date`, - isnull(`issue`.`due_date`) AS `has_due_date`, - `issue`.`repeat_cycle` AS `repeat_cycle`, - `issue`.`sprint_id` AS `sprint_id`, - `issue`.`due_date_sprint` AS `due_date_sprint`, - `sprint`.`name` AS `sprint_name`, - `sprint`.`start_date` AS `sprint_start_date`, - `sprint`.`end_date` AS `sprint_end_date`, - `type`.`name` AS `type_name`, - `status`.`name` AS `status_name`, - `status`.`closed` AS `status_closed`, - `priority`.`id` AS `priority_id`, - `priority`.`name` AS `priority_name`, - `author`.`username` AS `author_username`, - `author`.`name` AS `author_name`, - `author`.`email` AS `author_email`, - `author`.`task_color` AS `author_task_color`, - `owner`.`username` AS `owner_username`, - `owner`.`name` AS `owner_name`, - `owner`.`email` AS `owner_email`, - `owner`.`task_color` AS `owner_task_color` - FROM ((((((`issue` - LEFT JOIN `user` `author` - ON ((`issue`.`author_id` = `author`.`id`))) - LEFT JOIN `user` `owner` - ON ((`issue`.`owner_id` = `owner`.`id`))) - LEFT JOIN `issue_status` `status` - ON ((`issue`.`status` = `status`.`id`))) - LEFT JOIN `issue_priority` `priority` - ON ((`issue`.`priority` = `priority`.`value`))) - LEFT JOIN `issue_type` `type` - ON ((`issue`.`type_id` = `type`.`id`))) - LEFT JOIN `sprint` ON ((`issue`.`sprint_id` = `sprint`.`id`)))); +ALTER TABLE issue ADD size_estimate VARCHAR(20) AFTER name; - # Update version +ALTER VIEW `issue_detail` +AS ( + SELECT + `issue`.`id` AS `id`, + `issue`.`status` AS `status`, + `issue`.`type_id` AS `type_id`, + `issue`.`name` AS `name`, + `issue`.`size_estimate` AS `size_estimate`, + `issue`.`description` AS `description`, + `issue`.`parent_id` AS `parent_id`, + `issue`.`author_id` AS `author_id`, + `issue`.`owner_id` AS `owner_id`, + `issue`.`priority` AS `priority`, + `issue`.`hours_total` AS `hours_total`, + `issue`.`hours_remaining` AS `hours_remaining`, + `issue`.`hours_spent` AS `hours_spent`, + `issue`.`created_date` AS `created_date`, + `issue`.`closed_date` AS `closed_date`, + `issue`.`deleted_date` AS `deleted_date`, + `issue`.`start_date` AS `start_date`, + `issue`.`due_date` AS `due_date`, + isnull(`issue`.`due_date`) AS `has_due_date`, + `issue`.`repeat_cycle` AS `repeat_cycle`, + `issue`.`sprint_id` AS `sprint_id`, + `issue`.`due_date_sprint` AS `due_date_sprint`, + `sprint`.`name` AS `sprint_name`, + `sprint`.`start_date` AS `sprint_start_date`, + `sprint`.`end_date` AS `sprint_end_date`, + `type`.`name` AS `type_name`, + `status`.`name` AS `status_name`, + `status`.`closed` AS `status_closed`, + `priority`.`id` AS `priority_id`, + `priority`.`name` AS `priority_name`, + `author`.`username` AS `author_username`, + `author`.`name` AS `author_name`, + `author`.`email` AS `author_email`, + `author`.`task_color` AS `author_task_color`, + `owner`.`username` AS `owner_username`, + `owner`.`name` AS `owner_name`, + `owner`.`email` AS `owner_email`, + `owner`.`task_color` AS `owner_task_color` + FROM `issue` + LEFT JOIN `user` `author` ON `issue`.`author_id` = `author`.`id` + LEFT JOIN `user` `owner` ON `issue`.`owner_id` = `owner`.`id` + LEFT JOIN `issue_status` `status` ON `issue`.`status` = `status`.`id` + LEFT JOIN `issue_priority` `priority` ON `issue`.`priority` = `priority`.`value` + LEFT JOIN `issue_type` `type` ON `issue`.`type_id` = `type`.`id` + LEFT JOIN `sprint` ON `issue`.`sprint_id` = `sprint`.`id` +); + +# Update version UPDATE `config` SET `value` = '17.03.17' WHERE `attribute` = 'version'; diff --git a/db/17.03.23.sql b/db/17.03.23.sql new file mode 100644 index 00000000..50a78713 --- /dev/null +++ b/db/17.03.23.sql @@ -0,0 +1,52 @@ +-- Re-add a column removed in 17.03.17 +ALTER VIEW `issue_detail` AS +SELECT + `issue`.`id` AS `id`, + `issue`.`status` AS `status`, + `issue`.`type_id` AS `type_id`, + `issue`.`name` AS `name`, + `issue`.`size_estimate` AS `size_estimate`, + `issue`.`description` AS `description`, + `issue`.`parent_id` AS `parent_id`, + `issue`.`author_id` AS `author_id`, + `issue`.`owner_id` AS `owner_id`, + `issue`.`priority` AS `priority`, + `issue`.`hours_total` AS `hours_total`, + `issue`.`hours_remaining` AS `hours_remaining`, + `issue`.`hours_spent` AS `hours_spent`, + `issue`.`created_date` AS `created_date`, + `issue`.`closed_date` AS `closed_date`, + `issue`.`deleted_date` AS `deleted_date`, + `issue`.`start_date` AS `start_date`, + `issue`.`due_date` AS `due_date`, + ISNULL(`issue`.`due_date`) AS `has_due_date`, + `issue`.`repeat_cycle` AS `repeat_cycle`, + `issue`.`sprint_id` AS `sprint_id`, + `issue`.`due_date_sprint` AS `due_date_sprint`, + `sprint`.`name` AS `sprint_name`, + `sprint`.`start_date` AS `sprint_start_date`, + `sprint`.`end_date` AS `sprint_end_date`, + `type`.`name` AS `type_name`, + `status`.`name` AS `status_name`, + `status`.`closed` AS `status_closed`, + `priority`.`id` AS `priority_id`, + `priority`.`name` AS `priority_name`, + `author`.`username` AS `author_username`, + `author`.`name` AS `author_name`, + `author`.`email` AS `author_email`, + `author`.`task_color` AS `author_task_color`, + `owner`.`username` AS `owner_username`, + `owner`.`name` AS `owner_name`, + `owner`.`email` AS `owner_email`, + `owner`.`task_color` AS `owner_task_color`, + `parent`.`name` AS `parent_name` +FROM `issue` +LEFT JOIN `user` `author` on`issue`.`author_id` = `author`.`id` +LEFT JOIN `user` `owner` on`issue`.`owner_id` = `owner`.`id` +LEFT JOIN `issue_status` `status` on`issue`.`status` = `status`.`id` +LEFT JOIN `issue_priority` `priority` on`issue`.`priority` = `priority`.`value` +LEFT JOIN `issue_type` `type` on`issue`.`type_id` = `type`.`id` +LEFT JOIN `sprint` on`issue`.`sprint_id` = `sprint`.`id` +LEFT JOIN `issue` `parent` ON `issue`.`parent_id` = `parent`.`id`; + +UPDATE `config` SET `value` = '17.03.23' WHERE `attribute` = 'version'; diff --git a/db/database.sql b/db/database.sql index 1900254b..ef8f39db 100644 --- a/db/database.sql +++ b/db/database.sql @@ -10,6 +10,7 @@ CREATE TABLE `user` ( `name` varchar(32) NOT NULL, `password` char(40) DEFAULT NULL, `salt` char(32) DEFAULT NULL, + `reset_token` CHAR(96) NULL, `role` enum('user','admin','group') NOT NULL DEFAULT 'user', `rank` tinyint(1) unsigned NOT NULL DEFAULT '0', `task_color` char(6) DEFAULT NULL, @@ -43,6 +44,7 @@ CREATE TABLE `issue` ( `status` int(10) unsigned NOT NULL DEFAULT '1', `type_id` int(11) unsigned NOT NULL DEFAULT '1', `name` varchar(255) NOT NULL, + `size_estimate` VARCHAR(20) NULL, `description` text NOT NULL, `parent_id` int(11) unsigned DEFAULT NULL, `author_id` int(11) unsigned NOT NULL, @@ -236,7 +238,55 @@ DROP VIEW IF EXISTS `issue_comment_detail`; CREATE VIEW `issue_comment_detail` AS (select `c`.`id` AS `id`, `c`.`issue_id` AS `issue_id`, `c`.`user_id` AS `user_id`, `c`.`text` AS `text`, `c`.`file_id` AS `file_id`, `c`.`created_date` AS `created_date`, `u`.`username` AS `user_username`, `u`.`email` AS `user_email`, `u`.`name` AS `user_name`, `u`.`role` AS `user_role`, `u`.`task_color` AS `user_task_color`, `f`.`filename` AS `file_filename`, `f`.`filesize` AS `file_filesize`, `f`.`content_type` AS `file_content_type`, `f`.`downloads` AS `file_downloads`, `f`.`created_date` AS `file_created_date`, `f`.`deleted_date` AS `file_deleted_date`, `i`.`deleted_date` AS `issue_deleted_date` from `issue_comment` `c` join `user` `u` on `c`.`user_id` = `u`.`id` left join `issue_file` `f` on `c`.`file_id` = `f`.`id` JOIN `issue` `i` ON `i`.`id` = `c`.`issue_id`); DROP VIEW IF EXISTS `issue_detail`; -CREATE VIEW `issue_detail` AS (select `issue`.`id` AS `id`,`issue`.`status` AS `status`,`issue`.`type_id` AS `type_id`,`issue`.`name` AS `name`,`issue`.`description` AS `description`,`issue`.`parent_id` AS `parent_id`,`issue`.`author_id` AS `author_id`,`issue`.`owner_id` AS `owner_id`,`issue`.`priority` AS `priority`,`issue`.`hours_total` AS `hours_total`,`issue`.`hours_remaining` AS `hours_remaining`,`issue`.`hours_spent` AS `hours_spent`,`issue`.`created_date` AS `created_date`,`issue`.`closed_date` AS `closed_date`,`issue`.`deleted_date` AS `deleted_date`,`issue`.`start_date` AS `start_date`,`issue`.`due_date` AS `due_date`,isnull(`issue`.`due_date`) AS `has_due_date`,`issue`.`repeat_cycle` AS `repeat_cycle`,`issue`.`sprint_id` AS `sprint_id`,`issue`.`due_date_sprint` AS `due_date_sprint`,`sprint`.`name` AS `sprint_name`,`sprint`.`start_date` AS `sprint_start_date`,`sprint`.`end_date` AS `sprint_end_date`,`type`.`name` AS `type_name`,`status`.`name` AS `status_name`,`status`.`closed` AS `status_closed`,`priority`.`id` AS `priority_id`,`priority`.`name` AS `priority_name`,`author`.`username` AS `author_username`,`author`.`name` AS `author_name`,`author`.`email` AS `author_email`,`author`.`task_color` AS `author_task_color`,`owner`.`username` AS `owner_username`,`owner`.`name` AS `owner_name`,`owner`.`email` AS `owner_email`,`owner`.`task_color` AS `owner_task_color` from ((((((`issue` left join `user` `author` on((`issue`.`author_id` = `author`.`id`))) left join `user` `owner` on((`issue`.`owner_id` = `owner`.`id`))) left join `issue_status` `status` on((`issue`.`status` = `status`.`id`))) left join `issue_priority` `priority` on((`issue`.`priority` = `priority`.`value`))) left join `issue_type` `type` on((`issue`.`type_id` = `type`.`id`))) left join `sprint` on((`issue`.`sprint_id` = `sprint`.`id`)))); +CREATE VIEW `issue_detail` AS +SELECT + `issue`.`id` AS `id`, + `issue`.`status` AS `status`, + `issue`.`type_id` AS `type_id`, + `issue`.`name` AS `name`, + `issue`.`size_estimate` AS `size_estimate`, + `issue`.`description` AS `description`, + `issue`.`parent_id` AS `parent_id`, + `issue`.`author_id` AS `author_id`, + `issue`.`owner_id` AS `owner_id`, + `issue`.`priority` AS `priority`, + `issue`.`hours_total` AS `hours_total`, + `issue`.`hours_remaining` AS `hours_remaining`, + `issue`.`hours_spent` AS `hours_spent`, + `issue`.`created_date` AS `created_date`, + `issue`.`closed_date` AS `closed_date`, + `issue`.`deleted_date` AS `deleted_date`, + `issue`.`start_date` AS `start_date`, + `issue`.`due_date` AS `due_date`, + ISNULL(`issue`.`due_date`) AS `has_due_date`, + `issue`.`repeat_cycle` AS `repeat_cycle`, + `issue`.`sprint_id` AS `sprint_id`, + `issue`.`due_date_sprint` AS `due_date_sprint`, + `sprint`.`name` AS `sprint_name`, + `sprint`.`start_date` AS `sprint_start_date`, + `sprint`.`end_date` AS `sprint_end_date`, + `type`.`name` AS `type_name`, + `status`.`name` AS `status_name`, + `status`.`closed` AS `status_closed`, + `priority`.`id` AS `priority_id`, + `priority`.`name` AS `priority_name`, + `author`.`username` AS `author_username`, + `author`.`name` AS `author_name`, + `author`.`email` AS `author_email`, + `author`.`task_color` AS `author_task_color`, + `owner`.`username` AS `owner_username`, + `owner`.`name` AS `owner_name`, + `owner`.`email` AS `owner_email`, + `owner`.`task_color` AS `owner_task_color`, + `parent`.`name` AS `parent_name` +FROM `issue` +LEFT JOIN `user` `author` on`issue`.`author_id` = `author`.`id` +LEFT JOIN `user` `owner` on`issue`.`owner_id` = `owner`.`id` +LEFT JOIN `issue_status` `status` on`issue`.`status` = `status`.`id` +LEFT JOIN `issue_priority` `priority` on`issue`.`priority` = `priority`.`value` +LEFT JOIN `issue_type` `type` on`issue`.`type_id` = `type`.`id` +LEFT JOIN `sprint` on`issue`.`sprint_id` = `sprint`.`id` +LEFT JOIN `issue` `parent` ON `issue`.`parent_id` = `parent`.`id`; DROP VIEW IF EXISTS `issue_file_detail`; CREATE VIEW `issue_file_detail` AS (select `f`.`id` AS `id`, `f`.`issue_id` AS `issue_id`, `f`.`filename` AS `filename`, `f`.`disk_filename` AS `disk_filename`, `f`.`disk_directory` AS `disk_directory`, `f`.`filesize` AS `filesize`, `f`.`content_type` AS `content_type`, `f`.`digest` AS `digest`, `f`.`downloads` AS `downloads`, `f`.`user_id` AS `user_id`, `f`.`created_date` AS `created_date`, `f`.`deleted_date` AS `deleted_date`, `u`.`username` AS `user_username`, `u`.`email` AS `user_email`, `u`.`name` AS `user_name`, `u`.`task_color` AS `user_task_color` from (`issue_file` `f` join `user` `u` on ((`f`.`user_id` = `u`.`id`)))); @@ -303,4 +353,8 @@ CREATE TABLE `config` ( UNIQUE KEY `attribute` (`attribute`) ); -INSERT INTO `config` (`attribute`, `value`) VALUES ('version', '16.12.01'); +-- TODO: Set all default config values from config-base.ini in DB +-- This will allow us to deprecate and remove the file +INSERT INTO `config` (`attribute`,`value`) VALUES ('security.reset_ttl', '86400'); + +INSERT INTO `config` (`attribute`, `value`) VALUES ('version', '17.03.23'); diff --git a/lib/CHANGELOG b/lib/CHANGELOG index f831d7d6..d7ae4385 100755 --- a/lib/CHANGELOG +++ b/lib/CHANGELOG @@ -1,5 +1,62 @@ CHANGELOG +3.6.1 (2 April 2017) +* NEW: Recaptcha plugin (#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) +* NEW: Allow OPTIONS method to return a response body (#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) +* 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) +* 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) +* 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 diff --git a/lib/audit.php b/lib/audit.php index 4c91740d..0e69b56a 100644 --- a/lib/audit.php +++ b/lib/audit.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -108,7 +108,7 @@ function ispublic($addr) { **/ function isdesktop($agent=NULL) { if (!isset($agent)) - $agent=Base::instance()->get('AGENT'); + $agent=Base::instance()->AGENT; return (bool)preg_match('/('.self::UA_Desktop.')/i',$agent) && !$this->ismobile($agent); } @@ -120,7 +120,7 @@ function isdesktop($agent=NULL) { **/ function ismobile($agent=NULL) { if (!isset($agent)) - $agent=Base::instance()->get('AGENT'); + $agent=Base::instance()->AGENT; return (bool)preg_match('/('.self::UA_Mobile.')/i',$agent); } @@ -131,7 +131,7 @@ function ismobile($agent=NULL) { **/ function isbot($agent=NULL) { if (!isset($agent)) - $agent=Base::instance()->get('AGENT'); + $agent=Base::instance()->AGENT; return (bool)preg_match('/('.self::UA_Bot.')/i',$agent); } diff --git a/lib/auth.php b/lib/auth.php index 56f89ed5..b7717513 100644 --- a/lib/auth.php +++ b/lib/auth.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -160,12 +160,12 @@ protected function _smtp($id,$pw) { stream_set_blocking($socket,TRUE); $dialog(); $fw=Base::instance(); - $dialog('EHLO '.$fw->get('HOST')); + $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->get('HOST')); + $dialog('EHLO '.$fw->HOST); } // Authenticate $dialog('AUTH LOGIN'); @@ -196,7 +196,7 @@ function login($id,$pw,$realm=NULL) { **/ function basic($func=NULL) { $fw=Base::instance(); - $realm=$fw->get('REALM'); + $realm=$fw->REALM; $hdr=NULL; if (isset($_SERVER['HTTP_AUTHORIZATION'])) $hdr=$_SERVER['HTTP_AUTHORIZATION']; diff --git a/lib/base.php b/lib/base.php index b5a7bdca..329df9f3 100644 --- a/lib/base.php +++ b/lib/base.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess { //@{ Framework details const PACKAGE='Fat-Free Framework', - VERSION='3.6.0-Release'; + VERSION='3.6.1-Release'; //@} //@{ HTTP status codes (RFC 2616) @@ -96,7 +96,7 @@ final class Base extends Prefab implements ArrayAccess { //! Mapped PHP globals GLOBALS='GET|POST|COOKIE|REQUEST|SESSION|FILES|SERVER|ENV', //! HTTP verbs - VERBS='GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT', + VERBS='GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS', //! Default directory permissions MODE=0755, //! Syntax highlighting stylesheet @@ -128,6 +128,8 @@ final class Base extends Prefab implements ArrayAccess { $init, //! Language lookup sequence $languages, + //! Mutex locks + $locks=[], //! Default fallback language $fallback='en'; @@ -183,24 +185,6 @@ function($match) use(&$i,$args) { return $url; } - /** - * Assemble url from alias name - * @return string - * @param $name string - * @param $params array|string - * @param $query string|array - **/ - function alias($name,$params=[],$query=NULL) { - if (!is_array($params)) - $params=$this->parse($params); - if (empty($this->hive['ALIASES'][$name])) - user_error(sprintf(self::E_Named,$name),E_USER_ERROR); - $url=$this->build($this->hive['ALIASES'][$name],$params); - if (is_array($query)) - $query=http_build_query($query); - return $url.($query?('?'.$query):''); - } - /** * Parse string containing key-value pairs * @return array @@ -227,31 +211,33 @@ function parse($str) { * @param $str string **/ function compile($str) { - $fw=$this; return preg_replace_callback( - '/(?|::)*)/', - function($var) use($fw) { - return '$'.preg_replace_callback( - '/\.(\w+)\(|\.(\w+)|\[((?:[^\[\]]*|(?R))*)\]/', - function($expr) use($fw) { - return $expr[1]? - ((function_exists($expr[1])? - ('.'.$expr[1]): - ('['.var_export($expr[1],TRUE).']')).'('): - ('['. - (isset($expr[3])? - (strlen($expr[3])? - var_export( - trim($fw->compile($expr[3])), - TRUE): - ''): - var_export(ctype_digit($expr[2])? - (int)$expr[2]: - $expr[2],TRUE)). - ']'); - }, - $var[1] - ); + '/(?|::)\w+)?)'. + '((?:\.\w+|\[(?:(?:[^\[\]]*|(?R))*)\]|(?:\->|::)\w+|\()*)/', + function($expr) { + $str='$'.$expr[1]; + if (isset($expr[2])) + $str.=preg_replace_callback( + '/\.(\w+)(\()?|\[((?:[^\[\]]*|(?R))*)\]/', + function($sub) { + if (empty($sub[2])) { + if (ctype_digit($sub[1])) + $sub[1]=(int)$sub[1]; + $out='['. + (isset($sub[3])? + $this->compile($sub[3]): + var_export($sub[1],TRUE)). + ']'; + } + else + $out=function_exists($sub[1])? + $sub[0]: + ('['.var_export($sub[1],TRUE).']'.$sub[2]); + return $out; + }, + $expr[2] + ); + return $str; }, $str ); @@ -561,7 +547,7 @@ function flip($key) { **/ function push($key,$val) { $ref=&$this->ref($key); - $ref[] = $val; + $ref[]=$val; return $val; } @@ -625,7 +611,8 @@ function extend($key,$src,$keep=FALSE) { $ref=&$this->ref($key); if (!$ref) $ref=[]; - $out=array_replace_recursive(is_string($src)?$this->hive[$src]:$src,$ref); + $out=array_replace_recursive( + is_string($src)?$this->hive[$src]:$src,$ref); if ($keep) $ref=$out; return $out; @@ -705,7 +692,7 @@ function csv(array $args) { **/ function camelcase($str) { return preg_replace_callback( - '/_(\w)/', + '/_(\pL)/u', function($match) { return strtoupper($match[1]); }, @@ -719,7 +706,7 @@ function($match) { * @param $str string **/ function snakecase($str) { - return strtolower(preg_replace('/(?!^)[[:upper:]]/','_\0',$str)); + return strtolower(preg_replace('/(?!^)\p{Lu}/u','_\0',$str)); } /** @@ -839,12 +826,11 @@ function recursive($arg,$func,$stack=[]) { * @param $tags string **/ function clean($arg,$tags=NULL) { - $fw=$this; return $this->recursive($arg, - function($val) use($fw,$tags) { + function($val) use($tags) { if ($tags!='*') $val=trim(strip_tags($val, - '<'.implode('><',$fw->split($tags)).'>')); + '<'.implode('><',$this->split($tags)).'>')); return trim(preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/','',$val)); } @@ -871,9 +857,9 @@ function format() { // Get formatting rules $conv=localeconv(); return preg_replace_callback( - '/\{(?P\d+)\s*(?:,\s*(?P\w+)\s*'. + '/\{\s*(?P\d+)\s*(?:,\s*(?P\w+)\s*'. '(?:,\s*(?P(?:\w+(?:\s*\{.+?\}\s*,?\s*)?)*)'. - '(?:,\s*(?P.+?))?)?)?\}/', + '(?:,\s*(?P.+?))?)?)?\s*\}/', function($expr) use($args,$conv) { extract($expr); extract($conv); @@ -902,7 +888,7 @@ function($expr) use($args,$conv) { return number_format( $args[$pos],0,'',$thousands_sep); case 'currency': - $int=$cstm=false; + $int=$cstm=FALSE; if (isset($prop) && $cstm=!$int=($prop=='int')) $currency_symbol=$prop; @@ -1009,10 +995,11 @@ function language($code) { $country=@constant('ISO::CC_'.strtolower($parts[1]))) $locale.='-'.$country; } - $locales[]=$locale; + $locale=str_replace('-','_',$locale); $locales[]=$locale.'.'.ini_get('default_charset'); + $locales[]=$locale; } - setlocale(LC_ALL,str_replace('-','_',$locales)); + setlocale(LC_ALL,$locales); return implode(',',$this->languages); } @@ -1106,6 +1093,7 @@ function status($code) { **/ function expire($secs=0) { if (!$this->hive['CLI'] && !headers_sent()) { + $secs=(int)$secs; if ($this->hive['PACKAGE']) header('X-Powered-By: '.$this->hive['PACKAGE']); if ($this->hive['XFRAME']) @@ -1115,7 +1103,7 @@ function expire($secs=0) { if ($this->hive['VERB']=='GET' && $secs) { $time=microtime(TRUE); header_remove('Pragma'); - header('Cache-Control: max-age='.(int)$secs); + header('Cache-Control: max-age='.$secs); header('Expires: '.gmdate('r',$time+$secs)); header('Last-Modified: '.gmdate('r')); } @@ -1160,7 +1148,7 @@ function ip() { return isset($headers['Client-IP'])? $headers['Client-IP']: (isset($headers['X-Forwarded-For'])? - $headers['X-Forwarded-For']: + explode(',',$headers['X-Forwarded-For'])[0]: (isset($_SERVER['REMOTE_ADDR'])? $_SERVER['REMOTE_ADDR']:'')); } @@ -1182,11 +1170,12 @@ function trace(array $trace=NULL,$format=TRUE) { $trace=array_filter( $trace, function($frame) use($debug) { - return $debug && isset($frame['file']) && + return isset($frame['file']) && + ($debug>2 || ($frame['file']!=__FILE__ || $debug>1) && (empty($frame['function']) || !preg_match('/^(?:(?:trigger|user)_error|'. - '__call|call_user_func)/',$frame['function'])); + '__call|call_user_func)/',$frame['function']))); } ); if (!$format) @@ -1223,6 +1212,8 @@ function error($code,$text='',array $trace=NULL,$level=0) { $prior=$this->hive['ERROR']; $header=$this->status($code); $req=$this->hive['VERB'].' '.$this->hive['PATH']; + if ($this->hive['QUERY']) + $req.='?'.$this->hive['QUERY']; if (!$text) $text='HTTP '.$code.' ('.$req.')'; error_log($text); @@ -1315,6 +1306,24 @@ function mock($pattern, return $this->run(); } + /** + * Assemble url from alias name + * @return string + * @param $name string + * @param $params array|string + * @param $query string|array + **/ + function alias($name,$params=[],$query=NULL) { + if (!is_array($params)) + $params=$this->parse($params); + if (empty($this->hive['ALIASES'][$name])) + user_error(sprintf(self::E_Named,$name),E_USER_ERROR); + $url=$this->build($this->hive['ALIASES'][$name],$params); + if (is_array($query)) + $query=http_build_query($query); + return $url.($query?('?'.$query):''); + } + /** * Bind handler to route pattern * @return NULL @@ -1331,8 +1340,8 @@ function route($pattern,$handler,$ttl=0,$kbps=0) { $this->route($item,$handler,$ttl,$kbps); return; } - preg_match('/([\|\w]+)\h+(?:(?:@(\w+)\h*:\h*)?(@(\w+)|[^\h]+))'. - '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts); + preg_match('/([\|\w]+)\h+(?:(?:@?(.+?)\h*:\h*)?(@(\w+)|[^\h]+))'. + '(?:\h+\[('.implode('|',$types).')\])?/u',$pattern,$parts); if (isset($parts[2]) && $parts[2]) $this->hive['ALIASES'][$alias=$parts[2]]=$parts[3]; elseif (!empty($parts[4])) { @@ -1362,19 +1371,18 @@ function reroute($url=NULL,$permanent=FALSE) { $url=$this->hive['REALM']; if (is_array($url)) $url=call_user_func_array([$this,'alias'],$url); - elseif (preg_match('/^(?:@(\w+)(?:(\(.+?)\))*(\?.+)*)/',$url,$parts)) { - if (empty($this->hive['ALIASES'][$parts[1]])) - user_error(sprintf(self::E_Named,$parts[1]),E_USER_ERROR); + elseif (preg_match('/^(?:@?([^\/()?]+)(?:(\(.+?)\))*(\?.+)*)/', + $url,$parts) && + isset($this->hive['ALIASES'][$parts[1]])) $url=$this->hive['ALIASES'][$parts[1]]; - } $url=$this->build($url,isset($parts[2])?$this->parse($parts[2]):[]). (isset($parts[3])?$parts[3]:''); if (($handler=$this->hive['ONREROUTE']) && $this->call($handler,[$url,$permanent])!==FALSE) return; - if ($url[0]=='/') { + if ($url[0]=='/' && (empty($url[1]) || $url[1]!='/')) { $port=$this->hive['PORT']; - $port=in_array($port,[80,443])?'':':'.$port; + $port=in_array($port,[80,443])?'':(':'.$port); $url=$this->hive['SCHEME'].'://'. $this->hive['HOST'].$port.$this->hive['BASE'].$url; } @@ -1383,7 +1391,7 @@ function reroute($url=NULL,$permanent=FALSE) { $this->status($permanent?301:302); die; } - $this->mock('GET '.$url); + $this->mock('GET '.$url.' [cli]'); } /** @@ -1470,7 +1478,7 @@ function mask($pattern,$url=NULL) { '(?P<\3>[^\/\?]+)', $wild).'\/?$/'.$case.'um',$url,$args); foreach (array_keys($args) as $key) { - if (preg_match('/_\d+/',$key)) { + if (preg_match('/^_\d+$/',$key)) { if (empty($args['*'])) $args['*']=$args[$key]; else { @@ -1509,13 +1517,16 @@ function run() { array_multisort($paths,SORT_DESC,$keys,$vals); $this->hive['ROUTES']=array_combine($keys,$vals); // Convert to BASE-relative URL - $req=$this->rel(urldecode($this->hive['PATH'])); + $req=urldecode($this->hive['PATH']); + $preflight=FALSE; if ($cors=(isset($this->hive['HEADERS']['Origin']) && $this->hive['CORS']['origin'])) { $cors=$this->hive['CORS']; header('Access-Control-Allow-Origin: '.$cors['origin']); header('Access-Control-Allow-Credentials: '. - ($cors['credentials']?'true':'false')); + var_export($cors['credentials'],TRUE)); + $preflight= + isset($this->hive['HEADERS']['Access-Control-Request-Method']); } $allowed=[]; foreach ($this->hive['ROUTES'] as $pattern=>$routes) { @@ -1529,8 +1540,7 @@ function run() { $route=$routes[$ptr]; if (!$route) continue; - if ($this->hive['VERB']!='OPTIONS' && - isset($route[$this->hive['VERB']])) { + if (isset($route[$this->hive['VERB']]) && !$preflight) { if ($this->hive['VERB']=='GET' && preg_match('/.+\/$/',$this->hive['PATH'])) $this->reroute(substr($this->hive['PATH'],0,-1). @@ -1622,7 +1632,8 @@ function($id) use($args) { else echo $body; } - return $result; + if ($result || $this->hive['VERB']!='OPTIONS') + return $result; } $allowed=array_merge($allowed,array_keys($route)); } @@ -1815,33 +1826,36 @@ function relay($funcs,$args=NULL) { * Configure framework according to .ini-style file settings; * If optional 2nd arg is provided, template strings are interpreted * @return object - * @param $file string + * @param $source string|array * @param $allow bool **/ - function config($file,$allow=FALSE) { - preg_match_all( - '/(?<=^|\n)(?:'. - '\[(?
    .+?)\]|'. - '(?[^\h\r\n;].*?)\h*=\h*'. - '(?(?:\\\\\h*\r?\n|.+?)*)'. - ')(?=\r?\n|$)/', - $this->read($file), - $matches,PREG_SET_ORDER); - if ($matches) { - $sec='globals'; - $cmd=[]; - foreach ($matches as $match) { - if ($match['section']) { - $sec=$match['section']; - if (preg_match( - '/^(?!(?:global|config|route|map|redirect)s\b)'. - '((?:\.?\w)+)/i',$sec,$msec) && - !$this->exists($msec[0])) - $this->set($msec[0],NULL); - preg_match('/^(config|route|map|redirect)s\b|'. - '^((?:\.?\w)+)\s*\>\s*(.*)/i',$sec,$cmd); - } - else { + function config($source,$allow=FALSE) { + if (is_string($source)) + $source=$this->split($source); + foreach ($source as $file) { + preg_match_all( + '/(?<=^|\n)(?:'. + '\[(?
    .+?)\]|'. + '(?[^\h\r\n;].*?)\h*=\h*'. + '(?(?:\\\\\h*\r?\n|.+?)*)'. + ')(?=\r?\n|$)/', + $this->read($file), + $matches,PREG_SET_ORDER); + if ($matches) { + $sec='globals'; + $cmd=[]; + foreach ($matches as $match) { + if ($match['section']) { + $sec=$match['section']; + if (preg_match( + '/^(?!(?:global|config|route|map|redirect)s\b)'. + '((?:\.?\w)+)/i',$sec,$msec) && + !$this->exists($msec[0])) + $this->set($msec[0],NULL); + preg_match('/^(config|route|map|redirect)s\b|'. + '^((?:\.?\w)+)\s*\>\s*(.*)/i',$sec,$cmd); + continue; + } if ($allow) { $match['lval']=Preview::instance()-> resolve($match['lval']); @@ -1849,12 +1863,14 @@ function config($file,$allow=FALSE) { resolve($match['rval']); } if (!empty($cmd)) { - (isset($cmd[3])) ? - $this->call($cmd[3],[$match['lval'],$match['rval'],$cmd[2]]) - : call_user_func_array( + isset($cmd[3])? + $this->call($cmd[3], + [$match['lval'],$match['rval'],$cmd[2]]): + call_user_func_array( [$this,$cmd[1]], array_merge([$match['lval']], - str_getcsv($match['rval']))); + str_getcsv($match['rval'])) + ); } else { $rval=preg_replace( @@ -1875,7 +1891,8 @@ function($val) { return preg_replace('/\\\\"/','"',$val); }, // Mark quoted strings with 0x00 whitespace - str_getcsv(preg_replace('/(?[^:]+)(?:\:(?.+))?/', @@ -1923,9 +1940,11 @@ function mutex($id,$func,$args=NULL) { @unlink($lock); while (!($handle=@fopen($lock,'x')) && !connection_aborted()) usleep(mt_rand(0,100)); + $this->locks[$id]=$lock; $out=$this->call($func,$args); fclose($handle); @unlink($lock); + unset($this->locks[$id]); return $out; } @@ -2025,6 +2044,8 @@ function unload($cwd) { if (!($error=error_get_last()) && session_status()==PHP_SESSION_ACTIVE) session_commit(); + foreach ($this->locks as $lock) + @unlink($lock); $handler=$this->hive['UNLOAD']; if ((!$handler || $this->call($handler,$this)===FALSE) && $error && in_array($error['type'], @@ -2116,8 +2137,10 @@ function __unset($key) { * @param $key string * @param $args array **/ - function __call($key,$args) { - return call_user_func_array($this->get($key),$args); + function __call($key,array $args) { + if ($this->exists($key,$val)) + return call_user_func_array($val,$args); + user_error(sprintf(self::E_Method,$key),E_USER_ERROR); } //! Prohibit cloning @@ -2136,20 +2159,19 @@ function __construct() { @ini_set('register_globals',0); // Intercept errors/exceptions; PHP5.3-compatible $check=error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE)); - $fw=$this; set_exception_handler( - function($obj) use($fw) { - $fw->hive['EXCEPTION']=$obj; - $fw->error(500, + function($obj) { + $this->hive['EXCEPTION']=$obj; + $this->error(500, $obj->getmessage().' '. '['.$obj->getFile().':'.$obj->getLine().']', $obj->gettrace()); } ); set_error_handler( - function($level,$text) use($fw) { + function($level,$text,$file,$line) { if ($level & error_reporting()) - $fw->error(500,$text,NULL,$level); + $this->error(500,$text,NULL,$level); } ); if (!isset($_SERVER['SERVER_NAME'])) @@ -2222,7 +2244,8 @@ function($level,$text) use($fw) { if (!$cli) $base=rtrim($this->fixslashes( dirname($_SERVER['SCRIPT_NAME'])),'/'); - $uri=parse_url($_SERVER['REQUEST_URI']); + $uri=parse_url((preg_match('/^\w+:\/\//',$_SERVER['REQUEST_URI'])?'': + '//'.$_SERVER['SERVER_NAME']).$_SERVER['REQUEST_URI']); $path=preg_replace('/^'.preg_quote($base,'/').'/','',$uri['path']); session_cache_limiter(''); call_user_func_array('session_set_cookie_params', @@ -2236,8 +2259,10 @@ function($level,$text) use($fw) { 'httponly'=>TRUE ] ); - $port=0; - if (isset($_SERVER['SERVER_PORT'])) + $port=80; + if (isset($headers['X-Forwarded-Port'])) + $port=$headers['X-Forwarded-Port']; + elseif (isset($_SERVER['SERVER_PORT'])) $port=$_SERVER['SERVER_PORT']; // Default configuration $this->hive+=[ @@ -2281,6 +2306,7 @@ function($level,$text) use($fw) { $this->fallback, 'LOCALES'=>'./', 'LOGS'=>'./', + 'MB'=>extension_loaded('mbstring'), 'ONERROR'=>NULL, 'ONREROUTE'=>NULL, 'PACKAGE'=>self::PACKAGE, @@ -2295,8 +2321,8 @@ function($level,$text) use($fw) { 'QUIET'=>FALSE, 'RAW'=>FALSE, 'REALM'=>$scheme.'://'.$_SERVER['SERVER_NAME']. - ($port && $port!=80 && $port!=443? - (':'.$port):'').$_SERVER['REQUEST_URI'], + ($port && !in_array($port,[80,443])?(':'.$port):''). + $_SERVER['REQUEST_URI'], 'RESPONSE'=>'', 'ROOT'=>$_SERVER['DOCUMENT_ROOT'], 'ROUTES'=>[], @@ -2367,7 +2393,7 @@ function exists($key,&$val=NULL) { switch ($parts[0]) { case 'apc': case 'apcu': - $raw=apc_fetch($ndx); + $raw=call_user_func($parts[0].'_fetch',$ndx); break; case 'redis': $raw=$this->ref->get($ndx); @@ -2375,6 +2401,9 @@ function exists($key,&$val=NULL) { case 'memcache': $raw=memcache_get($this->ref,$ndx); break; + case 'memcached': + $raw=$this->ref->get($ndx); + break; case 'wincache': $raw=wincache_ucache_get($ndx); break; @@ -2415,11 +2444,13 @@ function set($key,$val,$ttl=0) { switch ($parts[0]) { case 'apc': case 'apcu': - return apc_store($ndx,$data,$ttl); + return call_user_func($parts[0].'_store',$ndx,$data,$ttl); case 'redis': - return $this->ref->set($ndx,$data, $ttl ? ['ex'=>$ttl] : []); + return $this->ref->set($ndx,$data,$ttl?['ex'=>$ttl]:[]); case 'memcache': return memcache_set($this->ref,$ndx,$data,0,$ttl); + case 'memcached': + return $this->ref->set($ndx,$data,$ttl); case 'wincache': return wincache_ucache_set($ndx,$data,$ttl); case 'xcache': @@ -2452,11 +2483,13 @@ function clear($key) { switch ($parts[0]) { case 'apc': case 'apcu': - return apc_delete($ndx); + return call_user_func($parts[0].'_delete',$ndx); case 'redis': return $this->ref->del($ndx); case 'memcache': return memcache_delete($this->ref,$ndx); + case 'memcached': + return $this->ref->delete($ndx); case 'wincache': return wincache_ucache_delete($ndx); case 'xcache': @@ -2471,40 +2504,32 @@ function clear($key) { * Clear contents of cache backend * @return bool * @param $suffix string - * @param $lifetime int **/ - function reset($suffix=NULL,$lifetime=0) { + function reset($suffix=NULL) { if (!$this->dsn) return TRUE; - $regex='/'.preg_quote($this->prefix.'.','/').'.+?'. + $regex='/'.preg_quote($this->prefix.'.','/').'.+'. preg_quote($suffix,'/').'/'; $parts=explode('=',$this->dsn,2); switch ($parts[0]) { case 'apc': case 'apcu': - $info=apc_cache_info('user'); + $info=call_user_func($parts[0].'_cache_info', + $parts[0]=='apcu'?FALSE:'user'); if (!empty($info['cache_list'])) { $key=array_key_exists('info', $info['cache_list'][0])?'info':'key'; - $mtkey=array_key_exists('mtime',$info['cache_list'][0])? - 'mtime':'modification_time'; foreach ($info['cache_list'] as $item) - if (preg_match($regex,$item[$key]) && - $item[$mtkey]+$lifetimeref->keys($this->prefix.'.*'.$suffix); - foreach($keys as $key) { - $val=$fw->unserialize($this->ref->get($key)); - if ($val[1]+$lifetimeref->del($key); - } + foreach($keys as $key) + $this->ref->del($key); return TRUE; case 'memcache': - $fw=Base::instance(); foreach (memcache_get_extended_stats( $this->ref,'slabs') as $slabs) foreach (array_filter(array_keys($slabs),'is_numeric') @@ -2513,17 +2538,19 @@ function reset($suffix=NULL,$lifetime=0) { $this->ref,'cachedump',$id) as $data) if (is_array($data)) foreach (array_keys($data) as $key) - if (preg_match($regex,$key) && - ($val=$fw->unserialize(memcache_get($this->ref,$key))) && - $val[1]+$lifetimeref,$key); return TRUE; + case 'memcached': + foreach ($this->ref->getallkeys()?:[] as $key) + if (preg_match($regex,$key)) + $this->ref->delete($key); + return TRUE; case 'wincache': $info=wincache_ucache_info(); foreach ($info['ucache_entries'] as $item) - if (preg_match($regex,$item['key_name']) && - $item['use_time']+$lifetimeprefix.'.'); @@ -2531,8 +2558,7 @@ function reset($suffix=NULL,$lifetime=0) { case 'folder': if ($glob=@glob($parts[1].'*')) foreach ($glob as $file) - if (preg_match($regex,basename($file)) && - filemtime($file)+$lifetimeref,$host,$port); } + elseif (preg_match('/^memcached=(.+)/',$dsn,$parts) && + extension_loaded('memcached')) + foreach ($fw->split($parts[1]) as $server) { + list($host,$port)=explode(':',$server)+[1=>11211]; + if (empty($this->ref)) + $this->ref=new Memcached(); + $this->ref->addServer($host,$port); + } if (empty($this->ref) && !preg_match('/^folder\h*=/',$dsn)) $dsn=($grep=preg_grep('/^(apc|wincache|xcache)/', array_map('strtolower',get_loaded_extensions())))? // Auto-detect current($grep): // Use filesystem as fallback - ('folder='.$fw->get('TEMP').'cache/'); + ('folder='.$fw->TEMP.'cache/'); if (preg_match('/^folder\h*=\h*(.+)/',$dsn,$parts) && !is_dir($parts[1])) mkdir($parts[1],Base::MODE,TRUE); } - $this->prefix=$fw->get('SEED'); + $this->prefix=$seed?:$fw->SEED; return $this->dsn=$dsn; } /** * Class constructor - * @return object * @param $dsn bool|string **/ function __construct($dsn=FALSE) { @@ -2595,10 +2629,14 @@ function __construct($dsn=FALSE) { //! View handler class View extends Prefab { + private + //! Temporary hive + $temp; + protected //! Template file - $view, - //! post-rendering handler + $file, + //! Post-rendering handler $trigger, //! Nesting level $level=0; @@ -2635,26 +2673,32 @@ function($val) use($fw) { * Create sandbox for template execution * @return string * @param $hive array + * @param $mime string **/ - protected function sandbox(array $hive=NULL) { - $this->level++; + protected function sandbox(array $hive=NULL,$mime=NULL) { $fw=Base::instance(); $implicit=FALSE; if (is_null($hive)) { $implicit=TRUE; $hive=$fw->hive(); } - if ($this->level<2 || $implicit) { - if ($fw->get('ESCAPE')) + if ($this->level<1 || $implicit) { + if (!$fw->CLI && !headers_sent() && + !preg_grep ('/^Content-Type:/',headers_list())) + header('Content-Type: '.($mime?:'text/html').'; '. + 'charset='.$fw->ENCODING); + if ($fw->ESCAPE) $hive=$this->esc($hive); if (isset($hive['ALIASES'])) $hive['ALIASES']=$fw->build($hive['ALIASES']); } - unset($fw,$implicit); - extract($hive); - unset($hive); + $this->temp=$hive; + unset($fw,$hive,$implicit,$mime); + extract($this->temp); + $this->temp=NULL; + $this->level++; ob_start(); - require($this->view); + require($this->file); $this->level--; return ob_get_clean(); } @@ -2667,21 +2711,18 @@ protected function sandbox(array $hive=NULL) { * @param $hive array * @param $ttl int **/ - function render($file,$mime='text/html',array $hive=NULL,$ttl=0) { + function render($file,$mime=NULL,array $hive=NULL,$ttl=0) { $fw=Base::instance(); $cache=Cache::instance(); if ($cache->exists($hash=$fw->hash($file),$data)) return $data; - foreach ($fw->split($fw->get('UI').';./') as $dir) - if (is_file($this->view=$fw->fixslashes($dir.$file))) { + foreach ($fw->split($fw->UI) as $dir) + if (is_file($this->file=$fw->fixslashes($dir.$file))) { if (isset($_COOKIE[session_name()]) && !headers_sent() && session_status()!=PHP_SESSION_ACTIVE) session_start(); $fw->sync('SESSION'); - if ($mime && !$fw->get('CLI') && !headers_sent()) - header('Content-Type: '.$mime.'; '. - 'charset='.$fw->get('ENCODING')); - $data=$this->sandbox($hive); + $data=$this->sandbox($hive,$mime); if(isset($this->trigger['afterrender'])) foreach($this->trigger['afterrender'] as $func) $data=$fw->call($func,$data); @@ -2706,14 +2747,12 @@ function afterrender($func) { class Preview extends View { protected - //! MIME type - $mime, //! token filter $filter=[ 'esc'=>'$this->esc', 'raw'=>'$this->raw', - 'alias'=>'\Base::instance()->alias', - 'format'=>'\Base::instance()->format' + 'alias'=>'Base::instance()->alias', + 'format'=>'Base::instance()->format' ]; /** @@ -2730,8 +2769,8 @@ function token($str) { foreach (Base::instance()->split($parts[2]) as $func) $str=is_string($cmd=$this->filter($func))? $cmd.'('.$str.')': - '\Base::instance()->call('. - '$this->filter(\''.$func.'\'),['.$str.'])'; + 'Base::instance()->'. + 'call($this->filter(\''.$func.'\'),['.$str.'])'; } return $str; } @@ -2745,6 +2784,7 @@ function token($str) { function filter($key=NULL,$func=NULL) { if (!$key) return array_keys($this->filter); + $key=strtolower($key); if (!$func) return $this->filter[$key]; $this->filter[$key]=$func; @@ -2756,22 +2796,20 @@ function filter($key=NULL,$func=NULL) { * @param $node string **/ protected function build($node) { - $self=$this; return preg_replace_callback( - '/\{\-(.+?)\-\}|\{\{(.+?)\}\}(\n+)?|(\{\*.*?\*\})/s', - function($expr) use($self) { + '/\{\-(.+?)\-\}|\{\{(.+?)\}\}(\n*)/s', + function($expr) { if ($expr[1]) return $expr[1]; - $str=trim($self->token($expr[2])); - return empty($expr[4])? - (''. - (isset($expr[3])?$expr[3]."\n":'')): - ''; + $str='token($expr[2])).' ?>'; + if (isset($expr[3])) + $str.=$expr[3]; + return $str; }, preg_replace_callback( '/\{~(.+?)~\}/s', - function($expr) use($self) { - return 'token($expr[1]).' ?>'; + function($expr) { + return 'token($expr[1]).' ?>'; }, $node ) @@ -2783,14 +2821,43 @@ function($expr) use($self) { * @return string * @param $str string * @param $hive array + * @param $ttl int + * @param $persist bool **/ - function resolve($str,array $hive=NULL) { - if (!$hive) - $hive=\Base::instance()->hive(); - extract($hive); - ob_start(); - eval(' ?>'.$this->build($str).'exists($hash=$fw->hash($str),$data)) + return $data; + // Remove PHP code and comments + $text=preg_replace( + '/\h*<\?(?!xml)(?:php|\s*=)?.+?\?>\h*|\{\*.+?\*\}/is','',$str); + if ($persist) { + if (!is_dir($tmp=$fw->TEMP)) + mkdir($tmp,Base::MODE,TRUE); + if (!is_file($this->file=($tmp. + $fw->SEED.'.'.$hash.'.php'))) + $fw->write($this->file,$this->build($text)); + if (isset($_COOKIE[session_name()]) && + !headers_sent() && session_status()!=PHP_SESSION_ACTIVE) + session_start(); + $fw->sync('SESSION'); + $data=$this->sandbox($hive); + } + else { + if (!$hive) + $hive=$fw->hive(); + if ($fw->ESCAPE) + $hive=$this->esc($hive); + extract($hive); + unset($hive); + ob_start(); + eval(' ?>'.$this->build($text).'set($hash,$data,$ttl); + return $data; } /** @@ -2804,39 +2871,32 @@ function resolve($str,array $hive=NULL) { function render($file,$mime=NULL,array $hive=NULL,$ttl=0) { $fw=Base::instance(); $cache=Cache::instance(); - if ($mime) - $this->mime=$mime; - elseif (!$this->mime) - $this->mime='text/html'; - if (!is_dir($tmp=$fw->get('TEMP'))) + if (!is_dir($tmp=$fw->TEMP)) mkdir($tmp,Base::MODE,TRUE); - foreach ($fw->split($fw->get('UI')) as $dir) { + foreach ($fw->split($fw->UI) as $dir) { if ($cache->exists($hash=$fw->hash($dir.$file),$data)) return $data; if (is_file($view=$fw->fixslashes($dir.$file))) { - if (!is_file($this->view=($tmp. - $fw->get('SEED').'.'.$fw->hash($view).'.php')) || - filemtime($this->view)file=($tmp. + $fw->SEED.'.'.$fw->hash($view).'.php')) || + filemtime($this->file)\h*'. - '|\{\*.+?\*\}/is','', + '/\h*<\?(?!xml)(?:php|\s*=)?.+?\?>\h*|'. + '\{\*.+?\*\}/is','', $fw->read($view)); if (method_exists($this,'parse')) $text=$this->parse($text); - $fw->write($this->view,$this->build($text)); + $fw->write($this->file,$this->build($text)); } if (isset($_COOKIE[session_name()]) && !headers_sent() && session_status()!=PHP_SESSION_ACTIVE) session_start(); $fw->sync('SESSION'); - if (!$fw->get('CLI') && !headers_sent()) - header('Content-Type: '.$this->mime.'; '. - 'charset='.$fw->get('ENCODING')); - $data=$this->sandbox($hive); + $data=$this->sandbox($hive,$mime); if(isset($this->trigger['afterrender'])) foreach ($this->trigger['afterrender'] as $func) - $data = $fw->call($func, $data); + $data=$fw->call($func, $data); if ($ttl) $cache->set($hash,$data,$ttl); return $data; diff --git a/lib/basket.php b/lib/basket.php index ffb498e7..f939c7cd 100644 --- a/lib/basket.php +++ b/lib/basket.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -193,7 +193,7 @@ function drop() { **/ function copyfrom($var) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; foreach ($var as $key=>$val) $this->set($key,$val); } diff --git a/lib/bcrypt.php b/lib/bcrypt.php index 2f44d39b..23554719 100644 --- a/lib/bcrypt.php +++ b/lib/bcrypt.php @@ -1,9 +1,7 @@ . * -* @deprecated use http://php.net/manual/en/ref.password.php instead (PHP 5.5+ only) **/ +/** +* 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 @@ -52,8 +53,6 @@ function hash($pw,$salt=NULL,$cost=self::COST) { else { $raw=16; $iv=''; - if (extension_loaded('mcrypt')) - $iv=mcrypt_create_iv($raw,MCRYPT_DEV_URANDOM); if (!$iv && extension_loaded('openssl')) $iv=openssl_random_pseudo_bytes($raw); if (!$iv) diff --git a/lib/cli/ws.php b/lib/cli/ws.php index 390fa733..c11e8add 100644 --- a/lib/cli/ws.php +++ b/lib/cli/ws.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/db/cursor.php b/lib/db/cursor.php index 9ea7cdc9..6edbc0e0 100644 --- a/lib/db/cursor.php +++ b/lib/db/cursor.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -70,9 +70,10 @@ 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,$ttl=0); + abstract function count($filter=NULL,array $options=NULL,$ttl=0); /** * Insert new record @@ -142,24 +143,26 @@ function findone($filter=NULL,array $options=NULL,$ttl=0) { * @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) { - $total=$this->count($filter,$ttl); + $pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0,$bounce=TRUE) { + $total=$this->count($filter,$options,$ttl); $count=ceil($total/$size); - $pos=max(0,min($pos,$count-1)); + if ($bounce) + $pos=max(0,min($pos,$count-1)); return [ - 'subset'=>$this->find($filter, + 'subset'=>($bounce || $pos<$count)?$this->find($filter, array_merge( $options?:[], ['limit'=>$size,'offset'=>$pos*$size] ), $ttl - ), + ):[], 'total'=>$total, 'limit'=>$size, 'count'=>$count, - 'pos'=>$pos<$count?$pos:0 + 'pos'=>$bounce?($pos<$count?$pos:0):$pos ]; } diff --git a/lib/db/jig.php b/lib/db/jig.php index d4337933..d0730144 100644 --- a/lib/db/jig.php +++ b/lib/db/jig.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -80,7 +80,7 @@ function write($file,array $data=NULL) { $fw=\Base::instance(); switch ($this->format) { case self::FORMAT_JSON: - $out=json_encode($data,@constant('JSON_PRETTY_PRINT')); + $out=json_encode($data,JSON_PRETTY_PRINT); break; case self::FORMAT_Serialized: $out=$fw->serialize($data); diff --git a/lib/db/jig/mapper.php b/lib/db/jig/mapper.php index 74335fcd..135dd612 100644 --- a/lib/db/jig/mapper.php +++ b/lib/db/jig/mapper.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -120,21 +120,20 @@ function cast($obj=NULL) { * @param $str string **/ function token($str) { - $self=$this; $str=preg_replace_callback( '/(?stringify(substr($expr[1],1)): (preg_match('/^\w+/', - $mix=$self->token($expr[2]))? + $mix=$this->token($expr[2]))? $fw->stringify($mix): $mix)). ']'; @@ -168,7 +167,7 @@ function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) { $db=$this->db; $now=microtime(TRUE); $data=[]; - if (!$fw->get('CACHE') || !$ttl || !($cached=$cache->exists( + if (!$fw->CACHE || !$ttl || !($cached=$cache->exists( $hash=$fw->hash($this->db->dir(). $fw->stringify([$filter,$options])).'.jig',$data)) || $cached[0]+$ttlget('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$data,$ttl); } @@ -286,11 +285,12 @@ function($val1,$val2) use($cols) { * Count records that match criteria * @return int * @param $filter array + * @param $options array * @param $ttl int **/ - function count($filter=NULL,$ttl=0) { + function count($filter=NULL,array $options=NULL,$ttl=0) { $now=microtime(TRUE); - $out=count($this->find($filter,NULL,$ttl,FALSE)); + $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; @@ -366,15 +366,16 @@ function update() { * Delete current record * @return bool * @param $filter array + * @param $quick bool **/ - function erase($filter=NULL) { + 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()) + if (!$mapper->erase(null,$quick)) return FALSE; return TRUE; } @@ -384,7 +385,7 @@ function erase($filter=NULL) { } else return FALSE; - if (isset($this->trigger['beforeerase']) && + if (!$quick && isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], [$this,$pkey])===FALSE) return FALSE; @@ -403,7 +404,7 @@ function erase($filter=NULL) { $db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. $this->file.' [erase] '. ($filter?preg_replace($keys,$vals,$filter[0],1):'')); - if (isset($this->trigger['aftererase'])) + if (!$quick && isset($this->trigger['aftererase'])) \Base::instance()->call($this->trigger['aftererase'], [$this,$pkey]); return TRUE; @@ -427,7 +428,7 @@ function reset() { **/ function copyfrom($var,$func=NULL) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) diff --git a/lib/db/jig/session.php b/lib/db/jig/session.php index d3a92d9b..e7fecb3a 100644 --- a/lib/db/jig/session.php +++ b/lib/db/jig/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -59,21 +59,22 @@ function close() { /** * Return session data in serialized format - * @return string|FALSE + * @return string * @param $id string **/ function read($id) { $this->load(['@session_id=?',$this->sid=$id]); if ($this->dry()) - return FALSE; + 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) + // NB: `session_destroy` can't be called at that stage; + // `session_start` not completed $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -178,12 +179,12 @@ function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL,$key=NULL) { ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + $this->_ip=$fw->IP; } } diff --git a/lib/db/mongo.php b/lib/db/mongo.php index fedec0cd..08481ebf 100644 --- a/lib/db/mongo.php +++ b/lib/db/mongo.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -37,6 +37,8 @@ class Mongo { $dsn, //! MongoDB object $db, + //! Legacy flag + $legacy, //! MongoDB log $log; @@ -63,7 +65,7 @@ function uuid() { **/ function log($flag=TRUE) { if ($flag) { - $cursor=$this->selectcollection('system.profile')->find(); + $cursor=$this->db->selectcollection('system.profile')->find(); foreach (iterator_to_array($cursor) as $frame) if (!preg_match('/\.system\..+$/',$frame['ns'])) $this->log.=date('r',$frame['ts']->sec).' ('. @@ -76,7 +78,10 @@ function log($flag=TRUE) { PHP_EOL; } else { $this->log=FALSE; - $this->setprofilinglevel(-1); + if ($this->legacy) + $this->db->setprofilinglevel(-1); + else + $this->db->command(['profile'=>-1]); } return $this->log; } @@ -87,8 +92,12 @@ function log($flag=TRUE) { **/ function drop() { $out=$this->db->drop(); - if ($this->log!==FALSE) - $this->setprofilinglevel(2); + if ($this->log!==FALSE) { + if ($this->legacy) + $this->db->setprofilinglevel(2); + else + $this->db->command(['profile'=>2]); + } return $out; } @@ -102,6 +111,14 @@ 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() { } @@ -114,9 +131,14 @@ private function __clone() { **/ function __construct($dsn,$dbname,array $options=NULL) { $this->uuid=\Base::instance()->hash($this->dsn=$dsn); - $class=class_exists('\MongoClient')?'\MongoClient':'\Mongo'; - $this->db=new \MongoDB(new $class($dsn,$options?:[]),$dbname); - $this->setprofilinglevel(2); + 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]); + } } } diff --git a/lib/db/mongo/mapper.php b/lib/db/mongo/mapper.php index 411c524b..f038b6c0 100644 --- a/lib/db/mongo/mapper.php +++ b/lib/db/mongo/mapper.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -28,6 +28,8 @@ class Mapper extends \DB\Cursor { protected //! MongoDB wrapper $db, + //! Legacy flag + $legacy, //! Mongo collection $collection, //! Mongo document @@ -144,7 +146,7 @@ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) { ] ); $tmp=$this->db->selectcollection( - $fw->get('HOST').'.'.$fw->get('BASE').'.'. + $fw->HOST.'.'.$fw->BASE.'.'. uniqid(NULL,TRUE).'.tmp' ); $tmp->batchinsert($grp['retval'],['w'=>1]); @@ -155,19 +157,29 @@ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) { $filter=$filter?:[]; $collection=$this->collection; } - $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(); + 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->get('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } @@ -200,16 +212,17 @@ 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 **/ - function count($filter=NULL,$ttl=0) { + function count($filter=NULL,array $options=NULL,$ttl=0) { $fw=\Base::instance(); $cache=\Cache::instance(); if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify( [$filter])).'.mongo',$result)) || !$ttl || $cached[0]+$ttlcollection->count($filter?:[]); - if ($fw->get('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } @@ -240,8 +253,14 @@ function insert() { \Base::instance()->call($this->trigger['beforeinsert'], [$this,['_id'=>$this->document['_id']]])===FALSE) return $this->document; - $this->collection->insert($this->document); - $pkey=['_id'=>$this->document['_id']]; + 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]); @@ -259,8 +278,11 @@ function update() { \Base::instance()->call($this->trigger['beforeupdate'], [$this,$pkey])===FALSE) return $this->document; - $this->collection->update( - $pkey,$this->document,['upsert'=>TRUE]); + $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]); @@ -270,18 +292,29 @@ function update() { /** * Delete current record * @return bool + * @param $quick bool * @param $filter array **/ - function erase($filter=NULL) { - if ($filter) - return $this->collection->remove($filter); + 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->collection-> - remove(['_id'=>$this->document['_id']]); + $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'], @@ -306,7 +339,7 @@ function reset() { **/ function copyfrom($var,$func=NULL) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) @@ -357,6 +390,7 @@ function getiterator() { **/ 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(); diff --git a/lib/db/mongo/session.php b/lib/db/mongo/session.php index 5ea894b8..98bafd63 100644 --- a/lib/db/mongo/session.php +++ b/lib/db/mongo/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -59,21 +59,22 @@ function close() { /** * Return session data in serialized format - * @return string|FALSE + * @return string * @param $id string **/ function read($id) { $this->load(['session_id'=>$this->sid=$id]); if ($this->dry()) - return FALSE; + 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) + // NB: `session_destroy` can't be called at that stage; + // `session_start` not completed $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -178,12 +179,12 @@ function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL,$key=NULL) ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + $this->_ip=$fw->IP; } } diff --git a/lib/db/sql.php b/lib/db/sql.php index 48b49cc1..5ded4da2 100644 --- a/lib/db/sql.php +++ b/lib/db/sql.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -113,16 +113,16 @@ function type($val) { /** * Cast value to PHP type - * @return scalar + * @return mixed * @param $type string - * @param $val scalar + * @param $val mixed **/ function value($type,$val) { switch ($type) { case self::PARAM_FLOAT: - return (float)(is_string($val) - ? str_replace(',','.',preg_replace('/([.,])(?!\d+$)/','',$val)) - : $val); + if (!is_string($val)) + $val=str_replace(',','.',$val); + return $val; case \PDO::PARAM_NULL: return (unset)$val; case \PDO::PARAM_INT: @@ -185,7 +185,7 @@ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE,$stamp=FALSE) { continue; $now=microtime(TRUE); $keys=$vals=[]; - if ($fw->get('CACHE') && $ttl && ($cached=$cache->exists( + 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)) { @@ -246,7 +246,7 @@ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE,$stamp=FALSE) { $result[$pos][trim($key,'\'"[]`')]=$val; } $this->rows=count($result); - if ($fw->get('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } @@ -297,6 +297,13 @@ function log($flag=TRUE) { * @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 @@ -354,32 +361,34 @@ function schema($table,$fields=NULL,$ttl=0) { ]; 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,$ttl) as $row) { - if (!$fields || in_array($row[$val[1]],$fields)) + 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'=> - preg_match('/int\b|integer/i',$row[$val[2]])? - \PDO::PARAM_INT: - (preg_match('/bool/i',$row[$val[2]])? - \PDO::PARAM_BOOL: - (preg_match( - '/blob|bytea|image|binary/i', - $row[$val[2]])?\PDO::PARAM_LOB: - (preg_match( - '/float|decimal|real|numeric|double/i', - $row[$val[2]])?self::PARAM_FLOAT: - \PDO::PARAM_STR))), + '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); @@ -492,7 +501,7 @@ function __construct($dsn,$user=NULL,$pw=NULL,array $options=NULL) { $options=[]; if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql') $options+=[\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '. - strtolower(str_replace('-','',$fw->get('ENCODING'))).';']; + strtolower(str_replace('-','',$fw->ENCODING)).';']; $this->pdo=new \PDO($dsn,$user,$pw,$options); $this->engine=$this->pdo->getattribute(\PDO::ATTR_DRIVER_NAME); } diff --git a/lib/db/sql/mapper.php b/lib/db/sql/mapper.php index ac4dd87c..bddd51b0 100644 --- a/lib/db/sql/mapper.php +++ b/lib/db/sql/mapper.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -39,7 +39,9 @@ class Mapper extends \DB\Cursor { //! Defined fields $fields, //! Adhoc fields - $adhoc=[]; + $adhoc=[], + //! Dynamic properties + $props=[]; /** * Return database type @@ -57,15 +59,6 @@ function table() { return $this->source; } - /** - * Return TRUE if field is defined - * @return bool - * @param $key string - **/ - function exists($key) { - return array_key_exists($key,$this->fields+$this->adhoc); - } - /** * Return TRUE if any/specified field value has changed * @return bool @@ -80,6 +73,15 @@ function changed($key=NULL) { 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 @@ -95,12 +97,14 @@ function set($key,$val) { $this->fields[$key]['changed']=TRUE; return $this->fields[$key]['value']=$val; } - // adjust result on existing expressions + // Adjust result on existing expressions if (isset($this->adhoc[$key])) $this->adhoc[$key]['value']=$val; - else + 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; } @@ -116,6 +120,8 @@ function &get($key) { 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); } @@ -127,26 +133,22 @@ function &get($key) { function clear($key) { if (array_key_exists($key,$this->adhoc)) unset($this->adhoc[$key]); + else + unset($this->props[$key]); } /** - * Get PHP type equivalent of PDO constant - * @return string - * @param $pdo string + * Invoke dynamic method + * @return mixed + * @param $func string + * @param $args array **/ - function type($pdo) { - switch ($pdo) { - case \PDO::PARAM_NULL: - return 'unset'; - case \PDO::PARAM_INT: - return 'int'; - case \PDO::PARAM_BOOL: - return 'bool'; - case \PDO::PARAM_STR: - return 'string'; - case \DB\SQL::PARAM_FLOAT: - return 'float'; - } + function __call($func,$args) { + return call_user_func_array( + (array_key_exists($func,$this->props)? + $this->props[$func]: + $this->$func),$args + ); } /** @@ -192,14 +194,13 @@ function($row) { } /** - * Build query string and execute - * @return static[] + * Build query string and arguments + * @return array * @param $fields string * @param $filter string|array * @param $options array - * @param $ttl int|array **/ - function select($fields,$filter=NULL,array $options=NULL,$ttl=0) { + function stringify($fields,$filter=NULL,array $options=NULL) { if (!$options) $options=[]; $options+=[ @@ -226,7 +227,8 @@ function($str) use($db) { return preg_replace_callback( '/\b(\w+)\h*(HAVING.+|$)/i', function($parts) use($db) { - return $db->quotekey($parts[1]).(isset($parts[2])?(' '.$parts[2]):''); + return $db->quotekey($parts[1]). + (isset($parts[2])?(' '.$parts[2]):''); }, $str ); @@ -275,6 +277,19 @@ function($str) use($db) { if ($options['offset']) $sql.=' OFFSET '.(int)$options['offset']; } + return [$sql,$args]; + } + + /** + * Build query string and execute + * @return object + * @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) { @@ -284,8 +299,6 @@ function($str) use($db) { $val=$this->db->value( $this->fields[$field]['pdo_type'],$val); } - elseif (array_key_exists($field,$this->adhoc)) - $this->adhoc[$field]['value']=$val; unset($val); } $out[]=$this->factory($row); @@ -324,24 +337,18 @@ function find($filter=NULL,array $options=NULL,$ttl=0) { * Count records that match criteria * @return int * @param $filter string|array + * @param $options array * @param $ttl int|array **/ - function count($filter=NULL,$ttl=0) { - $sql='SELECT COUNT(*) AS '. - $this->db->quotekey('rows').' FROM '.$this->table; - $args=[]; - if ($filter) { - 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; - } - $sql.=' WHERE '.$filter; - } + function count($filter=NULL,array $options=NULL,$ttl=0) { + $adhoc=''; + foreach ($this->adhoc as $key=>$field) + $adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key); + list($sql,$args)=$this->stringify('*'.$adhoc,$filter,$options); + $sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '. + 'FROM ('.$sql.') AS '.$this->db->quotekey('_temp'); $result=$this->db->exec($sql,$args,$ttl); - return $result[0]['rows']; + return $result[0]['_rows']; } /** @@ -409,8 +416,6 @@ function insert() { $actr++; $ckeys[]=$key; } - $field['changed']=FALSE; - unset($field); } if ($fields) { $this->db->exec( @@ -430,7 +435,7 @@ function insert() { if ($this->engine!='oci' && !($this->engine=='pgsql' && !$seq)) $this->_id=$this->db->lastinsertid($seq); // Reload to obtain default and auto-increment field values - if ($inc || $filter) + if ($reload=($inc || $filter)) $this->load($inc? [$inc.'=?',$this->db->value( $this->fields[$inc]['pdo_type'],$this->_id)]: @@ -438,6 +443,13 @@ function insert() { 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; } @@ -473,10 +485,10 @@ function update() { if ($pairs) { $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]); } + 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; @@ -489,10 +501,17 @@ function update() { /** * Delete current record * @return int + * @param $quick bool * @param $filter string|array **/ - function erase($filter=NULL) { - if ($filter) { + 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])? @@ -502,7 +521,8 @@ function erase($filter=NULL) { list($filter)=$filter; } return $this->db-> - exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args); + exec('DELETE FROM '.$this->table. + ($filter?' WHERE '.$filter:'').';',$args); } $args=[]; $ctr=0; @@ -566,7 +586,7 @@ function reset() { **/ function copyfrom($var,$func=NULL) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) diff --git a/lib/db/sql/session.php b/lib/db/sql/session.php index 56c1f430..fdd3f388 100644 --- a/lib/db/sql/session.php +++ b/lib/db/sql/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -59,13 +59,13 @@ function close() { /** * Return session data in serialized format - * @return string|FALSE + * @return string * @param $id string **/ function read($id) { $this->load(['session_id=?',$this->sid=$id]); if ($this->dry()) - return FALSE; + return ''; if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) { $fw=\Base::instance(); if (!isset($this->onsuspect) || @@ -73,7 +73,7 @@ function read($id) { //NB: `session_destroy` can't be called at that stage (`session_start` not completed) $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -200,12 +200,12 @@ function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$ ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + $this->_ip=$fw->IP; } } diff --git a/lib/f3.php b/lib/f3.php index d68cd1fb..3b96a63c 100644 --- a/lib/f3.php +++ b/lib/f3.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/image.php b/lib/image.php index a8514c22..3efc0cca 100644 --- a/lib/image.php +++ b/lib/image.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -409,7 +409,7 @@ function captcha($font,$size=24,$len=5, return FALSE; } $fw=Base::instance(); - foreach ($fw->split($path?:$fw->get('UI').';./') as $dir) + 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(), @@ -448,7 +448,7 @@ function captcha($font,$size=24,$len=5, } imagesavealpha($this->data,TRUE); if ($key) - $fw->set($key,$seed); + $fw->$key=$seed; return $this->save(); } user_error(self::E_Font,E_USER_ERROR); @@ -480,7 +480,7 @@ function render() { $format=$args?array_shift($args):'png'; if (PHP_SAPI!='cli') { header('Content-Type: image/'.$format); - header('X-Powered-By: '.Base::instance()->get('PACKAGE')); + header('X-Powered-By: '.Base::instance()->PACKAGE); } call_user_func_array( 'image'.$format, @@ -518,10 +518,10 @@ function data() { function save() { $fw=Base::instance(); if ($this->flag) { - if (!is_dir($dir=$fw->get('TEMP'))) + if (!is_dir($dir=$fw->TEMP)) mkdir($dir,Base::MODE,TRUE); $this->count++; - $fw->write($dir.'/'.$fw->get('SEED').'.'. + $fw->write($dir.'/'.$fw->SEED.'.'. $fw->hash($this->file).'-'.$this->count.'.png', $this->dump()); } @@ -535,8 +535,8 @@ function save() { **/ function restore($state=1) { $fw=Base::instance(); - if ($this->flag && is_file($file=($path=$fw->get('TEMP'). - $fw->get('SEED').'.'.$fw->hash($this->file).'-').$state.'.png')) { + 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)); @@ -589,7 +589,7 @@ function __construct($file=NULL,$flag=FALSE,$path=NULL) { // Create image from file $this->file=$file; if (!isset($path)) - $path=$fw->get('UI').';./'; + $path=$fw->UI.';./'; foreach ($fw->split($path,FALSE) as $dir) if (is_file($dir.$file)) return $this->load($fw->read($dir.$file)); @@ -605,7 +605,7 @@ function __destruct() { if (is_resource($this->data)) { imagedestroy($this->data); $fw=Base::instance(); - $path=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash($this->file); + $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)) diff --git a/lib/log.php b/lib/log.php old mode 100755 new mode 100644 index 2bbe4c27..422baf25 --- a/lib/log.php +++ b/lib/log.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -59,7 +59,7 @@ function erase() { **/ function __construct($file) { $fw=Base::instance(); - if (!is_dir($dir=$fw->get('LOGS'))) + if (!is_dir($dir=$fw->LOGS)) mkdir($dir,Base::MODE,TRUE); $this->file=$dir.$file; } diff --git a/lib/magic.php b/lib/magic.php index 76ea221e..2502bf19 100644 --- a/lib/magic.php +++ b/lib/magic.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/markdown.php b/lib/markdown.php index 1c3fb163..18995fb7 100644 --- a/lib/markdown.php +++ b/lib/markdown.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -64,7 +64,7 @@ protected function _pre($str) { protected function _fence($hint,$str) { $str=$this->snip($str); $fw=Base::instance(); - if ($fw->get('HIGHLIGHT')) { + if ($fw->HIGHLIGHT) { switch (strtolower($hint)) { case 'php': $str=$fw->highlight($str); @@ -288,20 +288,19 @@ protected function _p($str) { if (strlen($str)) { if (preg_match('/^(.+?\n)([>#].+)$/s',$str,$parts)) return $this->_p($parts[1]).$this->build($parts[2]); - $self=$this; $str=preg_replace_callback( '/([^<>\[]+)?(<[\?%].+?[\?%]>|<.+?>|\[.+?\]\s*\(.+?\))|'. '(.+)/s', - function($expr) use($self) { + function($expr) { $tmp=''; if (isset($expr[4])) - $tmp.=$self->esc($expr[4]); + $tmp.=$this->esc($expr[4]); else { if (isset($expr[1])) - $tmp.=$self->esc($expr[1]); + $tmp.=$this->esc($expr[1]); $tmp.=$expr[2]; if (isset($expr[3])) - $tmp.=$self->esc($expr[3]); + $tmp.=$this->esc($expr[3]); } return $tmp; }, @@ -347,17 +346,16 @@ function($expr) { * @param $str string **/ protected function _img($str) { - $self=$this; return preg_replace_callback( '/!(?:\[(.+?)\])?\h*\(?(?:\h*"(.*?)"\h*)?\)/', - function($expr) use($self) { + function($expr) { return ''.$self->esc($expr[1]).''; + (' title="'.$this->esc($expr[3]).'"')).' />'; }, $str ); @@ -369,15 +367,14 @@ function($expr) use($self) { * @param $str string **/ protected function _a($str) { - $self=$this; return preg_replace_callback( '/(??(?:\h*"(.*?)"\h*)?\)/', - function($expr) use($self) { - return ''.$self->scan($expr[1]).''; + (' title="'.$this->esc($expr[3]).'"')). + '>'.$this->scan($expr[1]).''; }, $str ); @@ -389,12 +386,11 @@ function($expr) use($self) { * @param $str string **/ protected function _auto($str) { - $self=$this; return preg_replace_callback( '/`.*?<(.+?)>.*?`|<(.+?)>/', - function($expr) use($self) { + function($expr) { if (empty($expr[1]) && parse_url($expr[2],PHP_URL_SCHEME)) { - $expr[2]=$self->esc($expr[2]); + $expr[2]=$this->esc($expr[2]); return ''.$expr[2].''; } return $expr[0]; @@ -409,12 +405,11 @@ function($expr) use($self) { * @param $str string **/ protected function _code($str) { - $self=$this; return preg_replace_callback( '/`` (.+?) ``|(?'. - $self->esc(empty($expr[1])?$expr[2]:$expr[1]).''; + $this->esc(empty($expr[1])?$expr[2]:$expr[1]).''; }, $str ); @@ -436,7 +431,7 @@ function esc($str) { foreach ($this->special as $key=>$val) $str=preg_replace('/'.preg_quote($key,'/').'/i',$val,$str); return htmlspecialchars($str,ENT_COMPAT, - Base::instance()->get('ENCODING'),FALSE); + Base::instance()->ENCODING,FALSE); } /** @@ -488,7 +483,6 @@ protected function build($str) { 'p'=>'/^(.+?(?:\n{2,}|\n*$))/s' ]; } - $self=$this; // Treat lines with nothing but whitespaces as empty lines $str=preg_replace('/\n\h+(?=\n)/',"\n",$str); // Initialize block parser @@ -509,16 +503,16 @@ protected function build($str) { '/(?esc($match[2]).'"'. + (''. + $this->esc($match[3]).'"')).'>'. // Link - $self->scan( + $this->scan( empty($expr[3])? (empty($expr[1])? $expr[4]: @@ -530,11 +524,11 @@ function($expr) use($match,$self) { (empty($expr[2])? '': (' alt="'. - $self->esc($expr[3]).'"')). + $this->esc($expr[3]).'"')). (empty($match[3])? '': (' title="'. - $self->esc($match[3]).'"')). + $this->esc($match[3]).'"')). ' />'); }, $tmp=$dst diff --git a/lib/matrix.php b/lib/matrix.php index f20e9890..f3d71821 100644 --- a/lib/matrix.php +++ b/lib/matrix.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/session.php b/lib/session.php index 583193d9..02f52a06 100644 --- a/lib/session.php +++ b/lib/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -58,13 +58,13 @@ function close() { /** * Return session data in serialized format - * @return string|FALSE + * @return string * @param $id string **/ function read($id) { $this->sid=$id; if (!$data=$this->_cache->get($id.'.@')) - return FALSE; + return ''; if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) { $fw=Base::instance(); if (!isset($this->onsuspect) || @@ -72,7 +72,7 @@ function read($id) { //NB: `session_destroy` can't be called at that stage (`session_start` not completed) $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -87,7 +87,7 @@ function read($id) { **/ function write($id,$data) { $fw=Base::instance(); - $jar=$fw->get('JAR'); + $jar=$fw->JAR; $this->_cache->set($id.'.@', [ 'data'=>$data, @@ -181,12 +181,12 @@ function __construct($onsuspect=NULL,$key=NULL,$cache=null) { ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + $this->_ip=$fw->IP; } } diff --git a/lib/smtp.php b/lib/smtp.php index 00ee9b2d..57c5f525 100644 --- a/lib/smtp.php +++ b/lib/smtp.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -45,6 +45,8 @@ class SMTP extends Magic { $user, //! Password $pw, + //! TLS/SSL stream context + $context, //! TCP/IP socket $socket, //! Server-client conversation @@ -117,7 +119,7 @@ function log() { * Send SMTP command and record server response * @return string * @param $cmd string - * @param $log bool + * @param $log bool|string * @param $mock bool **/ protected function dialog($cmd=NULL,$log=TRUE,$mock=FALSE) { @@ -177,7 +179,7 @@ function attach($file,$alias=NULL,$cid=NULL) { * Transmit message * @return bool * @param $message string - * @param $log bool + * @param $log bool|string * @param $mock bool **/ function send($message,$log=TRUE,$mock=FALSE) { @@ -192,7 +194,9 @@ function send($message,$log=TRUE,$mock=FALSE) { // Connect to the server if (!$mock) { $socket=&$this->socket; - $socket=@fsockopen($this->host,$this->port,$errno,$errstr); + $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; @@ -202,13 +206,13 @@ function send($message,$log=TRUE,$mock=FALSE) { // Get server's initial response $this->dialog(NULL,TRUE,$mock); // Announce presence - $reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock); + $reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock); if (strtolower($this->scheme)=='tls') { $this->dialog('STARTTLS',$log,$mock); if (!$mock) stream_socket_enable_crypto( $socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT); - $reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock); + $reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock); } if (preg_match('/8BITMIME/',$reply)) $headers['Content-Transfer-Encoding']='8bit'; @@ -221,16 +225,16 @@ function send($message,$log=TRUE,$mock=FALSE) { // Authenticate $this->dialog('AUTH LOGIN',$log,$mock); $this->dialog(base64_encode($this->user),$log,$mock); - $auth_rply=$this->dialog(base64_encode($this->pw),$log,$mock); - if (!preg_match('/^235\s.*/',$auth_rply)) { + $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['Message-Id'])) + $headers['Message-Id']='<'.uniqid('',TRUE).'@'.$this->host.'>'; if (empty($headers['Date'])) $headers['Date']=date('r'); // Required headers @@ -297,7 +301,7 @@ function send($message,$log=TRUE,$mock=FALSE) { $out.='Content-Type: application/octet-stream'.$eol; $out.='Content-Transfer-Encoding: base64'.$eol; if ($attachment['cid']) - $out.='Content-ID: '.$attachment['cid'].$eol; + $out.='Content-Id: '.$attachment['cid'].$eol; $out.='Content-Disposition: attachment; '. 'filename="'.$alias.'"'.$eol; $out.=$eol; @@ -307,7 +311,7 @@ function send($message,$log=TRUE,$mock=FALSE) { $out.=$eol; $out.='--'.$hash.'--'.$eol; $out.='.'; - $this->dialog($out,TRUE,$mock); + $this->dialog($out,preg_match('/verbose/i',$log),$mock); } else { // Send mail headers @@ -319,7 +323,7 @@ function send($message,$log=TRUE,$mock=FALSE) { $out.=$message.$eol; $out.='.'; // Send message - $this->dialog($out,TRUE,$mock); + $this->dialog($out,preg_match('/verbose/i',$log),$mock); } $this->dialog('QUIT',$log,$mock); if (!$mock && $socket) @@ -334,20 +338,21 @@ function send($message,$log=TRUE,$mock=FALSE) { * @param $scheme string * @param $user string * @param $pw string + * @param $ctx resource **/ function __construct( - $host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL) { + $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()->get('ENCODING') + 'charset='.Base::instance()->ENCODING ]; - $this->host=$host; - if (strtolower($this->scheme=strtolower($scheme))=='ssl') - $this->host='ssl://'.$host; + $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); } } diff --git a/lib/template.php b/lib/template.php index c9c0d61b..41d136c5 100644 --- a/lib/template.php +++ b/lib/template.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -77,7 +77,7 @@ protected function _include(array $node) { (preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])? $this->token($attrib['href']): Base::instance()->stringify($attrib['href'])).','. - '$this->mime,'.$hive.','.$ttl.'); ?>'); + 'NULL,'.$hive.','.$ttl.'); ?>'); } /** diff --git a/lib/test.php b/lib/test.php index 2070dab4..721faf76 100644 --- a/lib/test.php +++ b/lib/test.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/utf.php b/lib/utf.php index 22b41c3d..a010c66f 100644 --- a/lib/utf.php +++ b/lib/utf.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -191,7 +191,7 @@ function emojify($str) { ':,'=>'\u1f60f', // think ':/'=>'\u1f623', // skeptic '8O'=>'\u1f632', // oops - ]+Base::instance()->get('EMOJI'); + ]+Base::instance()->EMOJI; return $this->translate(str_replace(array_keys($map), array_values($map),$str)); } diff --git a/lib/web.php b/lib/web.php index 22864898..762725a0 100644 --- a/lib/web.php +++ b/lib/web.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -138,28 +138,35 @@ function send($file,$mime=NULL,$kbps=0,$force=TRUE,$name=NULL,$flush=TRUE) { 'filename="'.($name!==NULL?$name:basename($file)).'"'); header('Accept-Ranges: bytes'); header('Content-Length: '.$size); - header('X-Powered-By: '.Base::instance()->get('PACKAGE')); + header('X-Powered-By: '.Base::instance()->PACKAGE); } - $ctr=0; - $handle=fopen($file,'rb'); - $start=microtime(TRUE); - while (!feof($handle) && - ($info=stream_get_meta_data($handle)) && - !$info['timed_out'] && !connection_aborted()) { - if ($kbps) { - // Throttle output - $ctr++; - if ($ctr/$kbps>$elapsed=microtime(TRUE)-$start) - usleep(1e6*($ctr/$kbps-$elapsed)); - } - // Send 1KiB and reset timer - echo fread($handle,1024); - if ($flush) { - ob_flush(); - flush(); + if (!$kbps && $flush) { + while (ob_get_level()) + ob_end_clean(); + readfile($file); + } + else { + $ctr=0; + $handle=fopen($file,'rb'); + $start=microtime(TRUE); + while (!feof($handle) && + ($info=stream_get_meta_data($handle)) && + !$info['timed_out'] && !connection_aborted()) { + if ($kbps) { + // Throttle output + $ctr++; + if ($ctr/$kbps>$elapsed=microtime(TRUE)-$start) + usleep(1e6*($ctr/$kbps-$elapsed)); + } + // Send 1KiB and reset timer + echo fread($handle,1024); + if ($flush) { + ob_flush(); + flush(); + } } + fclose($handle); } - fclose($handle); return $size; } @@ -172,13 +179,13 @@ function send($file,$mime=NULL,$kbps=0,$force=TRUE,$name=NULL,$flush=TRUE) { **/ function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) { $fw=Base::instance(); - $dir=$fw->get('UPLOADS'); + $dir=$fw->UPLOADS; if (!is_dir($dir)) mkdir($dir,Base::MODE,TRUE); - if ($fw->get('VERB')=='PUT') { - $tmp=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash(uniqid()); - if (!$fw->get('RAW')) - $fw->write($tmp,$fw->get('BODY')); + if ($fw->VERB=='PUT') { + $tmp=$fw->TEMP.$fw->SEED.'.'.$fw->hash(uniqid()); + if (!$fw->RAW) + $fw->write($tmp,$fw->BODY); else { $src=@fopen('php://input','r'); $dst=@fopen($tmp,'w'); @@ -191,7 +198,7 @@ function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) { fclose($dst); fclose($src); } - $base=basename($fw->get('URI')); + $base=basename($fw->URI); $file=[ 'name'=>$dir. ($slug && preg_match('/(.+?)(\.\w+)?$/',$base,$parts)? @@ -482,9 +489,9 @@ function request($url,array $options=NULL) { $parts=parse_url($url); if (empty($parts['scheme'])) { // Local URL - $url=$fw->get('SCHEME').'://'. - $fw->get('HOST'). - ($url[0]!='/'?($fw->get('BASE').'/'):'').$url; + $url=$fw->SCHEME.'://'.$fw->HOST. + (in_array($fw->PORT,[80,443])?'':(':'.$fw->PORT)). + ($url[0]!='/'?($fw->BASE.'/'):'').$url; $parts=parse_url($url); } elseif (!preg_match('/https?/',$parts['scheme'])) @@ -537,7 +544,7 @@ function request($url,array $options=NULL) { 'ignore_errors'=>FALSE ]; $eol="\r\n"; - if ($fw->get('CACHE') && + if ($fw->CACHE && preg_match('/GET|HEAD/',$options['method'])) { $cache=Cache::instance(); if ($cache->exists( @@ -584,13 +591,13 @@ function minify($files,$mime=NULL,$header=TRUE,$path=NULL) { $cache=Cache::instance(); $dst=''; if (!isset($path)) - $path=$fw->get('UI').';./'; + $path=$fw->UI.';./'; foreach ($fw->split($path,FALSE) as $dir) foreach ($files as $file) if (is_file($save=$fw->fixslashes($dir.$file)) && is_bool(strpos($save,'../')) && preg_match('/\.(css|js)$/i',$file)) { - if ($fw->get('CACHE') && + if ($fw->CACHE && ($cached=$cache->exists( $hash=$fw->hash($save).'.'.$ext[0],$data)) && $cached[0]>filemtime($save)) @@ -685,7 +692,9 @@ function minify($files,$mime=NULL,$header=TRUE,$path=NULL) { preg_match('/[\w'.($ext[0]=='css'? '#\.%+\-*()\[\]':'\$').']{2}|'. '[+\-]{2}/', - substr($data,-1).$src[$ptr+1])) + substr($data,-1).$src[$ptr+1]) || + ($ext[0]=='css' && $ptr+2get('CACHE')) + if ($ext[0]=='css') + $data=str_replace(';}','}',$data); + if ($fw->CACHE) $cache->set($hash,$data); $dst.=$data; } } if (PHP_SAPI!='cli' && $header) - header('Content-Type: '.$mime.'; charset='.$fw->get('ENCODING')); + header('Content-Type: '.$mime.'; charset='.$fw->ENCODING); return $dst; } @@ -766,6 +777,62 @@ function whois($addr,$server='whois.internic.net') { return $info['timed_out']?FALSE:trim($response); } + /** + * Return preset diacritics translation table + * @return array + **/ + function diacritics() { + return [ + 'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A', + 'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A', + 'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C', + 'Č'=>'C','Ċ'=>'C','Ц'=>'C','Ч'=>'Ch','Ð'=>'Dj','Đ'=>'Dj', + 'Ď'=>'Dj','Д'=>'Dj','É'=>'E','Ę'=>'E','Ё'=>'E','Ė'=>'E', + 'Ê'=>'E','Ě'=>'E','Ē'=>'E','È'=>'E','Е'=>'E','Э'=>'E', + 'Ë'=>'E','Ĕ'=>'E','Ф'=>'F','Г'=>'G','Ģ'=>'G','Ġ'=>'G', + 'Ĝ'=>'G','Ğ'=>'G','Х'=>'H','Ĥ'=>'H','Ħ'=>'H','Ï'=>'I', + 'Ĭ'=>'I','İ'=>'I','Į'=>'I','Ī'=>'I','Í'=>'I','Ì'=>'I', + 'И'=>'I','Ǐ'=>'I','Ĩ'=>'I','Î'=>'I','IJ'=>'IJ','Ĵ'=>'J', + 'Й'=>'J','Я'=>'Ja','Ю'=>'Ju','К'=>'K','Ķ'=>'K','Ĺ'=>'L', + 'Л'=>'L','Ł'=>'L','Ŀ'=>'L','Ļ'=>'L','Ľ'=>'L','М'=>'M', + 'Н'=>'N','Ń'=>'N','Ñ'=>'N','Ņ'=>'N','Ň'=>'N','Ō'=>'O', + 'О'=>'O','Ǿ'=>'O','Ǒ'=>'O','Ơ'=>'O','Ŏ'=>'O','Ő'=>'O', + 'Ø'=>'O','Ö'=>'Oe','Õ'=>'O','Ó'=>'O','Ò'=>'O','Ô'=>'O', + 'Œ'=>'OE','П'=>'P','Ŗ'=>'R','Р'=>'R','Ř'=>'R','Ŕ'=>'R', + 'Ŝ'=>'S','Ş'=>'S','Š'=>'S','Ș'=>'S','Ś'=>'S','С'=>'S', + 'Ш'=>'Sh','Щ'=>'Shch','Ť'=>'T','Ŧ'=>'T','Ţ'=>'T','Ț'=>'T', + 'Т'=>'T','Ů'=>'U','Ű'=>'U','Ŭ'=>'U','Ũ'=>'U','Ų'=>'U', + 'Ū'=>'U','Ǜ'=>'U','Ǚ'=>'U','Ù'=>'U','Ú'=>'U','Ü'=>'Ue', + 'Ǘ'=>'U','Ǖ'=>'U','У'=>'U','Ư'=>'U','Ǔ'=>'U','Û'=>'U', + 'В'=>'V','Ŵ'=>'W','Ы'=>'Y','Ŷ'=>'Y','Ý'=>'Y','Ÿ'=>'Y', + 'Ź'=>'Z','З'=>'Z','Ż'=>'Z','Ž'=>'Z','Ж'=>'Zh','á'=>'a', + 'ă'=>'a','â'=>'a','à'=>'a','ā'=>'a','ǻ'=>'a','å'=>'a', + 'ä'=>'ae','ą'=>'a','ǎ'=>'a','ã'=>'a','а'=>'a','ª'=>'a', + 'æ'=>'ae','ǽ'=>'ae','б'=>'b','č'=>'c','ç'=>'c','ц'=>'c', + 'ċ'=>'c','ĉ'=>'c','ć'=>'c','ч'=>'ch','ð'=>'dj','ď'=>'dj', + 'д'=>'dj','đ'=>'dj','э'=>'e','é'=>'e','ё'=>'e','ë'=>'e', + 'ê'=>'e','е'=>'e','ĕ'=>'e','è'=>'e','ę'=>'e','ě'=>'e', + 'ė'=>'e','ē'=>'e','ƒ'=>'f','ф'=>'f','ġ'=>'g','ĝ'=>'g', + 'ğ'=>'g','г'=>'g','ģ'=>'g','х'=>'h','ĥ'=>'h','ħ'=>'h', + 'ǐ'=>'i','ĭ'=>'i','и'=>'i','ī'=>'i','ĩ'=>'i','į'=>'i', + 'ı'=>'i','ì'=>'i','î'=>'i','í'=>'i','ï'=>'i','ij'=>'ij', + 'ĵ'=>'j','й'=>'j','я'=>'ja','ю'=>'ju','ķ'=>'k','к'=>'k', + 'ľ'=>'l','ł'=>'l','ŀ'=>'l','ĺ'=>'l','ļ'=>'l','л'=>'l', + 'м'=>'m','ņ'=>'n','ñ'=>'n','ń'=>'n','н'=>'n','ň'=>'n', + 'ʼn'=>'n','ó'=>'o','ò'=>'o','ǒ'=>'o','ő'=>'o','о'=>'o', + 'ō'=>'o','º'=>'o','ơ'=>'o','ŏ'=>'o','ô'=>'o','ö'=>'oe', + 'õ'=>'o','ø'=>'o','ǿ'=>'o','œ'=>'oe','п'=>'p','р'=>'r', + 'ř'=>'r','ŕ'=>'r','ŗ'=>'r','ſ'=>'s','ŝ'=>'s','ș'=>'s', + 'š'=>'s','ś'=>'s','с'=>'s','ş'=>'s','ш'=>'sh','щ'=>'shch', + 'ß'=>'ss','ţ'=>'t','т'=>'t','ŧ'=>'t','ť'=>'t','ț'=>'t', + 'у'=>'u','ǘ'=>'u','ŭ'=>'u','û'=>'u','ú'=>'u','ų'=>'u', + 'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u', + 'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v', + 'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z', + 'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>'' + ]; + } + /** * Return a URL/filesystem-friendly version of string * @return string @@ -774,55 +841,7 @@ function whois($addr,$server='whois.internic.net') { function slug($text) { return trim(strtolower(preg_replace('/([^\pL\pN])+/u','-', trim(strtr(str_replace('\'','',$text), - [ - 'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A', - 'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A', - 'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C', - 'Č'=>'C','Ċ'=>'C','Ц'=>'C','Ч'=>'Ch','Ð'=>'Dj','Đ'=>'Dj', - 'Ď'=>'Dj','Д'=>'Dj','É'=>'E','Ę'=>'E','Ё'=>'E','Ė'=>'E', - 'Ê'=>'E','Ě'=>'E','Ē'=>'E','È'=>'E','Е'=>'E','Э'=>'E', - 'Ë'=>'E','Ĕ'=>'E','Ф'=>'F','Г'=>'G','Ģ'=>'G','Ġ'=>'G', - 'Ĝ'=>'G','Ğ'=>'G','Х'=>'H','Ĥ'=>'H','Ħ'=>'H','Ï'=>'I', - 'Ĭ'=>'I','İ'=>'I','Į'=>'I','Ī'=>'I','Í'=>'I','Ì'=>'I', - 'И'=>'I','Ǐ'=>'I','Ĩ'=>'I','Î'=>'I','IJ'=>'IJ','Ĵ'=>'J', - 'Й'=>'J','Я'=>'Ja','Ю'=>'Ju','К'=>'K','Ķ'=>'K','Ĺ'=>'L', - 'Л'=>'L','Ł'=>'L','Ŀ'=>'L','Ļ'=>'L','Ľ'=>'L','М'=>'M', - 'Н'=>'N','Ń'=>'N','Ñ'=>'N','Ņ'=>'N','Ň'=>'N','Ō'=>'O', - 'О'=>'O','Ǿ'=>'O','Ǒ'=>'O','Ơ'=>'O','Ŏ'=>'O','Ő'=>'O', - 'Ø'=>'O','Ö'=>'Oe','Õ'=>'O','Ó'=>'O','Ò'=>'O','Ô'=>'O', - 'Œ'=>'OE','П'=>'P','Ŗ'=>'R','Р'=>'R','Ř'=>'R','Ŕ'=>'R', - 'Ŝ'=>'S','Ş'=>'S','Š'=>'S','Ș'=>'S','Ś'=>'S','С'=>'S', - 'Ш'=>'Sh','Щ'=>'Shch','Ť'=>'T','Ŧ'=>'T','Ţ'=>'T','Ț'=>'T', - 'Т'=>'T','Ů'=>'U','Ű'=>'U','Ŭ'=>'U','Ũ'=>'U','Ų'=>'U', - 'Ū'=>'U','Ǜ'=>'U','Ǚ'=>'U','Ù'=>'U','Ú'=>'U','Ü'=>'Ue', - 'Ǘ'=>'U','Ǖ'=>'U','У'=>'U','Ư'=>'U','Ǔ'=>'U','Û'=>'U', - 'В'=>'V','Ŵ'=>'W','Ы'=>'Y','Ŷ'=>'Y','Ý'=>'Y','Ÿ'=>'Y', - 'Ź'=>'Z','З'=>'Z','Ż'=>'Z','Ž'=>'Z','Ж'=>'Zh','á'=>'a', - 'ă'=>'a','â'=>'a','à'=>'a','ā'=>'a','ǻ'=>'a','å'=>'a', - 'ä'=>'ae','ą'=>'a','ǎ'=>'a','ã'=>'a','а'=>'a','ª'=>'a', - 'æ'=>'ae','ǽ'=>'ae','б'=>'b','č'=>'c','ç'=>'c','ц'=>'c', - 'ċ'=>'c','ĉ'=>'c','ć'=>'c','ч'=>'ch','ð'=>'dj','ď'=>'dj', - 'д'=>'dj','đ'=>'dj','э'=>'e','é'=>'e','ё'=>'e','ë'=>'e', - 'ê'=>'e','е'=>'e','ĕ'=>'e','è'=>'e','ę'=>'e','ě'=>'e', - 'ė'=>'e','ē'=>'e','ƒ'=>'f','ф'=>'f','ġ'=>'g','ĝ'=>'g', - 'ğ'=>'g','г'=>'g','ģ'=>'g','х'=>'h','ĥ'=>'h','ħ'=>'h', - 'ǐ'=>'i','ĭ'=>'i','и'=>'i','ī'=>'i','ĩ'=>'i','į'=>'i', - 'ı'=>'i','ì'=>'i','î'=>'i','í'=>'i','ï'=>'i','ij'=>'ij', - 'ĵ'=>'j','й'=>'j','я'=>'ja','ю'=>'ju','ķ'=>'k','к'=>'k', - 'ľ'=>'l','ł'=>'l','ŀ'=>'l','ĺ'=>'l','ļ'=>'l','л'=>'l', - 'м'=>'m','ņ'=>'n','ñ'=>'n','ń'=>'n','н'=>'n','ň'=>'n', - 'ʼn'=>'n','ó'=>'o','ò'=>'o','ǒ'=>'o','ő'=>'o','о'=>'o', - 'ō'=>'o','º'=>'o','ơ'=>'o','ŏ'=>'o','ô'=>'o','ö'=>'oe', - 'õ'=>'o','ø'=>'o','ǿ'=>'o','œ'=>'oe','п'=>'p','р'=>'r', - 'ř'=>'r','ŕ'=>'r','ŗ'=>'r','ſ'=>'s','ŝ'=>'s','ș'=>'s', - 'š'=>'s','ś'=>'s','с'=>'s','ş'=>'s','ш'=>'sh','щ'=>'shch', - 'ß'=>'ss','ţ'=>'t','т'=>'t','ŧ'=>'t','ť'=>'t','ț'=>'t', - 'у'=>'u','ǘ'=>'u','ŭ'=>'u','û'=>'u','ú'=>'u','ų'=>'u', - 'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u', - 'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v', - 'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z', - 'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>'' - ]+Base::instance()->get('DIACRITICS'))))),'-'); + $this->diacritics()+Base::instance()->DIACRITICS)))),'-'); } /** @@ -874,9 +893,9 @@ function filler($count=1,$max=20,$std=TRUE) { **/ function gzdecode($str) { $fw=Base::instance(); - if (!is_dir($tmp=$fw->get('TEMP'))) + if (!is_dir($tmp=$fw->TEMP)) mkdir($tmp,Base::MODE,TRUE); - file_put_contents($file=$tmp.'/'.$fw->get('SEED').'.'. + file_put_contents($file=$tmp.'/'.$fw->SEED.'.'. $fw->hash(uniqid(NULL,TRUE)).'.gz',$str,LOCK_EX); ob_start(); readgzfile($file); diff --git a/lib/web/geo.php b/lib/web/geo.php index 7d7a0bce..c669024d 100644 --- a/lib/web/geo.php +++ b/lib/web/geo.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -55,7 +55,7 @@ function location($ip=NULL) { $fw=\Base::instance(); $web=\Web::instance(); if (!$ip) - $ip=$fw->get('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); diff --git a/lib/web/google/recaptcha.php b/lib/web/google/recaptcha.php new file mode 100644 index 00000000..9c32979c --- /dev/null +++ b/lib/web/google/recaptcha.php @@ -0,0 +1,58 @@ +. + +*/ + +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']; + } + +} diff --git a/lib/web/google/staticmap.php b/lib/web/google/staticmap.php old mode 100755 new mode 100644 index 6a3f1601..8310fe4c --- a/lib/web/google/staticmap.php +++ b/lib/web/google/staticmap.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/web/oauth2.php b/lib/web/oauth2.php index a41f9fb3..916674f8 100644 --- a/lib/web/oauth2.php +++ b/lib/web/oauth2.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -62,6 +62,8 @@ function request($uri,$method,$token=NULL) { ) ); $response=$web->request($uri,$options); + if ($response['error']) + user_error($response['error'],E_USER_ERROR); return $response['body'] && preg_grep('/HTTP\/1\.\d 200/',$response['headers'])? json_decode($response['body'],TRUE): @@ -121,7 +123,7 @@ function &get($key) { /** * Remove scope/claim * @return NULL - * @param $key + * @param $key string **/ function clear($key=NULL) { if ($key) diff --git a/lib/web/openid.php b/lib/web/openid.php index f795cb37..435d89ca 100644 --- a/lib/web/openid.php +++ b/lib/web/openid.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -143,9 +143,9 @@ protected function discover($proxy) { **/ function auth($proxy=NULL,$attr=[],array $reqd=NULL) { $fw=\Base::instance(); - $root=$fw->get('SCHEME').'://'.$fw->get('HOST'); + $root=$fw->SCHEME.'://'.$fw->HOST; if (empty($this->args['trust_root'])) - $this->args['trust_root']=$root.$fw->get('BASE').'/'; + $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'; diff --git a/lib/web/pingback.php b/lib/web/pingback.php index f063f104..a68430ca 100644 --- a/lib/web/pingback.php +++ b/lib/web/pingback.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -66,9 +66,9 @@ function inspect($source) { $web=\Web::instance(); $parts=parse_url($source); if (empty($parts['scheme']) || empty($parts['host']) || - $parts['host']==$fw->get('HOST')) { + $parts['host']==$fw->HOST) { $req=$web->request($source); - $doc=new \DOMDocument('1.0',$fw->get('ENCODING')); + $doc=new \DOMDocument('1.0',$fw->ENCODING); $doc->stricterrorchecking=FALSE; $doc->recover=TRUE; if (@$doc->loadhtml($req['body'])) { @@ -85,7 +85,7 @@ function inspect($source) { 'content'=>xmlrpc_encode_request( 'pingback.ping', [$source,$permalink], - ['encoding'=>$fw->get('ENCODING')] + ['encoding'=>$fw->ENCODING] ) ] ); @@ -110,29 +110,29 @@ function inspect($source) { function listen($func,$path=NULL) { $fw=\Base::instance(); if (PHP_SAPI!='cli') { - header('X-Powered-By: '.$fw->get('PACKAGE')); + header('X-Powered-By: '.$fw->PACKAGE); header('Content-Type: application/xml; '. - 'charset='.$charset=$fw->get('ENCODING')); + 'charset='.$charset=$fw->ENCODING); } if (!$path) - $path=$fw->get('BASE'); + $path=$fw->BASE; $web=\Web::instance(); - $args=xmlrpc_decode_request($fw->get('BODY'),$method,$charset); + $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->get('ENCODING')); + $doc=new \DOMDocument('1.0',$fw->ENCODING); // Check local page if pingback-enabled $parts=parse_url($permalink); if ((empty($parts['scheme']) || - $parts['host']==$fw->get('HOST')) && + $parts['host']==$fw->HOST) && preg_match('/^'.preg_quote($path,'/').'/'. - ($fw->get('CASELESS')?'i':''),$parts['path']) && + ($fw->CASELESS?'i':''),$parts['path']) && $this->enabled($permalink)) { // Check source $parts=parse_url($source); if ((empty($parts['scheme']) || - $parts['host']==$fw->get('HOST')) && + $parts['host']==$fw->HOST) && ($req=$web->request($source)) && $doc->loadhtml($req['body'])) { $links=$doc->getelementsbytagname('a');