Browse Source

Remove barcode generation classes and related exceptions

- Deleted BarcodeGeneratorHTML, BarcodeGeneratorJPG, BarcodeGeneratorPNG, BarcodeGeneratorSVG classes.
- Removed BarcodeException and its derived exceptions: InvalidCharacterException, InvalidCheckDigitException, InvalidFormatException, InvalidLengthException, UnknownTypeException.
- Removed JavaScript files ModalEslDetails.js and ModalTemplateSelector.js.
- Deleted label status and template HTML files: labelstatus.tpl and templates.tpl.
softword 2 months ago
parent
commit
0e19e3112c
63 changed files with 0 additions and 20207 deletions
  1. 0 6
      cfg/labels.ini
  2. 0 971
      lib/f3/CHANGELOG.md
  3. 0 621
      lib/f3/COPYING
  4. 0 191
      lib/f3/audit.php
  5. 0 262
      lib/f3/auth.php
  6. 0 3584
      lib/f3/base.php
  7. 0 239
      lib/f3/basket.php
  8. 0 96
      lib/f3/bcrypt.php
  9. 0 487
      lib/f3/cli/ws.php
  10. 0 1
      lib/f3/code.css
  11. 0 388
      lib/f3/db/cursor.php
  12. 0 175
      lib/f3/db/jig.php
  13. 0 541
      lib/f3/db/jig/mapper.php
  14. 0 194
      lib/f3/db/jig/session.php
  15. 0 145
      lib/f3/db/mongo.php
  16. 0 405
      lib/f3/db/mongo/mapper.php
  17. 0 194
      lib/f3/db/mongo/session.php
  18. 0 523
      lib/f3/db/sql.php
  19. 0 759
      lib/f3/db/sql/mapper.php
  20. 0 222
      lib/f3/db/sql/session.php
  21. 0 42
      lib/f3/f3.php
  22. 0 616
      lib/f3/image.php
  23. 0 71
      lib/f3/log.php
  24. 0 139
      lib/f3/magic.php
  25. 0 569
      lib/f3/markdown.php
  26. 0 139
      lib/f3/matrix.php
  27. 0 196
      lib/f3/session.php
  28. 0 360
      lib/f3/smtp.php
  29. 0 353
      lib/f3/template.php
  30. 0 98
      lib/f3/test.php
  31. 0 199
      lib/f3/utf.php
  32. 0 1017
      lib/f3/web.php
  33. 0 111
      lib/f3/web/geo.php
  34. 0 58
      lib/f3/web/google/recaptcha.php
  35. 0 65
      lib/f3/web/google/staticmap.php
  36. 0 163
      lib/f3/web/oauth2.php
  37. 0 248
      lib/f3/web/openid.php
  38. 0 176
      lib/f3/web/pingback.php
  39. 0 254
      src/Modules/restful/restful.php
  40. 0 6
      src/Modules/restful/restful.tpl
  41. 0 188
      src/OpenMind/DataManager/CSVImporter.php
  42. 0 20
      src/OpenMind/FindBox/FindBox.php
  43. 0 414
      src/OpenMind/FindBox/FindBoxCoreSystem.php
  44. 0 571
      src/OpenMind/FindBox/FindBoxService.php
  45. 0 115
      src/OpenMind/FindBox/QueueCommand.php
  46. 0 187
      src/OpenMind/FindBox/SmartESL.php
  47. 0 145
      src/OpenMind/System/GDSupport.php
  48. 0 120
      src/OpenMind/System/QueueSystem.php
  49. 0 2911
      src/Picqer/Barcode/BarcodeGenerator.php
  50. 0 43
      src/Picqer/Barcode/BarcodeGeneratorHTML.php
  51. 0 77
      src/Picqer/Barcode/BarcodeGeneratorJPG.php
  52. 0 76
      src/Picqer/Barcode/BarcodeGeneratorPNG.php
  53. 0 49
      src/Picqer/Barcode/BarcodeGeneratorSVG.php
  54. 0 7
      src/Picqer/Barcode/Exceptions/BarcodeException.php
  55. 0 7
      src/Picqer/Barcode/Exceptions/InvalidCharacterException.php
  56. 0 7
      src/Picqer/Barcode/Exceptions/InvalidCheckDigitException.php
  57. 0 7
      src/Picqer/Barcode/Exceptions/InvalidFormatException.php
  58. 0 7
      src/Picqer/Barcode/Exceptions/InvalidLengthException.php
  59. 0 7
      src/Picqer/Barcode/Exceptions/UnknownTypeException.php
  60. 0 99
      ui/admin/assets/js/ModalEslDetails.js
  61. 0 100
      ui/admin/assets/js/ModalTemplateSelector.js
  62. 0 153
      ui/admin/labelstatus.tpl
  63. 0 13
      ui/admin/templates.tpl

+ 0 - 6
cfg/labels.ini

@@ -1,6 +0,0 @@
-[LABELS.TEMPLATES]
-DT31=dimatec2_2
-DEFAULT=dimatec_default
-
-[BARCODE_COMMANDS]
-ANOMALIACODE=5

+ 0 - 971
lib/f3/CHANGELOG.md

@@ -1,971 +0,0 @@
-CHANGELOG
-
-3.7.2 (28 May 2020)
-*   CHANGED, View->sandbox: disable escaping when rendering as text/plain, bcosca/fatfree#654
-*   update HTTP protocol checks, #bcosca/fatfree#1190
-*   Base->clear: close vulnerability on variable compilation, bcosca/fatfree#1191
-*   DB\SQL\Mapper: fix empty ID after insert, bcosca/fatfree#1175
-*   DB\SQL\Mapper: fix using correct key variable for grouped sql pagination sets
-*   Fix return type of 'count' in Cursor->paginate() (bcosca/fatfree#1187)
-*   Bug fix, Web->minify: fix minification of ES6 template literals, bcosca/fatfree#1178
-*   Bug fix, config: refactoring custom section parser regex, bcosca/fatfree#1149
-*   Bug fix: token resolve on non-alias reroute paths, ref. 221f0c930f8664565c9825faeb9ed9af0f7a01c8
-*   Websocket: Improved event handler usage
-*   optimized internal get calls
-*   only use cached lexicon when a $ttl was given
-*   only use money_format up until php7.4, fixes bcosca/fatfree#1174
-
-3.7.1 (30. December 2019)
-*   Base->build: Add support for brace-enclosed route tokens
-*   Base->reroute, fix duplicate fragment issue on non-alias routes
-*   DB\SQL\Mapper: fix empty check for pkey when reloading after insert
-*   Web->minify: fix minification with multiple files, [bcosca/fatfree#1152](https://github.com/bcosca/fatfree/issues/1152), [#bcosca/fatfree#1169](https://github.com/bcosca/fatfree/issues/1169)
-
-3.7.0 (26. November 2019)
-*   NEW: Matrix, added select and walk methods for array processing and validation tools
-*   NEW: Added configurable file locking via LOCK var
-*   NEW: json support for dictionary files
-*   NEW: $die parameter on ONREROUTE hook
-*   NEW: Added SameSite cookie support for php7.3+ (JAR.samesite), [bcosca/fatfree#1165](https://github.com/bcosca/fatfree/issues/1165)
-*   NEW, DB\SQL\Mapper: added updateAll method to batch-update multiple records at once
-*   CHANGED, DB\SQL\Mapper: Throw error on update/erase if the table has no primary key, [#285](https://github.com/bcosca/fatfree-core/issues/285)
-*   Cache, Redis: Added ability to set a Redis password, [#287](https://github.com/bcosca/fatfree-core/issues/287)
-*   DB\SQL\Session: make datatype of data column configurable, [bcosca/fatfree#1130](https://github.com/bcosca/fatfree/issues/1130)
-*   DB\SQL\Mapper: only add adhoc fields in count queries that are used for grouping
-*   DB\SQL\Mapper: fixed inserting an already loaded record again (duplicating), [bcosca/fatfree#1093](https://github.com/bcosca/fatfree/issues/1093)
-*   Magic (Mappers): fix isset check on existing properties
-*   SMTP: added support for Bounce mail recipient ("Sender" header)
-*   OAuth2: make query string encode type configurable, [#268](https://github.com/bcosca/fatfree-core/issues/268) [#269](https://github.com/bcosca/fatfree-core/issues/269)
-*   Web: Added more cyrillic letters to diacritics, [bcosca/fatfree#1158](https://github.com/bcosca/fatfree/issues/1158)
-*   Web: Fixed url string falsely detected as comment section [9ac8e615](https://github.com/bcosca/fatfree-core/commit/9ac8e615ccaf750b49497a3c86161331b24e637f)
-*   Web: added file inspection for mime-type detection, [#270](https://github.com/bcosca/fatfree-core/issues/270), [bcosca/fatfree#1138](https://github.com/bcosca/fatfree/issues/1138)
-*   WS: Fixed processing all queued data frames inside the buffer, [#277](https://github.com/bcosca/fatfree-core/issues/277)
-*   WS: Allow packet size override
-*   Markdown: Support mixed `strong` and `italic` elements, [#276](https://github.com/bcosca/fatfree-core/issues/276)
-*   Markdown: Keep spaces around `=` sign in ini code blocks 
-*   Added route alias key name validation, [#243](https://github.com/bcosca/fatfree-core/issues/243)
-*   Added fragment argument to alias method, [#282](https://github.com/bcosca/fatfree-core/issues/282)
-*   Allow adding fragment to reroute, [#1156](https://github.com/bcosca/fatfree/issues/1156)
-*   Added additional HTTP status codes, [#283](https://github.com/bcosca/fatfree-core/issues/283)
-*   Added X-Forwarded-For IP to log entries, [bcosca/fatfree#1042](https://github.com/bcosca/fatfree/issues/1042)
-*   Bug fix: broken custom date/time formatting, [bcosca/fatfree#1147](https://github.com/bcosca/fatfree/issues/1147)
-*   Bug fix: duplicate UI path rendering edge-case in Views and minify, [bcosca/fatfree#1152](https://github.com/bcosca/fatfree/issues/1152)
-*   Bug fix: unicode chars in custom config section keys, [bcosca/fatfree#1149](https://github.com/bcosca/fatfree/issues/1149)
-*   Bug fix: ensure valid reroute path in location header, [bcosca/fatfree#1140](https://github.com/bcosca/fatfree/issues/1140)
-*   Bug fix: use dictionary path for lexicon caching-hash
-*   Bug fix, php7.3: number format ternary, [bcosca/fatfree#1142](https://github.com/bcosca/fatfree/issues/1142)
-*   fix PHPdoc and variable inspection, [bcosca/fatfree#865](https://github.com/bcosca/fatfree/issues/865), [bcosca/fatfree#1128](https://github.com/bcosca/fatfree/issues/1128)
-
-3.6.5 (24 December 2018)
-*	NEW: Log, added timestamp to each line
-*	NEW: Auth, added support for custom compare method, [#116](https://github.com/bcosca/fatfree-core/issues/116)
-*	NEW: cache tag support for mongo & jig mapper, ref [#166](https://github.com/bcosca/fatfree-core/issues/116)
-*	NEW: Allow PHP functions as template token filters
-*	Web: Fix double redirect bug when running cURL with open_basedir disabled
-*	Web: Cope with responses from HTTP/2 servers
-*	Web->filler: remove very first space, when $std is false
-*	Web\OAuth2: Cope with HTTP/2 responses
-*	Web\OAuth2: take Content-Type header into account for json decoding, [#250](https://github.com/bcosca/fatfree-core/issues/250) [#251](https://github.com/bcosca/fatfree-core/issues/251)
-*	Web\OAuth2: fixed empty results on some endpoints [#250](https://github.com/bcosca/fatfree-core/issues/250)
-*	DB\SQL\Mapper: optimize mapper->count memory usage
-*	DB\SQL\Mapper: New table alias operator
-*	DB\SQL\Mapper: fix count() performance on non-grouped result sets, [bcosca/fatfree#1114](https://github.com/bcosca/fatfree/issues/1114)
-*	DB\SQL: Support for CTE in postgreSQL, [bcosca/fatfree#1107](https://github.com/bcosca/fatfree/issues/1107), [bcosca/fatfree#1116](https://github.com/bcosca/fatfree/issues/1116), [bcosca/fatfree#1021](https://github.com/bcosca/fatfree/issues/1021)
-*	DB\SQL->log: Remove extraneous whitespace
-*	DB\SQL: Added ability to add inline comments per SQL query
-*	CLI\WS, Refactoring: Streamline socket server
-*	CLI\WS: Add option for dropping query in OAuth2 URI 
-*	CLI\WS: Add URL-safe base64 encoding
-*	CLI\WS: Detect errors in returned JSON values
-*	CLI\WS: Added support for Sec-WebSocket-Protocol header
-*	Matrix->calendar: Allow unix timestamp as date argument
-*	Basket: Access basket item by _id [#260](https://github.com/bcosca/fatfree-core/issues/260)
-*	SMTP: Added TLS 1.2 support [bcosca/fatfree#1115](https://github.com/bcosca/fatfree/issues/1115)
-*	SMTP->send: Respect $log argument
-*	Base->cast: recognize binary and octal numbers in config
-*	Base->cast: add awareness of hexadecimal literals
-*	Base->abort: Remove unnecessary Content-Encoding header
-*	Base->abort: Ensure headers have not been flushed
-*	Base->format: Differentiate between long- and full-date (with localized weekday) formats
-*	Base->format: Conform with intl extension's number output
-*	Enable route handler to override Access-Control headers in response to OPTIONS request, [#257](https://github.com/bcosca/fatfree-core/issues/257)
-*	Augment filters with a var_export function
-*	Bug fix php7.3: Fix template parse regex to be compatible with strict PCRE2 rules for hyphen placement in a character class
-*	Bug fix, Cache->set: update creation time when updating existing cache entries 
-*	Bug fix: incorrect ICU date/time formatting
-*	Bug fix, Jig: lazy write on empty data
-*	Bug fix: Method uppercase to avoid route failure [#252](https://github.com/bcosca/fatfree-core/issues/252)
-*	Fixed error description when (PSR-11) `CONTAINER` fails to resolve a class [#253](https://github.com/bcosca/fatfree-core/issues/253)
-*	Mitigate CSRF predictability/vulnerability
-*	Expose Mapper->factory() method
-
-3.6.4 (19 April 2018)
-*	NEW: Added Dependency Injection support with CONTAINER variable [#221](https://github.com/bcosca/fatfree-core/issues/221)
-*	NEW: configurable LOGGABLE error codes [#1091](https://github.com/bcosca/fatfree/issues/1091#issuecomment-364674701)
-*	NEW: JAR.lifetime option, [#178](https://github.com/bcosca/fatfree-core/issues/178)
-*	Template: reduced Prefab calls
-*	Template: optimized reflection for better derivative support, [bcosca/fatfree#1088](https://github.com/bcosca/fatfree/issues/1088)
-*	Template: optimized parsing for template attributes and tokens
-*	DB\Mongo: fixed logging with mongodb extention
-*	DB\Jig: added lazy-loading [#7e1cd9b9b89](https://github.com/bcosca/fatfree-core/commit/7e1cd9b9b89c4175d0f6b86ced9d9bd49c04ac39)
-*	DB\Jig\Mapper: Added group feature, bcosca/fatfree#616
-*	DB\SQL\Mapper: fix PostgreSQL RETURNING ID when no pkey is available, [bcosca/fatfree#1069](https://github.com/bcosca/fatfree/issues/1069), [#230](https://github.com/bcosca/fatfree-core/issues/230)
-*	DB\SQL\Mapper: disable order clause auto-quoting when it's already been quoted
-*	Web->location: add failsafe for geoip_region_name_by_code() [#GB:Bxyn9xn9AgAJ](https://groups.google.com/d/msg/f3-framework/APau4wnwNzE/Bxyn9xn9AgAJ)
-*	Web->request: Added proxy support [#e936361b](https://github.com/bcosca/fatfree-core/commit/e936361bc03010c4c7c38a396562e5e96a8a100d)
-*	Web->mime: Added JFIF format
-*	Markdown: handle line breaks in paragraph blocks, [bcosca/fatfree#1100](https://github.com/bcosca/fatfree/issues/1100)
-*	config: reduced cast calls on parsing config sections
-*	Patch empty SERVER_NAME [bcosca/fatfree#1084](https://github.com/bcosca/fatfree/issues/1084)
-*	Bugfix: unreliable request headers in Web->request() response [bcosca/fatfree#1092](https://github.com/bcosca/fatfree/issues/1092)
-*	Fixed, View->render: utilizing multiple UI paths, [bcosca/fatfree#1083](https://github.com/bcosca/fatfree/issues/1083)
-*	Fixed URL parsing with PHP 5.4 [#247](https://github.com/bcosca/fatfree-core/issues/247)
-*	Fixed PHP 7.2 warnings when session is active prematurely, [#238](https://github.com/bcosca/fatfree-core/issues/238)
-*	Fixed setcookie $expire variable type [#240](https://github.com/bcosca/fatfree-core/issues/240)
-*	Fixed expiration time when updating an existing cookie
-
-3.6.3 (31 December 2017)
-*	PHP7 fix: remove deprecated (unset) cast
-*	Web->request: restricted follow_location to 3XX responses only
-*	CLI mode: refactored arguments parsing
-*	CLI mode: fixed query string encoding
-*	SMTP: Refactor parsing of attachments
-*	SMTP: clean-up mail headers for multipart messages, [#1065](https://github.com/bcosca/fatfree/issues/1065)
-*	config: fixed performance issues on parsing config files
-*	config: cast command parameters in config entries to php type & constant, [#1030](https://github.com/bcosca/fatfree/issues/1030)
-*	config: reduced registry calls
-*	config: skip hive escaping when resolving dynamic config vars, [#1030](https://github.com/bcosca/fatfree/issues/1030)
-*	Bug fix: Incorrect cookie lifetime computation, [#1070](https://github.com/bcosca/fatfree/issues/1070), [#1016](https://github.com/bcosca/fatfree/issues/1016)
-*	DB\SQL\Mapper: use RETURNING option instead of a sequence query to get lastInsertId in PostgreSQL, [#1069](https://github.com/bcosca/fatfree/issues/1069), [#230](https://github.com/bcosca/fatfree-core/issues/230)
-*	DB\SQL\Session: check if _agent is too long for SQL based sessions [#236](https://github.com/bcosca/fatfree-core/issues/236)
-*	DB\SQL\Session: fix Session handler table creation issue on SQL Server, [#899](https://github.com/bcosca/fatfree/issues/899)
-*	DB\SQL: fix oracle db issue with empty error variable, [#1072](https://github.com/bcosca/fatfree/issues/1072)
-*	DB\SQL\Mapper: fix sorting issues on SQL Server, [#1052](https://github.com/bcosca/fatfree/issues/1052) [#225](https://github.com/bcosca/fatfree-core/issues/225)
-*	Prevent directory traversal attacks on filesystem based cache [#1073](https://github.com/bcosca/fatfree/issues/1073)
-*	Bug fix, Template: PHP constants used in include with attribute, [#983](https://github.com/bcosca/fatfree/issues/983)
-*	Bug fix, Template: Numeric value in expression alters PHP_EOL context
-*	Template: use existing linefeed instead of PHP_EOL, [#1048](https://github.com/bcosca/fatfree/issues/1048)
-*	Template: make newline interpolation handling configurable [#223](https://github.com/bcosca/fatfree-core/issues/223)
-*	Template: add beforerender to Preview
-*	fix custom FORMATS without modifiers
-*	Cache: Refactor Cache->reset for XCache
-*	Cache: loosen reset cache key pattern, [#1041](https://github.com/bcosca/fatfree/issues/1041)
-*	XCache: suffix reset only works if xcache.admin.enable_auth is disabled
-*	Added HTTP 103 as recently approved by the IETF
-*	LDAP changes to for AD flexibility [#227](https://github.com/bcosca/fatfree-core/issues/227)
-*	Hide debug trace from ajax errors when DEBUG=0 [#1071](https://github.com/bcosca/fatfree/issues/1071)
-*	fix View->render using potentially wrong cache entry
-
-3.6.2 (26 June 2017)
-*   Return a status code > 0 when dying on error [#220](https://github.com/bcosca/fatfree-core/issues/220)
-*   fix SMTP line width [#215](https://github.com/bcosca/fatfree-core/issues/215)
-*   Allow using a custom field for ldap user id checking [#217](https://github.com/bcosca/fatfree-core/issues/217)
-*   NEW: DB\SQL->exists: generic method to check if SQL table exists
-*   Pass handler to route handler and hooks [#1035](https://github.com/bcosca/fatfree/issues/1035)
-*   pass carriage return of multiline dictionary keys
-*   Better Web->slug customization
-*   fix incorrect header issue [#211](https://github.com/bcosca/fatfree-core/issues/211)
-*   fix schema issue on databases with case-sensitive collation, fixes [#209](https://github.com/bcosca/fatfree-core/issues/209)
-*   Add filter for deriving C-locale equivalent of a number
-*   Bug fix: @LANGUAGE remains unchanged after override
-*   abort: added Header pre-check
-*   Assemble URL after ONREROUTE
-*   Add reroute argument to skip script termination
-*   Invoke ONREROUTE after headers are sent
-*   SQLite switch to backtick as quote
-*   Bug fix: Incorrect timing in SQL query logs
-*   DB\SQL\Mapper: Cast return value of count to integer
-*   Patched $_SERVER['REQUEST_URI'] to ensure it contains a relative URI
-*   Tweak debug verbosity
-*   fix php carriage return issue in preview->build [#205](https://github.com/bcosca/fatfree-core/pull/205)
-*   fixed template string resolution [#205](https://github.com/bcosca/fatfree-core/pull/205)
-*   Fixed unexpected default seed on CACHE set [#1028](https://github.com/bcosca/fatfree/issues/1028)
-*   DB\SQL\Mapper: Optimized field escaping on options
-*   Optimize template conversion to PHP file
-
-3.6.1 (2 April 2017)
-*	NEW: Recaptcha plugin [#194](https://github.com/bcosca/fatfree-core/pull/194)
-*	NEW: MB variable for detecting multibyte support
-*	NEW: DB\SQL: Cache parsed schema for the TTL duration
-*	NEW: quick erase flag on Jig/Mongo/SQL mappers [#193](https://github.com/bcosca/fatfree-core/pull/193)
-*	NEW: Allow OPTIONS method to return a response body [#171](https://github.com/bcosca/fatfree-core/pull/171)
-*	NEW: Add support for Memcached (bcosca/fatfree#997)
-*	NEW: Rudimentary preload resource (HTTP2 server) support via template push()
-*	NEW: Add support for new MongoDB driver [#177](https://github.com/bcosca/fatfree-core/pull/177)
-*	Changed: template filter are all lowercase now
-*	Changed: Fix template lookup inconsistency: removed base dir from UI on render
-*	Changed: count() method now has an options argument [#192](https://github.com/bcosca/fatfree-core/pull/192)
-*	Changed: SMTP, Spit out error message if any
-*	\DB\SQL\Mapper: refactored row count strategy
-*	DB\SQL\Mapper: Allow non-scalar values to be assigned as mapper property
-*	DB\SQL::PARAM_FLOAT: remove cast to float (#106 and bcosca/fatfree#984) (#191)
-*	DB\SQL\mapper->erase: allow empty string
-*	DB\SQL\mapper->insert: fields reset after successful INSERT
-*	Add option to debounce Cursor->paginate subset [#195](https://github.com/bcosca/fatfree-core/pull/195)
-*	View: Don't delete sandboxed variables (#198)
-*	Preview: Optimize compilation of template expressions
-*	Preview: Use shorthand tag for direct rendering
-*	Preview->resolve(): new tweak to allow template persistence as option
-*	Web: Expose diacritics translation table
-*	SMTP: Enable logging of message body only when $log argument is 'verbose'
-*	SMTP: Convert headers to camelcase for consistency
-*	make cache seed more flexible, #164
-*	Improve trace details for DEBUG>2
-*	Enable config() to read from an array of input files
-*	Improved alias and reroute regex
-*	Make camelCase and snakeCase Unicode-aware
-*	format: Provision for optional whitespaces
-*	Break APCu-BC dependence
-*	Old PHP 5.3 cleanup
-*	Debug log must include HTTP query
-*	Recognize X-Forwarded-Port header (bcosca/fatfree#1002)
-*	Avoid use of deprecated mcrypt module
-*	Return only the client's IP when using the `X-Forwarded-For` header to deduce an IP address
-*	Remove orphan mutex locks on termination (#157)
-*	Use 80 as default port number to avoid issues when `$_SERVER['SERVER_PORT']` is not existing
-*	fread replaced with readfile() for simple send() usecase
-*	Bug fix: request URI with multiple leading slashes, #203
-*	Bug fix: Query generates wrong adhoc field value
-*	Bug fix: SMTP stream context issue #200
-*	Bug fix: child pseudo class selector in minify, bcosca/fatfree#1008
-*	Bug fix: "Undefined index: CLI" error (#197)
-*	Bug fix: cast Cache-Control expire time to int, bcosca/fatfree#1004
-*	Bug fix: Avoid issuance of multiple Content-Type headers for nested templates
-*	Bug fix: wildcard token issue with digits (bcosca/fatfree#996)
-*	Bug fix: afterupdate ignored when row does not change
-*	Bug fix: session handler read() method for PHP7 (need strict string) #184 #185
-*	Bug fix: reroute mocking in CLI mode (#183)
-*	Bug fix: Reroute authoritative relative references (#181)
-*	Bug fix: locales order and charset hyphen
-*	Bug fix: base stripped twice in router (#176)
-
-3.6.0 (19 November 2016)
-*	NEW: [cli] request type
-*	NEW: console-friendly CLI mode
-*	NEW: lexicon caching
-*	NEW: Silent operator skips startup error check (#125)
-*	NEW: DB\SQL->trans()
-*	NEW: custom config section parser, i.e. [conf > Foo::bar]
-*	NEW: support for cache tags in SQL
-*	NEW: custom FORMATS
-*	NEW: Mongo mapper fields whitelist
-*	NEW: WebSocket server
-*	NEW: Base->extend method (#158)
-*	NEW: Implement framework variable caching via config, i.e. FOO = "bar" | 3600
-*	NEW: Lightweight OAuth2 client
-*	NEW: SEED variable, configurable app-specific hashing prefix (#149, bcosca/fatfree#951, bcosca/fatfree#884, bcosca/fatfree#629)
-*	NEW: CLI variable
-*	NEW: Web->send, specify custom filename (#124)
-*	NEW: Web->send, added flushing flag (#131)
-*	NEW: Indexed route wildcards, now exposed in PARAMS['*']
-*	Changed: PHP 5.4 is now the minimum version requirement
-*	Changed: Prevent database wrappers from being cloned
-*	Changed: Router works on PATH instead of URI (#126) NB: PARAMS.0 no longer contains the query string
-*	Changed: Removed ALIASES autobuilding (#118)
-*	Changed: Route wildcards match empty strings (#119)
-*	Changed: Disable default debug highlighting, HIGHLIGHT is false now
-*	General PHP 5.4 optimizations
-*	Optimized config parsing
-*	Optimized Base->recursive
-*	Optimized header extraction
-*	Optimized cache/expire headers
-*	Optimized session_start behaviour (bcosca/fatfree#673)
-*	Optimized reroute regex
-*	Tweaked cookie removal
-*	Better route precedence order
-*	Performance tweak: reduced cache calls
-*	Refactored lexicon (LOCALES) build-up, much faster now
-*	Added turkish locale bug workaround
-*	Geo->tzinfo Update to UTC
-*	Added Xcache reset (bcosca/fatfree#928)
-*	Redis cache: allow db name in dsn
-*	SMTP: Improve server emulation responses
-*	SMTP: Optimize transmission envelope
-*	SMTP: Implement mock transmission
-*	SMTP: Various bug fixes and feature improvements
-*	SMTP: quit on failed authentication
-*	Geo->weather: force metric units
-*	Base->until: Implement CLI interoperability
-*	Base->format: looser plural syntax
-*	Base->format: Force decimal as default number format
-*	Base->merge: Added $keep flag to save result to the hive key
-*	Base->reroute: Allow array as URL argument for aliasing
-*	Base->alias: Allow query string (or array) to be appended to alias
-*	Permit reroute to named routes with URL query segment
-*	Sync COOKIE global on set()
-*	Permit non-hive variables to use JS dot notation
-*	RFC2616: Use absolute URIs for Location header
-*	Matrix->calendar: Check if calendar extension is loaded
-*	Markdown: require start of line/whitespace for text processing (#136)
-*	DB\[SQL|Jig|Mongo]->log(FALSE) disables logging
-*	DB\SQL->exec: Added timestamp toggle to db log
-*	DB\SQL->schema: Remove unnecessary line terminators
-*	DB\SQL\Mapper: allow array filter with empty string
-*	DB\SQL\Mapper: optimized handling for key-less tables
-*	DB\SQL\Mapper: added float support (#106)
-*	DB\SQL\Session: increased default column sizes (#148, bcosca/fatfree#931, bcosca/fatfree#950)
-*	Web: Catch cURL errors
-*	Optimize Web->receive (bcosca/fatfree#930)
-*	Web->minify: fix arbitrary file download vulnerability
-*	Web->request: fix cache control max-age detection (bcosca/fatfree#908)
-*	Web->request: Add request headers & error message to return value (bcosca/fatfree#737)
-*	Web->request: Refactored response to HTTP request
-*	Web->send flush while sending big files
-*	Image->rgb: allow hex strings
-*	Image->captcha: Check if GD module supports TrueType
-*	Image->load: Return FALSE on load failure
-*	Image->resize: keep aspect ratio when only width or height was given
-*	Updated OpenID lib (bcosca/fatfree#965)
-*	Audit->card: add new mastercard "2" BIN range (bcosca/fatfree#954)
-*	Deprecated: Bcrypt class
-*	Preview->render: optimized detection to remove short open PHP tags and allow xml tags (#133)
-*	Display file and line number in exception handler (bcosca/fatfree#967)
-*	Added error reporting level to Base->error and ERROR.level (bcosca/fatfree#957)
-*	Added optional custom cache instance to Session (#141)
-*	CLI-aware mock()
-*	XFRAME and PACKAGE can be switched off now (#128)
-*	Bug fix: wrong time calculation on memcache reset (#170)
-*	Bug fix: encode CLI parameters
-*	Bug fix: Close connection on abort explicitly (#162)
-*	Bug fix: Image->identicon, Avoid double-size sprite rotation (and possible segfault)
-*	Bug fix: Image->render and Image->dump, removed unnecessary 2nd argument (#146)
-*	Bug fix: Magic->offsetset, access property as array element (#147)
-*	Bug fix: multi-line custom template tag parsing (bcosca/fatfree#935)
-*	Bug fix: cache headers on errors (bcosca/fatfree#885)
-*	Bug fix: Web, deprecated CURLOPT_SSL_VERIFYHOST in curl
-*	Bug fix: Web, Invalid user error constant (bcosca/fatfree#962)
-*	Bug fix: Web->request, redirections for domain-less location (#135)
-*	Bug fix: DB\SQL\Mapper, reset changed flag after update (#142, #152)
-*	Bug fix: DB\SQL\Mapper, fix changed flag when using assignment operator #143 #150 #151
-*	Bug fix: DB\SQL\Mapper, revival of the HAVING clause
-*	Bug fix: DB\SQL\Mapper, pgsql with non-integer primary keys (bcosca/fatfree#916)
-*	Bug fix: DB\SQL\Session, quote table name (bcosca/fatfree#977)
-*	Bug fix: snakeCase returns word starting with underscore (bcosca/fatfree#927)
-*	Bug fix: mock does not populate PATH variable
-*	Bug fix: Geo->weather API key (#129)
-*	Bug fix: Incorrect compilation of array element with zero index
-*	Bug fix: Compilation of array construct is incorrect
-*	Bug fix: Trailing slash redirection on UTF-8 paths (#121)
-
-3.5.1 (31 December 2015)
-*	NEW: ttl attribute in <include> template tag
-*	NEW: allow anonymous function for template filter
-*	NEW: format modifier for international and custom currency symbol
-*	NEW: Image->data() returns image resource
-*	NEW: extract() get prefixed array keys from an assoc array
-*	NEW: Optimized and faster Template parser with full support for HTML5 empty tags
-*	NEW: Added support for {@token} encapsulation syntax in routes definition
-*	NEW: DB\SQL->exec(), automatically shift to 1-based query arguments
-*	NEW: abort() flush output
-*	Added referenced value to devoid()
-*	Template token filters are now resolved within Preview->token()
-*	Web->_curl: restrict redirections to HTTP
-*	Web->minify(), skip importing of external files
-*	Improved session and error handling in until()
-*	Get the error trace array with the new $format parameter
-*	Better support for unicode URLs
-*	Optimized TZ detection with date_default_timezone_get()
-*	format() Provide default decimal places
-*	Optimize code: remove redundant TTL checks
-*	Optimized timeout handling in Web->request()
-*	Improved PHPDoc hints
-*	Added missing russian DIACRITICS letters
-*	DB\Cursor: allow child implementation of reset()
-*	DB\Cursor: Copyfrom now does an internal call to set()
-*	DB\SQL: Provide the ability to disable SQL logging
-*	DB\SQL: improved query analysis to trigger fetchAll
-*	DB\SQL\Mapper: added support for binary table columns
-*	SQL,JIG,MONGO,CACHE Session handlers refactored and optimized
-*	SMTP Refactoring and optimization
-*	Bug fix: SMTP, Align quoted_printable_encode() with SMTP specs (dot-stuffing)
-*	Bug fix: SMTP, Send buffered optional headers to output
-*	Bug fix: SMTP, Content-Transfer-Encoding for non-TLS connections
-*	Bug fix: SMTP, Single attachment error
-*	Bug fix: Cursor->load not always mapping to first record
-*	Bug fix: dry SQL mapper should not trigger 'load'
-*	Bug fix: Code highlighting on empty text
-*	Bug fix: Image->resize, round dimensions instead of cast
-*	Bug fix: whitespace handling in $f3->compile()
-*	Bug fix: TTL of `View` and `Preview` (`Template`)
-*	Bug fix: token filter regex
-*	Bug fix: Template, empty attributes
-*	Bug fix: Preview->build() greedy regex
-*	Bug fix: Web->minify() single-line comment on last line
-*	Bug fix: Web->request(), follow_location with cURL and open_basedir
-*	Bug fix: Web->send() Single quotes around filename not interpreted correctly by some browsers
-
-3.5.0 (2 June 2015)
-*	NEW: until() method for long polling
-*	NEW: abort() to disconnect HTTP client (and continue execution)
-*	NEW: SQL Mapper->required() returns TRUE if field is not nullable
-*	NEW: PREMAP variable for allowing prefixes to handlers named after HTTP verbs
-*	NEW: [configs] section to allow config includes
-*	NEW: Test->passed() returns TRUE if no test failed
-*	NEW: SQL mapper changed() function
-*	NEW: fatfree-core composer support
-*	NEW: constants() method to expose constants
-*	NEW: Preview->filter() for configurable token filters
-*	NEW: CORS variable for Cross-Origin Resource Sharing support, #731
-*	Change in behavior: Switch to htmlspecialchars for escaping
-*	Change in behavior: No movement in cursor position after erase(), #797
-*	Change in behavior: ERROR.trace is a multiline string now
-*	Change in behavior: Strict token recognition in <include> href attribute
-*	Router fix: loose method search
-*	Better route precedence order, #12
-*	Preserve contents of ROUTES, #723
-*	Alias: allow array of parameters
-*	Improvements on reroute method
-*	Fix for custom Jig session files
-*	Audit: better mobile detection
-*	Audit: add argument to test string as browser agent
-*	DB mappers: abort insert/update/erase from hooks, #684
-*	DB mappers: Allow array inputs in copyfrom()
-*	Cache,SQL,Jig,Mongo Session: custom callback for suspect sessions
-*	Fix for unexpected HIVE values when defining an empty HIVE array
-*	SQL mapper: check for results from CALL and EXEC queries, #771
-*	SQL mapper: consider SQL schema prefix, #820
-*	SQL mapper: write to log before execution to
-	enable tracking of PDOStatement error
-*	Add SQL Mapper->table() to return table name
-*	Allow override of the schema in SQL Mapper->schema()
-*	Improvement: Keep JIG table as reference, #758
-*	Expand regex to include whitespaces in SQL DB dsn, #817
-*	View: Removed reserved variables $fw and $implicit
-*	Add missing newlines after template expansion
-*	Web->receive: fix for complex field names, #806
-*	Web: Improvements in socket engine
-*	Web: customizable user_agent for all engines, #822
-*	SMTP: Provision for Content-ID in attachments
-*	Image + minify: allow absolute paths
-*	Promote framework error to E_USER_ERROR
-*	Geo->weather switch to OpenWeather
-*	Expose mask() and grab() methods for routing
-*	Expose trace() method to expose the debug backtrace
-*	Implement recursion strategy using IteratorAggregate, #714
-*	Exempt whitespace between % and succeeding operator from being minified, #773
-*	Optimized error detection and ONERROR handler, fatfree-core#18
-*	Tweak error log output
-*	Optimized If-Modified-Since cache header usage
-*	Improved APCu compatibility, #724
-*	Bug fix: Web::send fails on filename with spaces, #810
-*	Bug fix: overwrite limit in findone()
-*	Bug fix: locale-specific edge cases affecting SQL schema, #772
-*	Bug fix: Newline stripping in config()
-*	Bug fix: bracket delimited identifier for sybase and dblib driver
-*	Bug fix: Mongo mapper collection->count driver compatibility
-*	Bug fix: SQL Mapper->set() forces adhoc value if already defined
-*	Bug fix: Mapper ignores HAVING clause
-*	Bug fix: Constructor invocation in call()
-*	Bug fix: Wrong element returned by ajax/sync request
-*	Bug fix: handling of non-consecutive compound key members
-*	Bug fix: Virtual fields not retrieved when group option is present, #757
-*	Bug fix: group option generates incorrect SQL query, #757
-*	Bug fix: ONERROR does not receive PARAMS on fatal error
-
-3.4.0 (1 January 2015)
-*	NEW: [redirects] section
-*	NEW: Custom config sections
-*	NEW: User-defined AUTOLOAD function
-*	NEW: ONREROUTE variable
-*	NEW: Provision for in-memory Jig database (#727)
-*	Return run() result (#687)
-*	Pass result of run() to mock() (#687)
-*	Add port suffix to REALM variable
-*	New attribute in <include> tag to extend hive
-*	Adjust unit tests and clean up templates
-*	Expose header-related methods
-*	Web->request: allow content array
-*	Preserve contents of ROUTES (#723)
-*	Smart detection of PHP functions in template expressions
-*	Add afterrender() hook to View class
-*	Implement ArrayAccess and magic properties on hive
-*	Improvement on mocking of superglobals and request body
-*	Fix table creation for pgsql handled sessions
-*	Add QUERY to hive
-*	Exempt E_NOTICE from default error_reporting()
-*	Add method to build alias routes from template, fixes #693
-*	Fix dangerous caching of cookie values
-*	Fix multiple encoding in nested templates
-*	Fix node attribute parsing for empty/zero values
-*	Apply URL encoding on BASE to emulate v2 behavior (#123)
-*	Improve Base->map performance (#595)
-*	Add simple backtrace for fatal errors
-*	Count Cursor->load() results (#581)
-*	Add form field name to Web->receive() callback arguments
-*	Fix missing newlines after template expansion
-*	Fix overwrite of ENCODING variable
-*	limit & offset workaround for SQL Server, fixes #671
-*	SQL Mapper->find: GROUP BY SQL compliant statement
-*	Bug fix: Missing abstract method fields()
-*	Bug fix: Auto escaping does not work with mapper objects (#710)
-*	Bug fix: 'with' attribute in <include> tag raise error when no token
-	inside
-*	View rendering: optional Content-Type header
-*	Bug fix: Undefined variable: cache (#705)
-*	Bug fix: Routing does not work if project base path includes valid
-	special URI character (#704)
-*	Bug fix: Template hash collision (#702)
-*	Bug fix: Property visibility is incorrect (#697)
-*	Bug fix: Missing Allow header on HTTP 405 response
-*	Bug fix: Double quotes in lexicon files (#681)
-*	Bug fix: Space should not be mandatory in ICU pluralization format string
-*	Bug fix: Incorrect log entry when SQL query contains a question mark
-*	Bug fix: Error stack trace
-*	Bug fix: Cookie expiration (#665)
-*	Bug fix: OR operator (||) parsed incorrectly
-*	Bug fix: Routing treatment of * wildcard character
-*	Bug fix:  Mapper copyfrom() method doesn't allow class/object callbacks
-	(#590)
-*	Bug fix: exists() creates elements/properties (#591)
-*	Bug fix: Wildcard in routing pattern consumes entire query string (#592)
-*	Bug fix: Workaround bug in latest MongoDB driver
-*	Bug fix: Default error handler silently fails for AJAX request with
-	DEBUG>0 (#599)
-*	Bug fix: Mocked BODY overwritten (#601)
-*	Bug fix: Undefined pkey (#607)
-
-3.3.0 (8 August 2014)
-*	NEW: Attribute in <include> tag to extend hive
-*	NEW: Image overlay with transparency and alignment control
-*	NEW: Allow redirection of specified route patterns to a URL
-*	Bug fix: Missing AND operator in SQL Server schema query (Issue #576)
-*	Count Cursor->load() results (Feature request #581)
-*	Mapper copyfrom() method doesn't allow class/object callbacks (Issue #590)
-*	Bug fix: exists() creates elements/properties (Issue #591)
-*	Bug fix: Wildcard in routing pattern consumes entire query string
-	(Issue #592)
-*	Tweak Base->map performance (Issue #595)
-*	Bug fix: Default error handler silently fails for AJAX request with
-	DEBUG>0 (Issue #599)
-*	Bug fix: Mocked BODY overwritten (Issue #601)
-*	Bug fix: Undefined pkey (Issue #607)
-*	Bug fix: beforeupdate() position (Issue #633)
-*	Bug fix: exists() return value for cached keys
-*	Bug fix: Missing error code in UNLOAD handler
-*	Bug fix: OR operator (||) parsed incorrectly
-*	Add input name parameter to custom slug function
-*	Apply URL encoding on BASE to emulate v2 behavior (Issue #123)
-*	Reduce mapper update() iterations
-*	Bug fix: Routing treatment of * wildcard character
-*	SQL Mapper->find: GROUP BY SQL compliant statement
-*	Work around bug in latest MongoDB driver
-*	Work around probable race condition and optimize cache access
-*	View rendering: Optional Content-Type header
-*	Fix missing newlines after template expansion
-*	Add form field name to Web->receive() callback arguments
-*	Quick reference: add RAW variable
-
-3.2.2 (19 March 2014)
-*	NEW: Locales set automatically (Feature request #522)
-*	NEW: Mapper dbtype()
-*	NEW: before- and after- triggers for all mappers
-*	NEW: Decode HTML5 entities if PHP>5.3 detected (Feature request #552)
-*	NEW: Send credentials only if AUTH is present in the SMTP extension
-	response (Feature request #545)
-*	NEW: BITMASK variable to allow ENT_COMPAT override
-*	NEW: Redis support for caching
-*	Enable SMTP feature detection
-*	Enable extended ICU custom date format (Feature request #555)
-*	Enable custom time ICU format
-*	Add option to turn off session table creation (Feature request #557)
-*	Enhanced template token rendering and custom filters (Feature request
-	#550)
-*	Avert multiple loads in DB-managed sessions (Feature request #558)
-*	Add EXEC to associative fetch
-*	Bug fix: Building template tokens breaks on inline OR condition (Issue
-	#573)
-*	Bug fix: SMTP->send does not use the $log parameter (Issue #571)
-*	Bug fix: Allow setting sqlsrv primary keys on insert (Issue #570)
-*	Bug fix: Generated query for obtaining table schema in sqlsrv incorrect
-	(Bug #565)
-*	Bug fix: SQL mapper flag set even when value has not changed (Bug #562)
-*	Bug fix: Add XFRAME config option (Feature request #546)
-*	Bug fix: Incorrect parsing of comments (Issue #541)
-*	Bug fix: Multiple Set-Cookie headers (Issue #533)
-*	Bug fix: Mapper is dry after save()
-*	Bug fix: Prevent infinite loop when error handler is triggered
-	(Issue #361)
-*	Bug fix: Mapper tweaks not passing primary keys as arguments
-*	Bug fix: Zero indexes in dot-notated arrays fail to compile
-*	Bug fix: Prevent GROUP clause double-escaping
-*	Bug fix: Regression of zlib compression bug
-*	Bug fix: Method copyto() does not include ad hoc fields
-*	Check existence of OpenID mode (Issue #529)
-*	Generate a 404 when a tokenized class doesn't exist
-*	Fix SQLite quotes (Issue #521)
-*	Bug fix: BASE is incorrect on Windows
-
-3.2.1 (7 January 2014)
-*	NEW: EMOJI variable, UTF->translate(), UTF->emojify(), and UTF->strrev()
-*	Allow empty strings in config()
-*	Add support for turning off php://input buffering via RAW
-	(FALSE by default)
-*	Add Cursor->load() and Cursor->find() TTL support
-*	Support Web->receive() large file downloads via PUT
-*	ONERROR safety check
-*	Fix session CSRF cookie detection
-*	Framework object now passed to route handler contructors
-*	Allow override of DIACRITICS
-*	Various code optimizations
-*	Support log disabling (Issue #483)
-*	Implicit mapper load() on authentication
-*	Declare abstract methods for Cursor derivatives
-*	Support single-quoted HTML/XML attributes (Feature request #503)
-*	Relax property visibility of mappers and derivatives
-*	Deprecated: {{~ ~}} instructions and {{* *}} comments; Use {~ ~} and
-	{* *} instead
-*	Minor fix: Audit->ipv4() return value
-*	Bug fix: Backslashes in BASE not converted on Windows
-*	Bug fix: UTF->substr() with negative offset and specified length
-*	Bug fix: Replace named URL tokens on render()
-*	Bug fix: BASE is not empty when run from document root
-*	Bug fix: stringify() recursion
-
-3.2.0 (18 December 2013)
-*	NEW: Automatic CSRF protection (with IP and User-Agent checks) for
-	sessions mapped to SQL-, Jig-, Mongo- and Cache-based backends
-*	NEW: Named routes
-*	NEW: PATH variable; returns the URL relative to BASE
-*	NEW: Image->captcha() color parameters
-*	NEW: Ability to access MongoCuror thru the cursor() method
-*	NEW: Mapper->fields() method returns array of field names
-*	NEW: Mapper onload(), oninsert(), onupdate(), and onerase() event
-	listeners/triggers
-*	NEW: Preview class (a lightweight template engine)
-*	NEW: rel() method derives path from URL relative to BASE; useful for
-	rerouting
-*	NEW: PREFIX variable for prepending a string to a dictionary term;
-	Enable support for prefixed dictionary arrays and .ini files (Feature
-	request #440)
-*	NEW: Google static map plugin
-*	NEW: devoid() method
-*	Introduce clean(); similar to scrub(), except that arg is passed by
-	value
-*	Use $ttl for cookie expiration (Issue #457)
-*	Fix needs_rehash() cost comparison
-*	Add pass-by-reference argument to exists() so if method returns TRUE,
-	a subsequent get() is unnecessary
-*	Improve MySQL support
-*	Move esc(), raw(), and dupe() to View class where they more
-	appropriately belong
-*	Allow user-defined fields in SQL mapper constructor (Feature request
-	#450)
-*	Re-implement the pre-3.0 template resolve() feature
-*	Remove redundant instances of session_commit()
-*	Add support for input filtering in Mapper->copyfrom()
-*	Prevent intrusive behavior of Mapper->copyfrom()
-*	Support multiple SQL primary keys
-*	Support custom tag attributes/inline tokens defined at runtime
-	(Feature request #438)
-*	Broader support for HTTP basic auth
-*	Prohibit Jig _id clear()
-*	Add support for detailed stringify() output
-*	Add base directory to UI path as fallback
-*	Support Test->expect() chaining
-*	Support __tostring() in stringify()
-*	Trigger error on invalid CAPTCHA length (Issue #458)
-*	Bug fix: exists() pass-by-reference argument returns incorrect value
-*	Bug fix: DB Exec does not return affected row if query contains a
-	sub-SELECT (Issue #437)
-*	Improve seed generator and add code for detecting of acceptable
-	limits in Image->captcha() (Feature request #460)
-*	Add decimal format ICU extension
-*	Bug fix: 404-reported URI contains HTTP query
-*	Bug fix: Data type detection in DB->schema()
-*	Bug fix: TZ initialization
-*	Bug fix: paginate() passes incorrect argument to count()
-*	Bug fix: Incorrect query when reloading after insert()
-*	Bug fix: SQL preg_match error in pdo_type matching (Issue #447)
-*	Bug fix: Missing merge() function (Issue #444)
-*	Bug fix: BASE misdefined in command line mode
-*	Bug fix: Stringifying hive may run infinite (Issue #436)
-*	Bug fix: Incomplete stringify() when DEBUG<3 (Issue #432)
-*	Bug fix: Redirection of basic auth (Issue #430)
-*	Bug fix: Filter only PHP code (including short tags) in templates
-*	Bug fix: Markdown paragraph parser does not convert PHP code blocks
-	properly
-*	Bug fix: identicon() colors on same keys are randomized
-*	Bug fix: quotekey() fails on aliased keys
-*	Bug fix: Missing _id in Jig->find() return value
-*	Bug fix: LANGUAGE/LOCALES handling
-*	Bug fix: Loose comparison in stringify()
-
-3.1.2 (5 November 2013)
-*	Abandon .chm help format; Package API documentation in plain HTML;
-	(Launch lib/api/index.html in your browser)
-*	Deprecate BAIL in favor of HALT (default: TRUE)
-*	Revert to 3.1.0 autoload behavior; Add support for lowercase folder
-	names
-*	Allow Spring-style HTTP method overrides
-*	Add support for SQL Server-based sessions
-*	Capture full X-Forwarded-For header
-*	Add protection against malicious scripts; Extra check if file was really
-	uploaded
-*	Pass-thru page limit in return value of Cursor->paginate()
-*	Optimize code: Implement single-pass escaping
-*	Short circuit Jig->find() if source file is empty
-*	Bug fix: PHP globals passed by reference in hive() result (Issue #424)
-*	Bug fix: ZIP mime type incorrect behavior
-*	Bug fix: Jig->erase() filter malfunction
-*	Bug fix: Mongo->select() group
-*	Bug fix: Unknown bcrypt constant
-
-3.1.1 (13 October 2013)
-*	NEW: Support OpenID attribute exchange
-*	NEW: BAIL variable enables/disables continuance of execution on non-fatal
-	errors
-*	Deprecate BAIL in favor of HALT (default: FALSE)
-*	Add support for Oracle
-*	Mark cached queries in log (Feature Request #405)
-*	Implement Bcrypt->needs_reshash()
-*	Add entropy to SQL cache hash; Add uuid() method to DB backends
-*	Find real document root; Simplify debug paths
-*	Permit OpenID required fields to be declared as comma-separated string or
-	array
-*	Pass modified filename as argument to user-defined function in
-	Web->receive()
-*	Quote keys in optional SQL clauses (Issue #408)
-*	Allow UNLOAD to override fatal error detection (Issue #404)
-*	Mutex operator precedence error (Issue #406)
-*	Bug fix: exists() malfunction (Issue #401)
-*	Bug fix: Jig mapper triggers error when loading from CACHE (Issue #403)
-*	Bug fix: Array index check
-*	Bug fix: OpenID verified() return value
-*	Bug fix: Basket->find() should return a set of results (Issue #407);
-	Also implemented findone() for consistency with mappers
-*	Bug fix: PostgreSQL last insert ID (Issue #410)
-*	Bug fix: $port component URL overwritten by _socket()
-*	Bug fix: Calculation of elapsed time
-
-3.1.0 (20 August 2013)
-*	NEW: Web->filler() returns a chunk of text from the standard
-	Lorem Ipsum passage
-*	Change in behavior: Drop support for JSON serialization
-*	SQL->exec() now returns value of RETURNING clause
-*	Add support for $ttl argument in count() (Issue #393)
-*	Allow UI to be overridden by custom $path
-*	Return result of PDO primitives: begintransaction(), rollback(), and
-	commit()
-*	Full support for PHP 5.5
-*	Flush buffers only when DEBUG=0
-*	Support class->method, class::method, and lambda functions as
-	Web->basic() arguments
-*	Commit session on Basket->save()
-*	Optional enlargement in Image->resize()
-*	Support authentication on hosts running PHP-CGI
-*	Change visibility level of Cache properties
-*	Prevent ONERROR recursion
-*	Work around Apache pre-2.4 VirtualDocumentRoot bug
-*	Prioritize cURL in HTTP engine detection
-*	Bug fix: Minify tricky JS
-*	Bug fix: desktop() detection
-*	Bug fix: Double-slash on TEMP-relative path
-*	Bug fix: Cursor mapping of first() and last() records
-*	Bug fix: Premature end of Web->receive() on multiple files
-*	Bug fix: German umlaute to its corresponding grammatically-correct
-	equivalent
-
-3.0.9 (12 June 2013)
-*	NEW: Web->whois()
-*	NEW: Template <switch> <case> tags
-*	Improve CACHE consistency
-*	Case-insensitive MIME type detection
-*	Support pre-PHP 5.3.4 in Prefab->instance()
-*	Refactor isdesktop() and ismobile(); Add isbot()
-*	Add support for Markdown strike-through
-*	Work around ODBC's lack of quote() support
-*	Remove useless Prefab destructor
-*	Support multiple cache instances
-*	Bug fix: Underscores in OpenId keys mangled
-*	Refactor format()
-*	Numerous tweaks
-*	Bug fix: MongoId object not preserved
-*	Bug fix: Double-quotes included in lexicon() string (Issue #341)
-*	Bug fix: UTF-8 formatting mangled on Windows (Issue #342)
-*	Bug fix: Cache->load() error when CACHE is FALSE (Issue #344)
-*	Bug fix: send() ternary expression
-*	Bug fix: Country code constants
-
-3.0.8 (17 May 2013)
-*	NEW: Bcrypt lightweight hashing library\
-*	Return total number of records in superset in Cursor->paginate()
-*	ONERROR short-circuit (Enhancement #334)
-*	Apply quotes/backticks on DB identifiers
-*	Allow enabling/disabling of SQL log
-*	Normalize glob() behavior (Issue #330)
-*	Bug fix: mbstring 2-byte text truncation (Issue #325)
-*	Bug fix: Unsupported operand types (Issue #324)
-
-3.0.7 (2 May 2013)
-*	NEW: route() now allows an array of routing patterns as first argument;
-	support array as first argument of map()
-*	NEW: entropy() for calculating password strength (NIST 800-63)
-*	NEW: AGENT variable containing auto-detected HTTP user agent string
-*	NEW: ismobile() and isdesktop() methods
-*	NEW: Prefab class and descendants now accept constructor arguments
-*	Change in behavior: Cache->exists() now returns timestamp and TTL of
-	cache entry or FALSE if not found (Feature request #315)
-*	Preserve timestamp and TTL when updating cache entry (Feature request
-	#316)
-*	Improved currency formatting with C99 compliance
-*	Suppress unnecessary program halt at startup caused by misconfigured
-	server
-*	Add support for dashes in custom attribute names in templates
-*	Bug fix: Routing precedene (Issue #313)
-*	Bug fix: Remove Jig _id element from document property
-*	Bug fix: Web->rss() error when not enough items in the feed (Issue #299)
-*	Bug fix: Web engine fallback (Issue #300)
-*	Bug fix: <strong> and <em> formatting
-*	Bug fix: Text rendering of text with trailing punctuation (Issue #303)
-*	Bug fix: Incorrect regex in SMTP
-
-3.0.6 (31 Mar 2013)
-*	NEW: Image->crop()
-*	Modify documentation blocks for PHPDoc interoperability
-*	Allow user to control whether Base->rerouet() uses a permanent or
-	temporary redirect
-*	Allow JAR elements to be set individually
-*	Refactor DB\SQL\Mapper->insert() to cope with autoincrement fields
-*	Trigger error when captcha() font is missing
-*	Remove unnecessary markdown regex recursion
-*	Check for scalars instead of DB\SQL strings
-*	Implement more comprehensive diacritics table
-*	Add option for disabling 401 errors when basic auth() fails
-*	Add markdown syntax highlighting for Apache configuration
-*	Markdown->render() deprecated to remove dependency on UI variable;
-	Feature replaced by Markdown->convert() to enable translation from
-	markdown string to HTML
-*	Optimize factory() code of all data mappers
-*	Apply backticks on MySQL table names
-*	Bug fix: Routing failure when directory path contains a tilde (Issue #291)
-*	Bug fix: Incorrect markdown parsing of strong/em sequences and inline HTML
-*	Bug fix: Cached page not echoed (Issue #278)
-*	Bug fix: Object properties not escaped when rendering
-*	Bug fix: OpenID error response ignored
-*	Bug fix: memcache_get_extended_stats() timeout
-*	Bug fix: Base->set() doesn't pass TTL to Cache->set()
-*	Bug fix: Base->scrub() ignores pass-thru * argument (Issue #274)
-
-3.0.5 (16 Feb 2013)
-*	NEW: Markdown class with PHP, HTML, and .ini syntax highlighting support
-*	NEW: Options for caching of select() and find() results
-*	NEW: Web->acceptable()
-*	Add send() argument for forcing downloads
-*	Provide read() option for applying Unix LF as standard line ending
-*	Bypass lexicon() call if LANGUAGE is undefined
-*	Load fallback language dictionary if LANGUAGE is undefined
-*	map() now checks existence of class/methods for non-tokenized URLs
-*	Improve error reporting of non-existent Template methods
-*	Address output buffer issues on some servers
-*	Bug fix: Setting DEBUG to 0 won't suppress the stack trace when the
-	content type is application/json (Issue #257)
-*	Bug fix: Image dump/render additional arguments shifted
-*	Bug fix: ob_clean() causes buffer issues with zlib compression
-*	Bug fix: minify() fails when commenting CSS @ rules (Issue #251)
-*	Bug fix: Handling of commas inside quoted strings
-*	Bug fix: Glitch in stringify() handling of closures
-*	Bug fix: dry() in mappers returns TRUE despite being hydrated by
-	factory() (Issue #265)
-*	Bug fix: expect() not handling flags correctly
-*	Bug fix: weather() fails when server is unreachable
-
-3.0.4 (29 Jan 2013)
-*	NEW: Support for ICU/CLDR pluralization
-*	NEW: User-defined FALLBACK language
-*	NEW: minify() now recognizes CSS @import directives
-*	NEW: UTF->bom() returns byte order mark for UTF-8 encoding
-*	Expose SQL\Mapper->schema()
-*	Change in behavior: Send error response as JSON string if AJAX request is
-	detected
-*	Deprecated: afind*() methods
-*	Discard output buffer in favor of debug output
-*	Make _id available to Jig queries
-*	Magic class now implements ArrayAccess
-*	Abort execution on startup errors
-*	Suppress stack trace on DEBUG level 0
-*	Allow single = as equality operator in Jig query expressions
-*	Abort OpenID discovery if Web->request() fails
-*	Mimic PHP *RECURSION* in stringify()
-*	Modify Jig parser to allow wildcard-search using preg_match()
-*	Abort execution after error() execution
-*	Concatenate cached/uncached minify() iterations; Prevent spillover
-	caching of previous minify() result
-*	Work around obscure PHP session id regeneration bug
-*	Revise algorithm for Jig filter involving undefined fields (Issue #230)
-*	Use checkdnsrr() instead of gethostbyname() in DNSBL check
-*	Auto-adjust pagination to cursor boundaries
-*	Add Romanian diacritics
-*	Bug fix: Root namespace reference and sorting with undefined Jig fields
-*	Bug fix: Greedy receive() regex
-*	Bug fix: Default LANGUAGE always 'en'
-*	Bug fix: minify() hammers cache backend
-*	Bug fix: Previous values of primary keys not saved during factory()
-	instantiation
-*	Bug fix: Jig find() fails when search key is not present in all records
-*	Bug fix: Jig SORT_DESC (Issue #233)
-*	Bug fix: Error reporting (Issue #225)
-*	Bug fix: language() return value
-
-3.0.3 (29 Dec 2013)
-*	NEW: [ajax] and [sync] routing pattern modifiers
-*	NEW: Basket class (session-based pseudo-mapper, shopping cart, etc.)
-*	NEW: Test->message() method
-*	NEW: DB profiling via DB->log()
-*	NEW: Matrix->calendar()
-*	NEW: Audit->card() and Audit->mod10() for credit card verification
-*	NEW: Geo->weather()
-*	NEW: Base->relay() accepts comma-separated callbacks; but unlike
-	Base->chain(), result of previous callback becomes argument of the next
-*	Numerous performance tweaks
-*	Interoperability with new MongoClient class
-*	Web->request() now recognizes gzip and deflate encoding
-*	Differences in behavior of Web->request() engines rectified
-*	mutex() now uses an ID as argument (instead of filename to make it clear
-	that specified file is not the target being locked, but a primitive
-	cross-platform semaphore)
-*	DB\SQL\Mapper field _id now returned even in the absence of any
-	auto-increment field
-*	Magic class spinned off as a separate file
-*	ISO 3166-1 alpha-2 table updated
-*	Apache redirect emulation for PHP 5.4 CLI server mode
-*	Framework instance now passed as argument to any user-defined shutdown
-	function
-*	Cache engine now used as storage for Web->minify() output
-*	Flag added for enabling/disabling Image class filter history
-*	Bug fix: Trailing routing token consumes HTTP query
-*	Bug fix: LANGUAGE spills over to LOCALES setting
-*	Bug fix: Inconsistent dry() return value
-*	Bug fix: URL-decoding
-
-3.0.2 (23 Dec 2013)
-*	NEW: Syntax-highlighted stack traces via Base->highlight(); boolean
-	HIGHLIGHT global variable can be used to enable/disable this feature
-*	NEW: Template engine <ignore> tag
-*	NEW: Image->captcha()
-*	NEW: DNSBL-based spammer detection (ported from 2.x)
-*	NEW: paginate(), first(), and last() methods for data mappers
-*	NEW: X-HTTP-Method-Override header now recognized
-*	NEW: Base->chain() method for executing callbacks in succession
-*	NEW: HOST global variable; derived from either $_SERVER['SERVER_NAME'] or
-	gethostname()
-*	NEW: REALM global variable representing full canonical URI
-*	NEW: Auth plug-in
-*	NEW: Pingback plug-in (implements both Pingback 1.0 protocol client and
-	server)
-*	NEW: DEBUG verbosity can now reach up to level 3; Base->stringify() drills
-	down to object properties at this setting
-*	NEW: HTTP PATCH method added to recognized HTTP ReST methods
-*	Web->slug() now trims trailing dashes
-*	Web->request() now allows relative local URLs as argument
-*	Use of PARAMS in route handlers now unnecessary; framework now passes two
-	arguments to route handlers: the framework object instance and an array
-	containing the captured values of tokens in route patterns
-*	Standardized timeout settings among Web->request() backends
-*	Session IDs regenerated for additional security
-*	Automatic HTTP 404 responses by Base->call() now restricted to route
-	handlers
-*	Empty comments in ini-style files now parsed properly
-*	Use file_get_contents() in methods that don't involve high concurrency
-
-3.0.1 (14 Dec 2013)
-*	Major rewrite of much of the framework's core features

+ 0 - 621
lib/f3/COPYING

@@ -1,621 +0,0 @@
-GNU GENERAL PUBLIC LICENSE
-Version 3, 29 June 2007
-
-Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
-Everyone is permitted to copy and distribute verbatim copies
-of this license document, but changing it is not allowed.
-
-Preamble
-
-The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
-The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
-When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
-For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
-Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
-For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
-Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
-Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
-The precise terms and conditions for copying, distribution and
-modification follow.
-
-TERMS AND CONDITIONS
-
-0. Definitions.
-
-"This License" refers to version 3 of the GNU General Public License.
-
-"Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-"The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
-To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-1. Source Code.
-
-The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
-A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-The Corresponding Source for a work in source code form is that
-same work.
-
-2. Basic Permissions.
-
-All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-4. Conveying Verbatim Copies.
-
-You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-5. Conveying Modified Source Versions.
-
-You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-a) The work must carry prominent notices stating that you modified
-it, and giving a relevant date.
-
-b) The work must carry prominent notices stating that it is
-released under this License and any conditions added under section
-7. This requirement modifies the requirement in section 4 to
-"keep intact all notices".
-
-c) You must license the entire work, as a whole, under this
-License to anyone who comes into possession of a copy. This
-License will therefore apply, along with any applicable section 7
-additional terms, to the whole of the work, and all its parts,
-regardless of how they are packaged. This License gives no
-permission to license the work in any other way, but it does not
-invalidate such permission if you have separately received it.
-
-d) If the work has interactive user interfaces, each must display
-Appropriate Legal Notices; however, if the Program has interactive
-interfaces that do not display Appropriate Legal Notices, your
-work need not make them do so.
-
-A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-6. Conveying Non-Source Forms.
-
-You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-a) Convey the object code in, or embodied in, a physical product
-(including a physical distribution medium), accompanied by the
-Corresponding Source fixed on a durable physical medium
-customarily used for software interchange.
-
-b) Convey the object code in, or embodied in, a physical product
-(including a physical distribution medium), accompanied by a
-written offer, valid for at least three years and valid for as
-long as you offer spare parts or customer support for that product
-model, to give anyone who possesses the object code either (1) a
-copy of the Corresponding Source for all the software in the
-product that is covered by this License, on a durable physical
-medium customarily used for software interchange, for a price no
-more than your reasonable cost of physically performing this
-conveying of source, or (2) access to copy the
-Corresponding Source from a network server at no charge.
-
-c) Convey individual copies of the object code with a copy of the
-written offer to provide the Corresponding Source. This
-alternative is allowed only occasionally and noncommercially, and
-only if you received the object code with such an offer, in accord
-with subsection 6b.
-
-d) Convey the object code by offering access from a designated
-place (gratis or for a charge), and offer equivalent access to the
-Corresponding Source in the same way through the same place at no
-further charge. You need not require recipients to copy the
-Corresponding Source along with the object code. If the place to
-copy the object code is a network server, the Corresponding Source
-may be on a different server (operated by you or a third party)
-that supports equivalent copying facilities, provided you maintain
-clear directions next to the object code saying where to find the
-Corresponding Source. Regardless of what server hosts the
-Corresponding Source, you remain obligated to ensure that it is
-available for as long as needed to satisfy these requirements.
-
-e) Convey the object code using peer-to-peer transmission, provided
-you inform other peers where the object code and Corresponding
-Source of the work are being offered to the general public at no
-charge under subsection 6d.
-
-A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-"Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-7. Additional Terms.
-
-"Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-a) Disclaiming warranty or limiting liability differently from the
-terms of sections 15 and 16 of this License; or
-
-b) Requiring preservation of specified reasonable legal notices or
-author attributions in that material or in the Appropriate Legal
-Notices displayed by works containing it; or
-
-c) Prohibiting misrepresentation of the origin of that material, or
-requiring that modified versions of such material be marked in
-reasonable ways as different from the original version; or
-
-d) Limiting the use for publicity purposes of names of licensors or
-authors of the material; or
-
-e) Declining to grant rights under trademark law for use of some
-trade names, trademarks, or service marks; or
-
-f) Requiring indemnification of licensors and authors of that
-material by anyone who conveys the material (or modified versions of
-it) with contractual assumptions of liability to the recipient, for
-any liability that these contractual assumptions directly impose on
-those licensors and authors.
-
-All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-8. Termination.
-
-You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-9. Acceptance Not Required for Having Copies.
-
-You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-10. Automatic Licensing of Downstream Recipients.
-
-Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
-An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-11. Patents.
-
-A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
-A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-12. No Surrender of Others' Freedom.
-
-If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-13. Use with the GNU Affero General Public License.
-
-Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
-14. Revised Versions of this License.
-
-The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-15. Disclaimer of Warranty.
-
-THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-16. Limitation of Liability.
-
-IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-17. Interpretation of Sections 15 and 16.
-
-If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-END OF TERMS AND CONDITIONS

+ 0 - 191
lib/f3/audit.php

@@ -1,191 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Data validator
-class Audit extends Prefab {
-
-	//@{ User agents
-	const
-		UA_Mobile='android|blackberry|phone|ipod|palm|windows\s+ce',
-		UA_Desktop='bsd|linux|os\s+[x9]|solaris|windows',
-		UA_Bot='bot|crawl|slurp|spider';
-	//@}
-
-	/**
-	*	Return TRUE if string is a valid URL
-	*	@return bool
-	*	@param $str string
-	**/
-	function url($str) {
-		return is_string(filter_var($str,FILTER_VALIDATE_URL));
-	}
-
-	/**
-	*	Return TRUE if string is a valid e-mail address;
-	*	Check DNS MX records if specified
-	*	@return bool
-	*	@param $str string
-	*	@param $mx boolean
-	**/
-	function email($str,$mx=TRUE) {
-		$hosts=[];
-		return is_string(filter_var($str,FILTER_VALIDATE_EMAIL)) &&
-			(!$mx || getmxrr(substr($str,strrpos($str,'@')+1),$hosts));
-	}
-
-	/**
-	*	Return TRUE if string is a valid IPV4 address
-	*	@return bool
-	*	@param $addr string
-	**/
-	function ipv4($addr) {
-		return (bool)filter_var($addr,FILTER_VALIDATE_IP,FILTER_FLAG_IPV4);
-	}
-
-	/**
-	*	Return TRUE if string is a valid IPV6 address
-	*	@return bool
-	*	@param $addr string
-	**/
-	function ipv6($addr) {
-		return (bool)filter_var($addr,FILTER_VALIDATE_IP,FILTER_FLAG_IPV6);
-	}
-
-	/**
-	*	Return TRUE if IP address is within private range
-	*	@return bool
-	*	@param $addr string
-	**/
-	function isprivate($addr) {
-		return !(bool)filter_var($addr,FILTER_VALIDATE_IP,
-			FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_PRIV_RANGE);
-	}
-
-	/**
-	*	Return TRUE if IP address is within reserved range
-	*	@return bool
-	*	@param $addr string
-	**/
-	function isreserved($addr) {
-		return !(bool)filter_var($addr,FILTER_VALIDATE_IP,
-			FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_RES_RANGE);
-	}
-
-	/**
-	*	Return TRUE if IP address is neither private nor reserved
-	*	@return bool
-	*	@param $addr string
-	**/
-	function ispublic($addr) {
-		return (bool)filter_var($addr,FILTER_VALIDATE_IP,
-			FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|
-			FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE);
-	}
-
-	/**
-	*	Return TRUE if user agent is a desktop browser
-	*	@return bool
-	*	@param $agent string
-	**/
-	function isdesktop($agent=NULL) {
-		if (!isset($agent))
-			$agent=Base::instance()->AGENT;
-		return (bool)preg_match('/('.self::UA_Desktop.')/i',$agent) &&
-			!$this->ismobile($agent);
-	}
-
-	/**
-	*	Return TRUE if user agent is a mobile device
-	*	@return bool
-	*	@param $agent string
-	**/
-	function ismobile($agent=NULL) {
-		if (!isset($agent))
-			$agent=Base::instance()->AGENT;
-		return (bool)preg_match('/('.self::UA_Mobile.')/i',$agent);
-	}
-
-	/**
-	*	Return TRUE if user agent is a Web bot
-	*	@return bool
-	*	@param $agent string
-	**/
-	function isbot($agent=NULL) {
-		if (!isset($agent))
-			$agent=Base::instance()->AGENT;
-		return (bool)preg_match('/('.self::UA_Bot.')/i',$agent);
-	}
-
-	/**
-	*	Return TRUE if specified ID has a valid (Luhn) Mod-10 check digit
-	*	@return bool
-	*	@param $id string
-	**/
-	function mod10($id) {
-		if (!ctype_digit($id))
-			return FALSE;
-		$id=strrev($id);
-		$sum=0;
-		for ($i=0,$l=strlen($id);$i<$l;$i++)
-			$sum+=$id[$i]+$i%2*(($id[$i]>4)*-4+$id[$i]%5);
-		return !($sum%10);
-	}
-
-	/**
-	*	Return credit card type if number is valid
-	*	@return string|FALSE
-	*	@param $id string
-	**/
-	function card($id) {
-		$id=preg_replace('/[^\d]/','',$id);
-		if ($this->mod10($id)) {
-			if (preg_match('/^3[47][0-9]{13}$/',$id))
-				return 'American Express';
-			if (preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/',$id))
-				return 'Diners Club';
-			if (preg_match('/^6(?:011|5[0-9][0-9])[0-9]{12}$/',$id))
-				return 'Discover';
-			if (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/',$id))
-				return 'JCB';
-			if (preg_match('/^5[1-5][0-9]{14}$|'.
-				'^(222[1-9]|2[3-6]\d{2}|27[0-1]\d|2720)\d{12}$/',$id))
-				return 'MasterCard';
-			if (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/',$id))
-				return 'Visa';
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Return entropy estimate of a password (NIST 800-63)
-	*	@return int|float
-	*	@param $str string
-	**/
-	function entropy($str) {
-		$len=strlen($str);
-		return 4*min($len,1)+($len>1?(2*(min($len,8)-1)):0)+
-			($len>8?(1.5*(min($len,20)-8)):0)+($len>20?($len-20):0)+
-			6*(bool)(preg_match(
-				'/[A-Z].*?[0-9[:punct:]]|[0-9[:punct:]].*?[A-Z]/',$str));
-	}
-
-}

+ 0 - 262
lib/f3/auth.php

@@ -1,262 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Authorization/authentication plug-in
-class Auth {
-
-	//@{ Error messages
-	const
-		E_LDAP='LDAP connection failure',
-		E_SMTP='SMTP connection failure';
-	//@}
-
-	protected
-		//! Auth storage
-		$storage,
-		//! Mapper object
-		$mapper,
-		//! Storage options
-		$args,
-		//! Custom compare function
-		$func;
-
-	/**
-	*	Jig storage handler
-	*	@return bool
-	*	@param $id string
-	*	@param $pw string
-	*	@param $realm string
-	**/
-	protected function _jig($id,$pw,$realm) {
-		$success = (bool)
-			call_user_func_array(
-				[$this->mapper,'load'],
-				[
-					array_merge(
-						[
-							'@'.$this->args['id'].'==?'.
-							($this->func?'':' AND @'.$this->args['pw'].'==?').
-							(isset($this->args['realm'])?
-								(' AND @'.$this->args['realm'].'==?'):''),
-							$id
-						],
-						($this->func?[]:[$pw]),
-						(isset($this->args['realm'])?[$realm]:[])
-					)
-				]
-			);
-		if ($success && $this->func)
-			$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
-		return $success;
-	}
-
-	/**
-	*	MongoDB storage handler
-	*	@return bool
-	*	@param $id string
-	*	@param $pw string
-	*	@param $realm string
-	**/
-	protected function _mongo($id,$pw,$realm) {
-		$success = (bool)
-			$this->mapper->load(
-				[$this->args['id']=>$id]+
-				($this->func?[]:[$this->args['pw']=>$pw])+
-				(isset($this->args['realm'])?
-					[$this->args['realm']=>$realm]:[])
-			);
-		if ($success && $this->func)
-			$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
-		return $success;
-	}
-
-	/**
-	*	SQL storage handler
-	*	@return bool
-	*	@param $id string
-	*	@param $pw string
-	*	@param $realm string
-	**/
-	protected function _sql($id,$pw,$realm) {
-		$success = (bool)
-			call_user_func_array(
-				[$this->mapper,'load'],
-				[
-					array_merge(
-						[
-							$this->args['id'].'=?'.
-							($this->func?'':' AND '.$this->args['pw'].'=?').
-							(isset($this->args['realm'])?
-								(' AND '.$this->args['realm'].'=?'):''),
-							$id
-						],
-						($this->func?[]:[$pw]),
-						(isset($this->args['realm'])?[$realm]:[])
-					)
-				]
-			);
-		if ($success && $this->func)
-			$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
-		return $success;
-	}
-
-	/**
-	*	LDAP storage handler
-	*	@return bool
-	*	@param $id string
-	*	@param $pw string
-	**/
-	protected function _ldap($id,$pw) {
-		$port=(int)($this->args['port']?:389);
-		$filter=$this->args['filter']=$this->args['filter']?:"uid=".$id;
-		$this->args['attr']=$this->args['attr']?:["uid"];
-		array_walk($this->args['attr'],
-		function($attr)use(&$filter,$id) {
-			$filter=str_ireplace($attr."=*",$attr."=".$id,$filter);});
-		$dc=@ldap_connect($this->args['dc'],$port);
-		if ($dc &&
-			ldap_set_option($dc,LDAP_OPT_PROTOCOL_VERSION,3) &&
-			ldap_set_option($dc,LDAP_OPT_REFERRALS,0) &&
-			ldap_bind($dc,$this->args['rdn'],$this->args['pw']) &&
-			($result=ldap_search($dc,$this->args['base_dn'],
-				$filter,$this->args['attr'])) &&
-			ldap_count_entries($dc,$result) &&
-			($info=ldap_get_entries($dc,$result)) &&
-			$info['count']==1 &&
-			@ldap_bind($dc,$info[0]['dn'],$pw) &&
-			@ldap_close($dc)) {
-			return in_array($id,(array_map(function($value){return $value[0];},
-				array_intersect_key($info[0],
-					array_flip($this->args['attr'])))),TRUE);
-		}
-		user_error(self::E_LDAP,E_USER_ERROR);
-	}
-
-	/**
-	*	SMTP storage handler
-	*	@return bool
-	*	@param $id string
-	*	@param $pw string
-	**/
-	protected function _smtp($id,$pw) {
-		$socket=@fsockopen(
-			(strtolower($this->args['scheme'])=='ssl'?
-				'ssl://':'').$this->args['host'],
-				$this->args['port']);
-		$dialog=function($cmd=NULL) use($socket) {
-			if (!is_null($cmd))
-				fputs($socket,$cmd."\r\n");
-			$reply='';
-			while (!feof($socket) &&
-				($info=stream_get_meta_data($socket)) &&
-				!$info['timed_out'] && $str=fgets($socket,4096)) {
-				$reply.=$str;
-				if (preg_match('/(?:^|\n)\d{3} .+\r\n/s',
-					$reply))
-					break;
-			}
-			return $reply;
-		};
-		if ($socket) {
-			stream_set_blocking($socket,TRUE);
-			$dialog();
-			$fw=Base::instance();
-			$dialog('EHLO '.$fw->HOST);
-			if (strtolower($this->args['scheme'])=='tls') {
-				$dialog('STARTTLS');
-				stream_socket_enable_crypto(
-					$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
-				$dialog('EHLO '.$fw->HOST);
-			}
-			// Authenticate
-			$dialog('AUTH LOGIN');
-			$dialog(base64_encode($id));
-			$reply=$dialog(base64_encode($pw));
-			$dialog('QUIT');
-			fclose($socket);
-			return (bool)preg_match('/^235 /',$reply);
-		}
-		user_error(self::E_SMTP,E_USER_ERROR);
-	}
-
-	/**
-	*	Login auth mechanism
-	*	@return bool
-	*	@param $id string
-	*	@param $pw string
-	*	@param $realm string
-	**/
-	function login($id,$pw,$realm=NULL) {
-		return $this->{'_'.$this->storage}($id,$pw,$realm);
-	}
-
-	/**
-	*	HTTP basic auth mechanism
-	*	@return bool
-	*	@param $func callback
-	**/
-	function basic($func=NULL) {
-		$fw=Base::instance();
-		$realm=$fw->REALM;
-		$hdr=NULL;
-		if (isset($_SERVER['HTTP_AUTHORIZATION']))
-			$hdr=$_SERVER['HTTP_AUTHORIZATION'];
-		elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
-			$hdr=$_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
-		if (!empty($hdr))
-			list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])=
-				explode(':',base64_decode(substr($hdr,6)));
-		if (isset($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) &&
-			$this->login(
-				$_SERVER['PHP_AUTH_USER'],
-				$func?
-					$fw->call($func,$_SERVER['PHP_AUTH_PW']):
-					$_SERVER['PHP_AUTH_PW'],
-				$realm
-			))
-			return TRUE;
-		if (PHP_SAPI!='cli')
-			header('WWW-Authenticate: Basic realm="'.$realm.'"');
-		$fw->status(401);
-		return FALSE;
-	}
-
-	/**
-	*	Instantiate class
-	*	@return object
-	*	@param $storage string|object
-	*	@param $args array
-	*	@param $func callback
-	**/
-	function __construct($storage,array $args=NULL,$func=NULL) {
-		if (is_object($storage) && is_a($storage,'DB\Cursor')) {
-			$this->storage=$storage->dbtype();
-			$this->mapper=$storage;
-			unset($ref);
-		}
-		else
-			$this->storage=$storage;
-		$this->args=$args;
-		$this->func=$func;
-	}
-
-}

+ 0 - 3584
lib/f3/base.php

@@ -1,3584 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Factory class for single-instance objects
-abstract class Prefab {
-
-	/**
-	*	Return class instance
-	*	@return static
-	**/
-	static function instance() {
-		if (!Registry::exists($class=get_called_class())) {
-			$ref=new ReflectionClass($class);
-			$args=func_get_args();
-			Registry::set($class,
-				$args?$ref->newinstanceargs($args):new $class);
-		}
-		return Registry::get($class);
-	}
-
-}
-
-//! Base structure
-final class Base extends Prefab implements ArrayAccess {
-
-	//@{ Framework details
-	const
-		PACKAGE='Fat-Free Framework',
-		VERSION='3.7.2-Release';
-	//@}
-
-	//@{ HTTP status codes (RFC 2616)
-	const
-		HTTP_100='Continue',
-		HTTP_101='Switching Protocols',
-		HTTP_103='Early Hints',
-		HTTP_200='OK',
-		HTTP_201='Created',
-		HTTP_202='Accepted',
-		HTTP_203='Non-Authorative Information',
-		HTTP_204='No Content',
-		HTTP_205='Reset Content',
-		HTTP_206='Partial Content',
-		HTTP_300='Multiple Choices',
-		HTTP_301='Moved Permanently',
-		HTTP_302='Found',
-		HTTP_303='See Other',
-		HTTP_304='Not Modified',
-		HTTP_305='Use Proxy',
-		HTTP_307='Temporary Redirect',
-		HTTP_308='Permanent Redirect',
-		HTTP_400='Bad Request',
-		HTTP_401='Unauthorized',
-		HTTP_402='Payment Required',
-		HTTP_403='Forbidden',
-		HTTP_404='Not Found',
-		HTTP_405='Method Not Allowed',
-		HTTP_406='Not Acceptable',
-		HTTP_407='Proxy Authentication Required',
-		HTTP_408='Request Timeout',
-		HTTP_409='Conflict',
-		HTTP_410='Gone',
-		HTTP_411='Length Required',
-		HTTP_412='Precondition Failed',
-		HTTP_413='Request Entity Too Large',
-		HTTP_414='Request-URI Too Long',
-		HTTP_415='Unsupported Media Type',
-		HTTP_416='Requested Range Not Satisfiable',
-		HTTP_417='Expectation Failed',
-		HTTP_421='Misdirected Request',
-		HTTP_422='Unprocessable Entity',
-		HTTP_423='Locked',
-		HTTP_429='Too Many Requests',
-		HTTP_451='Unavailable For Legal Reasons',
-		HTTP_500='Internal Server Error',
-		HTTP_501='Not Implemented',
-		HTTP_502='Bad Gateway',
-		HTTP_503='Service Unavailable',
-		HTTP_504='Gateway Timeout',
-		HTTP_505='HTTP Version Not Supported',
-		HTTP_507='Insufficient Storage',
-		HTTP_511='Network Authentication Required';
-	//@}
-
-	const
-		//! Mapped PHP globals
-		GLOBALS='GET|POST|COOKIE|REQUEST|SESSION|FILES|SERVER|ENV',
-		//! HTTP verbs
-		VERBS='GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS',
-		//! Default directory permissions
-		MODE=0755,
-		//! Syntax highlighting stylesheet
-		CSS='code.css';
-
-	//@{ Request types
-	const
-		REQ_SYNC=1,
-		REQ_AJAX=2,
-		REQ_CLI=4;
-	//@}
-
-	//@{ Error messages
-	const
-		E_Pattern='Invalid routing pattern: %s',
-		E_Named='Named route does not exist: %s',
-		E_Alias='Invalid named route alias: %s',
-		E_Fatal='Fatal error: %s',
-		E_Open='Unable to open %s',
-		E_Routes='No routes specified',
-		E_Class='Invalid class %s',
-		E_Method='Invalid method %s',
-		E_Hive='Invalid hive key %s';
-	//@}
-
-	private
-		//! Globals
-		$hive,
-		//! Initial settings
-		$init,
-		//! Language lookup sequence
-		$languages,
-		//! Mutex locks
-		$locks=[],
-		//! Default fallback language
-		$fallback='en';
-
-	/**
-	*	Sync PHP global with corresponding hive key
-	*	@return array
-	*	@param $key string
-	**/
-	function sync($key) {
-		return $this->hive[$key]=&$GLOBALS['_'.$key];
-	}
-
-	/**
-	*	Return the parts of specified hive key
-	*	@return array
-	*	@param $key string
-	**/
-	private function cut($key) {
-		return preg_split('/\[\h*[\'"]?(.+?)[\'"]?\h*\]|(->)|\./',
-			$key,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
-	}
-
-	/**
-	*	Replace tokenized URL with available token values
-	*	@return string
-	*	@param $url array|string
-	*	@param $args array
-	**/
-	function build($url,$args=[]) {
-		$args+=$this->hive['PARAMS'];
-		if (is_array($url))
-			foreach ($url as &$var) {
-				$var=$this->build($var,$args);
-				unset($var);
-			}
-		else {
-			$i=0;
-			$url=preg_replace_callback('/(\{)?@(\w+)(?(1)\})|(\*)/',
-				function($match) use(&$i,$args) {
-					if (isset($match[2]) &&
-						array_key_exists($match[2],$args))
-						return $args[$match[2]];
-					if (isset($match[3]) &&
-						array_key_exists($match[3],$args)) {
-						if (!is_array($args[$match[3]]))
-							return $args[$match[3]];
-						$i++;
-						return $args[$match[3]][$i-1];
-					}
-					return $match[0];
-				},$url);
-		}
-		return $url;
-	}
-
-	/**
-	*	Parse string containing key-value pairs
-	*	@return array
-	*	@param $str string
-	**/
-	function parse($str) {
-		preg_match_all('/(\w+|\*)\h*=\h*(?:\[(.+?)\]|(.+?))(?=,|$)/',
-			$str,$pairs,PREG_SET_ORDER);
-		$out=[];
-		foreach ($pairs as $pair)
-			if ($pair[2]) {
-				$out[$pair[1]]=[];
-				foreach (explode(',',$pair[2]) as $val)
-					array_push($out[$pair[1]],$val);
-			}
-			else
-				$out[$pair[1]]=trim($pair[3]);
-		return $out;
-	}
-
-	/**
-	 * Cast string variable to PHP type or constant
-	 * @param $val
-	 * @return mixed
-	 */
-	function cast($val) {
-		if (preg_match('/^(?:0x[0-9a-f]+|0[0-7]+|0b[01]+)$/i',$val))
-			return intval($val,0);
-		if (is_numeric($val))
-			return $val+0;
-		$val=trim($val);
-		if (preg_match('/^\w+$/i',$val) && defined($val))
-			return constant($val);
-		return $val;
-	}
-
-	/**
-	*	Convert JS-style token to PHP expression
-	*	@return string
-	*	@param $str string
-	*	@param $evaluate bool compile expressions as well or only convert variable access
-	**/
-	function compile($str, $evaluate=TRUE) {
-		return (!$evaluate)
-			? preg_replace_callback(
-				'/^@(\w+)((?:\..+|\[(?:(?:[^\[\]]*|(?R))*)\])*)/',
-				function($expr) {
-					$str='$'.$expr[1];
-					if (isset($expr[2]))
-						$str.=preg_replace_callback(
-							'/\.([^.\[\]]+)|\[((?:[^\[\]\'"]*|(?R))*)\]/',
-							function($sub) {
-								$val=isset($sub[2]) ? $sub[2] : $sub[1];
-								if (ctype_digit($val))
-									$val=(int)$val;
-								$out='['.$this->export($val).']';
-								return $out;
-							},
-							$expr[2]
-						);
-					return $str;
-				},
-				$str
-			)
-			: preg_replace_callback(
-			'/(?<!\w)@(\w+(?:(?:\->|::)\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]):
-										$this->export($sub[1])).
-								']';
-							}
-							else
-								$out=function_exists($sub[1])?
-									$sub[0]:
-									('['.$this->export($sub[1]).']'.$sub[2]);
-							return $out;
-						},
-						$expr[2]
-					);
-				return $str;
-			},
-			$str
-		);
-	}
-
-	/**
-	*	Get hive key reference/contents; Add non-existent hive keys,
-	*	array elements, and object properties by default
-	*	@return mixed
-	*	@param $key string
-	*	@param $add bool
-	*	@param $var mixed
-	**/
-	function &ref($key,$add=TRUE,&$var=NULL) {
-		$null=NULL;
-		$parts=$this->cut($key);
-		if ($parts[0]=='SESSION') {
-			if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
-				session_start();
-			$this->sync('SESSION');
-		}
-		elseif (!preg_match('/^\w+$/',$parts[0]))
-			user_error(sprintf(self::E_Hive,$this->stringify($key)),
-				E_USER_ERROR);
-		if (is_null($var)) {
-			if ($add)
-				$var=&$this->hive;
-			else
-				$var=$this->hive;
-		}
-		$obj=FALSE;
-		foreach ($parts as $part)
-			if ($part=='->')
-				$obj=TRUE;
-			elseif ($obj) {
-				$obj=FALSE;
-				if (!is_object($var))
-					$var=new stdClass;
-				if ($add || property_exists($var,$part))
-					$var=&$var->$part;
-				else {
-					$var=&$null;
-					break;
-				}
-			}
-			else {
-				if (!is_array($var))
-					$var=[];
-				if ($add || array_key_exists($part,$var))
-					$var=&$var[$part];
-				else {
-					$var=&$null;
-					break;
-				}
-			}
-		return $var;
-	}
-
-	/**
-	*	Return TRUE if hive key is set
-	*	(or return timestamp and TTL if cached)
-	*	@return bool
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function exists($key,&$val=NULL) {
-		$val=$this->ref($key,FALSE);
-		return isset($val)?
-			TRUE:
-			(Cache::instance()->exists($this->hash($key).'.var',$val)?:FALSE);
-	}
-
-	/**
-	*	Return TRUE if hive key is empty and not cached
-	*	@param $key string
-	*	@param $val mixed
-	*	@return bool
-	**/
-	function devoid($key,&$val=NULL) {
-		$val=$this->ref($key,FALSE);
-		return empty($val) &&
-			(!Cache::instance()->exists($this->hash($key).'.var',$val) ||
-				!$val);
-	}
-
-	/**
-	*	Bind value to hive key
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	*	@param $ttl int
-	**/
-	function set($key,$val,$ttl=0) {
-		$time=(int)$this->hive['TIME'];
-		if (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
-			$this->set('REQUEST'.$expr[2],$val);
-			if ($expr[1]=='COOKIE') {
-				$parts=$this->cut($key);
-				$jar=$this->unserialize($this->serialize($this->hive['JAR']));
-				unset($jar['lifetime']);
-				if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
-					unset($jar['expire']);
-					if (isset($_COOKIE[$parts[1]]))
-						setcookie($parts[1],NULL,['expires'=>0]+$jar);
-					if ($ttl)
-						$jar['expires']=$time+$ttl;
-					setcookie($parts[1],$val,$jar);
-				} else {
-					unset($jar['samesite']);
-					if (isset($_COOKIE[$parts[1]]))
-						call_user_func_array('setcookie',
-							array_merge([$parts[1],NULL],['expire'=>0]+$jar));
-					if ($ttl)
-						$jar['expire']=$time+$ttl;
-					call_user_func_array('setcookie',[$parts[1],$val]+$jar);
-				}
-				$_COOKIE[$parts[1]]=$val;
-				return $val;
-			}
-		}
-		else switch ($key) {
-		case 'CACHE':
-			$val=Cache::instance()->load($val);
-			break;
-		case 'ENCODING':
-			ini_set('default_charset',$val);
-			if (extension_loaded('mbstring'))
-				mb_internal_encoding($val);
-			break;
-		case 'FALLBACK':
-			$this->fallback=$val;
-			$lang=$this->language($this->hive['LANGUAGE']);
-		case 'LANGUAGE':
-			if (!isset($lang))
-				$val=$this->language($val);
-			$lex=$this->lexicon($this->hive['LOCALES'],$ttl);
-		case 'LOCALES':
-			if (isset($lex) || $lex=$this->lexicon($val,$ttl))
-				foreach ($lex as $dt=>$dd) {
-					$ref=&$this->ref($this->hive['PREFIX'].$dt);
-					$ref=$dd;
-					unset($ref);
-				}
-			break;
-		case 'TZ':
-			date_default_timezone_set($val);
-			break;
-		}
-		$ref=&$this->ref($key);
-		$ref=$val;
-		if (preg_match('/^JAR\b/',$key)) {
-			if ($key=='JAR.lifetime')
-				$this->set('JAR.expire',$val==0?0:
-					(is_int($val)?$time+$val:strtotime($val)));
-			else {
-				if ($key=='JAR.expire')
-					$this->hive['JAR']['lifetime']=max(0,$val-$time);
-				$jar=$this->unserialize($this->serialize($this->hive['JAR']));
-				unset($jar['expire']);
-				if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
-					if (version_compare(PHP_VERSION, '7.3.0') >= 0)
-						session_set_cookie_params($jar);
-					else {
-						unset($jar['samesite']);
-						call_user_func_array('session_set_cookie_params',$jar);
-					}
-			}
-		}
-		if ($ttl)
-			// Persist the key-value pair
-			Cache::instance()->set($this->hash($key).'.var',$val,$ttl);
-		return $ref;
-	}
-
-	/**
-	*	Retrieve contents of hive key
-	*	@return mixed
-	*	@param $key string
-	*	@param $args string|array
-	**/
-	function get($key,$args=NULL) {
-		if (is_string($val=$this->ref($key,FALSE)) && !is_null($args))
-			return call_user_func_array(
-				[$this,'format'],
-				array_merge([$val],is_array($args)?$args:[$args])
-			);
-		if (is_null($val)) {
-			// Attempt to retrieve from cache
-			if (Cache::instance()->exists($this->hash($key).'.var',$data))
-				return $data;
-		}
-		return $val;
-	}
-
-	/**
-	*	Unset hive key
-	*	@param $key string
-	**/
-	function clear($key) {
-		// Normalize array literal
-		$cache=Cache::instance();
-		$parts=$this->cut($key);
-		if ($key=='CACHE')
-			// Clear cache contents
-			$cache->reset();
-		elseif (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
-			$this->clear('REQUEST'.$expr[2]);
-			if ($expr[1]=='COOKIE') {
-				$parts=$this->cut($key);
-				$jar=$this->hive['JAR'];
-				unset($jar['lifetime']);
-				$jar['expire']=0;
-				if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
-					$jar['expires']=$jar['expire'];
-					unset($jar['expire']);
-					setcookie($parts[1],NULL,$jar);
-				} else {
-					unset($jar['samesite']);
-					call_user_func_array('setcookie',
-						array_merge([$parts[1],NULL],$jar));
-				}
-				unset($_COOKIE[$parts[1]]);
-			}
-		}
-		elseif ($parts[0]=='SESSION') {
-			if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
-				session_start();
-			if (empty($parts[1])) {
-				// End session
-				session_unset();
-				session_destroy();
-				$this->clear('COOKIE.'.session_name());
-			}
-			$this->sync('SESSION');
-		}
-		if (!isset($parts[1]) && array_key_exists($parts[0],$this->init))
-			// Reset global to default value
-			$this->hive[$parts[0]]=$this->init[$parts[0]];
-		else {
-			$val=preg_replace('/^(\$hive)/','$this->hive',
-				$this->compile('@hive.'.$key, FALSE));
-			eval('unset('.$val.');');
-			if ($parts[0]=='SESSION') {
-				session_commit();
-				session_start();
-			}
-			if ($cache->exists($hash=$this->hash($key).'.var'))
-				// Remove from cache
-				$cache->clear($hash);
-		}
-	}
-
-	/**
-	*	Return TRUE if hive variable is 'on'
-	*	@return bool
-	*	@param $key string
-	**/
-	function checked($key) {
-		$ref=&$this->ref($key);
-		return $ref=='on';
-	}
-
-	/**
-	*	Return TRUE if property has public visibility
-	*	@return bool
-	*	@param $obj object
-	*	@param $key string
-	**/
-	function visible($obj,$key) {
-		if (property_exists($obj,$key)) {
-			$ref=new ReflectionProperty(get_class($obj),$key);
-			$out=$ref->ispublic();
-			unset($ref);
-			return $out;
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Multi-variable assignment using associative array
-	*	@param $vars array
-	*	@param $prefix string
-	*	@param $ttl int
-	**/
-	function mset(array $vars,$prefix='',$ttl=0) {
-		foreach ($vars as $key=>$val)
-			$this->set($prefix.$key,$val,$ttl);
-	}
-
-	/**
-	*	Publish hive contents
-	*	@return array
-	**/
-	function hive() {
-		return $this->hive;
-	}
-
-	/**
-	*	Copy contents of hive variable to another
-	*	@return mixed
-	*	@param $src string
-	*	@param $dst string
-	**/
-	function copy($src,$dst) {
-		$ref=&$this->ref($dst);
-		return $ref=$this->ref($src,FALSE);
-	}
-
-	/**
-	*	Concatenate string to hive string variable
-	*	@return string
-	*	@param $key string
-	*	@param $val string
-	**/
-	function concat($key,$val) {
-		$ref=&$this->ref($key);
-		$ref.=$val;
-		return $ref;
-	}
-
-	/**
-	*	Swap keys and values of hive array variable
-	*	@return array
-	*	@param $key string
-	*	@public
-	**/
-	function flip($key) {
-		$ref=&$this->ref($key);
-		return $ref=array_combine(array_values($ref),array_keys($ref));
-	}
-
-	/**
-	*	Add element to the end of hive array variable
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function push($key,$val) {
-		$ref=&$this->ref($key);
-		$ref[]=$val;
-		return $val;
-	}
-
-	/**
-	*	Remove last element of hive array variable
-	*	@return mixed
-	*	@param $key string
-	**/
-	function pop($key) {
-		$ref=&$this->ref($key);
-		return array_pop($ref);
-	}
-
-	/**
-	*	Add element to the beginning of hive array variable
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function unshift($key,$val) {
-		$ref=&$this->ref($key);
-		array_unshift($ref,$val);
-		return $val;
-	}
-
-	/**
-	*	Remove first element of hive array variable
-	*	@return mixed
-	*	@param $key string
-	**/
-	function shift($key) {
-		$ref=&$this->ref($key);
-		return array_shift($ref);
-	}
-
-	/**
-	*	Merge array with hive array variable
-	*	@return array
-	*	@param $key string
-	*	@param $src string|array
-	*	@param $keep bool
-	**/
-	function merge($key,$src,$keep=FALSE) {
-		$ref=&$this->ref($key);
-		if (!$ref)
-			$ref=[];
-		$out=array_merge($ref,is_string($src)?$this->hive[$src]:$src);
-		if ($keep)
-			$ref=$out;
-		return $out;
-	}
-
-	/**
-	*	Extend hive array variable with default values from $src
-	*	@return array
-	*	@param $key string
-	*	@param $src string|array
-	*	@param $keep bool
-	**/
-	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);
-		if ($keep)
-			$ref=$out;
-		return $out;
-	}
-
-	/**
-	*	Convert backslashes to slashes
-	*	@return string
-	*	@param $str string
-	**/
-	function fixslashes($str) {
-		return $str?strtr($str,'\\','/'):$str;
-	}
-
-	/**
-	*	Split comma-, semi-colon, or pipe-separated string
-	*	@return array
-	*	@param $str string
-	*	@param $noempty bool
-	**/
-	function split($str,$noempty=TRUE) {
-		return array_map('trim',
-			preg_split('/[,;|]/',$str,0,$noempty?PREG_SPLIT_NO_EMPTY:0));
-	}
-
-	/**
-	*	Convert PHP expression/value to compressed exportable string
-	*	@return string
-	*	@param $arg mixed
-	*	@param $stack array
-	**/
-	function stringify($arg,array $stack=NULL) {
-		if ($stack) {
-			foreach ($stack as $node)
-				if ($arg===$node)
-					return '*RECURSION*';
-		}
-		else
-			$stack=[];
-		switch (gettype($arg)) {
-			case 'object':
-				$str='';
-				foreach (get_object_vars($arg) as $key=>$val)
-					$str.=($str?',':'').
-						$this->export($key).'=>'.
-						$this->stringify($val,
-							array_merge($stack,[$arg]));
-				return get_class($arg).'::__set_state(['.$str.'])';
-			case 'array':
-				$str='';
-				$num=isset($arg[0]) &&
-					ctype_digit(implode('',array_keys($arg)));
-				foreach ($arg as $key=>$val)
-					$str.=($str?',':'').
-						($num?'':($this->export($key).'=>')).
-						$this->stringify($val,array_merge($stack,[$arg]));
-				return '['.$str.']';
-			default:
-				return $this->export($arg);
-		}
-	}
-
-	/**
-	*	Flatten array values and return as CSV string
-	*	@return string
-	*	@param $args array
-	**/
-	function csv(array $args) {
-		return implode(',',array_map('stripcslashes',
-			array_map([$this,'stringify'],$args)));
-	}
-
-	/**
-	*	Convert snakecase string to camelcase
-	*	@return string
-	*	@param $str string
-	**/
-	function camelcase($str) {
-		return preg_replace_callback(
-			'/_(\pL)/u',
-			function($match) {
-				return strtoupper($match[1]);
-			},
-			$str
-		);
-	}
-
-	/**
-	*	Convert camelcase string to snakecase
-	*	@return string
-	*	@param $str string
-	**/
-	function snakecase($str) {
-		return strtolower(preg_replace('/(?!^)\p{Lu}/u','_\0',$str));
-	}
-
-	/**
-	*	Return -1 if specified number is negative, 0 if zero,
-	*	or 1 if the number is positive
-	*	@return int
-	*	@param $num mixed
-	**/
-	function sign($num) {
-		return $num?($num/abs($num)):0;
-	}
-
-	/**
-	*	Extract values of array whose keys start with the given prefix
-	*	@return array
-	*	@param $arr array
-	*	@param $prefix string
-	**/
-	function extract($arr,$prefix) {
-		$out=[];
-		foreach (preg_grep('/^'.preg_quote($prefix,'/').'/',array_keys($arr))
-			as $key)
-			$out[substr($key,strlen($prefix))]=$arr[$key];
-		return $out;
-	}
-
-	/**
-	*	Convert class constants to array
-	*	@return array
-	*	@param $class object|string
-	*	@param $prefix string
-	**/
-	function constants($class,$prefix='') {
-		$ref=new ReflectionClass($class);
-		return $this->extract($ref->getconstants(),$prefix);
-	}
-
-	/**
-	*	Generate 64bit/base36 hash
-	*	@return string
-	*	@param $str
-	**/
-	function hash($str) {
-		return str_pad(base_convert(
-			substr(sha1($str),-16),16,36),11,'0',STR_PAD_LEFT);
-	}
-
-	/**
-	*	Return Base64-encoded equivalent
-	*	@return string
-	*	@param $data string
-	*	@param $mime string
-	**/
-	function base64($data,$mime) {
-		return 'data:'.$mime.';base64,'.base64_encode($data);
-	}
-
-	/**
-	*	Convert special characters to HTML entities
-	*	@return string
-	*	@param $str string
-	**/
-	function encode($str) {
-		return @htmlspecialchars($str,$this->hive['BITMASK'],
-			$this->hive['ENCODING'])?:$this->scrub($str);
-	}
-
-	/**
-	*	Convert HTML entities back to characters
-	*	@return string
-	*	@param $str string
-	**/
-	function decode($str) {
-		return htmlspecialchars_decode($str,$this->hive['BITMASK']);
-	}
-
-	/**
-	*	Invoke callback recursively for all data types
-	*	@return mixed
-	*	@param $arg mixed
-	*	@param $func callback
-	*	@param $stack array
-	**/
-	function recursive($arg,$func,$stack=[]) {
-		if ($stack) {
-			foreach ($stack as $node)
-				if ($arg===$node)
-					return $arg;
-		}
-		switch (gettype($arg)) {
-			case 'object':
-				$ref=new ReflectionClass($arg);
-				if ($ref->iscloneable()) {
-					$arg=clone($arg);
-					$cast=is_a($arg,'IteratorAggregate')?
-						iterator_to_array($arg):get_object_vars($arg);
-					foreach ($cast as $key=>$val)
-						$arg->$key=$this->recursive(
-							$val,$func,array_merge($stack,[$arg]));
-				}
-				return $arg;
-			case 'array':
-				$copy=[];
-				foreach ($arg as $key=>$val)
-					$copy[$key]=$this->recursive($val,$func,
-						array_merge($stack,[$arg]));
-				return $copy;
-		}
-		return $func($arg);
-	}
-
-	/**
-	*	Remove HTML tags (except those enumerated) and non-printable
-	*	characters to mitigate XSS/code injection attacks
-	*	@return mixed
-	*	@param $arg mixed
-	*	@param $tags string
-	**/
-	function clean($arg,$tags=NULL) {
-		return $this->recursive($arg,
-			function($val) use($tags) {
-				if ($tags!='*')
-					$val=trim(strip_tags($val,
-						'<'.implode('><',$this->split($tags)).'>'));
-				return trim(preg_replace(
-					'/[\x00-\x08\x0B\x0C\x0E-\x1F]/','',$val));
-			}
-		);
-	}
-
-	/**
-	*	Similar to clean(), except that variable is passed by reference
-	*	@return mixed
-	*	@param $var mixed
-	*	@param $tags string
-	**/
-	function scrub(&$var,$tags=NULL) {
-		return $var=$this->clean($var,$tags);
-	}
-
-	/**
-	*	Return locale-aware formatted string
-	*	@return string
-	**/
-	function format() {
-		$args=func_get_args();
-		$val=array_shift($args);
-		// Get formatting rules
-		$conv=localeconv();
-		return preg_replace_callback(
-			'/\{\s*(?P<pos>\d+)\s*(?:,\s*(?P<type>\w+)\s*'.
-			'(?:,\s*(?P<mod>(?:\w+(?:\s*\{.+?\}\s*,?\s*)?)*)'.
-			'(?:,\s*(?P<prop>.+?))?)?)?\s*\}/',
-			function($expr) use($args,$conv) {
-				/**
-				 * @var string $pos
-				 * @var string $mod
-				 * @var string $type
-				 * @var string $prop
-				 */
-				extract($expr);
-				/**
-				 * @var string $thousands_sep
-				 * @var string $negative_sign
-				 * @var string $positive_sign
-				 * @var string $frac_digits
-				 * @var string $decimal_point
-				 * @var string $int_curr_symbol
-				 * @var string $currency_symbol
-				 */
-				extract($conv);
-				if (!array_key_exists($pos,$args))
-					return $expr[0];
-				if (isset($type)) {
-					if (isset($this->hive['FORMATS'][$type]))
-						return $this->call(
-							$this->hive['FORMATS'][$type],
-							[
-								$args[$pos],
-								isset($mod)?$mod:null,
-								isset($prop)?$prop:null
-							]
-						);
-					switch ($type) {
-						case 'plural':
-							preg_match_all('/(?<tag>\w+)'.
-								'(?:\s*\{\s*(?<data>.+?)\s*\})/',
-								$mod,$matches,PREG_SET_ORDER);
-							$ord=['zero','one','two'];
-							foreach ($matches as $match) {
-								/** @var string $tag */
-								/** @var string $data */
-								extract($match);
-								if (isset($ord[$args[$pos]]) &&
-									$tag==$ord[$args[$pos]] || $tag=='other')
-									return str_replace('#',$args[$pos],$data);
-							}
-						case 'number':
-							if (isset($mod))
-								switch ($mod) {
-									case 'integer':
-										return number_format(
-											$args[$pos],0,'',$thousands_sep);
-									case 'currency':
-										$int=$cstm=FALSE;
-										if (isset($prop) &&
-											$cstm=!$int=($prop=='int'))
-											$currency_symbol=$prop;
-										if (!$cstm &&
-											function_exists('money_format') &&
-											version_compare(PHP_VERSION,'7.4.0')<0)
-											return money_format(
-												'%'.($int?'i':'n'),$args[$pos]);
-										$fmt=[
-											0=>'(nc)',1=>'(n c)',
-											2=>'(nc)',10=>'+nc',
-											11=>'+n c',12=>'+ nc',
-											20=>'nc+',21=>'n c+',
-											22=>'nc +',30=>'n+c',
-											31=>'n +c',32=>'n+ c',
-											40=>'nc+',41=>'n c+',
-											42=>'nc +',100=>'(cn)',
-											101=>'(c n)',102=>'(cn)',
-											110=>'+cn',111=>'+c n',
-											112=>'+ cn',120=>'cn+',
-											121=>'c n+',122=>'cn +',
-											130=>'+cn',131=>'+c n',
-											132=>'+ cn',140=>'c+n',
-											141=>'c+ n',142=>'c +n'
-										];
-										if ($args[$pos]<0) {
-											$sgn=$negative_sign;
-											$pre='n';
-										}
-										else {
-											$sgn=$positive_sign;
-											$pre='p';
-										}
-										return str_replace(
-											['+','n','c'],
-											[$sgn,number_format(
-												abs($args[$pos]),
-												$frac_digits,
-												$decimal_point,
-												$thousands_sep),
-												$int?$int_curr_symbol
-													:$currency_symbol],
-											$fmt[(int)(
-												(${$pre.'_cs_precedes'}%2).
-												(${$pre.'_sign_posn'}%5).
-												(${$pre.'_sep_by_space'}%3)
-											)]
-										);
-									case 'percent':
-										return number_format(
-											$args[$pos]*100,0,$decimal_point,
-											$thousands_sep).'%';
-								}
-							$frac=$args[$pos]-(int)$args[$pos];
-							return number_format(
-								$args[$pos],
-								isset($prop)?
-									$prop:
-									($frac?strlen($frac)-2:0),
-								$decimal_point,$thousands_sep);
-						case 'date':
-							if (empty($mod) || $mod=='short')
-								$prop='%x';
-							elseif ($mod=='full')
-								$prop='%A, %d %B %Y';
-							elseif ($mod!='custom')
-								$prop='%d %B %Y';
-							return strftime($prop,$args[$pos]);
-						case 'time':
-							if (empty($mod) || $mod=='short')
-								$prop='%X';
-							elseif ($mod!='custom')
-								$prop='%r';
-							return strftime($prop,$args[$pos]);
-						default:
-							return $expr[0];
-					}
-				}
-				return $args[$pos];
-			},
-			$val
-		);
-	}
-
-	/**
-	*	Return string representation of expression
-	*	@return string
-	*	@param $expr mixed
-	**/
-	function export($expr) {
-		return var_export($expr,TRUE);
-	}
-
-	/**
-	*	Assign/auto-detect language
-	*	@return string
-	*	@param $code string
-	**/
-	function language($code) {
-		$code=preg_replace('/\h+|;q=[0-9.]+/','',$code);
-		$code.=($code?',':'').$this->fallback;
-		$this->languages=[];
-		foreach (array_reverse(explode(',',$code)) as $lang)
-			if (preg_match('/^(\w{2})(?:-(\w{2}))?\b/i',$lang,$parts)) {
-				// Generic language
-				array_unshift($this->languages,$parts[1]);
-				if (isset($parts[2])) {
-					// Specific language
-					$parts[0]=$parts[1].'-'.($parts[2]=strtoupper($parts[2]));
-					array_unshift($this->languages,$parts[0]);
-				}
-			}
-		$this->languages=array_unique($this->languages);
-		$locales=[];
-		$windows=preg_match('/^win/i',PHP_OS);
-		// Work around PHP's Turkish locale bug
-		foreach (preg_grep('/^(?!tr)/i',$this->languages) as $locale) {
-			if ($windows) {
-				$parts=explode('-',$locale);
-				$locale=@constant('ISO::LC_'.$parts[0]);
-				if (isset($parts[1]) &&
-					$country=@constant('ISO::CC_'.strtolower($parts[1])))
-					$locale.='-'.$country;
-			}
-			$locale=str_replace('-','_',$locale);
-			$locales[]=$locale.'.'.ini_get('default_charset');
-			$locales[]=$locale;
-		}
-		setlocale(LC_ALL,$locales);
-		return $this->hive['LANGUAGE']=implode(',',$this->languages);
-	}
-
-	/**
-	*	Return lexicon entries
-	*	@return array
-	*	@param $path string
-	*	@param $ttl int
-	**/
-	function lexicon($path,$ttl=0) {
-		$languages=$this->languages?:explode(',',$this->fallback);
-		$cache=Cache::instance();
-		if ($ttl && $cache->exists(
-			$hash=$this->hash(implode(',',$languages).$path).'.dic',$lex))
-			return $lex;
-		$lex=[];
-		foreach ($languages as $lang)
-			foreach ($this->split($path) as $dir)
-				if ((is_file($file=($base=$dir.$lang).'.php') ||
-					is_file($file=$base.'.php')) &&
-					is_array($dict=require($file)))
-					$lex+=$dict;
-				elseif (is_file($file=$base.'.json') &&
-					is_array($dict=json_decode(file_get_contents($file), true)))
-					$lex+=$dict;
-				elseif (is_file($file=$base.'.ini')) {
-					preg_match_all(
-						'/(?<=^|\n)(?:'.
-							'\[(?<prefix>.+?)\]|'.
-							'(?<lval>[^\h\r\n;].*?)\h*=\h*'.
-							'(?<rval>(?:\\\\\h*\r?\n|.+?)*)'.
-						')(?=\r?\n|$)/',
-						$this->read($file),$matches,PREG_SET_ORDER);
-					if ($matches) {
-						$prefix='';
-						foreach ($matches as $match)
-							if ($match['prefix'])
-								$prefix=$match['prefix'].'.';
-							elseif (!array_key_exists(
-								$key=$prefix.$match['lval'],$lex))
-								$lex[$key]=trim(preg_replace(
-									'/\\\\\h*\r?\n/',"\n",$match['rval']));
-					}
-				}
-		if ($ttl)
-			$cache->set($hash,$lex,$ttl);
-		return $lex;
-	}
-
-	/**
-	*	Return string representation of PHP value
-	*	@return string
-	*	@param $arg mixed
-	**/
-	function serialize($arg) {
-		switch (strtolower($this->hive['SERIALIZER'])) {
-			case 'igbinary':
-				return igbinary_serialize($arg);
-			default:
-				return serialize($arg);
-		}
-	}
-
-	/**
-	*	Return PHP value derived from string
-	*	@return string
-	*	@param $arg mixed
-	**/
-	function unserialize($arg) {
-		switch (strtolower($this->hive['SERIALIZER'])) {
-			case 'igbinary':
-				return igbinary_unserialize($arg);
-			default:
-				return unserialize($arg);
-		}
-	}
-
-	/**
-	*	Send HTTP status header; Return text equivalent of status code
-	*	@return string
-	*	@param $code int
-	**/
-	function status($code) {
-		$reason=@constant('self::HTTP_'.$code);
-		if (!$this->hive['CLI'] && !headers_sent())
-			header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$reason);
-		return $reason;
-	}
-
-	/**
-	*	Send cache metadata to HTTP client
-	*	@param $secs int
-	**/
-	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'])
-				header('X-Frame-Options: '.$this->hive['XFRAME']);
-			header('X-XSS-Protection: 1; mode=block');
-			header('X-Content-Type-Options: nosniff');
-			if ($this->hive['VERB']=='GET' && $secs) {
-				$time=microtime(TRUE);
-				header_remove('Pragma');
-				header('Cache-Control: max-age='.$secs);
-				header('Expires: '.gmdate('r',$time+$secs));
-				header('Last-Modified: '.gmdate('r'));
-			}
-			else {
-				header('Pragma: no-cache');
-				header('Cache-Control: no-cache, no-store, must-revalidate');
-				header('Expires: '.gmdate('r',0));
-			}
-		}
-	}
-
-	/**
-	*	Return HTTP user agent
-	*	@return string
-	**/
-	function agent() {
-		$headers=$this->hive['HEADERS'];
-		return isset($headers['X-Operamini-Phone-UA'])?
-			$headers['X-Operamini-Phone-UA']:
-			(isset($headers['X-Skyfire-Phone'])?
-				$headers['X-Skyfire-Phone']:
-				(isset($headers['User-Agent'])?
-					$headers['User-Agent']:''));
-	}
-
-	/**
-	*	Return TRUE if XMLHttpRequest detected
-	*	@return bool
-	**/
-	function ajax() {
-		$headers=$this->hive['HEADERS'];
-		return isset($headers['X-Requested-With']) &&
-			$headers['X-Requested-With']=='XMLHttpRequest';
-	}
-
-	/**
-	*	Sniff IP address
-	*	@return string
-	**/
-	function ip() {
-		$headers=$this->hive['HEADERS'];
-		return isset($headers['Client-IP'])?
-			$headers['Client-IP']:
-			(isset($headers['X-Forwarded-For'])?
-				explode(',',$headers['X-Forwarded-For'])[0]:
-				(isset($_SERVER['REMOTE_ADDR'])?
-					$_SERVER['REMOTE_ADDR']:''));
-	}
-
-	/**
-	*	Return filtered stack trace as a formatted string (or array)
-	*	@return string|array
-	*	@param $trace array|NULL
-	*	@param $format bool
-	**/
-	function trace(array $trace=NULL,$format=TRUE) {
-		if (!$trace) {
-			$trace=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
-			$frame=$trace[0];
-			if (isset($frame['file']) && $frame['file']==__FILE__)
-				array_shift($trace);
-		}
-		$debug=$this->hive['DEBUG'];
-		$trace=array_filter(
-			$trace,
-			function($frame) use($debug) {
-				return isset($frame['file']) &&
-					($debug>1 ||
-					($frame['file']!=__FILE__ || $debug) &&
-					(empty($frame['function']) ||
-					!preg_match('/^(?:(?:trigger|user)_error|'.
-						'__call|call_user_func)/',$frame['function'])));
-			}
-		);
-		if (!$format)
-			return $trace;
-		$out='';
-		$eol="\n";
-		// Analyze stack trace
-		foreach ($trace as $frame) {
-			$line='';
-			if (isset($frame['class']))
-				$line.=$frame['class'].$frame['type'];
-			if (isset($frame['function']))
-				$line.=$frame['function'].'('.
-					($debug>2 && isset($frame['args'])?
-						$this->csv($frame['args']):'').')';
-			$src=$this->fixslashes(str_replace($_SERVER['DOCUMENT_ROOT'].
-				'/','',$frame['file'])).':'.$frame['line'];
-			$out.='['.$src.'] '.$line.$eol;
-		}
-		return $out;
-	}
-
-	/**
-	*	Log error; Execute ONERROR handler if defined, else display
-	*	default error page (HTML for synchronous requests, JSON string
-	*	for AJAX requests)
-	*	@param $code int
-	*	@param $text string
-	*	@param $trace array
-	*	@param $level int
-	**/
-	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.')';
-		$trace=$this->trace($trace);
-		$loggable=$this->hive['LOGGABLE'];
-		if (!is_array($loggable))
-			$loggable=$this->split($loggable);
-		foreach ($loggable as $status)
-			if ($status=='*' ||
-				preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',$code)) {
-				error_log($text);
-				foreach (explode("\n",$trace) as $nexus)
-					if ($nexus)
-						error_log($nexus);
-				break;
-			}
-		if ($highlight=!$this->hive['CLI'] && !$this->hive['AJAX'] &&
-			$this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS))
-			$trace=$this->highlight($trace);
-		$this->hive['ERROR']=[
-			'status'=>$header,
-			'code'=>$code,
-			'text'=>$text,
-			'trace'=>$trace,
-			'level'=>$level
-		];
-		$this->expire(-1);
-		$handler=$this->hive['ONERROR'];
-		$this->hive['ONERROR']=NULL;
-		$eol="\n";
-		if ((!$handler ||
-			$this->call($handler,[$this,$this->hive['PARAMS']],
-				'beforeroute,afterroute')===FALSE) &&
-			!$prior && !$this->hive['CLI'] && !$this->hive['QUIET'])
-			echo $this->hive['AJAX']?
-				json_encode(
-					array_diff_key(
-						$this->hive['ERROR'],
-						$this->hive['DEBUG']?
-							[]:
-							['trace'=>1]
-					)
-				):
-				('<!DOCTYPE html>'.$eol.
-				'<html>'.$eol.
-				'<head>'.
-					'<title>'.$code.' '.$header.'</title>'.
-					($highlight?
-						('<style>'.$this->read($css).'</style>'):'').
-				'</head>'.$eol.
-				'<body>'.$eol.
-					'<h1>'.$header.'</h1>'.$eol.
-					'<p>'.$this->encode($text?:$req).'</p>'.$eol.
-					($this->hive['DEBUG']?('<pre>'.$trace.'</pre>'.$eol):'').
-				'</body>'.$eol.
-				'</html>');
-		if ($this->hive['HALT'])
-			die(1);
-	}
-
-	/**
-	*	Mock HTTP request
-	*	@return mixed
-	*	@param $pattern string
-	*	@param $args array
-	*	@param $headers array
-	*	@param $body string
-	**/
-	function mock($pattern,
-		array $args=NULL,array $headers=NULL,$body=NULL) {
-		if (!$args)
-			$args=[];
-		$types=['sync','ajax','cli'];
-		preg_match('/([\|\w]+)\h+(?:@(\w+)(?:(\(.+?)\))*|([^\h]+))'.
-			'(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts);
-		$verb=strtoupper($parts[1]);
-		if ($parts[2]) {
-			if (empty($this->hive['ALIASES'][$parts[2]]))
-				user_error(sprintf(self::E_Named,$parts[2]),E_USER_ERROR);
-			$parts[4]=$this->hive['ALIASES'][$parts[2]];
-			$parts[4]=$this->build($parts[4],
-				isset($parts[3])?$this->parse($parts[3]):[]);
-		}
-		if (empty($parts[4]))
-			user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR);
-		$url=parse_url($parts[4]);
-		parse_str(@$url['query'],$GLOBALS['_GET']);
-		if (preg_match('/GET|HEAD/',$verb))
-			$GLOBALS['_GET']=array_merge($GLOBALS['_GET'],$args);
-		$GLOBALS['_POST']=$verb=='POST'?$args:[];
-		$GLOBALS['_REQUEST']=array_merge($GLOBALS['_GET'],$GLOBALS['_POST']);
-		foreach ($headers?:[] as $key=>$val)
-			$_SERVER['HTTP_'.strtr(strtoupper($key),'-','_')]=$val;
-		$this->hive['VERB']=$verb;
-		$this->hive['PATH']=$url['path'];
-		$this->hive['URI']=$this->hive['BASE'].$url['path'];
-		if ($GLOBALS['_GET'])
-			$this->hive['URI'].='?'.http_build_query($GLOBALS['_GET']);
-		$this->hive['BODY']='';
-		if (!preg_match('/GET|HEAD/',$verb))
-			$this->hive['BODY']=$body?:http_build_query($args);
-		$this->hive['AJAX']=isset($parts[5]) &&
-			preg_match('/ajax/i',$parts[5]);
-		$this->hive['CLI']=isset($parts[5]) &&
-			preg_match('/cli/i',$parts[5]);
-		return $this->run();
-	}
-
-	/**
-	*	Assemble url from alias name
-	*	@return string
-	*	@param $name string
-	*	@param $params array|string
-	*	@param $query string|array
-	*	@param $fragment string
-	**/
-	function alias($name,$params=[],$query=NULL,$fragment=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):'').($fragment?'#'.$fragment:'');
-	}
-
-	/**
-	*	Bind handler to route pattern
-	*	@return NULL
-	*	@param $pattern string|array
-	*	@param $handler callback
-	*	@param $ttl int
-	*	@param $kbps int
-	**/
-	function route($pattern,$handler,$ttl=0,$kbps=0) {
-		$types=['sync','ajax','cli'];
-		$alias=null;
-		if (is_array($pattern)) {
-			foreach ($pattern as $item)
-				$this->route($item,$handler,$ttl,$kbps);
-			return;
-		}
-		preg_match('/([\|\w]+)\h+(?:(?:@?(.+?)\h*:\h*)?(@(\w+)|[^\h]+))'.
-			'(?:\h+\[('.implode('|',$types).')\])?/u',$pattern,$parts);
-		if (isset($parts[2]) && $parts[2]) {
-			if (!preg_match('/^\w+$/',$parts[2]))
-				user_error(sprintf(self::E_Alias,$parts[2]),E_USER_ERROR);
-			$this->hive['ALIASES'][$alias=$parts[2]]=$parts[3];
-		}
-		elseif (!empty($parts[4])) {
-			if (empty($this->hive['ALIASES'][$parts[4]]))
-				user_error(sprintf(self::E_Named,$parts[4]),E_USER_ERROR);
-			$parts[3]=$this->hive['ALIASES'][$alias=$parts[4]];
-		}
-		if (empty($parts[3]))
-			user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR);
-		$type=empty($parts[5])?0:constant('self::REQ_'.strtoupper($parts[5]));
-		foreach ($this->split($parts[1]) as $verb) {
-			if (!preg_match('/'.self::VERBS.'/',$verb))
-				$this->error(501,$verb.' '.$this->hive['URI']);
-			$this->hive['ROUTES'][$parts[3]][$type][strtoupper($verb)]=
-				[$handler,$ttl,$kbps,$alias];
-		}
-	}
-
-	/**
-	*	Reroute to specified URI
-	*	@return NULL
-	*	@param $url array|string
-	*	@param $permanent bool
-	*	@param $die bool
-	**/
-	function reroute($url=NULL,$permanent=FALSE,$die=TRUE) {
-		if (!$url)
-			$url=$this->hive['REALM'];
-		if (is_array($url))
-			$url=call_user_func_array([$this,'alias'],$url);
-		elseif (preg_match('/^(?:@([^\/()?#]+)(?:\((.+?)\))*(\?[^#]+)*(#.+)*)/',
-			$url,$parts) && isset($this->hive['ALIASES'][$parts[1]]))
-			$url=$this->build($this->hive['ALIASES'][$parts[1]],
-					isset($parts[2])?$this->parse($parts[2]):[]).
-				(isset($parts[3])?$parts[3]:'').(isset($parts[4])?$parts[4]:'');
-		else
-			$url=$this->build($url);
-		if (($handler=$this->hive['ONREROUTE']) &&
-			$this->call($handler,[$url,$permanent,$die])!==FALSE)
-			return;
-		if ($url[0]!='/' && !preg_match('/^\w+:\/\//i',$url))
-			$url='/'.$url;
-		if ($url[0]=='/' && (empty($url[1]) || $url[1]!='/')) {
-			$port=$this->hive['PORT'];
-			$port=in_array($port,[80,443])?'':(':'.$port);
-			$url=$this->hive['SCHEME'].'://'.
-				$this->hive['HOST'].$port.$this->hive['BASE'].$url;
-		}
-		if ($this->hive['CLI'])
-			$this->mock('GET '.$url.' [cli]');
-		else {
-			header('Location: '.$url);
-			$this->status($permanent?301:302);
-			if ($die)
-				die;
-		}
-	}
-
-	/**
-	*	Provide ReST interface by mapping HTTP verb to class method
-	*	@return NULL
-	*	@param $url string
-	*	@param $class string|object
-	*	@param $ttl int
-	*	@param $kbps int
-	**/
-	function map($url,$class,$ttl=0,$kbps=0) {
-		if (is_array($url)) {
-			foreach ($url as $item)
-				$this->map($item,$class,$ttl,$kbps);
-			return;
-		}
-		foreach (explode('|',self::VERBS) as $method)
-			$this->route($method.' '.$url,is_string($class)?
-				$class.'->'.$this->hive['PREMAP'].strtolower($method):
-				[$class,$this->hive['PREMAP'].strtolower($method)],
-				$ttl,$kbps);
-	}
-
-	/**
-	*	Redirect a route to another URL
-	*	@return NULL
-	*	@param $pattern string|array
-	*	@param $url string
-	*	@param $permanent bool
-	*/
-	function redirect($pattern,$url,$permanent=TRUE) {
-		if (is_array($pattern)) {
-			foreach ($pattern as $item)
-				$this->redirect($item,$url,$permanent);
-			return;
-		}
-		$this->route($pattern,function($fw) use($url,$permanent) {
-			$fw->reroute($url,$permanent);
-		});
-	}
-
-	/**
-	*	Return TRUE if IPv4 address exists in DNSBL
-	*	@return bool
-	*	@param $ip string
-	**/
-	function blacklisted($ip) {
-		if ($this->hive['DNSBL'] &&
-			!in_array($ip,
-				is_array($this->hive['EXEMPT'])?
-					$this->hive['EXEMPT']:
-					$this->split($this->hive['EXEMPT']))) {
-			// Reverse IPv4 dotted quad
-			$rev=implode('.',array_reverse(explode('.',$ip)));
-			foreach (is_array($this->hive['DNSBL'])?
-				$this->hive['DNSBL']:
-				$this->split($this->hive['DNSBL']) as $server)
-				// DNSBL lookup
-				if (checkdnsrr($rev.'.'.$server,'A'))
-					return TRUE;
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Applies the specified URL mask and returns parameterized matches
-	*	@return $args array
-	*	@param $pattern string
-	*	@param $url string|NULL
-	**/
-	function mask($pattern,$url=NULL) {
-		if (!$url)
-			$url=$this->rel($this->hive['URI']);
-		$case=$this->hive['CASELESS']?'i':'';
-		$wild=preg_quote($pattern,'/');
-		$i=0;
-		while (is_int($pos=strpos($wild,'\*'))) {
-			$wild=substr_replace($wild,'(?P<_'.$i.'>[^\?]*)',$pos,2);
-			$i++;
-		}
-		preg_match('/^'.
-			preg_replace(
-				'/((\\\{)?@(\w+\b)(?(2)\\\}))/',
-				'(?P<\3>[^\/\?]+)',
-				$wild).'\/?$/'.$case.'um',$url,$args);
-		foreach (array_keys($args) as $key) {
-			if (preg_match('/^_\d+$/',$key)) {
-				if (empty($args['*']))
-					$args['*']=$args[$key];
-				else {
-					if (is_string($args['*']))
-						$args['*']=[$args['*']];
-					array_push($args['*'],$args[$key]);
-				}
-				unset($args[$key]);
-			}
-			elseif (is_numeric($key) && $key)
-				unset($args[$key]);
-		}
-		return $args;
-	}
-
-	/**
-	*	Match routes against incoming URI
-	*	@return mixed
-	**/
-	function run() {
-		if ($this->blacklisted($this->hive['IP']))
-			// Spammer detected
-			$this->error(403);
-		if (!$this->hive['ROUTES'])
-			// No routes defined
-			user_error(self::E_Routes,E_USER_ERROR);
-		// Match specific routes first
-		$paths=[];
-		foreach ($keys=array_keys($this->hive['ROUTES']) as $key) {
-			$path=preg_replace('/@\w+/','*@',$key);
-			if (substr($path,-1)!='*')
-				$path.='+';
-			$paths[]=$path;
-		}
-		$vals=array_values($this->hive['ROUTES']);
-		array_multisort($paths,SORT_DESC,$keys,$vals);
-		$this->hive['ROUTES']=array_combine($keys,$vals);
-		// Convert to BASE-relative URL
-		$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: '.
-				$this->export($cors['credentials']));
-			$preflight=
-				isset($this->hive['HEADERS']['Access-Control-Request-Method']);
-		}
-		$allowed=[];
-		foreach ($this->hive['ROUTES'] as $pattern=>$routes) {
-			if (!$args=$this->mask($pattern,$req))
-				continue;
-			ksort($args);
-			$route=NULL;
-			$ptr=$this->hive['CLI']?self::REQ_CLI:$this->hive['AJAX']+1;
-			if (isset($routes[$ptr][$this->hive['VERB']]) ||
-				isset($routes[$ptr=0]))
-				$route=$routes[$ptr];
-			if (!$route)
-				continue;
-			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).
-						($this->hive['QUERY']?('?'.$this->hive['QUERY']):''));
-				list($handler,$ttl,$kbps,$alias)=$route[$this->hive['VERB']];
-				// Capture values of route pattern tokens
-				$this->hive['PARAMS']=$args;
-				// Save matching route
-				$this->hive['ALIAS']=$alias;
-				$this->hive['PATTERN']=$pattern;
-				if ($cors && $cors['expose'])
-					header('Access-Control-Expose-Headers: '.
-						(is_array($cors['expose'])?
-							implode(',',$cors['expose']):$cors['expose']));
-				if (is_string($handler)) {
-					// Replace route pattern tokens in handler if any
-					$handler=preg_replace_callback('/({)?@(\w+\b)(?(1)})/',
-						function($id) use($args) {
-							$pid=count($id)>2?2:1;
-							return isset($args[$id[$pid]])?
-								$args[$id[$pid]]:
-								$id[0];
-						},
-						$handler
-					);
-					if (preg_match('/(.+)\h*(?:->|::)/',$handler,$match) &&
-						!class_exists($match[1]))
-						$this->error(404);
-				}
-				// Process request
-				$result=NULL;
-				$body='';
-				$now=microtime(TRUE);
-				if (preg_match('/GET|HEAD/',$this->hive['VERB']) && $ttl) {
-					// Only GET and HEAD requests are cacheable
-					$headers=$this->hive['HEADERS'];
-					$cache=Cache::instance();
-					$cached=$cache->exists(
-						$hash=$this->hash($this->hive['VERB'].' '.
-							$this->hive['URI']).'.url',$data);
-					if ($cached) {
-						if (isset($headers['If-Modified-Since']) &&
-							strtotime($headers['If-Modified-Since'])+
-								$ttl>$now) {
-							$this->status(304);
-							die;
-						}
-						// Retrieve from cache backend
-						list($headers,$body,$result)=$data;
-						if (!$this->hive['CLI'])
-							array_walk($headers,'header');
-						$this->expire($cached[0]+$ttl-$now);
-					}
-					else
-						// Expire HTTP client-cached page
-						$this->expire($ttl);
-				}
-				else
-					$this->expire(0);
-				if (!strlen($body)) {
-					if (!$this->hive['RAW'] && !$this->hive['BODY'])
-						$this->hive['BODY']=file_get_contents('php://input');
-					ob_start();
-					// Call route handler
-					$result=$this->call($handler,[$this,$args,$handler],
-						'beforeroute,afterroute');
-					$body=ob_get_clean();
-					if (isset($cache) && !error_get_last()) {
-						// Save to cache backend
-						$cache->set($hash,[
-							// Remove cookies
-							preg_grep('/Set-Cookie\:/',headers_list(),
-								PREG_GREP_INVERT),$body,$result],$ttl);
-					}
-				}
-				$this->hive['RESPONSE']=$body;
-				if (!$this->hive['QUIET']) {
-					if ($kbps) {
-						$ctr=0;
-						foreach (str_split($body,1024) as $part) {
-							// Throttle output
-							$ctr++;
-							if ($ctr/$kbps>($elapsed=microtime(TRUE)-$now) &&
-								!connection_aborted())
-								usleep(1e6*($ctr/$kbps-$elapsed));
-							echo $part;
-						}
-					}
-					else
-						echo $body;
-				}
-				if ($result || $this->hive['VERB']!='OPTIONS')
-					return $result;
-			}
-			$allowed=array_merge($allowed,array_keys($route));
-		}
-		if (!$allowed)
-			// URL doesn't match any route
-			$this->error(404);
-		elseif (!$this->hive['CLI']) {
-			if (!preg_grep('/Allow:/',$headers_send=headers_list()))
-				// Unhandled HTTP method
-				header('Allow: '.implode(',',array_unique($allowed)));
-			if ($cors) {
-				if (!preg_grep('/Access-Control-Allow-Methods:/',$headers_send))
-					header('Access-Control-Allow-Methods: OPTIONS,'.
-						implode(',',$allowed));
-				if ($cors['headers'] &&
-					!preg_grep('/Access-Control-Allow-Headers:/',$headers_send))
-					header('Access-Control-Allow-Headers: '.
-						(is_array($cors['headers'])?
-							implode(',',$cors['headers']):
-							$cors['headers']));
-				if ($cors['ttl']>0)
-					header('Access-Control-Max-Age: '.$cors['ttl']);
-			}
-			if ($this->hive['VERB']!='OPTIONS')
-				$this->error(405);
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Loop until callback returns TRUE (for long polling)
-	*	@return mixed
-	*	@param $func callback
-	*	@param $args array
-	*	@param $timeout int
-	**/
-	function until($func,$args=NULL,$timeout=60) {
-		if (!$args)
-			$args=[];
-		$time=time();
-		$max=ini_get('max_execution_time');
-		$limit=max(0,($max?min($timeout,$max):$timeout)-1);
-		$out='';
-		// Turn output buffering on
-		ob_start();
-		// Not for the weak of heart
-		while (
-			// No error occurred
-			!$this->hive['ERROR'] &&
-			// Got time left?
-			time()-$time+1<$limit &&
-			// Still alive?
-			!connection_aborted() &&
-			// Restart session
-			!headers_sent() &&
-			(session_status()==PHP_SESSION_ACTIVE || session_start()) &&
-			// CAUTION: Callback will kill host if it never becomes truthy!
-			!$out=$this->call($func,$args)) {
-			if (!$this->hive['CLI'])
-				session_commit();
-			// Hush down
-			sleep(1);
-		}
-		ob_flush();
-		flush();
-		return $out;
-	}
-
-	/**
-	*	Disconnect HTTP client;
-	*	Set FcgidOutputBufferSize to zero if server uses mod_fcgid;
-	*	Disable mod_deflate when rendering text/html output
-	**/
-	function abort() {
-		if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
-			session_start();
-		$out='';
-		while (ob_get_level())
-			$out=ob_get_clean().$out;
-		if (!headers_sent()) {
-			header('Content-Length: '.strlen($out));
-			header('Connection: close');
-		}
-		session_commit();
-		echo $out;
-		flush();
-		if (function_exists('fastcgi_finish_request'))
-			fastcgi_finish_request();
-	}
-
-	/**
-	*	Grab the real route handler behind the string expression
-	*	@return string|array
-	*	@param $func string
-	*	@param $args array
-	**/
-	function grab($func,$args=NULL) {
-		if (preg_match('/(.+)\h*(->|::)\h*(.+)/s',$func,$parts)) {
-			// Convert string to executable PHP callback
-			if (!class_exists($parts[1]))
-				user_error(sprintf(self::E_Class,$parts[1]),E_USER_ERROR);
-			if ($parts[2]=='->') {
-				if (is_subclass_of($parts[1],'Prefab'))
-					$parts[1]=call_user_func($parts[1].'::instance');
-				elseif (isset($this->hive['CONTAINER'])) {
-					$container=$this->hive['CONTAINER'];
-					if (is_object($container) && is_callable([$container,'has'])
-						&& $container->has($parts[1])) // PSR11
-						$parts[1]=call_user_func([$container,'get'],$parts[1]);
-					elseif (is_callable($container))
-						$parts[1]=call_user_func($container,$parts[1],$args);
-					elseif (is_string($container) &&
-						is_subclass_of($container,'Prefab'))
-						$parts[1]=call_user_func($container.'::instance')->
-							get($parts[1]);
-					else
-						user_error(sprintf(self::E_Class,
-							$this->stringify($parts[1])),
-							E_USER_ERROR);
-				}
-				else {
-					$ref=new ReflectionClass($parts[1]);
-					$parts[1]=method_exists($parts[1],'__construct') && $args?
-						$ref->newinstanceargs($args):
-						$ref->newinstance();
-				}
-			}
-			$func=[$parts[1],$parts[3]];
-		}
-		return $func;
-	}
-
-	/**
-	*	Execute callback/hooks (supports 'class->method' format)
-	*	@return mixed|FALSE
-	*	@param $func callback
-	*	@param $args mixed
-	*	@param $hooks string
-	**/
-	function call($func,$args=NULL,$hooks='') {
-		if (!is_array($args))
-			$args=[$args];
-		// Grab the real handler behind the string representation
-		if (is_string($func))
-			$func=$this->grab($func,$args);
-		// Execute function; abort if callback/hook returns FALSE
-		if (!is_callable($func))
-			// No route handler
-			if ($hooks=='beforeroute,afterroute') {
-				$allowed=[];
-				if (is_array($func))
-					$allowed=array_intersect(
-						array_map('strtoupper',get_class_methods($func[0])),
-						explode('|',self::VERBS)
-					);
-				header('Allow: '.implode(',',$allowed));
-				$this->error(405);
-			}
-			else
-				user_error(sprintf(self::E_Method,
-					is_string($func)?$func:$this->stringify($func)),
-					E_USER_ERROR);
-		$obj=FALSE;
-		if (is_array($func)) {
-			$hooks=$this->split($hooks);
-			$obj=TRUE;
-		}
-		// Execute pre-route hook if any
-		if ($obj && $hooks && in_array($hook='beforeroute',$hooks) &&
-			method_exists($func[0],$hook) &&
-			call_user_func_array([$func[0],$hook],$args)===FALSE)
-			return FALSE;
-		// Execute callback
-		$out=call_user_func_array($func,$args?:[]);
-		if ($out===FALSE)
-			return FALSE;
-		// Execute post-route hook if any
-		if ($obj && $hooks && in_array($hook='afterroute',$hooks) &&
-			method_exists($func[0],$hook) &&
-			call_user_func_array([$func[0],$hook],$args)===FALSE)
-			return FALSE;
-		return $out;
-	}
-
-	/**
-	*	Execute specified callbacks in succession; Apply same arguments
-	*	to all callbacks
-	*	@return array
-	*	@param $funcs array|string
-	*	@param $args mixed
-	**/
-	function chain($funcs,$args=NULL) {
-		$out=[];
-		foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
-			$out[]=$this->call($func,$args);
-		return $out;
-	}
-
-	/**
-	*	Execute specified callbacks in succession; Relay result of
-	*	previous callback as argument to the next callback
-	*	@return array
-	*	@param $funcs array|string
-	*	@param $args mixed
-	**/
-	function relay($funcs,$args=NULL) {
-		foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
-			$args=[$this->call($func,$args)];
-		return array_shift($args);
-	}
-
-	/**
-	*	Configure framework according to .ini-style file settings;
-	*	If optional 2nd arg is provided, template strings are interpreted
-	*	@return object
-	*	@param $source string|array
-	*	@param $allow bool
-	**/
-	function config($source,$allow=FALSE) {
-		if (is_string($source))
-			$source=$this->split($source);
-		if ($allow)
-			$preview=Preview::instance();
-		foreach ($source as $file) {
-			preg_match_all(
-				'/(?<=^|\n)(?:'.
-					'\[(?<section>.+?)\]|'.
-					'(?<lval>[^\h\r\n;].*?)\h*=\h*'.
-					'(?<rval>(?:\\\\\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)'.
-							'(.*?)(?:\s*[:>])/i',$sec,$msec) &&
-							!$this->exists($msec[1]))
-							$this->set($msec[1],NULL);
-						preg_match('/^(config|route|map|redirect)s\b|'.
-							'^(.+?)\s*\>\s*(.*)/i',$sec,$cmd);
-						continue;
-					}
-					if ($allow)
-						foreach (['lval','rval'] as $ndx)
-							$match[$ndx]=$preview->
-								resolve($match[$ndx],NULL,0,FALSE,FALSE);
-					if (!empty($cmd)) {
-						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($cmd[1]=='config'?
-								$this->cast($match['rval']):
-									$match['rval']))
-						);
-					}
-					else {
-						$rval=preg_replace(
-							'/\\\\\h*(\r?\n)/','\1',$match['rval']);
-						$ttl=NULL;
-						if (preg_match('/^(.+)\|\h*(\d+)$/',$rval,$tmp)) {
-							array_shift($tmp);
-							list($rval,$ttl)=$tmp;
-						}
-						$args=array_map(
-							function($val) {
-								$val=$this->cast($val);
-								if (is_string($val))
-									$val=strlen($val)?
-										preg_replace('/\\\\"/','"',$val):
-										NULL;
-								return $val;
-							},
-							// Mark quoted strings with 0x00 whitespace
-							str_getcsv(preg_replace(
-								'/(?<!\\\\)(")(.*?)\1/',
-								"\\1\x00\\2\\1",trim($rval)))
-						);
-						preg_match('/^(?<section>[^:]+)(?:\:(?<func>.+))?/',
-							$sec,$parts);
-						$func=isset($parts['func'])?$parts['func']:NULL;
-						$custom=(strtolower($parts['section'])!='globals');
-						if ($func)
-							$args=[$this->call($func,$args)];
-						if (count($args)>1)
-							$args=[$args];
-						if (isset($ttl))
-							$args=array_merge($args,[$ttl]);
-						call_user_func_array(
-							[$this,'set'],
-							array_merge(
-								[
-									($custom?($parts['section'].'.'):'').
-									$match['lval']
-								],
-								$args
-							)
-						);
-					}
-				}
-			}
-		}
-		return $this;
-	}
-
-	/**
-	*	Create mutex, invoke callback then drop ownership when done
-	*	@return mixed
-	*	@param $id string
-	*	@param $func callback
-	*	@param $args mixed
-	**/
-	function mutex($id,$func,$args=NULL) {
-		if (!is_dir($tmp=$this->hive['TEMP']))
-			mkdir($tmp,self::MODE,TRUE);
-		// Use filesystem lock
-		if (is_file($lock=$tmp.
-			$this->hive['SEED'].'.'.$this->hash($id).'.lock') &&
-			filemtime($lock)+ini_get('max_execution_time')<microtime(TRUE))
-			// Stale lock
-			@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;
-	}
-
-	/**
-	*	Read file (with option to apply Unix LF as standard line ending)
-	*	@return string
-	*	@param $file string
-	*	@param $lf bool
-	**/
-	function read($file,$lf=FALSE) {
-		$out=@file_get_contents($file);
-		return $lf?preg_replace('/\r\n|\r/',"\n",$out):$out;
-	}
-
-	/**
-	*	Exclusive file write
-	*	@return int|FALSE
-	*	@param $file string
-	*	@param $data mixed
-	*	@param $append bool
-	**/
-	function write($file,$data,$append=FALSE) {
-		return file_put_contents($file,$data,$this->hive['LOCK']|($append?FILE_APPEND:0));
-	}
-
-	/**
-	*	Apply syntax highlighting
-	*	@return string
-	*	@param $text string
-	**/
-	function highlight($text) {
-		$out='';
-		$pre=FALSE;
-		$text=trim($text);
-		if ($text && !preg_match('/^<\?php/',$text)) {
-			$text='<?php '.$text;
-			$pre=TRUE;
-		}
-		foreach (token_get_all($text) as $token)
-			if ($pre)
-				$pre=FALSE;
-			else
-				$out.='<span'.
-					(is_array($token)?
-						(' class="'.
-							substr(strtolower(token_name($token[0])),2).'">'.
-							$this->encode($token[1]).''):
-						('>'.$this->encode($token))).
-					'</span>';
-		return $out?('<code>'.$out.'</code>'):$text;
-	}
-
-	/**
-	*	Dump expression with syntax highlighting
-	*	@param $expr mixed
-	**/
-	function dump($expr) {
-		echo $this->highlight($this->stringify($expr));
-	}
-
-	/**
-	*	Return path (and query parameters) relative to the base directory
-	*	@return string
-	*	@param $url string
-	**/
-	function rel($url) {
-		return preg_replace('/^(?:https?:\/\/)?'.
-			preg_quote($this->hive['BASE'],'/').'(\/.*|$)/','\1',$url);
-	}
-
-	/**
-	*	Namespace-aware class autoloader
-	*	@return mixed
-	*	@param $class string
-	**/
-	protected function autoload($class) {
-		$class=$this->fixslashes(ltrim($class,'\\'));
-		/** @var callable $func */
-		$func=NULL;
-		if (is_array($path=$this->hive['AUTOLOAD']) &&
-			isset($path[1]) && is_callable($path[1]))
-			list($path,$func)=$path;
-		foreach ($this->split($this->hive['PLUGINS'].';'.$path) as $auto)
-			if ($func && is_file($file=$func($auto.$class).'.php') ||
-				is_file($file=$auto.$class.'.php') ||
-				is_file($file=$auto.strtolower($class).'.php') ||
-				is_file($file=strtolower($auto.$class).'.php'))
-				return require($file);
-	}
-
-	/**
-	*	Execute framework/application shutdown sequence
-	*	@param $cwd string
-	**/
-	function unload($cwd) {
-		chdir($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'],
-			[E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR]))
-			// Fatal error detected
-			$this->error(500,
-				sprintf(self::E_Fatal,$error['message']),[$error]);
-	}
-
-	/**
-	*	Convenience method for checking hive key
-	*	@return mixed
-	*	@param $key string
-	**/
-	function offsetexists($key) {
-		return $this->exists($key);
-	}
-
-	/**
-	*	Convenience method for assigning hive value
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function offsetset($key,$val) {
-		return $this->set($key,$val);
-	}
-
-	/**
-	*	Convenience method for retrieving hive value
-	*	@return mixed
-	*	@param $key string
-	**/
-	function &offsetget($key) {
-		$val=&$this->ref($key);
-		return $val;
-	}
-
-	/**
-	*	Convenience method for removing hive key
-	*	@param $key string
-	**/
-	function offsetunset($key) {
-		$this->clear($key);
-	}
-
-	/**
-	*	Alias for offsetexists()
-	*	@return mixed
-	*	@param $key string
-	**/
-	function __isset($key) {
-		return $this->offsetexists($key);
-	}
-
-	/**
-	*	Alias for offsetset()
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function __set($key,$val) {
-		return $this->offsetset($key,$val);
-	}
-
-	/**
-	*	Alias for offsetget()
-	*	@return mixed
-	*	@param $key string
-	**/
-	function &__get($key) {
-		$val=&$this->offsetget($key);
-		return $val;
-	}
-
-	/**
-	*	Alias for offsetunset()
-	*	@param $key string
-	**/
-	function __unset($key) {
-		$this->offsetunset($key);
-	}
-
-	/**
-	*	Call function identified by hive key
-	*	@return mixed
-	*	@param $key string
-	*	@param $args array
-	**/
-	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
-	private function __clone() {
-	}
-
-	//! Bootstrap
-	function __construct() {
-		// Managed directives
-		ini_set('default_charset',$charset='UTF-8');
-		if (extension_loaded('mbstring'))
-			mb_internal_encoding($charset);
-		ini_set('display_errors',0);
-		// Deprecated directives
-		@ini_set('magic_quotes_gpc',0);
-		@ini_set('register_globals',0);
-		// Intercept errors/exceptions; PHP5.3-compatible
-		$check=error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE));
-		set_exception_handler(
-			function($obj) {
-				/** @var Exception $obj */
-				$this->hive['EXCEPTION']=$obj;
-				$this->error(500,
-					$obj->getmessage().' '.
-					'['.$obj->getFile().':'.$obj->getLine().']',
-					$obj->gettrace());
-			}
-		);
-		set_error_handler(
-			function($level,$text,$file,$line) {
-				if ($level & error_reporting())
-					$this->error(500,$text,NULL,$level);
-			}
-		);
-		if (!isset($_SERVER['SERVER_NAME']) || $_SERVER['SERVER_NAME']==='')
-			$_SERVER['SERVER_NAME']=gethostname();
-		$headers=[];
-		if ($cli=PHP_SAPI=='cli') {
-			// Emulate HTTP request
-			$_SERVER['REQUEST_METHOD']='GET';
-			if (!isset($_SERVER['argv'][1])) {
-				$_SERVER['argc']++;
-				$_SERVER['argv'][1]='/';
-			}
-			$req=$query='';
-			if (substr($_SERVER['argv'][1],0,1)=='/') {
-				$req=$_SERVER['argv'][1];
-				$query=parse_url($req,PHP_URL_QUERY);
-			} else {
-				foreach($_SERVER['argv'] as $i=>$arg) {
-					if (!$i) continue;
-					if (preg_match('/^\-(\-)?(\w+)(?:\=(.*))?$/',$arg,$m)) {
-						foreach($m[1]?[$m[2]]:str_split($m[2]) as $k)
-							$query.=($query?'&':'').urlencode($k).'=';
-						if (isset($m[3]))
-							$query.=urlencode($m[3]);
-					} else
-						$req.='/'.$arg;
-				}
-				if (!$req)
-					$req='/';
-				if ($query)
-					$req.='?'.$query;
-			}
-			$_SERVER['REQUEST_URI']=$req;
-			parse_str($query,$GLOBALS['_GET']);
-		}
-		elseif (function_exists('getallheaders')) {
-			foreach (getallheaders() as $key=>$val) {
-				$tmp=strtoupper(strtr($key,'-','_'));
-				// TODO: use ucwords delimiters for php 5.4.32+ & 5.5.16+
-				$key=strtr(ucwords(strtolower(strtr($key,'-',' '))),' ','-');
-				$headers[$key]=$val;
-				if (isset($_SERVER['HTTP_'.$tmp]))
-					$headers[$key]=&$_SERVER['HTTP_'.$tmp];
-			}
-		}
-		else {
-			if (isset($_SERVER['CONTENT_LENGTH']))
-				$headers['Content-Length']=&$_SERVER['CONTENT_LENGTH'];
-			if (isset($_SERVER['CONTENT_TYPE']))
-				$headers['Content-Type']=&$_SERVER['CONTENT_TYPE'];
-			foreach (array_keys($_SERVER) as $key)
-				if (substr($key,0,5)=='HTTP_')
-					$headers[strtr(ucwords(strtolower(strtr(
-						substr($key,5),'_',' '))),' ','-')]=&$_SERVER[$key];
-		}
-		if (isset($headers['X-HTTP-Method-Override']))
-			$_SERVER['REQUEST_METHOD']=$headers['X-HTTP-Method-Override'];
-		elseif ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['_method']))
-			$_SERVER['REQUEST_METHOD']=strtoupper($_POST['_method']);
-		$scheme=isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' ||
-			isset($headers['X-Forwarded-Proto']) &&
-			$headers['X-Forwarded-Proto']=='https'?'https':'http';
-		// Create hive early on to expose header methods
-		$this->hive=['HEADERS'=>&$headers];
-		if (function_exists('apache_setenv')) {
-			// Work around Apache pre-2.4 VirtualDocumentRoot bug
-			$_SERVER['DOCUMENT_ROOT']=str_replace($_SERVER['SCRIPT_NAME'],'',
-				$_SERVER['SCRIPT_FILENAME']);
-			apache_setenv("DOCUMENT_ROOT",$_SERVER['DOCUMENT_ROOT']);
-		}
-		$_SERVER['DOCUMENT_ROOT']=realpath($_SERVER['DOCUMENT_ROOT']);
-		$base='';
-		if (!$cli)
-			$base=rtrim($this->fixslashes(
-				dirname($_SERVER['SCRIPT_NAME'])),'/');
-		$uri=parse_url((preg_match('/^\w+:\/\//',$_SERVER['REQUEST_URI'])?'':
-				$scheme.'://'.$_SERVER['SERVER_NAME']).$_SERVER['REQUEST_URI']);
-		$_SERVER['REQUEST_URI']=$uri['path'].
-			(isset($uri['query'])?'?'.$uri['query']:'').
-			(isset($uri['fragment'])?'#'.$uri['fragment']:'');
-		$path=preg_replace('/^'.preg_quote($base,'/').'/','',$uri['path']);
-		$jar=[
-			'expire'=>0,
-			'lifetime'=>0,
-			'path'=>$base?:'/',
-			'domain'=>is_int(strpos($_SERVER['SERVER_NAME'],'.')) &&
-				!filter_var($_SERVER['SERVER_NAME'],FILTER_VALIDATE_IP)?
-				$_SERVER['SERVER_NAME']:'',
-			'secure'=>($scheme=='https'),
-			'httponly'=>TRUE,
-			'samesite'=>'Lax',
-		];
-		$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+=[
-			'AGENT'=>$this->agent(),
-			'AJAX'=>$this->ajax(),
-			'ALIAS'=>NULL,
-			'ALIASES'=>[],
-			'AUTOLOAD'=>'./',
-			'BASE'=>$base,
-			'BITMASK'=>ENT_COMPAT,
-			'BODY'=>NULL,
-			'CACHE'=>FALSE,
-			'CASELESS'=>TRUE,
-			'CLI'=>$cli,
-			'CORS'=>[
-				'headers'=>'',
-				'origin'=>FALSE,
-				'credentials'=>FALSE,
-				'expose'=>FALSE,
-				'ttl'=>0
-			],
-			'DEBUG'=>0,
-			'DIACRITICS'=>[],
-			'DNSBL'=>'',
-			'EMOJI'=>[],
-			'ENCODING'=>$charset,
-			'ERROR'=>NULL,
-			'ESCAPE'=>TRUE,
-			'EXCEPTION'=>NULL,
-			'EXEMPT'=>NULL,
-			'FALLBACK'=>$this->fallback,
-			'FORMATS'=>[],
-			'FRAGMENT'=>isset($uri['fragment'])?$uri['fragment']:'',
-			'HALT'=>TRUE,
-			'HIGHLIGHT'=>FALSE,
-			'HOST'=>$_SERVER['SERVER_NAME'],
-			'IP'=>$this->ip(),
-			'JAR'=>$jar,
-			'LANGUAGE'=>isset($headers['Accept-Language'])?
-				$this->language($headers['Accept-Language']):
-				$this->fallback,
-			'LOCALES'=>'./',
-			'LOCK'=>LOCK_EX,
-			'LOGGABLE'=>'*',
-			'LOGS'=>'./',
-			'MB'=>extension_loaded('mbstring'),
-			'ONERROR'=>NULL,
-			'ONREROUTE'=>NULL,
-			'PACKAGE'=>self::PACKAGE,
-			'PARAMS'=>[],
-			'PATH'=>$path,
-			'PATTERN'=>NULL,
-			'PLUGINS'=>$this->fixslashes(__DIR__).'/',
-			'PORT'=>$port,
-			'PREFIX'=>NULL,
-			'PREMAP'=>'',
-			'QUERY'=>isset($uri['query'])?$uri['query']:'',
-			'QUIET'=>FALSE,
-			'RAW'=>FALSE,
-			'REALM'=>$scheme.'://'.$_SERVER['SERVER_NAME'].
-				($port && !in_array($port,[80,443])?(':'.$port):'').
-				$_SERVER['REQUEST_URI'],
-			'RESPONSE'=>'',
-			'ROOT'=>$_SERVER['DOCUMENT_ROOT'],
-			'ROUTES'=>[],
-			'SCHEME'=>$scheme,
-			'SEED'=>$this->hash($_SERVER['SERVER_NAME'].$base),
-			'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php',
-			'TEMP'=>'tmp/',
-			'TIME'=>&$_SERVER['REQUEST_TIME_FLOAT'],
-			'TZ'=>@date_default_timezone_get(),
-			'UI'=>'./',
-			'UNLOAD'=>NULL,
-			'UPLOADS'=>'./',
-			'URI'=>&$_SERVER['REQUEST_URI'],
-			'VERB'=>&$_SERVER['REQUEST_METHOD'],
-			'VERSION'=>self::VERSION,
-			'XFRAME'=>'SAMEORIGIN'
-		];
-		if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) {
-			unset($jar['expire']);
-			session_cache_limiter('');
-			if (version_compare(PHP_VERSION, '7.3.0') >= 0)
-				session_set_cookie_params($jar);
-			else {
-				unset($jar['samesite']);
-				call_user_func_array('session_set_cookie_params',$jar);
-			}
-		}
-		if (PHP_SAPI=='cli-server' &&
-			preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
-			$this->reroute('/');
-		if (ini_get('auto_globals_jit'))
-			// Override setting
-			$GLOBALS+=['_ENV'=>$_ENV,'_REQUEST'=>$_REQUEST];
-		// Sync PHP globals with corresponding hive keys
-		$this->init=$this->hive;
-		foreach (explode('|',self::GLOBALS) as $global) {
-			$sync=$this->sync($global);
-			$this->init+=[
-				$global=>preg_match('/SERVER|ENV/',$global)?$sync:[]
-			];
-		}
-		if ($check && $error=error_get_last())
-			// Error detected
-			$this->error(500,
-				sprintf(self::E_Fatal,$error['message']),[$error]);
-		date_default_timezone_set($this->hive['TZ']);
-		// Register framework autoloader
-		spl_autoload_register([$this,'autoload']);
-		// Register shutdown handler
-		register_shutdown_function([$this,'unload'],getcwd());
-	}
-
-}
-
-//! Cache engine
-class Cache extends Prefab {
-
-	protected
-		//! Cache DSN
-		$dsn,
-		//! Prefix for cache entries
-		$prefix,
-		//! MemCache or Redis object
-		$ref;
-
-	/**
-	*	Return timestamp and TTL of cache entry or FALSE if not found
-	*	@return array|FALSE
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function exists($key,&$val=NULL) {
-		$fw=Base::instance();
-		if (!$this->dsn)
-			return FALSE;
-		$ndx=$this->prefix.'.'.$key;
-		$parts=explode('=',$this->dsn,2);
-		switch ($parts[0]) {
-			case 'apc':
-			case 'apcu':
-				$raw=call_user_func($parts[0].'_fetch',$ndx);
-				break;
-			case 'redis':
-				$raw=$this->ref->get($ndx);
-				break;
-			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;
-			case 'xcache':
-				$raw=xcache_get($ndx);
-				break;
-			case 'folder':
-				$raw=$fw->read($parts[1].$ndx);
-				break;
-		}
-		if (!empty($raw)) {
-			list($val,$time,$ttl)=(array)$fw->unserialize($raw);
-			if ($ttl===0 || $time+$ttl>microtime(TRUE))
-				return [$time,$ttl];
-			$val=null;
-			$this->clear($key);
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Store value in cache
-	*	@return mixed|FALSE
-	*	@param $key string
-	*	@param $val mixed
-	*	@param $ttl int
-	**/
-	function set($key,$val,$ttl=0) {
-		$fw=Base::instance();
-		if (!$this->dsn)
-			return TRUE;
-		$ndx=$this->prefix.'.'.$key;
-		if ($cached=$this->exists($key))
-			$ttl=$cached[1];
-		$data=$fw->serialize([$val,microtime(TRUE),$ttl]);
-		$parts=explode('=',$this->dsn,2);
-		switch ($parts[0]) {
-			case 'apc':
-			case 'apcu':
-				return call_user_func($parts[0].'_store',$ndx,$data,$ttl);
-			case 'redis':
-				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':
-				return xcache_set($ndx,$data,$ttl);
-			case 'folder':
-				return $fw->write($parts[1].
-					str_replace(['/','\\'],'',$ndx),$data);
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Retrieve value of cache entry
-	*	@return mixed|FALSE
-	*	@param $key string
-	**/
-	function get($key) {
-		return $this->dsn && $this->exists($key,$data)?$data:FALSE;
-	}
-
-	/**
-	*	Delete cache entry
-	*	@return bool
-	*	@param $key string
-	**/
-	function clear($key) {
-		if (!$this->dsn)
-			return;
-		$ndx=$this->prefix.'.'.$key;
-		$parts=explode('=',$this->dsn,2);
-		switch ($parts[0]) {
-			case 'apc':
-			case 'apcu':
-				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':
-				return xcache_unset($ndx);
-			case 'folder':
-				return @unlink($parts[1].$ndx);
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Clear contents of cache backend
-	*	@return bool
-	*	@param $suffix string
-	**/
-	function reset($suffix=NULL) {
-		if (!$this->dsn)
-			return TRUE;
-		$regex='/'.preg_quote($this->prefix.'.','/').'.*'.
-			preg_quote($suffix,'/').'/';
-		$parts=explode('=',$this->dsn,2);
-		switch ($parts[0]) {
-			case 'apc':
-			case 'apcu':
-				$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';
-					foreach ($info['cache_list'] as $item)
-						if (preg_match($regex,$item[$key]))
-							call_user_func($parts[0].'_delete',$item[$key]);
-				}
-				return TRUE;
-			case 'redis':
-				$keys=$this->ref->keys($this->prefix.'.*'.$suffix);
-				foreach($keys as $key)
-					$this->ref->del($key);
-				return TRUE;
-			case 'memcache':
-				foreach (memcache_get_extended_stats(
-					$this->ref,'slabs') as $slabs)
-					foreach (array_filter(array_keys($slabs),'is_numeric')
-						as $id)
-						foreach (memcache_get_extended_stats(
-							$this->ref,'cachedump',$id) as $data)
-							if (is_array($data))
-								foreach (array_keys($data) as $key)
-									if (preg_match($regex,$key))
-										memcache_delete($this->ref,$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']))
-						wincache_ucache_delete($item['key_name']);
-				return TRUE;
-			case 'xcache':
-				if ($suffix && !ini_get('xcache.admin.enable_auth')) {
-					$cnt=xcache_count(XC_TYPE_VAR);
-					for ($i=0;$i<$cnt;$i++) {
-						$list=xcache_list(XC_TYPE_VAR,$i);
-						foreach ($list['cache_list'] as $item)
-							if (preg_match($regex,$item['name']))
-								xcache_unset($item['name']);
-					}
-				} else
-					xcache_unset_by_prefix($this->prefix.'.');
-				return TRUE;
-			case 'folder':
-				if ($glob=@glob($parts[1].'*'))
-					foreach ($glob as $file)
-						if (preg_match($regex,basename($file)))
-							@unlink($file);
-				return TRUE;
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Load/auto-detect cache backend
-	*	@return string
-	*	@param $dsn bool|string
-	*	@param $seed bool|string
-	**/
-	function load($dsn,$seed=NULL) {
-		$fw=Base::instance();
-		if ($dsn=trim($dsn)) {
-			if (preg_match('/^redis=(.+)/',$dsn,$parts) &&
-				extension_loaded('redis')) {
-				list($host,$port,$db,$password)=explode(':',$parts[1])+[1=>6379,2=>NULL,3=>NULL];
-				$this->ref=new Redis;
-				if(!$this->ref->connect($host,$port,2))
-					$this->ref=NULL;
-				if(!empty($password))
-					$this->ref->auth($password);
-				if(isset($db))
-					$this->ref->select($db);
-			}
-			elseif (preg_match('/^memcache=(.+)/',$dsn,$parts) &&
-				extension_loaded('memcache'))
-				foreach ($fw->split($parts[1]) as $server) {
-					list($host,$port)=explode(':',$server)+[1=>11211];
-					if (empty($this->ref))
-						$this->ref=@memcache_connect($host,$port)?:NULL;
-					else
-						memcache_add_server($this->ref,$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->TEMP.'cache/');
-			if (preg_match('/^folder\h*=\h*(.+)/',$dsn,$parts) &&
-				!is_dir($parts[1]))
-				mkdir($parts[1],Base::MODE,TRUE);
-		}
-		$this->prefix=$seed?:$fw->SEED;
-		return $this->dsn=$dsn;
-	}
-
-	/**
-	*	Class constructor
-	*	@param $dsn bool|string
-	**/
-	function __construct($dsn=FALSE) {
-		if ($dsn)
-			$this->load($dsn);
-	}
-
-}
-
-//! View handler
-class View extends Prefab {
-
-	private
-		//! Temporary hive
-		$temp;
-
-	protected
-		//! Template file
-		$file,
-		//! Post-rendering handler
-		$trigger,
-		//! Nesting level
-		$level=0;
-
-	/** @var \Base Framework instance */
-	protected $fw;
-
-	function __construct() {
-		$this->fw=\Base::instance();
-	}
-
-	/**
-	*	Encode characters to equivalent HTML entities
-	*	@return string
-	*	@param $arg mixed
-	**/
-	function esc($arg) {
-		return $this->fw->recursive($arg,
-			function($val) {
-				return is_string($val)?$this->fw->encode($val):$val;
-			}
-		);
-	}
-
-	/**
-	*	Decode HTML entities to equivalent characters
-	*	@return string
-	*	@param $arg mixed
-	**/
-	function raw($arg) {
-		return $this->fw->recursive($arg,
-			function($val) {
-				return is_string($val)?$this->fw->decode($val):$val;
-			}
-		);
-	}
-
-	/**
-	*	Create sandbox for template execution
-	*	@return string
-	*	@param $hive array
-	*	@param $mime string
-	**/
-	protected function sandbox(array $hive=NULL,$mime=NULL) {
-		$fw=$this->fw;
-		$implicit=FALSE;
-		if (is_null($hive)) {
-			$implicit=TRUE;
-			$hive=$fw->hive();
-		}
-		if ($this->level<1 || $implicit) {
-			if (!$fw->CLI && $mime && !headers_sent() &&
-				!preg_grep ('/^Content-Type:/',headers_list()))
-				header('Content-Type: '.$mime.'; '.
-					'charset='.$fw->ENCODING);
-			if ($fw->ESCAPE && (!$mime ||
-					preg_match('/^(text\/html|(application|text)\/(.+\+)?xml)$/i',$mime)))
-				$hive=$this->esc($hive);
-			if (isset($hive['ALIASES']))
-				$hive['ALIASES']=$fw->build($hive['ALIASES']);
-		}
-		$this->temp=$hive;
-		unset($fw,$hive,$implicit,$mime);
-		extract($this->temp);
-		$this->temp=NULL;
-		$this->level++;
-		ob_start();
-		require($this->file);
-		$this->level--;
-		return ob_get_clean();
-	}
-
-	/**
-	*	Render template
-	*	@return string
-	*	@param $file string
-	*	@param $mime string
-	*	@param $hive array
-	*	@param $ttl int
-	**/
-	function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
-		$fw=$this->fw;
-		$cache=Cache::instance();
-		foreach ($fw->split($fw->UI) as $dir) {
-			if ($cache->exists($hash=$fw->hash($dir.$file),$data))
-				return $data;
-			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');
-				$data=$this->sandbox($hive,$mime);
-				if (isset($this->trigger['afterrender']))
-					foreach($this->trigger['afterrender'] as $func)
-						$data=$fw->call($func,[$data, $dir.$file]);
-				if ($ttl)
-					$cache->set($hash,$data,$ttl);
-				return $data;
-			}
-		}
-		user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
-	}
-
-	/**
-	*	post rendering handler
-	*	@param $func callback
-	*/
-	function afterrender($func) {
-		$this->trigger['afterrender'][]=$func;
-	}
-
-}
-
-//! Lightweight template engine
-class Preview extends View {
-
-	protected
-		//! token filter
-		$filter=[
-			'c'=>'$this->c',
-			'esc'=>'$this->esc',
-			'raw'=>'$this->raw',
-			'export'=>'Base::instance()->export',
-			'alias'=>'Base::instance()->alias',
-			'format'=>'Base::instance()->format'
-		];
-
-	protected
-		//! newline interpolation
-		$interpolation=true;
-
-	/**
-	 * Enable/disable markup parsing interpolation
-	 * mainly used for adding appropriate newlines
-	 * @param $bool bool
-	 */
-	function interpolation($bool) {
-		$this->interpolation=$bool;
-	}
-
-	/**
-	*	Return C-locale equivalent of number
-	*	@return string
-	*	@param $val int|float
-	**/
-	function c($val) {
-		$locale=setlocale(LC_NUMERIC,0);
-		setlocale(LC_NUMERIC,'C');
-		$out=(string)(float)$val;
-		$locale=setlocale(LC_NUMERIC,$locale);
-		return $out;
-	}
-
-	/**
-	*	Convert token to variable
-	*	@return string
-	*	@param $str string
-	**/
-	function token($str) {
-		$str=trim(preg_replace('/\{\{(.+?)\}\}/s','\1',$this->fw->compile($str)));
-		if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
-			$str,$parts)) {
-			$str=trim($parts[1]);
-			foreach ($this->fw->split(trim($parts[2],"\xC2\xA0")) as $func)
-				$str=((empty($this->filter[$cmd=$func]) &&
-					function_exists($cmd)) ||
-					is_string($cmd=$this->filter($func)))?
-					$cmd.'('.$str.')':
-					'Base::instance()->'.
-						'call($this->filter(\''.$func.'\'),['.$str.'])';
-		}
-		return $str;
-	}
-
-	/**
-	*	Register or get (one specific or all) token filters
-	*	@param string $key
-	*	@param string|closure $func
-	*	@return array|closure|string
-	*/
-	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;
-	}
-
-	/**
-	*	Assemble markup
-	*	@return string
-	*	@param $node string
-	**/
-	protected function build($node) {
-		return preg_replace_callback(
-			'/\{~(.+?)~\}|\{\*(.+?)\*\}|\{\-(.+?)\-\}|'.
-			'\{\{(.+?)\}\}((\r?\n)*)/s',
-			function($expr) {
-				if ($expr[1])
-					$str='<?php '.$this->token($expr[1]).' ?>';
-				elseif ($expr[2])
-					return '';
-				elseif ($expr[3])
-					$str=$expr[3];
-				else {
-					$str='<?= ('.trim($this->token($expr[4])).')'.
-						($this->interpolation?
-							(!empty($expr[6])?'."'.$expr[6].'"':''):'').' ?>';
-					if (isset($expr[5]))
-						$str.=$expr[5];
-				}
-				return $str;
-			},
-			$node
-		);
-	}
-
-	/**
-	*	Render template string
-	*	@return string
-	*	@param $node string|array
-	*	@param $hive array
-	*	@param $ttl int
-	*	@param $persist bool
-	*	@param $escape bool
-	**/
-	function resolve($node,array $hive=NULL,$ttl=0,$persist=FALSE,$escape=NULL) {
-		$fw=$this->fw;
-		$cache=Cache::instance();
-		if ($escape!==NULL) {
-			$esc=$fw->ESCAPE;
-			$fw->ESCAPE=$escape;
-		}
-		if ($ttl || $persist)
-			$hash=$fw->hash($fw->serialize($node));
-		if ($ttl && $cache->exists($hash,$data))
-			return $data;
-		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($node));
-			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($node).'<?php ');
-			$data=ob_get_clean();
-		}
-		if ($ttl)
-			$cache->set($hash,$data,$ttl);
-		if ($escape!==NULL)
-			$fw->ESCAPE=$esc;
-		return $data;
-	}
-
-	/**
-	 *	Parse template string
-	 *	@return string
-	 *	@param $text string
-	 **/
-	function parse($text) {
-		// Remove PHP code and comments
-		return preg_replace(
-			'/\h*<\?(?!xml)(?:php|\s*=)?.+?\?>\h*|'.
-			'\{\*.+?\*\}/is','', $text);
-	}
-
-	/**
-	*	Render template
-	*	@return string
-	*	@param $file string
-	*	@param $mime string
-	*	@param $hive array
-	*	@param $ttl int
-	**/
-	function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
-		$fw=$this->fw;
-		$cache=Cache::instance();
-		if (!is_dir($tmp=$fw->TEMP))
-			mkdir($tmp,Base::MODE,TRUE);
-		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->file=($tmp.
-					$fw->SEED.'.'.$fw->hash($view).'.php')) ||
-					filemtime($this->file)<filemtime($view)) {
-					$contents=$fw->read($view);
-					if (isset($this->trigger['beforerender']))
-						foreach ($this->trigger['beforerender'] as $func)
-							$contents=$fw->call($func, [$contents, $view]);
-					$text=$this->parse($contents);
-					$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,$mime);
-				if(isset($this->trigger['afterrender']))
-					foreach ($this->trigger['afterrender'] as $func)
-						$data=$fw->call($func, [$data, $view]);
-				if ($ttl)
-					$cache->set($hash,$data,$ttl);
-				return $data;
-			}
-		}
-		user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
-	}
-
-	/**
-	 *	post rendering handler
-	 *	@param $func callback
-	 */
-	function beforerender($func) {
-		$this->trigger['beforerender'][]=$func;
-	}
-
-}
-
-//! ISO language/country codes
-class ISO extends Prefab {
-
-	//@{ ISO 3166-1 country codes
-	const
-		CC_af='Afghanistan',
-		CC_ax='Åland Islands',
-		CC_al='Albania',
-		CC_dz='Algeria',
-		CC_as='American Samoa',
-		CC_ad='Andorra',
-		CC_ao='Angola',
-		CC_ai='Anguilla',
-		CC_aq='Antarctica',
-		CC_ag='Antigua and Barbuda',
-		CC_ar='Argentina',
-		CC_am='Armenia',
-		CC_aw='Aruba',
-		CC_au='Australia',
-		CC_at='Austria',
-		CC_az='Azerbaijan',
-		CC_bs='Bahamas',
-		CC_bh='Bahrain',
-		CC_bd='Bangladesh',
-		CC_bb='Barbados',
-		CC_by='Belarus',
-		CC_be='Belgium',
-		CC_bz='Belize',
-		CC_bj='Benin',
-		CC_bm='Bermuda',
-		CC_bt='Bhutan',
-		CC_bo='Bolivia',
-		CC_bq='Bonaire, Sint Eustatius and Saba',
-		CC_ba='Bosnia and Herzegovina',
-		CC_bw='Botswana',
-		CC_bv='Bouvet Island',
-		CC_br='Brazil',
-		CC_io='British Indian Ocean Territory',
-		CC_bn='Brunei Darussalam',
-		CC_bg='Bulgaria',
-		CC_bf='Burkina Faso',
-		CC_bi='Burundi',
-		CC_kh='Cambodia',
-		CC_cm='Cameroon',
-		CC_ca='Canada',
-		CC_cv='Cape Verde',
-		CC_ky='Cayman Islands',
-		CC_cf='Central African Republic',
-		CC_td='Chad',
-		CC_cl='Chile',
-		CC_cn='China',
-		CC_cx='Christmas Island',
-		CC_cc='Cocos (Keeling) Islands',
-		CC_co='Colombia',
-		CC_km='Comoros',
-		CC_cg='Congo',
-		CC_cd='Congo, The Democratic Republic of',
-		CC_ck='Cook Islands',
-		CC_cr='Costa Rica',
-		CC_ci='Côte d\'ivoire',
-		CC_hr='Croatia',
-		CC_cu='Cuba',
-		CC_cw='Curaçao',
-		CC_cy='Cyprus',
-		CC_cz='Czech Republic',
-		CC_dk='Denmark',
-		CC_dj='Djibouti',
-		CC_dm='Dominica',
-		CC_do='Dominican Republic',
-		CC_ec='Ecuador',
-		CC_eg='Egypt',
-		CC_sv='El Salvador',
-		CC_gq='Equatorial Guinea',
-		CC_er='Eritrea',
-		CC_ee='Estonia',
-		CC_et='Ethiopia',
-		CC_fk='Falkland Islands (Malvinas)',
-		CC_fo='Faroe Islands',
-		CC_fj='Fiji',
-		CC_fi='Finland',
-		CC_fr='France',
-		CC_gf='French Guiana',
-		CC_pf='French Polynesia',
-		CC_tf='French Southern Territories',
-		CC_ga='Gabon',
-		CC_gm='Gambia',
-		CC_ge='Georgia',
-		CC_de='Germany',
-		CC_gh='Ghana',
-		CC_gi='Gibraltar',
-		CC_gr='Greece',
-		CC_gl='Greenland',
-		CC_gd='Grenada',
-		CC_gp='Guadeloupe',
-		CC_gu='Guam',
-		CC_gt='Guatemala',
-		CC_gg='Guernsey',
-		CC_gn='Guinea',
-		CC_gw='Guinea-Bissau',
-		CC_gy='Guyana',
-		CC_ht='Haiti',
-		CC_hm='Heard Island and McDonald Islands',
-		CC_va='Holy See (Vatican City State)',
-		CC_hn='Honduras',
-		CC_hk='Hong Kong',
-		CC_hu='Hungary',
-		CC_is='Iceland',
-		CC_in='India',
-		CC_id='Indonesia',
-		CC_ir='Iran, Islamic Republic of',
-		CC_iq='Iraq',
-		CC_ie='Ireland',
-		CC_im='Isle of Man',
-		CC_il='Israel',
-		CC_it='Italy',
-		CC_jm='Jamaica',
-		CC_jp='Japan',
-		CC_je='Jersey',
-		CC_jo='Jordan',
-		CC_kz='Kazakhstan',
-		CC_ke='Kenya',
-		CC_ki='Kiribati',
-		CC_kp='Korea, Democratic People\'s Republic of',
-		CC_kr='Korea, Republic of',
-		CC_kw='Kuwait',
-		CC_kg='Kyrgyzstan',
-		CC_la='Lao People\'s Democratic Republic',
-		CC_lv='Latvia',
-		CC_lb='Lebanon',
-		CC_ls='Lesotho',
-		CC_lr='Liberia',
-		CC_ly='Libya',
-		CC_li='Liechtenstein',
-		CC_lt='Lithuania',
-		CC_lu='Luxembourg',
-		CC_mo='Macao',
-		CC_mk='Macedonia, The Former Yugoslav Republic of',
-		CC_mg='Madagascar',
-		CC_mw='Malawi',
-		CC_my='Malaysia',
-		CC_mv='Maldives',
-		CC_ml='Mali',
-		CC_mt='Malta',
-		CC_mh='Marshall Islands',
-		CC_mq='Martinique',
-		CC_mr='Mauritania',
-		CC_mu='Mauritius',
-		CC_yt='Mayotte',
-		CC_mx='Mexico',
-		CC_fm='Micronesia, Federated States of',
-		CC_md='Moldova, Republic of',
-		CC_mc='Monaco',
-		CC_mn='Mongolia',
-		CC_me='Montenegro',
-		CC_ms='Montserrat',
-		CC_ma='Morocco',
-		CC_mz='Mozambique',
-		CC_mm='Myanmar',
-		CC_na='Namibia',
-		CC_nr='Nauru',
-		CC_np='Nepal',
-		CC_nl='Netherlands',
-		CC_nc='New Caledonia',
-		CC_nz='New Zealand',
-		CC_ni='Nicaragua',
-		CC_ne='Niger',
-		CC_ng='Nigeria',
-		CC_nu='Niue',
-		CC_nf='Norfolk Island',
-		CC_mp='Northern Mariana Islands',
-		CC_no='Norway',
-		CC_om='Oman',
-		CC_pk='Pakistan',
-		CC_pw='Palau',
-		CC_ps='Palestinian Territory, Occupied',
-		CC_pa='Panama',
-		CC_pg='Papua New Guinea',
-		CC_py='Paraguay',
-		CC_pe='Peru',
-		CC_ph='Philippines',
-		CC_pn='Pitcairn',
-		CC_pl='Poland',
-		CC_pt='Portugal',
-		CC_pr='Puerto Rico',
-		CC_qa='Qatar',
-		CC_re='Réunion',
-		CC_ro='Romania',
-		CC_ru='Russian Federation',
-		CC_rw='Rwanda',
-		CC_bl='Saint Barthélemy',
-		CC_sh='Saint Helena, Ascension and Tristan da Cunha',
-		CC_kn='Saint Kitts and Nevis',
-		CC_lc='Saint Lucia',
-		CC_mf='Saint Martin (French Part)',
-		CC_pm='Saint Pierre and Miquelon',
-		CC_vc='Saint Vincent and The Grenadines',
-		CC_ws='Samoa',
-		CC_sm='San Marino',
-		CC_st='Sao Tome and Principe',
-		CC_sa='Saudi Arabia',
-		CC_sn='Senegal',
-		CC_rs='Serbia',
-		CC_sc='Seychelles',
-		CC_sl='Sierra Leone',
-		CC_sg='Singapore',
-		CC_sk='Slovakia',
-		CC_sx='Sint Maarten (Dutch Part)',
-		CC_si='Slovenia',
-		CC_sb='Solomon Islands',
-		CC_so='Somalia',
-		CC_za='South Africa',
-		CC_gs='South Georgia and The South Sandwich Islands',
-		CC_ss='South Sudan',
-		CC_es='Spain',
-		CC_lk='Sri Lanka',
-		CC_sd='Sudan',
-		CC_sr='Suriname',
-		CC_sj='Svalbard and Jan Mayen',
-		CC_sz='Swaziland',
-		CC_se='Sweden',
-		CC_ch='Switzerland',
-		CC_sy='Syrian Arab Republic',
-		CC_tw='Taiwan, Province of China',
-		CC_tj='Tajikistan',
-		CC_tz='Tanzania, United Republic of',
-		CC_th='Thailand',
-		CC_tl='Timor-Leste',
-		CC_tg='Togo',
-		CC_tk='Tokelau',
-		CC_to='Tonga',
-		CC_tt='Trinidad and Tobago',
-		CC_tn='Tunisia',
-		CC_tr='Turkey',
-		CC_tm='Turkmenistan',
-		CC_tc='Turks and Caicos Islands',
-		CC_tv='Tuvalu',
-		CC_ug='Uganda',
-		CC_ua='Ukraine',
-		CC_ae='United Arab Emirates',
-		CC_gb='United Kingdom',
-		CC_us='United States',
-		CC_um='United States Minor Outlying Islands',
-		CC_uy='Uruguay',
-		CC_uz='Uzbekistan',
-		CC_vu='Vanuatu',
-		CC_ve='Venezuela',
-		CC_vn='Viet Nam',
-		CC_vg='Virgin Islands, British',
-		CC_vi='Virgin Islands, U.S.',
-		CC_wf='Wallis and Futuna',
-		CC_eh='Western Sahara',
-		CC_ye='Yemen',
-		CC_zm='Zambia',
-		CC_zw='Zimbabwe';
-	//@}
-
-	//@{ ISO 639-1 language codes (Windows-compatibility subset)
-	const
-		LC_af='Afrikaans',
-		LC_am='Amharic',
-		LC_ar='Arabic',
-		LC_as='Assamese',
-		LC_ba='Bashkir',
-		LC_be='Belarusian',
-		LC_bg='Bulgarian',
-		LC_bn='Bengali',
-		LC_bo='Tibetan',
-		LC_br='Breton',
-		LC_ca='Catalan',
-		LC_co='Corsican',
-		LC_cs='Czech',
-		LC_cy='Welsh',
-		LC_da='Danish',
-		LC_de='German',
-		LC_dv='Divehi',
-		LC_el='Greek',
-		LC_en='English',
-		LC_es='Spanish',
-		LC_et='Estonian',
-		LC_eu='Basque',
-		LC_fa='Persian',
-		LC_fi='Finnish',
-		LC_fo='Faroese',
-		LC_fr='French',
-		LC_gd='Scottish Gaelic',
-		LC_gl='Galician',
-		LC_gu='Gujarati',
-		LC_he='Hebrew',
-		LC_hi='Hindi',
-		LC_hr='Croatian',
-		LC_hu='Hungarian',
-		LC_hy='Armenian',
-		LC_id='Indonesian',
-		LC_ig='Igbo',
-		LC_is='Icelandic',
-		LC_it='Italian',
-		LC_ja='Japanese',
-		LC_ka='Georgian',
-		LC_kk='Kazakh',
-		LC_km='Khmer',
-		LC_kn='Kannada',
-		LC_ko='Korean',
-		LC_lb='Luxembourgish',
-		LC_lo='Lao',
-		LC_lt='Lithuanian',
-		LC_lv='Latvian',
-		LC_mi='Maori',
-		LC_ml='Malayalam',
-		LC_mr='Marathi',
-		LC_ms='Malay',
-		LC_mt='Maltese',
-		LC_ne='Nepali',
-		LC_nl='Dutch',
-		LC_no='Norwegian',
-		LC_oc='Occitan',
-		LC_or='Oriya',
-		LC_pl='Polish',
-		LC_ps='Pashto',
-		LC_pt='Portuguese',
-		LC_qu='Quechua',
-		LC_ro='Romanian',
-		LC_ru='Russian',
-		LC_rw='Kinyarwanda',
-		LC_sa='Sanskrit',
-		LC_si='Sinhala',
-		LC_sk='Slovak',
-		LC_sl='Slovenian',
-		LC_sq='Albanian',
-		LC_sv='Swedish',
-		LC_ta='Tamil',
-		LC_te='Telugu',
-		LC_th='Thai',
-		LC_tk='Turkmen',
-		LC_tr='Turkish',
-		LC_tt='Tatar',
-		LC_uk='Ukrainian',
-		LC_ur='Urdu',
-		LC_vi='Vietnamese',
-		LC_wo='Wolof',
-		LC_yo='Yoruba',
-		LC_zh='Chinese';
-	//@}
-
-	/**
-	*	Return list of languages indexed by ISO 639-1 language code
-	*	@return array
-	**/
-	function languages() {
-		return \Base::instance()->constants($this,'LC_');
-	}
-
-	/**
-	*	Return list of countries indexed by ISO 3166-1 country code
-	*	@return array
-	**/
-	function countries() {
-		return \Base::instance()->constants($this,'CC_');
-	}
-
-}
-
-//! Container for singular object instances
-final class Registry {
-
-	private static
-		//! Object catalog
-		$table;
-
-	/**
-	*	Return TRUE if object exists in catalog
-	*	@return bool
-	*	@param $key string
-	**/
-	static function exists($key) {
-		return isset(self::$table[$key]);
-	}
-
-	/**
-	*	Add object to catalog
-	*	@return object
-	*	@param $key string
-	*	@param $obj object
-	**/
-	static function set($key,$obj) {
-		return self::$table[$key]=$obj;
-	}
-
-	/**
-	*	Retrieve object from catalog
-	*	@return object
-	*	@param $key string
-	**/
-	static function get($key) {
-		return self::$table[$key];
-	}
-
-	/**
-	*	Delete object from catalog
-	*	@param $key string
-	**/
-	static function clear($key) {
-		self::$table[$key]=NULL;
-		unset(self::$table[$key]);
-	}
-
-	//! Prohibit cloning
-	private function __clone() {
-	}
-
-	//! Prohibit instantiation
-	private function __construct() {
-	}
-
-}
-
-return Base::instance();

+ 0 - 239
lib/f3/basket.php

@@ -1,239 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Session-based pseudo-mapper
-class Basket extends Magic {
-
-	//@{ Error messages
-	const
-		E_Field='Undefined field %s';
-	//@}
-
-	protected
-		//! Session key
-		$key,
-		//! Current item identifier
-		$id,
-		//! Current item contents
-		$item=[];
-
-	/**
-	*	Return TRUE if field is defined
-	*	@return bool
-	*	@param $key string
-	**/
-	function exists($key) {
-		return array_key_exists($key,$this->item);
-	}
-
-	/**
-	*	Assign value to field
-	*	@return scalar|FALSE
-	*	@param $key string
-	*	@param $val scalar
-	**/
-	function set($key,$val) {
-		return ($key=='_id')?FALSE:($this->item[$key]=$val);
-	}
-
-	/**
-	*	Retrieve value of field
-	*	@return scalar|FALSE
-	*	@param $key string
-	**/
-	function &get($key) {
-		if ($key=='_id')
-			return $this->id;
-		if (array_key_exists($key,$this->item))
-			return $this->item[$key];
-		user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
-		return FALSE;
-	}
-
-	/**
-	*	Delete field
-	*	@return NULL
-	*	@param $key string
-	**/
-	function clear($key) {
-		unset($this->item[$key]);
-	}
-
-	/**
-	*	Return items that match key/value pair;
-	*	If no key/value pair specified, return all items
-	*	@return array
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function find($key=NULL,$val=NULL) {
-		$out=[];
-		if (isset($_SESSION[$this->key])) {
-			foreach ($_SESSION[$this->key] as $id=>$item)
-				if (!isset($key) ||
-					array_key_exists($key,$item) && $item[$key]==$val ||
-					$key=='_id' && $id==$val) {
-					$obj=clone($this);
-					$obj->id=$id;
-					$obj->item=$item;
-					$out[]=$obj;
-				}
-		}
-		return $out;
-	}
-
-	/**
-	*	Return first item that matches key/value pair
-	*	@return object|FALSE
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function findone($key,$val) {
-		return ($data=$this->find($key,$val))?$data[0]:FALSE;
-	}
-
-	/**
-	*	Map current item to matching key/value pair
-	*	@return array
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function load($key,$val) {
-		if ($found=$this->find($key,$val)) {
-			$this->id=$found[0]->id;
-			return $this->item=$found[0]->item;
-		}
-		$this->reset();
-		return [];
-	}
-
-	/**
-	*	Return TRUE if current item is empty/undefined
-	*	@return bool
-	**/
-	function dry() {
-		return !$this->item;
-	}
-
-	/**
-	*	Return number of items in basket
-	*	@return int
-	**/
-	function count() {
-		return isset($_SESSION[$this->key])?count($_SESSION[$this->key]):0;
-	}
-
-	/**
-	*	Save current item
-	*	@return array
-	**/
-	function save() {
-		if (!$this->id)
-			$this->id=uniqid(NULL,TRUE);
-		$_SESSION[$this->key][$this->id]=$this->item;
-		return $this->item;
-	}
-
-	/**
-	*	Erase item matching key/value pair
-	*	@return bool
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function erase($key,$val) {
-		$found=$this->find($key,$val);
-		if ($found && $id=$found[0]->id) {
-			unset($_SESSION[$this->key][$id]);
-			if ($id==$this->id)
-				$this->reset();
-			return TRUE;
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Reset cursor
-	*	@return NULL
-	**/
-	function reset() {
-		$this->id=NULL;
-		$this->item=[];
-	}
-
-	/**
-	*	Empty basket
-	*	@return NULL
-	**/
-	function drop() {
-		unset($_SESSION[$this->key]);
-	}
-
-	/**
-	*	Hydrate item using hive array variable
-	*	@return NULL
-	*	@param $var array|string
-	**/
-	function copyfrom($var) {
-		if (is_string($var))
-			$var=\Base::instance()->$var;
-		foreach ($var as $key=>$val)
-			$this->set($key,$val);
-	}
-
-	/**
-	*	Populate hive array variable with item contents
-	*	@return NULL
-	*	@param $key string
-	**/
-	function copyto($key) {
-		$var=&\Base::instance()->ref($key);
-		foreach ($this->item as $key=>$field)
-			$var[$key]=$field;
-	}
-
-	/**
-	*	Check out basket contents
-	*	@return array
-	**/
-	function checkout() {
-		if (isset($_SESSION[$this->key])) {
-			$out=$_SESSION[$this->key];
-			unset($_SESSION[$this->key]);
-			return $out;
-		}
-		return [];
-	}
-
-	/**
-	*	Instantiate class
-	*	@return void
-	*	@param $key string
-	**/
-	function __construct($key='basket') {
-		$this->key=$key;
-		if (session_status()!=PHP_SESSION_ACTIVE)
-			session_start();
-		Base::instance()->sync('SESSION');
-		$this->reset();
-	}
-
-}

+ 0 - 96
lib/f3/bcrypt.php

@@ -1,96 +0,0 @@
-<?php
-
-/**
-*	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-*
-*	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-*
-*	This is free software: you can redistribute it and/or modify it under the
-*	terms of the GNU General Public License as published by the Free Software
-*	Foundation, either version 3 of the License, or later.
-*
-*	Fat-Free Framework is distributed in the hope that it will be useful,
-*	but WITHOUT ANY WARRANTY; without even the implied warranty of
-*	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-*	General Public License for more details.
-*
-*	You should have received a copy of the GNU General Public License along
-*	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-*
-**/
-
-/**
-*	Lightweight password hashing library (PHP 5.5+ only)
-*	@deprecated Use http://php.net/manual/en/ref.password.php instead
-**/
-class Bcrypt extends Prefab {
-
-	//@{ Error messages
-	const
-		E_CostArg='Invalid cost parameter',
-		E_SaltArg='Salt must be at least 22 alphanumeric characters';
-	//@}
-
-	//! Default cost
-	const
-		COST=10;
-
-	/**
-	*	Generate bcrypt hash of string
-	*	@return string|FALSE
-	*	@param $pw string
-	*	@param $salt string
-	*	@param $cost int
-	**/
-	function hash($pw,$salt=NULL,$cost=self::COST) {
-		if ($cost<4 || $cost>31)
-			user_error(self::E_CostArg,E_USER_ERROR);
-		$len=22;
-		if ($salt) {
-			if (!preg_match('/^[[:alnum:]\.\/]{'.$len.',}$/',$salt))
-				user_error(self::E_SaltArg,E_USER_ERROR);
-		}
-		else {
-			$raw=16;
-			$iv='';
-			if (!$iv && extension_loaded('openssl'))
-				$iv=openssl_random_pseudo_bytes($raw);
-			if (!$iv)
-				for ($i=0;$i<$raw;$i++)
-					$iv.=chr(mt_rand(0,255));
-			$salt=str_replace('+','.',base64_encode($iv));
-		}
-		$salt=substr($salt,0,$len);
-		$hash=crypt($pw,sprintf('$2y$%02d$',$cost).$salt);
-		return strlen($hash)>13?$hash:FALSE;
-	}
-
-	/**
-	*	Check if password is still strong enough
-	*	@return bool
-	*	@param $hash string
-	*	@param $cost int
-	**/
-	function needs_rehash($hash,$cost=self::COST) {
-		list($pwcost)=sscanf($hash,"$2y$%d$");
-		return $pwcost<$cost;
-	}
-
-	/**
-	*	Verify password against hash using timing attack resistant approach
-	*	@return bool
-	*	@param $pw string
-	*	@param $hash string
-	**/
-	function verify($pw,$hash) {
-		$val=crypt($pw,$hash);
-		$len=strlen($val);
-		if ($len!=strlen($hash) || $len<14)
-			return FALSE;
-		$out=0;
-		for ($i=0;$i<$len;$i++)
-			$out|=(ord($val[$i])^ord($hash[$i]));
-		return $out===0;
-	}
-
-}

+ 0 - 487
lib/f3/cli/ws.php

@@ -1,487 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace CLI;
-
-//! RFC6455 WebSocket server
-class WS {
-
-	const
-		//! UUID magic string
-		Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
-		//! Max packet size
-		Packet=65536;
-
-	//@{ Mask bits for first byte of header
-	const
-		Text=0x01,
-		Binary=0x02,
-		Close=0x08,
-		Ping=0x09,
-		Pong=0x0a,
-		OpCode=0x0f,
-		Finale=0x80;
-	//@}
-
-	//@{ Mask bits for second byte of header
-	const
-		Length=0x7f;
-	//@}
-
-	protected
-		$addr,
-		$ctx,
-		$wait,
-		$sockets,
-		$protocol,
-		$agents=[],
-		$events=[];
-
-	/**
-	*	Allocate stream socket
-	*	@return NULL
-	*	@param $socket resource
-	**/
-	function alloc($socket) {
-		if (is_bool($buf=$this->read($socket)))
-			return;
-		// Get WebSocket headers
-		$hdrs=[];
-		$EOL="\r\n";
-		$verb=NULL;
-		$uri=NULL;
-		foreach (explode($EOL,trim($buf)) as $line)
-			if (preg_match('/^(\w+)\s(.+)\sHTTP\/[\d.]{1,3}$/',
-				trim($line),$match)) {
-				$verb=$match[1];
-				$uri=$match[2];
-			}
-			else
-			if (preg_match('/^(.+): (.+)/',trim($line),$match))
-				// Standardize header
-				$hdrs[
-					strtr(
-						ucwords(
-							strtolower(
-								strtr($match[1],'-',' ')
-							)
-						),' ','-'
-					)
-				]=$match[2];
-			else {
-				$this->close($socket);
-				return;
-			}
-		if (empty($hdrs['Upgrade']) &&
-			empty($hdrs['Sec-Websocket-Key'])) {
-			// Not a WebSocket request
-			if ($verb && $uri)
-				$this->write(
-					$socket,
-					'HTTP/1.1 400 Bad Request'.$EOL.
-					'Connection: close'.$EOL.$EOL
-				);
-			$this->close($socket);
-			return;
-		}
-		// Handshake
-		$buf='HTTP/1.1 101 Switching Protocols'.$EOL.
-			'Upgrade: websocket'.$EOL.
-			'Connection: Upgrade'.$EOL;
-		if (isset($hdrs['Sec-Websocket-Protocol']))
-			$buf.='Sec-WebSocket-Protocol: '.
-				$hdrs['Sec-Websocket-Protocol'].$EOL;
-		$buf.='Sec-WebSocket-Accept: '.
-			base64_encode(
-				sha1($hdrs['Sec-Websocket-Key'].WS::Magic,TRUE)
-			).$EOL.$EOL;
-		if ($this->write($socket,$buf)) {
-			// Connect agent to server
-			$this->sockets[(int)$socket]=$socket;
-			$this->agents[(int)$socket]=
-				new Agent($this,$socket,$verb,$uri,$hdrs);
-		}
-	}
-
-	/**
-	*	Close stream socket
-	*	@return NULL
-	*	@param $socket resource
-	**/
-	function close($socket) {
-		if (isset($this->agents[(int)$socket]))
-			unset($this->sockets[(int)$socket],$this->agents[(int)$socket]);
-		stream_socket_shutdown($socket,STREAM_SHUT_WR);
-		@fclose($socket);
-	}
-
-	/**
-	*	Read from stream socket
-	*	@return string|FALSE
-	*	@param $socket resource
-	*	@param $len int
-	**/
-	function read($socket,$len=0) {
-		if (!$len)
-			$len=WS::Packet;
-		if (is_string($buf=@fread($socket,$len)) &&
-			strlen($buf) && strlen($buf)<$len)
-			return $buf;
-		if (isset($this->events['error']) &&
-			is_callable($func=$this->events['error']))
-			$func($this);
-		$this->close($socket);
-		return FALSE;
-	}
-
-	/**
-	*	Write to stream socket
-	*	@return int|FALSE
-	*	@param $socket resource
-	*	@param $buf string
-	**/
-	function write($socket,$buf) {
-		for ($i=0,$bytes=0;$i<strlen($buf);$i+=$bytes) {
-			if (($bytes=@fwrite($socket,substr($buf,$i))) &&
-				@fflush($socket))
-				continue;
-			if (isset($this->events['error']) &&
-				is_callable($func=$this->events['error']))
-				$func($this);
-			$this->close($socket);
-			return FALSE;
-		}
-		return $bytes;
-	}
-
-	/**
-	*	Return socket agents
-	*	@return array
-	*	@param $uri string
-	***/
-	function agents($uri=NULL) {
-		return array_filter(
-			$this->agents,
-			/**
-			 * @var $val Agent
-			 * @return bool
-			 */
-			function($val) use($uri) {
-				return $uri?($val->uri()==$uri):TRUE;
-			}
-		);
-	}
-
-	/**
-	*	Return event handlers
-	*	@return array
-	**/
-	function events() {
-		return $this->events;
-	}
-
-	/**
-	*	Bind function to event handler
-	*	@return object
-	*	@param $event string
-	*	@param $func callable
-	**/
-	function on($event,$func) {
-		$this->events[$event]=$func;
-		return $this;
-	}
-
-	/**
-	*	Terminate server
-	**/
-	function kill() {
-		die;
-	}
-
-	/**
-	*	Execute the server process
-	**/
-	function run() {
-		// Assign signal handlers
-		declare(ticks=1);
-		pcntl_signal(SIGINT,[$this,'kill']);
-		pcntl_signal(SIGTERM,[$this,'kill']);
-		gc_enable();
-		// Activate WebSocket listener
-		$listen=stream_socket_server(
-			$this->addr,$errno,$errstr,
-			STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,
-			$this->ctx
-		);
-		$socket=socket_import_stream($listen);
-		register_shutdown_function(function() use($listen) {
-			foreach ($this->sockets as $socket)
-				if ($socket!=$listen)
-					$this->close($socket);
-			$this->close($listen);
-			if (isset($this->events['stop']) &&
-				is_callable($func=$this->events['stop']))
-				$func($this);
-		});
-		if ($errstr)
-			user_error($errstr,E_USER_ERROR);
-		if (isset($this->events['start']) &&
-			is_callable($func=$this->events['start']))
-			$func($this);
-		$this->sockets=[(int)$listen=>$listen];
-		$empty=[];
-		$wait=$this->wait;
-		while (TRUE) {
-			$active=$this->sockets;
-			$mark=microtime(TRUE);
-			$count=@stream_select(
-				$active,$empty,$empty,(int)$wait,round(1e6*($wait-(int)$wait))
-			);
-			if (is_bool($count) && $wait) {
-				if (isset($this->events['error']) &&
-					is_callable($func=$this->events['error']))
-					$func($this);
-				die;
-			}
-			if ($count) {
-				// Process active connections
-				foreach ($active as $socket) {
-					if (!is_resource($socket))
-						continue;
-					if ($socket==$listen) {
-						if ($socket=@stream_socket_accept($listen,0))
-							$this->alloc($socket);
-						else
-						if (isset($this->events['error']) &&
-							is_callable($func=$this->events['error']))
-							$func($this);
-					}
-					else {
-						$id=(int)$socket;
-						if (isset($this->agents[$id]))
-							$this->agents[$id]->fetch();
-					}
-				}
-				$wait-=microtime(TRUE)-$mark;
-				while ($wait<1e-6) {
-					$wait+=$this->wait;
-					$count=0;
-				}
-			}
-			if (!$count) {
-				$mark=microtime(TRUE);
-				foreach ($this->sockets as $id=>$socket) {
-					if (!is_resource($socket))
-						continue;
-					if ($socket!=$listen &&
-						isset($this->agents[$id]) &&
-						isset($this->events['idle']) &&
-						is_callable($func=$this->events['idle']))
-						$func($this->agents[$id]);
-				}
-				$wait=$this->wait-microtime(TRUE)+$mark;
-			}
-			gc_collect_cycles();
-		}
-	}
-
-	/**
-	*	@param $addr string
-	*	@param $ctx resource
-	*	@param $wait int
-	**/
-	function __construct($addr,$ctx=NULL,$wait=60) {
-		$this->addr=$addr;
-		$this->ctx=$ctx?:stream_context_create();
-		$this->wait=$wait;
-		$this->events=[];
-	}
-
-}
-
-//! RFC6455 remote socket
-class Agent {
-
-	protected
-		$server,
-		$id,
-		$socket,
-		$flag,
-		$verb,
-		$uri,
-		$headers;
-
-	/**
-	*	Return server instance
-	*	@return WS
-	**/
-	function server() {
-		return $this->server;
-	}
-
-	/**
-	*	Return socket ID
-	*	@return string
-	**/
-	function id() {
-		return $this->id;
-	}
-
-	/**
-	*	Return socket
-	*	@return resource
-	**/
-	function socket() {
-		return $this->socket;
-	}
-
-	/**
-	*	Return request method
-	*	@return string
-	**/
-	function verb() {
-		return $this->verb;
-	}
-
-	/**
-	*	Return request URI
-	*	@return string
-	**/
-	function uri() {
-		return $this->uri;
-	}
-
-	/**
-	*	Return socket headers
-	*	@return array
-	**/
-	function headers() {
-		return $this->headers;
-	}
-
-	/**
-	*	Frame and transmit payload
-	*	@return string|FALSE
-	*	@param $op int
-	*	@param $data string
-	**/
-	function send($op,$data='') {
-		$server=$this->server;
-		$mask=WS::Finale | $op & WS::OpCode;
-		$len=strlen($data);
-		$buf='';
-		if ($len>0xffff)
-			$buf=pack('CCNN',$mask,0x7f,$len);
-		elseif ($len>0x7d)
-			$buf=pack('CCn',$mask,0x7e,$len);
-		else
-			$buf=pack('CC',$mask,$len);
-		$buf.=$data;
-		if (is_bool($server->write($this->socket,$buf)))
-			return FALSE;
-		if (!in_array($op,[WS::Pong,WS::Close]) &&
-			isset($this->server->events['send']) &&
-			is_callable($func=$this->server->events['send']))
-			$func($this,$op,$data);
-		return $data;
-	}
-
-	/**
-	*	Retrieve and unmask payload
-	*	@return bool|NULL
-	**/
-	function fetch() {
-		// Unmask payload
-		$server=$this->server;
-		if (is_bool($buf=$server->read($this->socket)))
-			return FALSE;
-		while($buf) {
-			$op=ord($buf[0]) & WS::OpCode;
-			$len=ord($buf[1]) & WS::Length;
-			$pos=2;
-			if ($len==0x7e) {
-				$len=ord($buf[2])*256+ord($buf[3]);
-				$pos+=2;
-			}
-			else
-			if ($len==0x7f) {
-				for ($i=0,$len=0;$i<8;$i++)
-					$len=$len*256+ord($buf[$i+2]);
-				$pos+=8;
-			}
-			for ($i=0,$mask=[];$i<4;$i++)
-				$mask[$i]=ord($buf[$pos+$i]);
-			$pos+=4;
-			if (strlen($buf)<$len+$pos)
-				return FALSE;
-			for ($i=0,$data='';$i<$len;$i++)
-				$data.=chr(ord($buf[$pos+$i])^$mask[$i%4]);
-			// Dispatch
-			switch ($op & WS::OpCode) {
-			case WS::Ping:
-				$this->send(WS::Pong);
-				break;
-			case WS::Close:
-				$server->close($this->socket);
-				break;
-			case WS::Text:
-				$data=trim($data);
-			case WS::Binary:
-				if (isset($this->server->events['receive']) &&
-					is_callable($func=$this->server->events['receive']))
-					$func($this,$op,$data);
-				break;
-			}
-			$buf = substr($buf, $len+$pos);
-		}
-	}
-
-	/**
-	*	Destroy object
-	**/
-	function __destruct() {
-		if (isset($this->server->events['disconnect']) &&
-			is_callable($func=$this->server->events['disconnect']))
-			$func($this);
-	}
-
-	/**
-	*	@param $server WS
-	*	@param $socket resource
-	*	@param $verb string
-	*	@param $uri string
-	*	@param $hdrs array
-	**/
-	function __construct($server,$socket,$verb,$uri,array $hdrs) {
-		$this->server=$server;
-		$this->id=stream_socket_get_name($socket,TRUE);
-		$this->socket=$socket;
-		$this->verb=$verb;
-		$this->uri=$uri;
-		$this->headers=$hdrs;
-
-		if (isset($server->events['connect']) &&
-			is_callable($func=$server->events['connect']))
-			$func($this);
-	}
-
-}

+ 0 - 1
lib/f3/code.css

@@ -1 +0,0 @@
-code{word-wrap:break-word;color:black}.comment,.doc_comment,.ml_comment{color:dimgray;font-style:italic}.variable{color:blueviolet}.const,.constant_encapsed_string,.class_c,.dir,.file,.func_c,.halt_compiler,.line,.method_c,.lnumber,.dnumber{color:crimson}.string,.and_equal,.boolean_and,.boolean_or,.concat_equal,.dec,.div_equal,.inc,.is_equal,.is_greater_or_equal,.is_identical,.is_not_equal,.is_not_identical,.is_smaller_or_equal,.logical_and,.logical_or,.logical_xor,.minus_equal,.mod_equal,.mul_equal,.ns_c,.ns_separator,.or_equal,.plus_equal,.sl,.sl_equal,.sr,.sr_equal,.xor_equal,.start_heredoc,.end_heredoc,.object_operator,.paamayim_nekudotayim{color:black}.abstract,.array,.array_cast,.as,.break,.case,.catch,.class,.clone,.continue,.declare,.default,.do,.echo,.else,.elseif,.empty.enddeclare,.endfor,.endforach,.endif,.endswitch,.endwhile,.eval,.exit,.extends,.final,.for,.foreach,.function,.global,.goto,.if,.implements,.include,.include_once,.instanceof,.interface,.isset,.list,.namespace,.new,.print,.private,.public,.protected,.require,.require_once,.return,.static,.switch,.throw,.try,.unset,.use,.var,.while{color:royalblue}.open_tag,.open_tag_with_echo,.close_tag{color:orange}.ini_section{color:black}.ini_key{color:royalblue}.ini_value{color:crimson}.xml_tag{color:dodgerblue}.xml_attr{color:blueviolet}.xml_data{color:red}.section{color:black}.directive{color:blue}.data{color:dimgray}

+ 0 - 388
lib/f3/db/cursor.php

@@ -1,388 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB;
-
-//! Simple cursor implementation
-abstract class Cursor extends \Magic implements \IteratorAggregate {
-
-	//@{ Error messages
-	const
-		E_Field='Undefined field %s';
-	//@}
-
-	protected
-		//! Query results
-		$query=[],
-		//! Current position
-		$ptr=0,
-		//! Event listeners
-		$trigger=[];
-
-	/**
-	*	Return database type
-	*	@return string
-	**/
-	abstract function dbtype();
-
-	/**
-	*	Return field names
-	*	@return array
-	**/
-	abstract function fields();
-
-	/**
-	*	Return fields of mapper object as an associative array
-	*	@return array
-	*	@param $obj object
-	**/
-	abstract function cast($obj=NULL);
-
-	/**
-	*	Return records (array of mapper objects) that match criteria
-	*	@return array
-	*	@param $filter string|array
-	*	@param $options array
-	*	@param $ttl int
-	**/
-	abstract function find($filter=NULL,array $options=NULL,$ttl=0);
-
-	/**
-	*	Count records that match criteria
-	*	@return int
-	*	@param $filter array
-	*	@param $options array
-	*	@param $ttl int
-	**/
-	abstract function count($filter=NULL,array $options=NULL,$ttl=0);
-
-	/**
-	*	Insert new record
-	*	@return array
-	**/
-	abstract function insert();
-
-	/**
-	*	Update current record
-	*	@return array
-	**/
-	abstract function update();
-
-	/**
-	*	Hydrate mapper object using hive array variable
-	*	@return NULL
-	*	@param $var array|string
-	*	@param $func callback
-	**/
-	abstract function copyfrom($var,$func=NULL);
-
-	/**
-	*	Populate hive array variable with mapper fields
-	*	@return NULL
-	*	@param $key string
-	**/
-	abstract function copyto($key);
-
-	/**
-	*	Get cursor's equivalent external iterator
-	*	Causes a fatal error in PHP 5.3.5 if uncommented
-	*	return ArrayIterator
-	**/
-	abstract function getiterator();
-
-
-	/**
-	*	Return TRUE if current cursor position is not mapped to any record
-	*	@return bool
-	**/
-	function dry() {
-		return empty($this->query[$this->ptr]);
-	}
-
-	/**
-	*	Return first record (mapper object) that matches criteria
-	*	@return static|FALSE
-	*	@param $filter string|array
-	*	@param $options array
-	*	@param $ttl int
-	**/
-	function findone($filter=NULL,array $options=NULL,$ttl=0) {
-		if (!$options)
-			$options=[];
-		// Override limit
-		$options['limit']=1;
-		return ($data=$this->find($filter,$options,$ttl))?$data[0]:FALSE;
-	}
-
-	/**
-	*	Return array containing subset of records matching criteria,
-	*	total number of records in superset, specified limit, number of
-	*	subsets available, and actual subset position
-	*	@return array
-	*	@param $pos int
-	*	@param $size int
-	*	@param $filter string|array
-	*	@param $options array
-	*	@param $ttl int
-	*	@param $bounce bool
-	**/
-	function paginate(
-		$pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0,$bounce=TRUE) {
-		$total=$this->count($filter,$options,$ttl);
-		$count=(int)ceil($total/$size);
-		if ($bounce)
-			$pos=max(0,min($pos,$count-1));
-		return [
-			'subset'=>($bounce || $pos<$count)?$this->find($filter,
-				array_merge(
-					$options?:[],
-					['limit'=>$size,'offset'=>$pos*$size]
-				),
-				$ttl
-			):[],
-			'total'=>$total,
-			'limit'=>$size,
-			'count'=>$count,
-			'pos'=>$bounce?($pos<$count?$pos:0):$pos
-		];
-	}
-
-	/**
-	*	Map to first record that matches criteria
-	*	@return array|FALSE
-	*	@param $filter string|array
-	*	@param $options array
-	*	@param $ttl int
-	**/
-	function load($filter=NULL,array $options=NULL,$ttl=0) {
-		$this->reset();
-		return ($this->query=$this->find($filter,$options,$ttl)) &&
-			$this->skip(0)?$this->query[$this->ptr]:FALSE;
-	}
-
-	/**
-	*	Return the count of records loaded
-	*	@return int
-	**/
-	function loaded() {
-		return count($this->query);
-	}
-
-	/**
-	*	Map to first record in cursor
-	*	@return mixed
-	**/
-	function first() {
-		return $this->skip(-$this->ptr);
-	}
-
-	/**
-	*	Map to last record in cursor
-	*	@return mixed
-	**/
-	function last() {
-		return $this->skip(($ofs=count($this->query)-$this->ptr)?$ofs-1:0);
-	}
-
-	/**
-	*	Map to nth record relative to current cursor position
-	*	@return mixed
-	*	@param $ofs int
-	**/
-	function skip($ofs=1) {
-		$this->ptr+=$ofs;
-		return $this->ptr>-1 && $this->ptr<count($this->query)?
-			$this->query[$this->ptr]:FALSE;
-	}
-
-	/**
-	*	Map next record
-	*	@return mixed
-	**/
-	function next() {
-		return $this->skip();
-	}
-
-	/**
-	*	Map previous record
-	*	@return mixed
-	**/
-	function prev() {
-		return $this->skip(-1);
-	}
-
-	/**
-	 * Return whether current iterator position is valid.
-	 */
-	function valid() {
-		return !$this->dry();
-	}
-
-	/**
-	*	Save mapped record
-	*	@return mixed
-	**/
-	function save() {
-		return $this->query?$this->update():$this->insert();
-	}
-
-	/**
-	*	Delete current record
-	*	@return int|bool
-	**/
-	function erase() {
-		$this->query=array_slice($this->query,0,$this->ptr,TRUE)+
-			array_slice($this->query,$this->ptr,NULL,TRUE);
-		$this->skip(0);
-	}
-
-	/**
-	*	Define onload trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function onload($func) {
-		return $this->trigger['load']=$func;
-	}
-
-	/**
-	*	Define beforeinsert trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function beforeinsert($func) {
-		return $this->trigger['beforeinsert']=$func;
-	}
-
-	/**
-	*	Define afterinsert trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function afterinsert($func) {
-		return $this->trigger['afterinsert']=$func;
-	}
-
-	/**
-	*	Define oninsert trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function oninsert($func) {
-		return $this->afterinsert($func);
-	}
-
-	/**
-	*	Define beforeupdate trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function beforeupdate($func) {
-		return $this->trigger['beforeupdate']=$func;
-	}
-
-	/**
-	*	Define afterupdate trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function afterupdate($func) {
-		return $this->trigger['afterupdate']=$func;
-	}
-
-	/**
-	*	Define onupdate trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function onupdate($func) {
-		return $this->afterupdate($func);
-	}
-
-	/**
-	*	Define beforesave trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function beforesave($func) {
-		$this->trigger['beforeinsert']=$func;
-		$this->trigger['beforeupdate']=$func;
-		return $func;
-	}
-
-	/**
-	*	Define aftersave trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function aftersave($func) {
-		$this->trigger['afterinsert']=$func;
-		$this->trigger['afterupdate']=$func;
-		return $func;
-	}
-
-	/**
-	*	Define onsave trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function onsave($func) {
-		return $this->aftersave($func);
-	}
-
-	/**
-	*	Define beforeerase trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function beforeerase($func) {
-		return $this->trigger['beforeerase']=$func;
-	}
-
-	/**
-	*	Define aftererase trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function aftererase($func) {
-		return $this->trigger['aftererase']=$func;
-	}
-
-	/**
-	*	Define onerase trigger
-	*	@return callback
-	*	@param $func callback
-	**/
-	function onerase($func) {
-		return $this->aftererase($func);
-	}
-
-	/**
-	*	Reset cursor
-	*	@return NULL
-	**/
-	function reset() {
-		$this->query=[];
-		$this->ptr=0;
-	}
-
-}

+ 0 - 175
lib/f3/db/jig.php

@@ -1,175 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB;
-
-//! In-memory/flat-file DB wrapper
-class Jig {
-
-	//@{ Storage formats
-	const
-		FORMAT_JSON=0,
-		FORMAT_Serialized=1;
-	//@}
-
-	protected
-		//! UUID
-		$uuid,
-		//! Storage location
-		$dir,
-		//! Current storage format
-		$format,
-		//! Jig log
-		$log,
-		//! Memory-held data
-		$data,
-		//! lazy load/save files
-		$lazy;
-
-	/**
-	*	Read data from memory/file
-	*	@return array
-	*	@param $file string
-	**/
-	function &read($file) {
-		if (!$this->dir || !is_file($dst=$this->dir.$file)) {
-			if (!isset($this->data[$file]))
-				$this->data[$file]=[];
-			return $this->data[$file];
-		}
-		if ($this->lazy && isset($this->data[$file]))
-			return $this->data[$file];
-		$fw=\Base::instance();
-		$raw=$fw->read($dst);
-		switch ($this->format) {
-			case self::FORMAT_JSON:
-				$data=json_decode($raw,TRUE);
-				break;
-			case self::FORMAT_Serialized:
-				$data=$fw->unserialize($raw);
-				break;
-		}
-		$this->data[$file] = $data;
-		return $this->data[$file];
-	}
-
-	/**
-	*	Write data to memory/file
-	*	@return int
-	*	@param $file string
-	*	@param $data array
-	**/
-	function write($file,array $data=NULL) {
-		if (!$this->dir || $this->lazy)
-			return count($this->data[$file]=$data);
-		$fw=\Base::instance();
-		switch ($this->format) {
-			case self::FORMAT_JSON:
-				$out=json_encode($data,JSON_PRETTY_PRINT);
-				break;
-			case self::FORMAT_Serialized:
-				$out=$fw->serialize($data);
-				break;
-		}
-		return $fw->write($this->dir.$file,$out);
-	}
-
-	/**
-	*	Return directory
-	*	@return string
-	**/
-	function dir() {
-		return $this->dir;
-	}
-
-	/**
-	*	Return UUID
-	*	@return string
-	**/
-	function uuid() {
-		return $this->uuid;
-	}
-
-	/**
-	*	Return profiler results (or disable logging)
-	*	@param $flag bool
-	*	@return string
-	**/
-	function log($flag=TRUE) {
-		if ($flag)
-			return $this->log;
-		$this->log=FALSE;
-	}
-
-	/**
-	*	Jot down log entry
-	*	@return NULL
-	*	@param $frame string
-	**/
-	function jot($frame) {
-		if ($frame)
-			$this->log.=date('r').' '.$frame.PHP_EOL;
-	}
-
-	/**
-	*	Clean storage
-	*	@return NULL
-	**/
-	function drop() {
-		if ($this->lazy) // intentional
-			$this->data=[];
-		if (!$this->dir)
-			$this->data=[];
-		elseif ($glob=@glob($this->dir.'/*',GLOB_NOSORT))
-			foreach ($glob as $file)
-				@unlink($file);
-	}
-
-	//! Prohibit cloning
-	private function __clone() {
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $dir string
-	*	@param $format int
-	**/
-	function __construct($dir=NULL,$format=self::FORMAT_JSON,$lazy=FALSE) {
-		if ($dir && !is_dir($dir))
-			mkdir($dir,\Base::MODE,TRUE);
-		$this->uuid=\Base::instance()->hash($this->dir=$dir);
-		$this->format=$format;
-		$this->lazy=$lazy;
-	}
-
-	/**
-	*	save file on destruction
-	**/
-	function __destruct() {
-		if ($this->lazy) {
-			$this->lazy = FALSE;
-			foreach ($this->data?:[] as $file => $data)
-				$this->write($file,$data);
-		}
-	}
-
-}

+ 0 - 541
lib/f3/db/jig/mapper.php

@@ -1,541 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB\Jig;
-
-//! Flat-file DB mapper
-class Mapper extends \DB\Cursor {
-
-	protected
-		//! Flat-file DB wrapper
-		$db,
-		//! Data file
-		$file,
-		//! Document identifier
-		$id,
-		//! Document contents
-		$document=[],
-		//! field map-reduce handlers
-		$_reduce;
-
-	/**
-	*	Return database type
-	*	@return string
-	**/
-	function dbtype() {
-		return 'Jig';
-	}
-
-	/**
-	*	Return TRUE if field is defined
-	*	@return bool
-	*	@param $key string
-	**/
-	function exists($key) {
-		return array_key_exists($key,$this->document);
-	}
-
-	/**
-	*	Assign value to field
-	*	@return scalar|FALSE
-	*	@param $key string
-	*	@param $val scalar
-	**/
-	function set($key,$val) {
-		return ($key=='_id')?FALSE:($this->document[$key]=$val);
-	}
-
-	/**
-	*	Retrieve value of field
-	*	@return scalar|FALSE
-	*	@param $key string
-	**/
-	function &get($key) {
-		if ($key=='_id')
-			return $this->id;
-		if (array_key_exists($key,$this->document))
-			return $this->document[$key];
-		user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
-	}
-
-	/**
-	*	Delete field
-	*	@return NULL
-	*	@param $key string
-	**/
-	function clear($key) {
-		if ($key!='_id')
-			unset($this->document[$key]);
-	}
-
-	/**
-	*	Convert array to mapper object
-	*	@return object
-	*	@param $id string
-	*	@param $row array
-	**/
-	function factory($id,$row) {
-		$mapper=clone($this);
-		$mapper->reset();
-		$mapper->id=$id;
-		foreach ($row as $field=>$val)
-			$mapper->document[$field]=$val;
-		$mapper->query=[clone($mapper)];
-		if (isset($mapper->trigger['load']))
-			\Base::instance()->call($mapper->trigger['load'],$mapper);
-		return $mapper;
-	}
-
-	/**
-	*	Return fields of mapper object as an associative array
-	*	@return array
-	*	@param $obj object
-	**/
-	function cast($obj=NULL) {
-		if (!$obj)
-			$obj=$this;
-		return $obj->document+['_id'=>$this->id];
-	}
-
-	/**
-	*	Convert tokens in string expression to variable names
-	*	@return string
-	*	@param $str string
-	**/
-	function token($str) {
-		$str=preg_replace_callback(
-			'/(?<!\w)@(\w(?:[\w\.\[\]])*)/',
-			function($token) {
-				// Convert from JS dot notation to PHP array notation
-				return '$'.preg_replace_callback(
-					'/(\.\w+)|\[((?:[^\[\]]*|(?R))*)\]/',
-					function($expr) {
-						$fw=\Base::instance();
-						return
-							'['.
-							($expr[1]?
-								$fw->stringify(substr($expr[1],1)):
-								(preg_match('/^\w+/',
-									$mix=$this->token($expr[2]))?
-									$fw->stringify($mix):
-									$mix)).
-							']';
-					},
-					$token[1]
-				);
-			},
-			$str
-		);
-		return trim($str);
-	}
-
-	/**
-	*	Return records that match criteria
-	*	@return static[]|FALSE
-	*	@param $filter array
-	*	@param $options array
-	*	@param $ttl int|array
-	*	@param $log bool
-	**/
-	function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) {
-		if (!$options)
-			$options=[];
-		$options+=[
-			'order'=>NULL,
-			'limit'=>0,
-			'offset'=>0,
-			'group'=>NULL,
-		];
-		$fw=\Base::instance();
-		$cache=\Cache::instance();
-		$db=$this->db;
-		$now=microtime(TRUE);
-		$data=[];
-		$tag='';
-		if (is_array($ttl))
-			list($ttl,$tag)=$ttl;
-		if (!$fw->CACHE || !$ttl || !($cached=$cache->exists(
-			$hash=$fw->hash($this->db->dir().
-				$fw->stringify([$filter,$options])).($tag?'.'.$tag:'').'.jig',$data)) ||
-			$cached[0]+$ttl<microtime(TRUE)) {
-			$data=$db->read($this->file);
-			if (is_null($data))
-				return FALSE;
-			foreach ($data as $id=>&$doc) {
-				$doc['_id']=$id;
-				unset($doc);
-			}
-			if ($filter) {
-				if (!is_array($filter))
-					return FALSE;
-				// Normalize equality operator
-				$expr=preg_replace('/(?<=[^<>!=])=(?!=)/','==',$filter[0]);
-				// Prepare query arguments
-				$args=isset($filter[1]) && is_array($filter[1])?
-					$filter[1]:
-					array_slice($filter,1,NULL,TRUE);
-				$args=is_array($args)?$args:[1=>$args];
-				$keys=$vals=[];
-				$tokens=array_slice(
-					token_get_all('<?php '.$this->token($expr)),1);
-				$data=array_filter($data,
-					function($_row) use($fw,$args,$tokens) {
-						$_expr='';
-						$ctr=0;
-						$named=FALSE;
-						foreach ($tokens as $token) {
-							if (is_string($token))
-								if ($token=='?') {
-									// Positional
-									$ctr++;
-									$key=$ctr;
-								}
-								else {
-									if ($token==':')
-										$named=TRUE;
-									else
-										$_expr.=$token;
-									continue;
-								}
-							elseif ($named &&
-								token_name($token[0])=='T_STRING') {
-								$key=':'.$token[1];
-								$named=FALSE;
-							}
-							else {
-								$_expr.=$token[1];
-								continue;
-							}
-							$_expr.=$fw->stringify(
-								is_string($args[$key])?
-									addcslashes($args[$key],'\''):
-									$args[$key]);
-						}
-						// Avoid conflict with user code
-						unset($fw,$tokens,$args,$ctr,$token,$key,$named);
-						extract($_row);
-						// Evaluate pseudo-SQL expression
-						return eval('return '.$_expr.';');
-					}
-				);
-			}
-			if (isset($options['group'])) {
-				$cols=array_reverse($fw->split($options['group']));
-				// sort into groups
-				$data=$this->sort($data,$options['group']);
-				foreach($data as $i=>&$row) {
-					if (!isset($prev)) {
-						$prev=$row;
-						$prev_i=$i;
-					}
-					$drop=false;
-					foreach ($cols as $col)
-						if ($prev_i!=$i && array_key_exists($col,$row) &&
-							array_key_exists($col,$prev) && $row[$col]==$prev[$col])
-							// reduce/modify
-							$drop=!isset($this->_reduce[$col]) || call_user_func_array(
-								$this->_reduce[$col][0],[&$prev,&$row])!==FALSE;
-						elseif (isset($this->_reduce[$col])) {
-							$null=null;
-							// initial
-							call_user_func_array($this->_reduce[$col][0],[&$row,&$null]);
-						}
-					if ($drop)
-						unset($data[$i]);
-					else {
-						$prev=&$row;
-						$prev_i=$i;
-					}
-					unset($row);
-				}
-				// finalize
-				if ($this->_reduce[$col][1])
-					foreach($data as $i=>&$row) {
-						$row=call_user_func($this->_reduce[$col][1],$row);
-						if (!$row)
-							unset($data[$i]);
-						unset($row);
-					}
-			}
-			if (isset($options['order']))
-				$data=$this->sort($data,$options['order']);
-			$data=array_slice($data,
-				$options['offset'],$options['limit']?:NULL,TRUE);
-			if ($fw->CACHE && $ttl)
-				// Save to cache backend
-				$cache->set($hash,$data,$ttl);
-		}
-		$out=[];
-		foreach ($data as $id=>&$doc) {
-			unset($doc['_id']);
-			$out[]=$this->factory($id,$doc);
-			unset($doc);
-		}
-		if ($log && isset($args)) {
-			if ($filter)
-				foreach ($args as $key=>$val) {
-					$vals[]=$fw->stringify(is_array($val)?$val[0]:$val);
-					$keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/';
-				}
-			$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
-				$this->file.' [find] '.
-				($filter?preg_replace($keys,$vals,$filter[0],1):''));
-		}
-		return $out;
-	}
-
-	/**
-	*	Sort a collection
-	*	@param $data
-	*	@param $cond
-	*	@return mixed
-	*/
-	protected function sort($data,$cond) {
-		$cols=\Base::instance()->split($cond);
-		uasort(
-			$data,
-			function($val1,$val2) use($cols) {
-				foreach ($cols as $col) {
-					$parts=explode(' ',$col,2);
-					$order=empty($parts[1])?
-						SORT_ASC:
-						constant($parts[1]);
-					$col=$parts[0];
-					if (!array_key_exists($col,$val1))
-						$val1[$col]=NULL;
-					if (!array_key_exists($col,$val2))
-						$val2[$col]=NULL;
-					list($v1,$v2)=[$val1[$col],$val2[$col]];
-					if ($out=strnatcmp($v1,$v2)*
-						(($order==SORT_ASC)*2-1))
-						return $out;
-				}
-				return 0;
-			}
-		);
-		return $data;
-	}
-
-	/**
-	*	Add reduce handler for grouped fields
-	*	@param $key string
-	*	@param $handler callback
-	*	@param $finalize callback
-	*/
-	function reduce($key,$handler,$finalize=null){
-		$this->_reduce[$key]=[$handler,$finalize];
-	}
-
-	/**
-	*	Count records that match criteria
-	*	@return int
-	*	@param $filter array
-	*	@param $options array
-	*	@param $ttl int|array
-	**/
-	function count($filter=NULL,array $options=NULL,$ttl=0) {
-		$now=microtime(TRUE);
-		$out=count($this->find($filter,$options,$ttl,FALSE));
-		$this->db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
-			$this->file.' [count] '.($filter?json_encode($filter):''));
-		return $out;
-	}
-
-	/**
-	*	Return record at specified offset using criteria of previous
-	*	load() call and make it active
-	*	@return array
-	*	@param $ofs int
-	**/
-	function skip($ofs=1) {
-		$this->document=($out=parent::skip($ofs))?$out->document:[];
-		$this->id=$out?$out->id:NULL;
-		if ($this->document && isset($this->trigger['load']))
-			\Base::instance()->call($this->trigger['load'],$this);
-		return $out;
-	}
-
-	/**
-	*	Insert new record
-	*	@return array
-	**/
-	function insert() {
-		if ($this->id)
-			return $this->update();
-		$db=$this->db;
-		$now=microtime(TRUE);
-		while (($id=uniqid(NULL,TRUE)) &&
-			($data=&$db->read($this->file)) && isset($data[$id]) &&
-			!connection_aborted())
-			usleep(mt_rand(0,100));
-		$this->id=$id;
-		$pkey=['_id'=>$this->id];
-		if (isset($this->trigger['beforeinsert']) &&
-			\Base::instance()->call($this->trigger['beforeinsert'],
-				[$this,$pkey])===FALSE)
-			return $this->document;
-		$data[$id]=$this->document;
-		$db->write($this->file,$data);
-		$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
-			$this->file.' [insert] '.json_encode($this->document));
-		if (isset($this->trigger['afterinsert']))
-			\Base::instance()->call($this->trigger['afterinsert'],
-				[$this,$pkey]);
-		$this->load(['@_id=?',$this->id]);
-		return $this->document;
-	}
-
-	/**
-	*	Update current record
-	*	@return array
-	**/
-	function update() {
-		$db=$this->db;
-		$now=microtime(TRUE);
-		$data=&$db->read($this->file);
-		if (isset($this->trigger['beforeupdate']) &&
-			\Base::instance()->call($this->trigger['beforeupdate'],
-				[$this,['_id'=>$this->id]])===FALSE)
-			return $this->document;
-		$data[$this->id]=$this->document;
-		$db->write($this->file,$data);
-		$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
-			$this->file.' [update] '.json_encode($this->document));
-		if (isset($this->trigger['afterupdate']))
-			\Base::instance()->call($this->trigger['afterupdate'],
-				[$this,['_id'=>$this->id]]);
-		return $this->document;
-	}
-
-	/**
-	*	Delete current record
-	*	@return bool
-	*	@param $filter array
-	*	@param $quick bool
-	**/
-	function erase($filter=NULL,$quick=FALSE) {
-		$db=$this->db;
-		$now=microtime(TRUE);
-		$data=&$db->read($this->file);
-		$pkey=['_id'=>$this->id];
-		if ($filter) {
-			foreach ($this->find($filter,NULL,FALSE) as $mapper)
-				if (!$mapper->erase(null,$quick))
-					return FALSE;
-			return TRUE;
-		}
-		elseif (isset($this->id)) {
-			unset($data[$this->id]);
-			parent::erase();
-		}
-		else
-			return FALSE;
-		if (!$quick && isset($this->trigger['beforeerase']) &&
-			\Base::instance()->call($this->trigger['beforeerase'],
-				[$this,$pkey])===FALSE)
-			return FALSE;
-		$db->write($this->file,$data);
-		if ($filter) {
-			$args=isset($filter[1]) && is_array($filter[1])?
-				$filter[1]:
-				array_slice($filter,1,NULL,TRUE);
-			$args=is_array($args)?$args:[1=>$args];
-			foreach ($args as $key=>$val) {
-				$vals[]=\Base::instance()->
-					stringify(is_array($val)?$val[0]:$val);
-				$keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/';
-			}
-		}
-		$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
-			$this->file.' [erase] '.
-			($filter?preg_replace($keys,$vals,$filter[0],1):''));
-		if (!$quick && isset($this->trigger['aftererase']))
-			\Base::instance()->call($this->trigger['aftererase'],
-				[$this,$pkey]);
-		return TRUE;
-	}
-
-	/**
-	*	Reset cursor
-	*	@return NULL
-	**/
-	function reset() {
-		$this->id=NULL;
-		$this->document=[];
-		parent::reset();
-	}
-
-	/**
-	*	Hydrate mapper object using hive array variable
-	*	@return NULL
-	*	@param $var array|string
-	*	@param $func callback
-	**/
-	function copyfrom($var,$func=NULL) {
-		if (is_string($var))
-			$var=\Base::instance()->$var;
-		if ($func)
-			$var=call_user_func($func,$var);
-		foreach ($var as $key=>$val)
-			$this->set($key,$val);
-	}
-
-	/**
-	*	Populate hive array variable with mapper fields
-	*	@return NULL
-	*	@param $key string
-	**/
-	function copyto($key) {
-		$var=&\Base::instance()->ref($key);
-		foreach ($this->document as $key=>$field)
-			$var[$key]=$field;
-	}
-
-	/**
-	*	Return field names
-	*	@return array
-	**/
-	function fields() {
-		return array_keys($this->document);
-	}
-
-	/**
-	*	Retrieve external iterator for fields
-	*	@return object
-	**/
-	function getiterator() {
-		return new \ArrayIterator($this->cast());
-	}
-
-	/**
-	*	Instantiate class
-	*	@return void
-	*	@param $db object
-	*	@param $file string
-	**/
-	function __construct(\DB\Jig $db,$file) {
-		$this->db=$db;
-		$this->file=$file;
-		$this->reset();
-	}
-
-}

+ 0 - 194
lib/f3/db/jig/session.php

@@ -1,194 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB\Jig;
-
-//! Jig-managed session handler
-class Session extends Mapper {
-
-	protected
-		//! Session ID
-		$sid,
-		//! Anti-CSRF token
-		$_csrf,
-		//! User agent
-		$_agent,
-		//! IP,
-		$_ip,
-		//! Suspect callback
-		$onsuspect;
-
-	/**
-	*	Open session
-	*	@return TRUE
-	*	@param $path string
-	*	@param $name string
-	**/
-	function open($path,$name) {
-		return TRUE;
-	}
-
-	/**
-	*	Close session
-	*	@return TRUE
-	**/
-	function close() {
-		$this->reset();
-		$this->sid=NULL;
-		return TRUE;
-	}
-
-	/**
-	*	Return session data in serialized format
-	*	@return string
-	*	@param $id string
-	**/
-	function read($id) {
-		$this->load(['@session_id=?',$this->sid=$id]);
-		if ($this->dry())
-			return '';
-		if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
-			$fw=\Base::instance();
-			if (!isset($this->onsuspect) ||
-				$fw->call($this->onsuspect,[$this,$id])===FALSE) {
-				// NB: `session_destroy` can't be called at that stage;
-				// `session_start` not completed
-				$this->destroy($id);
-				$this->close();
-				unset($fw->{'COOKIE.'.session_name()});
-				$fw->error(403);
-			}
-		}
-		return $this->get('data');
-	}
-
-	/**
-	*	Write session data
-	*	@return TRUE
-	*	@param $id string
-	*	@param $data string
-	**/
-	function write($id,$data) {
-		$this->set('session_id',$id);
-		$this->set('data',$data);
-		$this->set('ip',$this->_ip);
-		$this->set('agent',$this->_agent);
-		$this->set('stamp',time());
-		$this->save();
-		return TRUE;
-	}
-
-	/**
-	*	Destroy session
-	*	@return TRUE
-	*	@param $id string
-	**/
-	function destroy($id) {
-		$this->erase(['@session_id=?',$id]);
-		return TRUE;
-	}
-
-	/**
-	*	Garbage collector
-	*	@return TRUE
-	*	@param $max int
-	**/
-	function cleanup($max) {
-		$this->erase(['@stamp+?<?',$max,time()]);
-		return TRUE;
-	}
-
-	/**
-	 *	Return session id (if session has started)
-	 *	@return string|NULL
-	 **/
-	function sid() {
-		return $this->sid;
-	}
-
-	/**
-	 *	Return anti-CSRF token
-	 *	@return string
-	 **/
-	function csrf() {
-		return $this->_csrf;
-	}
-
-	/**
-	 *	Return IP address
-	 *	@return string
-	 **/
-	function ip() {
-		return $this->_ip;
-	}
-
-	/**
-	*	Return Unix timestamp
-	*	@return string|FALSE
-	**/
-	function stamp() {
-		if (!$this->sid)
-			session_start();
-		return $this->dry()?FALSE:$this->get('stamp');
-	}
-
-	/**
-	*	Return HTTP user agent
-	*	@return string|FALSE
-	**/
-	function agent() {
-		return $this->_agent;
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $db \DB\Jig
-	*	@param $file string
-	*	@param $onsuspect callback
-	*	@param $key string
-	**/
-	function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL,$key=NULL) {
-		parent::__construct($db,$file);
-		$this->onsuspect=$onsuspect;
-		session_set_save_handler(
-			[$this,'open'],
-			[$this,'close'],
-			[$this,'read'],
-			[$this,'write'],
-			[$this,'destroy'],
-			[$this,'cleanup']
-		);
-		register_shutdown_function('session_commit');
-		$fw=\Base::instance();
-		$headers=$fw->HEADERS;
-		$this->_csrf=$fw->hash($fw->SEED.
-			extension_loaded('openssl')?
-				implode(unpack('L',openssl_random_pseudo_bytes(4))):
-				mt_rand()
-			);
-		if ($key)
-			$fw->$key=$this->_csrf;
-		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		$this->_ip=$fw->IP;
-	}
-
-}

+ 0 - 145
lib/f3/db/mongo.php

@@ -1,145 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB;
-
-//! MongoDB wrapper
-class Mongo {
-
-	//@{
-	const
-		E_Profiler='MongoDB profiler is disabled';
-	//@}
-
-	protected
-		//! UUID
-		$uuid,
-		//! Data source name
-		$dsn,
-		//! MongoDB object
-		$db,
-		//! Legacy flag
-		$legacy,
-		//! MongoDB log
-		$log;
-
-	/**
-	*	Return data source name
-	*	@return string
-	**/
-	function dsn() {
-		return $this->dsn;
-	}
-
-	/**
-	*	Return UUID
-	*	@return string
-	**/
-	function uuid() {
-		return $this->uuid;
-	}
-
-	/**
-	*	Return MongoDB profiler results (or disable logging)
-	*	@param $flag bool
-	*	@return string
-	**/
-	function log($flag=TRUE) {
-		if ($flag) {
-			$cursor=$this->db->selectcollection('system.profile')->find();
-			foreach (iterator_to_array($cursor) as $frame)
-				if (!preg_match('/\.system\..+$/',$frame['ns']))
-					$this->log.=date('r',$this->legacy() ?
-						$frame['ts']->sec : (round((string)$frame['ts'])/1000)).
-						' ('.sprintf('%.1f',$frame['millis']).'ms) '.
-						$frame['ns'].' ['.$frame['op'].'] '.
-						(empty($frame['query'])?
-							'':json_encode($frame['query'])).
-						(empty($frame['command'])?
-							'':json_encode($frame['command'])).
-						PHP_EOL;
-		} else {
-			$this->log=FALSE;
-			if ($this->legacy)
-				$this->db->setprofilinglevel(-1);
-			else
-				$this->db->command(['profile'=>-1]);
-		}
-		return $this->log;
-	}
-
-	/**
-	*	Intercept native call to re-enable profiler
-	*	@return int
-	**/
-	function drop() {
-		$out=$this->db->drop();
-		if ($this->log!==FALSE) {
-			if ($this->legacy)
-				$this->db->setprofilinglevel(2);
-			else
-				$this->db->command(['profile'=>2]);
-		}
-		return $out;
-	}
-
-	/**
-	*	Redirect call to MongoDB object
-	*	@return mixed
-	*	@param $func string
-	*	@param $args array
-	**/
-	function __call($func,array $args) {
-		return call_user_func_array([$this->db,$func],$args);
-	}
-
-	/**
-	*	Return TRUE if legacy driver is loaded
-	*	@return bool
-	**/
-	function legacy() {
-		return $this->legacy;
-	}
-
-	//! Prohibit cloning
-	private function __clone() {
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $dsn string
-	*	@param $dbname string
-	*	@param $options array
-	**/
-	function __construct($dsn,$dbname,array $options=NULL) {
-		$this->uuid=\Base::instance()->hash($this->dsn=$dsn);
-		if ($this->legacy=class_exists('\MongoClient')) {
-			$this->db=new \MongoDB(new \MongoClient($dsn,$options?:[]),$dbname);
-			$this->db->setprofilinglevel(2);
-		}
-		else {
-			$this->db=(new \MongoDB\Client($dsn,$options?:[]))->$dbname;
-			$this->db->command(['profile'=>2]);
-		}
-	}
-
-}

+ 0 - 405
lib/f3/db/mongo/mapper.php

@@ -1,405 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB\Mongo;
-
-//! MongoDB mapper
-class Mapper extends \DB\Cursor {
-
-	protected
-		//! MongoDB wrapper
-		$db,
-		//! Legacy flag
-		$legacy,
-		//! Mongo collection
-		$collection,
-		//! Mongo document
-		$document=[],
-		//! Mongo cursor
-		$cursor,
-		//! Defined fields
-		$fields;
-
-	/**
-	*	Return database type
-	*	@return string
-	**/
-	function dbtype() {
-		return 'Mongo';
-	}
-
-	/**
-	*	Return TRUE if field is defined
-	*	@return bool
-	*	@param $key string
-	**/
-	function exists($key) {
-		return array_key_exists($key,$this->document);
-	}
-
-	/**
-	*	Assign value to field
-	*	@return scalar|FALSE
-	*	@param $key string
-	*	@param $val scalar
-	**/
-	function set($key,$val) {
-		return $this->document[$key]=$val;
-	}
-
-	/**
-	*	Retrieve value of field
-	*	@return scalar|FALSE
-	*	@param $key string
-	**/
-	function &get($key) {
-		if ($this->exists($key))
-			return $this->document[$key];
-		user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
-	}
-
-	/**
-	*	Delete field
-	*	@return NULL
-	*	@param $key string
-	**/
-	function clear($key) {
-		unset($this->document[$key]);
-	}
-
-	/**
-	*	Convert array to mapper object
-	*	@return static
-	*	@param $row array
-	**/
-	function factory($row) {
-		$mapper=clone($this);
-		$mapper->reset();
-		foreach ($row as $key=>$val)
-			$mapper->document[$key]=$val;
-		$mapper->query=[clone($mapper)];
-		if (isset($mapper->trigger['load']))
-			\Base::instance()->call($mapper->trigger['load'],$mapper);
-		return $mapper;
-	}
-
-	/**
-	*	Return fields of mapper object as an associative array
-	*	@return array
-	*	@param $obj object
-	**/
-	function cast($obj=NULL) {
-		if (!$obj)
-			$obj=$this;
-		return $obj->document;
-	}
-
-	/**
-	*	Build query and execute
-	*	@return static[]
-	*	@param $fields string
-	*	@param $filter array
-	*	@param $options array
-	*	@param $ttl int|array
-	**/
-	function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
-		if (!$options)
-			$options=[];
-		$options+=[
-			'group'=>NULL,
-			'order'=>NULL,
-			'limit'=>0,
-			'offset'=>0
-		];
-		$tag='';
-		if (is_array($ttl))
-			list($ttl,$tag)=$ttl;
-		$fw=\Base::instance();
-		$cache=\Cache::instance();
-		if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn().
-			$fw->stringify([$fields,$filter,$options])).($tag?'.'.$tag:'').'.mongo',
-			$result)) || !$ttl || $cached[0]+$ttl<microtime(TRUE)) {
-			if ($options['group']) {
-				$grp=$this->collection->group(
-					$options['group']['keys'],
-					$options['group']['initial'],
-					$options['group']['reduce'],
-					[
-						'condition'=>$filter,
-						'finalize'=>$options['group']['finalize']
-					]
-				);
-				$tmp=$this->db->selectcollection(
-					$fw->HOST.'.'.$fw->BASE.'.'.
-					uniqid(NULL,TRUE).'.tmp'
-				);
-				$tmp->batchinsert($grp['retval'],['w'=>1]);
-				$filter=[];
-				$collection=$tmp;
-			}
-			else {
-				$filter=$filter?:[];
-				$collection=$this->collection;
-			}
-			if ($this->legacy) {
-				$this->cursor=$collection->find($filter,$fields?:[]);
-				if ($options['order'])
-					$this->cursor=$this->cursor->sort($options['order']);
-				if ($options['limit'])
-					$this->cursor=$this->cursor->limit($options['limit']);
-				if ($options['offset'])
-					$this->cursor=$this->cursor->skip($options['offset']);
-				$result=[];
-				while ($this->cursor->hasnext())
-					$result[]=$this->cursor->getnext();
-			}
-			else {
-				$this->cursor=$collection->find($filter,[
-					'sort'=>$options['order'],
-					'limit'=>$options['limit'],
-					'skip'=>$options['offset']
-				]);
-				$result=$this->cursor->toarray();
-			}
-			if ($options['group'])
-				$tmp->drop();
-			if ($fw->CACHE && $ttl)
-				// Save to cache backend
-				$cache->set($hash,$result,$ttl);
-		}
-		$out=[];
-		foreach ($result as $doc)
-			$out[]=$this->factory($doc);
-		return $out;
-	}
-
-	/**
-	*	Return records that match criteria
-	*	@return static[]
-	*	@param $filter array
-	*	@param $options array
-	*	@param $ttl int|array
-	**/
-	function find($filter=NULL,array $options=NULL,$ttl=0) {
-		if (!$options)
-			$options=[];
-		$options+=[
-			'group'=>NULL,
-			'order'=>NULL,
-			'limit'=>0,
-			'offset'=>0
-		];
-		return $this->select($this->fields,$filter,$options,$ttl);
-	}
-
-	/**
-	*	Count records that match criteria
-	*	@return int
-	*	@param $filter array
-	*	@param $options array
-	*	@param $ttl int|array
-	**/
-	function count($filter=NULL,array $options=NULL,$ttl=0) {
-		$fw=\Base::instance();
-		$cache=\Cache::instance();
-		$tag='';
-		if (is_array($ttl))
-			list($ttl,$tag)=$ttl;
-		if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
-			[$filter])).($tag?'.'.$tag:'').'.mongo',$result)) || !$ttl ||
-			$cached[0]+$ttl<microtime(TRUE)) {
-			$result=$this->collection->count($filter?:[]);
-			if ($fw->CACHE && $ttl)
-				// Save to cache backend
-				$cache->set($hash,$result,$ttl);
-		}
-		return $result;
-	}
-
-	/**
-	*	Return record at specified offset using criteria of previous
-	*	load() call and make it active
-	*	@return array
-	*	@param $ofs int
-	**/
-	function skip($ofs=1) {
-		$this->document=($out=parent::skip($ofs))?$out->document:[];
-		if ($this->document && isset($this->trigger['load']))
-			\Base::instance()->call($this->trigger['load'],$this);
-		return $out;
-	}
-
-	/**
-	*	Insert new record
-	*	@return array
-	**/
-	function insert() {
-		if (isset($this->document['_id']))
-			return $this->update();
-		if (isset($this->trigger['beforeinsert']) &&
-			\Base::instance()->call($this->trigger['beforeinsert'],
-				[$this,['_id'=>$this->document['_id']]])===FALSE)
-			return $this->document;
-		if ($this->legacy) {
-			$this->collection->insert($this->document);
-			$pkey=['_id'=>$this->document['_id']];
-		}
-		else {
-			$result=$this->collection->insertone($this->document);
-			$pkey=['_id'=>$result->getinsertedid()];
-		}
-		if (isset($this->trigger['afterinsert']))
-			\Base::instance()->call($this->trigger['afterinsert'],
-				[$this,$pkey]);
-		$this->load($pkey);
-		return $this->document;
-	}
-
-	/**
-	*	Update current record
-	*	@return array
-	**/
-	function update() {
-		$pkey=['_id'=>$this->document['_id']];
-		if (isset($this->trigger['beforeupdate']) &&
-			\Base::instance()->call($this->trigger['beforeupdate'],
-				[$this,$pkey])===FALSE)
-			return $this->document;
-		$upsert=['upsert'=>TRUE];
-		if ($this->legacy)
-			$this->collection->update($pkey,$this->document,$upsert);
-		else
-			$this->collection->replaceone($pkey,$this->document,$upsert);
-		if (isset($this->trigger['afterupdate']))
-			\Base::instance()->call($this->trigger['afterupdate'],
-				[$this,$pkey]);
-		return $this->document;
-	}
-
-	/**
-	*	Delete current record
-	*	@return bool
-	*	@param $quick bool
-	*	@param $filter array
-	**/
-	function erase($filter=NULL,$quick=TRUE) {
-		if ($filter) {
-			if (!$quick) {
-				foreach ($this->find($filter) as $mapper)
-					if (!$mapper->erase())
-						return FALSE;
-				return TRUE;
-			}
-			return $this->legacy?
-				$this->collection->remove($filter):
-				$this->collection->deletemany($filter);
-		}
-		$pkey=['_id'=>$this->document['_id']];
-		if (isset($this->trigger['beforeerase']) &&
-			\Base::instance()->call($this->trigger['beforeerase'],
-				[$this,$pkey])===FALSE)
-			return FALSE;
-		$result=$this->legacy?
-			$this->collection->remove(['_id'=>$this->document['_id']]):
-			$this->collection->deleteone(['_id'=>$this->document['_id']]);
-		parent::erase();
-		if (isset($this->trigger['aftererase']))
-			\Base::instance()->call($this->trigger['aftererase'],
-				[$this,$pkey]);
-		return $result;
-	}
-
-	/**
-	*	Reset cursor
-	*	@return NULL
-	**/
-	function reset() {
-		$this->document=[];
-		parent::reset();
-	}
-
-	/**
-	*	Hydrate mapper object using hive array variable
-	*	@return NULL
-	*	@param $var array|string
-	*	@param $func callback
-	**/
-	function copyfrom($var,$func=NULL) {
-		if (is_string($var))
-			$var=\Base::instance()->$var;
-		if ($func)
-			$var=call_user_func($func,$var);
-		foreach ($var as $key=>$val)
-			$this->set($key,$val);
-	}
-
-	/**
-	*	Populate hive array variable with mapper fields
-	*	@return NULL
-	*	@param $key string
-	**/
-	function copyto($key) {
-		$var=&\Base::instance()->ref($key);
-		foreach ($this->document as $key=>$field)
-			$var[$key]=$field;
-	}
-
-	/**
-	*	Return field names
-	*	@return array
-	**/
-	function fields() {
-		return array_keys($this->document);
-	}
-
-	/**
-	*	Return the cursor from last query
-	*	@return object|NULL
-	**/
-	function cursor() {
-		return $this->cursor;
-	}
-
-	/**
-	*	Retrieve external iterator for fields
-	*	@return object
-	**/
-	function getiterator() {
-		return new \ArrayIterator($this->cast());
-	}
-
-	/**
-	*	Instantiate class
-	*	@return void
-	*	@param $db object
-	*	@param $collection string
-	*	@param $fields array
-	**/
-	function __construct(\DB\Mongo $db,$collection,$fields=NULL) {
-		$this->db=$db;
-		$this->legacy=$db->legacy();
-		$this->collection=$db->selectcollection($collection);
-		$this->fields=$fields;
-		$this->reset();
-	}
-
-}

+ 0 - 194
lib/f3/db/mongo/session.php

@@ -1,194 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB\Mongo;
-
-//! MongoDB-managed session handler
-class Session extends Mapper {
-
-	protected
-		//! Session ID
-		$sid,
-		//! Anti-CSRF token
-		$_csrf,
-		//! User agent
-		$_agent,
-		//! IP,
-		$_ip,
-		//! Suspect callback
-		$onsuspect;
-
-	/**
-	*	Open session
-	*	@return TRUE
-	*	@param $path string
-	*	@param $name string
-	**/
-	function open($path,$name) {
-		return TRUE;
-	}
-
-	/**
-	*	Close session
-	*	@return TRUE
-	**/
-	function close() {
-		$this->reset();
-		$this->sid=NULL;
-		return TRUE;
-	}
-
-	/**
-	*	Return session data in serialized format
-	*	@return string
-	*	@param $id string
-	**/
-	function read($id) {
-		$this->load(['session_id'=>$this->sid=$id]);
-		if ($this->dry())
-			return '';
-		if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
-			$fw=\Base::instance();
-			if (!isset($this->onsuspect) ||
-				$fw->call($this->onsuspect,[$this,$id])===FALSE) {
-				// NB: `session_destroy` can't be called at that stage;
-				// `session_start` not completed
-				$this->destroy($id);
-				$this->close();
-				unset($fw->{'COOKIE.'.session_name()});
-				$fw->error(403);
-			}
-		}
-		return $this->get('data');
-	}
-
-	/**
-	*	Write session data
-	*	@return TRUE
-	*	@param $id string
-	*	@param $data string
-	**/
-	function write($id,$data) {
-		$this->set('session_id',$id);
-		$this->set('data',$data);
-		$this->set('ip',$this->_ip);
-		$this->set('agent',$this->_agent);
-		$this->set('stamp',time());
-		$this->save();
-		return TRUE;
-	}
-
-	/**
-	*	Destroy session
-	*	@return TRUE
-	*	@param $id string
-	**/
-	function destroy($id) {
-		$this->erase(['session_id'=>$id]);
-		return TRUE;
-	}
-
-	/**
-	*	Garbage collector
-	*	@return TRUE
-	*	@param $max int
-	**/
-	function cleanup($max) {
-		$this->erase(['$where'=>'this.stamp+'.$max.'<'.time()]);
-		return TRUE;
-	}
-
-	/**
-	 *	Return session id (if session has started)
-	 *	@return string|NULL
-	 **/
-	function sid() {
-		return $this->sid;
-	}
-
-	/**
-	 *	Return anti-CSRF token
-	 *	@return string
-	 **/
-	function csrf() {
-		return $this->_csrf;
-	}
-
-	/**
-	 *	Return IP address
-	 *	@return string
-	 **/
-	function ip() {
-		return $this->_ip;
-	}
-
-	/**
-	 *	Return Unix timestamp
-	 *	@return string|FALSE
-	 **/
-	function stamp() {
-		if (!$this->sid)
-			session_start();
-		return $this->dry()?FALSE:$this->get('stamp');
-	}
-
-	/**
-	 *	Return HTTP user agent
-	 *	@return string
-	 **/
-	function agent() {
-		return $this->_agent;
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $db \DB\Mongo
-	*	@param $table string
-	*	@param $onsuspect callback
-	*	@param $key string
-	**/
-	function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL,$key=NULL) {
-		parent::__construct($db,$table);
-		$this->onsuspect=$onsuspect;
-		session_set_save_handler(
-			[$this,'open'],
-			[$this,'close'],
-			[$this,'read'],
-			[$this,'write'],
-			[$this,'destroy'],
-			[$this,'cleanup']
-		);
-		register_shutdown_function('session_commit');
-		$fw=\Base::instance();
-		$headers=$fw->HEADERS;
-		$this->_csrf=$fw->hash($fw->SEED.
-			extension_loaded('openssl')?
-				implode(unpack('L',openssl_random_pseudo_bytes(4))):
-				mt_rand()
-			);
-		if ($key)
-			$fw->$key=$this->_csrf;
-		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		$this->_ip=$fw->IP;
-	}
-
-}

+ 0 - 523
lib/f3/db/sql.php

@@ -1,523 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB;
-
-//! PDO wrapper
-class SQL {
-
-	//@{ Error messages
-	const
-		E_PKey='Table %s does not have a primary key';
-	//@}
-
-	const
-		PARAM_FLOAT='float';
-
-	protected
-		//! UUID
-		$uuid,
-		//! Raw PDO
-		$pdo,
-		//! Data source name
-		$dsn,
-		//! Database engine
-		$engine,
-		//! Database name
-		$dbname,
-		//! Transaction flag
-		$trans=FALSE,
-		//! Number of rows affected by query
-		$rows=0,
-		//! SQL log
-		$log;
-
-	/**
-	*	Begin SQL transaction
-	*	@return bool
-	**/
-	function begin() {
-		$out=$this->pdo->begintransaction();
-		$this->trans=TRUE;
-		return $out;
-	}
-
-	/**
-	*	Rollback SQL transaction
-	*	@return bool
-	**/
-	function rollback() {
-		$out=$this->pdo->rollback();
-		$this->trans=FALSE;
-		return $out;
-	}
-
-	/**
-	*	Commit SQL transaction
-	*	@return bool
-	**/
-	function commit() {
-		$out=$this->pdo->commit();
-		$this->trans=FALSE;
-		return $out;
-	}
-
-	/**
-	*	Return transaction flag
-	*	@return bool
-	**/
-	function trans() {
-		return $this->trans;
-	}
-
-	/**
-	*	Map data type of argument to a PDO constant
-	*	@return int
-	*	@param $val scalar
-	**/
-	function type($val) {
-		switch (gettype($val)) {
-			case 'NULL':
-				return \PDO::PARAM_NULL;
-			case 'boolean':
-				return \PDO::PARAM_BOOL;
-			case 'integer':
-				return \PDO::PARAM_INT;
-			case 'resource':
-				return \PDO::PARAM_LOB;
-			case 'float':
-				return self::PARAM_FLOAT;
-			default:
-				return \PDO::PARAM_STR;
-		}
-	}
-
-	/**
-	*	Cast value to PHP type
-	*	@return mixed
-	*	@param $type string
-	*	@param $val mixed
-	**/
-	function value($type,$val) {
-		switch ($type) {
-			case self::PARAM_FLOAT:
-				if (!is_string($val))
-					$val=str_replace(',','.',$val);
-				return $val;
-			case \PDO::PARAM_NULL:
-				return NULL;
-			case \PDO::PARAM_INT:
-				return (int)$val;
-			case \PDO::PARAM_BOOL:
-				return (bool)$val;
-			case \PDO::PARAM_STR:
-				return (string)$val;
-			case \PDO::PARAM_LOB:
-				return (binary)$val;
-		}
-	}
-
-	/**
-	*	Execute SQL statement(s)
-	*	@return array|int|FALSE
-	*	@param $cmds string|array
-	*	@param $args string|array
-	*	@param $ttl int|array
-	*	@param $log bool
-	*	@param $stamp bool
-	**/
-	function exec($cmds,$args=NULL,$ttl=0,$log=TRUE,$stamp=FALSE) {
-		$tag='';
-		if (is_array($ttl))
-			list($ttl,$tag)=$ttl;
-		$auto=FALSE;
-		if (is_null($args))
-			$args=[];
-		elseif (is_scalar($args))
-			$args=[1=>$args];
-		if (is_array($cmds)) {
-			if (count($args)<($count=count($cmds)))
-				// Apply arguments to SQL commands
-				$args=array_fill(0,$count,$args);
-			if (!$this->trans) {
-				$this->begin();
-				$auto=TRUE;
-			}
-		}
-		else {
-			$count=1;
-			$cmds=[$cmds];
-			$args=[$args];
-		}
-		if ($this->log===FALSE)
-			$log=FALSE;
-		$fw=\Base::instance();
-		$cache=\Cache::instance();
-		$result=FALSE;
-		for ($i=0;$i<$count;$i++) {
-			$cmd=$cmds[$i];
-			$arg=$args[$i];
-			// ensure 1-based arguments
-			if (array_key_exists(0,$arg)) {
-				array_unshift($arg,'');
-				unset($arg[0]);
-			}
-			if (!preg_replace('/(^\s+|[\s;]+$)/','',$cmd))
-				continue;
-			$now=microtime(TRUE);
-			$keys=$vals=[];
-			if ($fw->CACHE && $ttl && ($cached=$cache->exists(
-				$hash=$fw->hash($this->dsn.$cmd.
-				$fw->stringify($arg)).($tag?'.'.$tag:'').'.sql',$result)) &&
-				$cached[0]+$ttl>microtime(TRUE)) {
-				foreach ($arg as $key=>$val) {
-					$vals[]=$fw->stringify(is_array($val)?$val[0]:$val);
-					$keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key).
-						'/';
-				}
-				if ($log)
-					$this->log.=($stamp?(date('r').' '):'').'('.
-						sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
-						'[CACHED] '.
-						preg_replace($keys,$vals,
-							str_replace('?',chr(0).'?',$cmd),1).PHP_EOL;
-			}
-			elseif (is_object($query=$this->pdo->prepare($cmd))) {
-				foreach ($arg as $key=>$val) {
-					if (is_array($val)) {
-						// User-specified data type
-						$query->bindvalue($key,$val[0],
-							$val[1]==self::PARAM_FLOAT?\PDO::PARAM_STR:$val[1]);
-						$vals[]=$fw->stringify($this->value($val[1],$val[0]));
-					}
-					else {
-						// Convert to PDO data type
-						$query->bindvalue($key,$val,
-							($type=$this->type($val))==self::PARAM_FLOAT?
-								\PDO::PARAM_STR:$type);
-						$vals[]=$fw->stringify($this->value($type,$val));
-					}
-					$keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key).
-						'/';
-				}
-				if ($log)
-					$this->log.=($stamp?(date('r').' '):'').'(-0ms) '.
-						preg_replace($keys,$vals,
-							str_replace('?',chr(0).'?',$cmd),1).PHP_EOL;
-				$query->execute();
-				if ($log)
-					$this->log=str_replace('(-0ms)',
-						'('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms)',
-						$this->log);
-				if (($error=$query->errorinfo()) && $error[0]!=\PDO::ERR_NONE) {
-					// Statement-level error occurred
-					if ($this->trans)
-						$this->rollback();
-					user_error('PDOStatement: '.$error[2],E_USER_ERROR);
-				}
-				if (preg_match('/(?:^[\s\(]*'.
-					'(?:WITH|EXPLAIN|SELECT|PRAGMA|SHOW)|RETURNING)\b/is',$cmd) ||
-					(preg_match('/^\s*(?:CALL|EXEC)\b/is',$cmd) &&
-						$query->columnCount())) {
-					$result=$query->fetchall(\PDO::FETCH_ASSOC);
-					// Work around SQLite quote bug
-					if (preg_match('/sqlite2?/',$this->engine))
-						foreach ($result as $pos=>$rec) {
-							unset($result[$pos]);
-							$result[$pos]=[];
-							foreach ($rec as $key=>$val)
-								$result[$pos][trim($key,'\'"[]`')]=$val;
-						}
-					$this->rows=count($result);
-					if ($fw->CACHE && $ttl)
-						// Save to cache backend
-						$cache->set($hash,$result,$ttl);
-				}
-				else
-					$this->rows=$result=$query->rowcount();
-				$query->closecursor();
-				unset($query);
-			}
-			elseif (($error=$this->errorinfo()) && $error[0]!=\PDO::ERR_NONE) {
-				// PDO-level error occurred
-				if ($this->trans)
-					$this->rollback();
-				user_error('PDO: '.$error[2],E_USER_ERROR);
-			}
-
-		}
-		if ($this->trans && $auto)
-			$this->commit();
-		return $result;
-	}
-
-	/**
-	*	Return number of rows affected by last query
-	*	@return int
-	**/
-	function count() {
-		return $this->rows;
-	}
-
-	/**
-	*	Return SQL profiler results (or disable logging)
-	*	@return string
-	*	@param $flag bool
-	**/
-	function log($flag=TRUE) {
-		if ($flag)
-			return $this->log;
-		$this->log=FALSE;
-	}
-
-	/**
-	*	Return TRUE if table exists
-	*	@return bool
-	*	@param $table string
-	**/
-	function exists($table) {
-		$mode=$this->pdo->getAttribute(\PDO::ATTR_ERRMODE);
-		$this->pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_SILENT);
-		$out=$this->pdo->
-			query('SELECT 1 FROM '.$this->quotekey($table).' LIMIT 1');
-		$this->pdo->setAttribute(\PDO::ATTR_ERRMODE,$mode);
-		return is_object($out);
-	}
-
-	/**
-	*	Retrieve schema of SQL table
-	*	@return array|FALSE
-	*	@param $table string
-	*	@param $fields array|string
-	*	@param $ttl int|array
-	**/
-	function schema($table,$fields=NULL,$ttl=0) {
-		$fw=\Base::instance();
-		$cache=\Cache::instance();
-		if ($fw->CACHE && $ttl &&
-			($cached=$cache->exists(
-				$hash=$fw->hash($this->dsn.$table).'.schema',$result)) &&
-			$cached[0]+$ttl>microtime(TRUE))
-			return $result;
-		if (strpos($table,'.'))
-			list($schema,$table)=explode('.',$table);
-		// Supported engines
-		$cmd=[
-			'sqlite2?'=>[
-				'PRAGMA table_info(`'.$table.'`)',
-				'name','type','dflt_value','notnull',0,'pk',TRUE],
-			'mysql'=>[
-				'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`',
-				'Field','Type','Default','Null','YES','Key','PRI'],
-			'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>[
-				'SELECT '.
-					'C.COLUMN_NAME AS field,'.
-					'C.DATA_TYPE AS type,'.
-					'C.COLUMN_DEFAULT AS defval,'.
-					'C.IS_NULLABLE AS nullable,'.
-					'T.CONSTRAINT_TYPE AS pkey '.
-				'FROM INFORMATION_SCHEMA.COLUMNS AS C '.
-				'LEFT OUTER JOIN '.
-					'INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K '.
-					'ON '.
-						'C.TABLE_NAME=K.TABLE_NAME AND '.
-						'C.COLUMN_NAME=K.COLUMN_NAME AND '.
-						'C.TABLE_SCHEMA=K.TABLE_SCHEMA '.
-						($this->dbname?
-							('AND C.TABLE_CATALOG=K.TABLE_CATALOG '):'').
-				'LEFT OUTER JOIN '.
-					'INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS T ON '.
-						'K.TABLE_NAME=T.TABLE_NAME AND '.
-						'K.CONSTRAINT_NAME=T.CONSTRAINT_NAME AND '.
-						'K.TABLE_SCHEMA=T.TABLE_SCHEMA '.
-						($this->dbname?
-							('AND K.TABLE_CATALOG=T.TABLE_CATALOG '):'').
-				'WHERE '.
-					'C.TABLE_NAME='.$this->quote($table).
-					($this->dbname?
-						(' AND C.TABLE_CATALOG='.
-							$this->quote($this->dbname)):''),
-				'field','type','defval','nullable','YES','pkey','PRIMARY KEY'],
-			'oci'=>[
-				'SELECT c.column_name AS field, '.
-					'c.data_type AS type, '.
-					'c.data_default AS defval, '.
-					'c.nullable AS nullable, '.
-					'(SELECT t.constraint_type '.
-						'FROM all_cons_columns acc '.
-						'LEFT OUTER JOIN all_constraints t '.
-						'ON acc.constraint_name=t.constraint_name '.
-						'WHERE acc.table_name='.$this->quote($table).' '.
-						'AND acc.column_name=c.column_name '.
-						'AND constraint_type='.$this->quote('P').') AS pkey '.
-				'FROM all_tab_cols c '.
-				'WHERE c.table_name='.$this->quote($table),
-				'FIELD','TYPE','DEFVAL','NULLABLE','Y','PKEY','P']
-		];
-		if (is_string($fields))
-			$fields=\Base::instance()->split($fields);
-		$conv=[
-			'int\b|integer'=>\PDO::PARAM_INT,
-			'bool'=>\PDO::PARAM_BOOL,
-			'blob|bytea|image|binary'=>\PDO::PARAM_LOB,
-			'float|real|double|decimal|numeric'=>self::PARAM_FLOAT,
-			'.+'=>\PDO::PARAM_STR
-		];
-		foreach ($cmd as $key=>$val)
-			if (preg_match('/'.$key.'/',$this->engine)) {
-				$rows=[];
-				foreach ($this->exec($val[0],NULL) as $row)
-					if (!$fields || in_array($row[$val[1]],$fields)) {
-						foreach ($conv as $regex=>$type)
-							if (preg_match('/'.$regex.'/i',$row[$val[2]]))
-								break;
-						$rows[$row[$val[1]]]=[
-							'type'=>$row[$val[2]],
-							'pdo_type'=>$type,
-							'default'=>is_string($row[$val[3]])?
-								preg_replace('/^\s*([\'"])(.*)\1\s*/','\2',
-								$row[$val[3]]):$row[$val[3]],
-							'nullable'=>$row[$val[4]]==$val[5],
-							'pkey'=>$row[$val[6]]==$val[7]
-						];
-					}
-				if ($fw->CACHE && $ttl)
-					// Save to cache backend
-					$cache->set($hash,$rows,$ttl);
-				return $rows;
-			}
-		user_error(sprintf(self::E_PKey,$table),E_USER_ERROR);
-		return FALSE;
-	}
-
-	/**
-	*	Quote string
-	*	@return string
-	*	@param $val mixed
-	*	@param $type int
-	**/
-	function quote($val,$type=\PDO::PARAM_STR) {
-		return $this->engine=='odbc'?
-			(is_string($val)?
-				\Base::instance()->stringify(str_replace('\'','\'\'',$val)):
-				$val):
-			$this->pdo->quote($val,$type);
-	}
-
-	/**
-	*	Return UUID
-	*	@return string
-	**/
-	function uuid() {
-		return $this->uuid;
-	}
-
-	/**
-	*	Return parent object
-	*	@return \PDO
-	**/
-	function pdo() {
-		return $this->pdo;
-	}
-
-	/**
-	*	Return database engine
-	*	@return string
-	**/
-	function driver() {
-		return $this->engine;
-	}
-
-	/**
-	*	Return server version
-	*	@return string
-	**/
-	function version() {
-		return $this->pdo->getattribute(\PDO::ATTR_SERVER_VERSION);
-	}
-
-	/**
-	*	Return database name
-	*	@return string
-	**/
-	function name() {
-		return $this->dbname;
-	}
-
-	/**
-	*	Return quoted identifier name
-	*	@return string
-	*	@param $key
-	*	@param bool $split
-	 **/
-	function quotekey($key, $split=TRUE) {
-		$delims=[
-			'sqlite2?|mysql'=>'``',
-			'pgsql|oci'=>'""',
-			'mssql|sqlsrv|odbc|sybase|dblib'=>'[]'
-		];
-		$use='';
-		foreach ($delims as $engine=>$delim)
-			if (preg_match('/'.$engine.'/',$this->engine)) {
-				$use=$delim;
-				break;
-			}
-		return $use[0].($split ? implode($use[1].'.'.$use[0],explode('.',$key))
-			: $key).$use[1];
-	}
-
-	/**
-	*	Redirect call to PDO object
-	*	@return mixed
-	*	@param $func string
-	*	@param $args array
-	**/
-	function __call($func,array $args) {
-		return call_user_func_array([$this->pdo,$func],$args);
-	}
-
-	//! Prohibit cloning
-	private function __clone() {
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $dsn string
-	*	@param $user string
-	*	@param $pw string
-	*	@param $options array
-	**/
-	function __construct($dsn,$user=NULL,$pw=NULL,array $options=NULL) {
-		$fw=\Base::instance();
-		$this->uuid=$fw->hash($this->dsn=$dsn);
-		if (preg_match('/^.+?(?:dbname|database)=(.+?)(?=;|$)/is',$dsn,$parts))
-			$this->dbname=$parts[1];
-		if (!$options)
-			$options=[];
-		if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql')
-			$options+=[\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '.
-				strtolower(str_replace('-','',$fw->ENCODING)).';'];
-		$this->pdo=new \PDO($dsn,$user,$pw,$options);
-		$this->engine=$this->pdo->getattribute(\PDO::ATTR_DRIVER_NAME);
-	}
-
-}

+ 0 - 759
lib/f3/db/sql/mapper.php

@@ -1,759 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB\SQL;
-
-//! SQL data mapper
-class Mapper extends \DB\Cursor {
-
-	//@{ Error messages
-	const
-		E_PKey='Table %s does not have a primary key';
-	//@}
-
-	protected
-		//! PDO wrapper
-		$db,
-		//! Database engine
-		$engine,
-		//! SQL table
-		$source,
-		//! SQL table (quoted)
-		$table,
-		//! Alias for SQL table
-		$as,
-		//! Last insert ID
-		$_id,
-		//! Defined fields
-		$fields,
-		//! Adhoc fields
-		$adhoc=[],
-		//! Dynamic properties
-		$props=[];
-
-	/**
-	*	Return database type
-	*	@return string
-	**/
-	function dbtype() {
-		return 'SQL';
-	}
-
-	/**
-	*	Return mapped table
-	*	@return string
-	**/
-	function table() {
-		return $this->source;
-	}
-
-	/**
-	*	Return TRUE if any/specified field value has changed
-	*	@return bool
-	*	@param $key string
-	**/
-	function changed($key=NULL) {
-		if (isset($key))
-			return $this->fields[$key]['changed'];
-		foreach($this->fields as $key=>$field)
-			if ($field['changed'])
-				return TRUE;
-		return FALSE;
-	}
-
-	/**
-	*	Return TRUE if field is defined
-	*	@return bool
-	*	@param $key string
-	**/
-	function exists($key) {
-		return array_key_exists($key,$this->fields+$this->adhoc);
-	}
-
-	/**
-	*	Assign value to field
-	*	@return scalar
-	*	@param $key string
-	*	@param $val scalar
-	**/
-	function set($key,$val) {
-		if (array_key_exists($key,$this->fields)) {
-			$val=is_null($val) && $this->fields[$key]['nullable']?
-				NULL:$this->db->value($this->fields[$key]['pdo_type'],$val);
-			if ($this->fields[$key]['initial']!==$val ||
-				$this->fields[$key]['default']!==$val && is_null($val))
-				$this->fields[$key]['changed']=TRUE;
-			return $this->fields[$key]['value']=$val;
-		}
-		// Adjust result on existing expressions
-		if (isset($this->adhoc[$key]))
-			$this->adhoc[$key]['value']=$val;
-		elseif (is_string($val))
-			// Parenthesize expression in case it's a subquery
-			$this->adhoc[$key]=['expr'=>'('.$val.')','value'=>NULL];
-		else
-			$this->props[$key]=$val;
-		return $val;
-	}
-
-	/**
-	*	Retrieve value of field
-	*	@return scalar
-	*	@param $key string
-	**/
-	function &get($key) {
-		if ($key=='_id')
-			return $this->_id;
-		elseif (array_key_exists($key,$this->fields))
-			return $this->fields[$key]['value'];
-		elseif (array_key_exists($key,$this->adhoc))
-			return $this->adhoc[$key]['value'];
-		elseif (array_key_exists($key,$this->props))
-			return $this->props[$key];
-		user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
-	}
-
-	/**
-	*	Clear value of field
-	*	@return NULL
-	*	@param $key string
-	**/
-	function clear($key) {
-		if (array_key_exists($key,$this->adhoc))
-			unset($this->adhoc[$key]);
-		else
-			unset($this->props[$key]);
-	}
-
-	/**
-	*	Invoke dynamic method
-	*	@return mixed
-	*	@param $func string
-	*	@param $args array
-	**/
-	function __call($func,$args) {
-		return call_user_func_array(
-			(array_key_exists($func,$this->props)?
-				$this->props[$func]:
-				$this->$func),$args
-		);
-	}
-
-	/**
-	*	Convert array to mapper object
-	*	@return static
-	*	@param $row array
-	**/
-	function factory($row) {
-		$mapper=clone($this);
-		$mapper->reset();
-		foreach ($row as $key=>$val) {
-			if (array_key_exists($key,$this->fields))
-				$var='fields';
-			elseif (array_key_exists($key,$this->adhoc))
-				$var='adhoc';
-			else
-				continue;
-			$mapper->{$var}[$key]['value']=$val;
-			$mapper->{$var}[$key]['initial']=$val;
-			if ($var=='fields' && $mapper->{$var}[$key]['pkey'])
-				$mapper->{$var}[$key]['previous']=$val;
-		}
-		$mapper->query=[clone($mapper)];
-		if (isset($mapper->trigger['load']))
-			\Base::instance()->call($mapper->trigger['load'],$mapper);
-		return $mapper;
-	}
-
-	/**
-	*	Return fields of mapper object as an associative array
-	*	@return array
-	*	@param $obj object
-	**/
-	function cast($obj=NULL) {
-		if (!$obj)
-			$obj=$this;
-		return array_map(
-			function($row) {
-				return $row['value'];
-			},
-			$obj->fields+$obj->adhoc
-		);
-	}
-
-	/**
-	*	Build query string and arguments
-	*	@return array
-	*	@param $fields string
-	*	@param $filter string|array
-	*	@param $options array
-	**/
-	function stringify($fields,$filter=NULL,array $options=NULL) {
-		if (!$options)
-			$options=[];
-		$options+=[
-			'group'=>NULL,
-			'order'=>NULL,
-			'limit'=>0,
-			'offset'=>0,
-			'comment'=>NULL
-		];
-		$db=$this->db;
-		$sql='SELECT '.$fields.' FROM '.$this->table;
-		if (isset($this->as))
-			$sql.=' AS '.$this->db->quotekey($this->as);
-		$args=[];
-		if (is_array($filter)) {
-			$args=isset($filter[1]) && is_array($filter[1])?
-				$filter[1]:
-				array_slice($filter,1,NULL,TRUE);
-			$args=is_array($args)?$args:[1=>$args];
-			list($filter)=$filter;
-		}
-		if ($filter)
-			$sql.=' WHERE '.$filter;
-		if ($options['group']) {
-			$sql.=' GROUP BY '.implode(',',array_map(
-				function($str) use($db) {
-					return preg_replace_callback(
-						'/\b(\w+[._\-\w]*)\h*(HAVING.+|$)/i',
-						function($parts) use($db) {
-							return $db->quotekey($parts[1]).
-								(isset($parts[2])?(' '.$parts[2]):'');
-						},
-						$str
-					);
-				},
-				explode(',',$options['group'])));
-		}
-		if ($options['order']) {
-			$char=substr($db->quotekey(''),0,1);// quoting char
-			$order=' ORDER BY '.(is_bool(strpos($options['order'],$char))?
-				implode(',',array_map(function($str) use($db) {
-					return preg_match('/^\h*(\w+[._\-\w]*)'.
-						'(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i',
-						$str,$parts)?
-						($db->quotekey($parts[1]).
-						(isset($parts[2])?(' '.$parts[2]):'')):$str;
-				},explode(',',$options['order']))):
-				$options['order']);
-		}
-		// SQL Server fixes
-		if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) &&
-			($options['limit'] || $options['offset'])) {
-			// order by pkey when no ordering option was given
-			if (!$options['order'])
-				foreach ($this->fields as $key=>$field)
-					if ($field['pkey']) {
-						$order=' ORDER BY '.$db->quotekey($key);
-						break;
-					}
-			$ofs=$options['offset']?(int)$options['offset']:0;
-			$lmt=$options['limit']?(int)$options['limit']:0;
-			if (strncmp($db->version(),'11',2)>=0) {
-				// SQL Server >= 2012
-				$sql.=$order.' OFFSET '.$ofs.' ROWS';
-				if ($lmt)
-					$sql.=' FETCH NEXT '.$lmt.' ROWS ONLY';
-			}
-			else {
-				// SQL Server 2008
-				$sql=preg_replace('/SELECT/',
-					'SELECT '.
-					($lmt>0?'TOP '.($ofs+$lmt):'').' ROW_NUMBER() '.
-					'OVER ('.$order.') AS rnum,',$sql.$order,1);
-				$sql='SELECT * FROM ('.$sql.') x WHERE rnum > '.($ofs);
-			}
-		}
-		else {
-			if (isset($order))
-				$sql.=$order;
-			if ($options['limit'])
-				$sql.=' LIMIT '.(int)$options['limit'];
-			if ($options['offset'])
-				$sql.=' OFFSET '.(int)$options['offset'];
-		}
-		if ($options['comment'])
-			$sql.="\n".' /* '.$options['comment'].' */';
-		return [$sql,$args];
-	}
-
-	/**
-	*	Build query string and execute
-	*	@return static[]
-	*	@param $fields string
-	*	@param $filter string|array
-	*	@param $options array
-	*	@param $ttl int|array
-	**/
-	function select($fields,$filter=NULL,array $options=NULL,$ttl=0) {
-		list($sql,$args)=$this->stringify($fields,$filter,$options);
-		$result=$this->db->exec($sql,$args,$ttl);
-		$out=[];
-		foreach ($result as &$row) {
-			foreach ($row as $field=>&$val) {
-				if (array_key_exists($field,$this->fields)) {
-					if (!is_null($val) || !$this->fields[$field]['nullable'])
-						$val=$this->db->value(
-							$this->fields[$field]['pdo_type'],$val);
-				}
-				unset($val);
-			}
-			$out[]=$this->factory($row);
-			unset($row);
-		}
-		return $out;
-	}
-
-	/**
-	*	Return records that match criteria
-	*	@return static[]
-	*	@param $filter string|array
-	*	@param $options array
-	*	@param $ttl int|array
-	**/
-	function find($filter=NULL,array $options=NULL,$ttl=0) {
-		if (!$options)
-			$options=[];
-		$options+=[
-			'group'=>NULL,
-			'order'=>NULL,
-			'limit'=>0,
-			'offset'=>0
-		];
-		$adhoc='';
-		foreach ($this->adhoc as $key=>$field)
-			$adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
-		return $this->select(
-			($options['group'] && !preg_match('/mysql|sqlite/',$this->engine)?
-				$options['group']:
-				implode(',',array_map([$this->db,'quotekey'],
-					array_keys($this->fields)))).$adhoc,$filter,$options,$ttl);
-	}
-
-	/**
-	*	Count records that match criteria
-	*	@return int
-	*	@param $filter string|array
-	*	@param $options array
-	*	@param $ttl int|array
-	**/
-	function count($filter=NULL,array $options=NULL,$ttl=0) {
-		$adhoc=[];
-		// with grouping involved, we need to wrap the actualy query and count the results
-		if ($subquery_mode=($options && !empty($options['group']))) {
-			$group_string=preg_replace('/HAVING.+$/i','',$options['group']);
-			$group_fields=array_flip(array_map('trim',explode(',',$group_string)));
-			foreach ($this->adhoc as $key=>$field)
-				// add adhoc fields that are used for grouping
-				if (isset($group_fields[$key]))
-					$adhoc[]=$field['expr'].' AS '.$this->db->quotekey($key);
-			$fields=implode(',',$adhoc);
-			if (empty($fields))
-				// Select at least one field, ideally the grouping fields
-				// or sqlsrv fails
-				$fields=$group_string;
-			if (preg_match('/mssql|dblib|sqlsrv/',$this->engine))
-				$fields='TOP 100 PERCENT '.$fields;
-		} else {
-			// for simple count just add a new adhoc counter
-			$fields='COUNT(*) AS '.$this->db->quotekey('_rows');
-		}
-		list($sql,$args)=$this->stringify($fields,$filter,$options);
-		if ($subquery_mode)
-			$sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '.
-				'FROM ('.$sql.') AS '.$this->db->quotekey('_temp');
-		$result=$this->db->exec($sql,$args,$ttl);
-		unset($this->adhoc['_rows']);
-		return (int)$result[0]['_rows'];
-	}
-	/**
-	*	Return record at specified offset using same criteria as
-	*	previous load() call and make it active
-	*	@return static
-	*	@param $ofs int
-	**/
-	function skip($ofs=1) {
-		$out=parent::skip($ofs);
-		$dry=$this->dry();
-		foreach ($this->fields as $key=>&$field) {
-			$field['value']=$dry?NULL:$out->fields[$key]['value'];
-			$field['initial']=$field['value'];
-			$field['changed']=FALSE;
-			if ($field['pkey'])
-				$field['previous']=$dry?NULL:$out->fields[$key]['value'];
-			unset($field);
-		}
-		foreach ($this->adhoc as $key=>&$field) {
-			$field['value']=$dry?NULL:$out->adhoc[$key]['value'];
-			unset($field);
-		}
-		if (!$dry && isset($this->trigger['load']))
-			\Base::instance()->call($this->trigger['load'],$this);
-		return $out;
-	}
-
-	/**
-	*	Insert new record
-	*	@return static
-	**/
-	function insert() {
-		$args=[];
-		$actr=0;
-		$nctr=0;
-		$fields='';
-		$values='';
-		$filter='';
-		$pkeys=[];
-		$nkeys=[];
-		$ckeys=[];
-		$inc=NULL;
-		foreach ($this->fields as $key=>$field)
-			if ($field['pkey'])
-				$pkeys[$key]=$field['previous'];
-		if (isset($this->trigger['beforeinsert']) &&
-			\Base::instance()->call($this->trigger['beforeinsert'],
-				[$this,$pkeys])===FALSE)
-			return $this;
-		if ($this->valid())
-			// duplicate record
-			foreach ($this->fields as $key=>&$field) {
-				$field['changed']=true;
-				if ($field['pkey'] && !$inc && $field['pdo_type']==\PDO::PARAM_INT
-					&& !$field['nullable'])
-					$inc=$key;
-				unset($field);
-			}
-		foreach ($this->fields as $key=>&$field) {
-			if ($field['pkey']) {
-				$field['previous']=$field['value'];
-				if (!$inc && $field['pdo_type']==\PDO::PARAM_INT &&
-					empty($field['value']) && !$field['nullable'] &&
-					is_null($field['default']))
-					$inc=$key;
-				$filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
-				$nkeys[$nctr+1]=[$field['value'],$field['pdo_type']];
-				$nctr++;
-			}
-			if ($field['changed'] && $key!=$inc) {
-				$fields.=($actr?',':'').$this->db->quotekey($key);
-				$values.=($actr?',':'').'?';
-				$args[$actr+1]=[$field['value'],$field['pdo_type']];
-				$actr++;
-				$ckeys[]=$key;
-			}
-			unset($field);
-		}
-		if ($fields) {
-			$add=$aik='';
-			if ($this->engine=='pgsql' && !empty($pkeys)) {
-				$names=array_keys($pkeys);
-				$aik=end($names);
-				$add=' RETURNING '.$this->db->quotekey($aik);
-			}
-			$lID=$this->db->exec(
-				(preg_match('/mssql|dblib|sqlsrv/',$this->engine) &&
-				array_intersect(array_keys($pkeys),$ckeys)?
-					'SET IDENTITY_INSERT '.$this->table.' ON;':'').
-				'INSERT INTO '.$this->table.' ('.$fields.') '.
-				'VALUES ('.$values.')'.$add,$args
-			);
-			if ($this->engine=='pgsql' && $lID && $aik)
-				$this->_id=$lID[0][$aik];
-			elseif ($this->engine!='oci')
-				$this->_id=$this->db->lastinsertid();
-			// Reload to obtain default and auto-increment field values
-			if ($reload=(($inc && $this->_id) || $filter))
-				$this->load($inc?
-					[$inc.'=?',$this->db->value(
-						$this->fields[$inc]['pdo_type'],$this->_id)]:
-					[$filter,$nkeys]);
-			if (isset($this->trigger['afterinsert']))
-				\Base::instance()->call($this->trigger['afterinsert'],
-					[$this,$pkeys]);
-			// reset changed flag after calling afterinsert
-			if (!$reload)
-				foreach ($this->fields as $key=>&$field) {
-					$field['changed']=FALSE;
-					$field['initial']=$field['value'];
-					unset($field);
-				}
-		}
-		return $this;
-	}
-
-	/**
-	*	Update current record
-	*	@return static
-	**/
-	function update() {
-		$args=[];
-		$ctr=0;
-		$pairs='';
-		$pkeys=[];
-		foreach ($this->fields as $key=>$field)
-			if ($field['pkey'])
-				$pkeys[$key]=$field['previous'];
-		if (isset($this->trigger['beforeupdate']) &&
-			\Base::instance()->call($this->trigger['beforeupdate'],
-				[$this,$pkeys])===FALSE)
-			return $this;
-		foreach ($this->fields as $key=>$field)
-			if ($field['changed']) {
-				$pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
-				$args[++$ctr]=[$field['value'],$field['pdo_type']];
-			}
-		if ($pairs) {
-			$filter='';
-			foreach ($this->fields as $key=>$field)
-				if ($field['pkey']) {
-					$filter.=($filter?' AND ':' WHERE ').
-						$this->db->quotekey($key).'=?';
-					$args[++$ctr]=[$field['previous'],$field['pdo_type']];
-				}
-			if (!$filter)
-				user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR);
-			$sql='UPDATE '.$this->table.' SET '.$pairs.$filter;
-			$this->db->exec($sql,$args);
-		}
-		if (isset($this->trigger['afterupdate']))
-			\Base::instance()->call($this->trigger['afterupdate'],
-				[$this,$pkeys]);
-		// reset changed flag after calling afterupdate
-		foreach ($this->fields as $key=>&$field) {
-				$field['changed']=FALSE;
-				$field['initial']=$field['value'];
-				unset($field);
-			}
-		return $this;
-	}
-
-	/**
-	 * batch-update multiple records at once
-	 * @param string|array $filter
-	 * @return int
-	 */
-	function updateAll($filter=NULL) {
-		$args=[];
-		$ctr=$out=0;
-		$pairs='';
-		foreach ($this->fields as $key=>$field)
-			if ($field['changed']) {
-				$pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
-				$args[++$ctr]=[$field['value'],$field['pdo_type']];
-			}
-		if ($filter)
-			if (is_array($filter)) {
-				$cond=array_shift($filter);
-				$args=array_merge($args,$filter);
-				$filter=' WHERE '.$cond;
-			} else
-				$filter=' WHERE '.$filter;
-		if ($pairs) {
-			$sql='UPDATE '.$this->table.' SET '.$pairs.$filter;
-			$out = $this->db->exec($sql,$args);
-		}
-		// reset changed flag after calling afterupdate
-		foreach ($this->fields as $key=>&$field) {
-			$field['changed']=FALSE;
-			$field['initial']=$field['value'];
-			unset($field);
-		}
-		return $out;
-	}
-
-
-	/**
-	*	Delete current record
-	*	@return int
-	*	@param $quick bool
-	*	@param $filter string|array
-	**/
-	function erase($filter=NULL,$quick=TRUE) {
-		if (isset($filter)) {
-			if (!$quick) {
-				$out=0;
-				foreach ($this->find($filter) as $mapper)
-					$out+=$mapper->erase();
-				return $out;
-			}
-			$args=[];
-			if (is_array($filter)) {
-				$args=isset($filter[1]) && is_array($filter[1])?
-					$filter[1]:
-					array_slice($filter,1,NULL,TRUE);
-				$args=is_array($args)?$args:[1=>$args];
-				list($filter)=$filter;
-			}
-			return $this->db->
-				exec('DELETE FROM '.$this->table.
-				($filter?' WHERE '.$filter:'').';',$args);
-		}
-		$args=[];
-		$ctr=0;
-		$filter='';
-		$pkeys=[];
-		foreach ($this->fields as $key=>&$field) {
-			if ($field['pkey']) {
-				$filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
-				$args[$ctr+1]=[$field['previous'],$field['pdo_type']];
-				$pkeys[$key]=$field['previous'];
-				$ctr++;
-			}
-			$field['value']=NULL;
-			$field['changed']=(bool)$field['default'];
-			if ($field['pkey'])
-				$field['previous']=NULL;
-			unset($field);
-		}
-		if (!$filter)
-			user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR);
-		foreach ($this->adhoc as &$field) {
-			$field['value']=NULL;
-			unset($field);
-		}
-		parent::erase();
-		if (isset($this->trigger['beforeerase']) &&
-			\Base::instance()->call($this->trigger['beforeerase'],
-				[$this,$pkeys])===FALSE)
-			return 0;
-		$out=$this->db->
-			exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args);
-		if (isset($this->trigger['aftererase']))
-			\Base::instance()->call($this->trigger['aftererase'],
-				[$this,$pkeys]);
-		return $out;
-	}
-
-	/**
-	*	Reset cursor
-	*	@return NULL
-	**/
-	function reset() {
-		foreach ($this->fields as &$field) {
-			$field['value']=NULL;
-			$field['initial']=NULL;
-			$field['changed']=FALSE;
-			if ($field['pkey'])
-				$field['previous']=NULL;
-			unset($field);
-		}
-		foreach ($this->adhoc as &$field) {
-			$field['value']=NULL;
-			unset($field);
-		}
-		parent::reset();
-	}
-
-	/**
-	*	Hydrate mapper object using hive array variable
-	*	@return NULL
-	*	@param $var array|string
-	*	@param $func callback
-	**/
-	function copyfrom($var,$func=NULL) {
-		if (is_string($var))
-			$var=\Base::instance()->$var;
-		if ($func)
-			$var=call_user_func($func,$var);
-		foreach ($var as $key=>$val)
-			if (in_array($key,array_keys($this->fields)))
-				$this->set($key,$val);
-	}
-
-	/**
-	*	Populate hive array variable with mapper fields
-	*	@return NULL
-	*	@param $key string
-	**/
-	function copyto($key) {
-		$var=&\Base::instance()->ref($key);
-		foreach ($this->fields+$this->adhoc as $key=>$field)
-			$var[$key]=$field['value'];
-	}
-
-	/**
-	*	Return schema and, if the first argument is provided, update it
-	*	@return array
-	*	@param $fields NULL|array
-	**/
-	function schema($fields=null) {
-		if ($fields)
-			$this->fields = $fields;
-		return $this->fields;
-	}
-
-	/**
-	*	Return field names
-	*	@return array
-	*	@param $adhoc bool
-	**/
-	function fields($adhoc=TRUE) {
-		return array_keys($this->fields+($adhoc?$this->adhoc:[]));
-	}
-
-	/**
-	*	Return TRUE if field is not nullable
-	*	@return bool
-	*	@param $field string
-	**/
-	function required($field) {
-		return isset($this->fields[$field]) &&
-			!$this->fields[$field]['nullable'];
-	}
-
-	/**
-	*	Retrieve external iterator for fields
-	*	@return object
-	**/
-	function getiterator() {
-		return new \ArrayIterator($this->cast());
-	}
-
-	/**
-	*	Assign alias for table
-	*	@param $alias string
-	**/
-	function alias($alias) {
-		$this->as=$alias;
-		return $this;
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $db \DB\SQL
-	*	@param $table string
-	*	@param $fields array|string
-	*	@param $ttl int|array
-	**/
-	function __construct(\DB\SQL $db,$table,$fields=NULL,$ttl=60) {
-		$this->db=$db;
-		$this->engine=$db->driver();
-		if ($this->engine=='oci')
-			$table=strtoupper($table);
-		$this->source=$table;
-		$this->table=$this->db->quotekey($table);
-		$this->fields=$db->schema($table,$fields,$ttl);
-		$this->reset();
-	}
-
-}

+ 0 - 222
lib/f3/db/sql/session.php

@@ -1,222 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace DB\SQL;
-
-//! SQL-managed session handler
-class Session extends Mapper {
-
-	protected
-		//! Session ID
-		$sid,
-		//! Anti-CSRF token
-		$_csrf,
-		//! User agent
-		$_agent,
-		//! IP,
-		$_ip,
-		//! Suspect callback
-		$onsuspect;
-
-	/**
-	*	Open session
-	*	@return TRUE
-	*	@param $path string
-	*	@param $name string
-	**/
-	function open($path,$name) {
-		return TRUE;
-	}
-
-	/**
-	*	Close session
-	*	@return TRUE
-	**/
-	function close() {
-		$this->reset();
-		$this->sid=NULL;
-		return TRUE;
-	}
-
-	/**
-	*	Return session data in serialized format
-	*	@return string
-	*	@param $id string
-	**/
-	function read($id) {
-		$this->load(['session_id=?',$this->sid=$id]);
-		if ($this->dry())
-			return '';
-		if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
-			$fw=\Base::instance();
-			if (!isset($this->onsuspect) ||
-				$fw->call($this->onsuspect,[$this,$id])===FALSE) {
-				//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
-				$this->destroy($id);
-				$this->close();
-				unset($fw->{'COOKIE.'.session_name()});
-				$fw->error(403);
-			}
-		}
-		return $this->get('data');
-	}
-
-	/**
-	*	Write session data
-	*	@return TRUE
-	*	@param $id string
-	*	@param $data string
-	**/
-	function write($id,$data) {
-		$this->set('session_id',$id);
-		$this->set('data',$data);
-		$this->set('ip',$this->_ip);
-		$this->set('agent',$this->_agent);
-		$this->set('stamp',time());
-		$this->save();
-		return TRUE;
-	}
-
-	/**
-	*	Destroy session
-	*	@return TRUE
-	*	@param $id string
-	**/
-	function destroy($id) {
-		$this->erase(['session_id=?',$id]);
-		return TRUE;
-	}
-
-	/**
-	*	Garbage collector
-	*	@return TRUE
-	*	@param $max int
-	**/
-	function cleanup($max) {
-		$this->erase(['stamp+?<?',$max,time()]);
-		return TRUE;
-	}
-
-	/**
-	*	Return session id (if session has started)
-	*	@return string|NULL
-	**/
-	function sid() {
-		return $this->sid;
-	}
-
-	/**
-	*	Return anti-CSRF token
-	*	@return string
-	**/
-	function csrf() {
-		return $this->_csrf;
-	}
-
-	/**
-	*	Return IP address
-	*	@return string
-	**/
-	function ip() {
-		return $this->_ip;
-	}
-
-	/**
-	*	Return Unix timestamp
-	*	@return string|FALSE
-	**/
-	function stamp() {
-		if (!$this->sid)
-			session_start();
-		return $this->dry()?FALSE:$this->get('stamp');
-	}
-
-	/**
-	*	Return HTTP user agent
-	*	@return string
-	**/
-	function agent() {
-		return $this->_agent;
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $db \DB\SQL
-	*	@param $table string
-	*	@param $force bool
-	*	@param $onsuspect callback
-	*	@param $key string
-	*	@param $type string, column type for data field
-	**/
-	function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$key=NULL,$type='TEXT') {
-		if ($force) {
-			$eol="\n";
-			$tab="\t";
-			$sqlsrv=preg_match('/mssql|sqlsrv|sybase/',$db->driver());
-			$db->exec(
-				($sqlsrv?
-					('IF NOT EXISTS (SELECT * FROM sysobjects WHERE '.
-						'name='.$db->quote($table).' AND xtype=\'U\') '.
-						'CREATE TABLE dbo.'):
-					('CREATE TABLE IF NOT EXISTS '.
-						((($name=$db->name())&&$db->driver()!='pgsql')?
-							($db->quotekey($name,FALSE).'.'):''))).
-				$db->quotekey($table,FALSE).' ('.$eol.
-					($sqlsrv?$tab.$db->quotekey('id').' INT IDENTITY,'.$eol:'').
-					$tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol.
-					$tab.$db->quotekey('data').' '.$type.','.$eol.
-					$tab.$db->quotekey('ip').' VARCHAR(45),'.$eol.
-					$tab.$db->quotekey('agent').' VARCHAR(300),'.$eol.
-					$tab.$db->quotekey('stamp').' INTEGER,'.$eol.
-					$tab.'PRIMARY KEY ('.$db->quotekey($sqlsrv?'id':'session_id').')'.$eol.
-				($sqlsrv?',CONSTRAINT [UK_session_id] UNIQUE(session_id)':'').
-				');'
-			);
-		}
-		parent::__construct($db,$table);
-		$this->onsuspect=$onsuspect;
-		session_set_save_handler(
-			[$this,'open'],
-			[$this,'close'],
-			[$this,'read'],
-			[$this,'write'],
-			[$this,'destroy'],
-			[$this,'cleanup']
-		);
-		register_shutdown_function('session_commit');
-		$fw=\Base::instance();
-		$headers=$fw->HEADERS;
-		$this->_csrf=$fw->hash($fw->SEED.
-			extension_loaded('openssl')?
-				implode(unpack('L',openssl_random_pseudo_bytes(4))):
-				mt_rand()
-			);
-		if ($key)
-			$fw->$key=$this->_csrf;
-		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		if (strlen($this->_agent) > 300) {
-			$this->_agent = substr($this->_agent, 0, 300);
-		}
-		$this->_ip=$fw->IP;
-	}
-
-}

+ 0 - 42
lib/f3/f3.php

@@ -1,42 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Legacy mode enabler
-class F3 {
-
-	static
-		//! Framework instance
-		$fw;
-
-	/**
-	*	Forward function calls to framework
-	*	@return mixed
-	*	@param $func callback
-	*	@param $args array
-	**/
-	static function __callstatic($func,array $args) {
-		if (!self::$fw)
-			self::$fw=Base::instance();
-		return call_user_func_array([self::$fw,$func],$args);
-	}
-
-}

+ 0 - 616
lib/f3/image.php

@@ -1,616 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Image manipulation tools
-class Image {
-
-	//@{ Messages
-	const
-		E_Color='Invalid color specified: %s',
-		E_File='File not found',
-		E_Font='CAPTCHA font not found',
-		E_TTF='No TrueType support in GD module',
-		E_Length='Invalid CAPTCHA length: %s';
-	//@}
-
-	//@{ Positional cues
-	const
-		POS_Left=1,
-		POS_Center=2,
-		POS_Right=4,
-		POS_Top=8,
-		POS_Middle=16,
-		POS_Bottom=32;
-	//@}
-
-	protected
-		//! Source filename
-		$file,
-		//! Image resource
-		$data,
-		//! Enable/disable history
-		$flag=FALSE,
-		//! Filter count
-		$count=0;
-
-	/**
-	*	Convert RGB hex triad to array
-	*	@return array|FALSE
-	*	@param $color int|string
-	**/
-	function rgb($color) {
-		if (is_string($color))
-			$color=hexdec($color);
-		$hex=str_pad($hex=dechex($color),$color<4096?3:6,'0',STR_PAD_LEFT);
-		if (($len=strlen($hex))>6)
-			user_error(sprintf(self::E_Color,'0x'.$hex),E_USER_ERROR);
-		$color=str_split($hex,$len/3);
-		foreach ($color as &$hue) {
-			$hue=hexdec(str_repeat($hue,6/$len));
-			unset($hue);
-		}
-		return $color;
-	}
-
-	/**
-	*	Invert image
-	*	@return object
-	**/
-	function invert() {
-		imagefilter($this->data,IMG_FILTER_NEGATE);
-		return $this->save();
-	}
-
-	/**
-	*	Adjust brightness (range:-255 to 255)
-	*	@return object
-	*	@param $level int
-	**/
-	function brightness($level) {
-		imagefilter($this->data,IMG_FILTER_BRIGHTNESS,$level);
-		return $this->save();
-	}
-
-	/**
-	*	Adjust contrast (range:-100 to 100)
-	*	@return object
-	*	@param $level int
-	**/
-	function contrast($level) {
-		imagefilter($this->data,IMG_FILTER_CONTRAST,$level);
-		return $this->save();
-	}
-
-	/**
-	*	Convert to grayscale
-	*	@return object
-	**/
-	function grayscale() {
-		imagefilter($this->data,IMG_FILTER_GRAYSCALE);
-		return $this->save();
-	}
-
-	/**
-	*	Adjust smoothness
-	*	@return object
-	*	@param $level int
-	**/
-	function smooth($level) {
-		imagefilter($this->data,IMG_FILTER_SMOOTH,$level);
-		return $this->save();
-	}
-
-	/**
-	*	Emboss the image
-	*	@return object
-	**/
-	function emboss() {
-		imagefilter($this->data,IMG_FILTER_EMBOSS);
-		return $this->save();
-	}
-
-	/**
-	*	Apply sepia effect
-	*	@return object
-	**/
-	function sepia() {
-		imagefilter($this->data,IMG_FILTER_GRAYSCALE);
-		imagefilter($this->data,IMG_FILTER_COLORIZE,90,60,45);
-		return $this->save();
-	}
-
-	/**
-	*	Pixelate the image
-	*	@return object
-	*	@param $size int
-	**/
-	function pixelate($size) {
-		imagefilter($this->data,IMG_FILTER_PIXELATE,$size,TRUE);
-		return $this->save();
-	}
-
-	/**
-	*	Blur the image using Gaussian filter
-	*	@return object
-	*	@param $selective bool
-	**/
-	function blur($selective=FALSE) {
-		imagefilter($this->data,
-			$selective?IMG_FILTER_SELECTIVE_BLUR:IMG_FILTER_GAUSSIAN_BLUR);
-		return $this->save();
-	}
-
-	/**
-	*	Apply sketch effect
-	*	@return object
-	**/
-	function sketch() {
-		imagefilter($this->data,IMG_FILTER_MEAN_REMOVAL);
-		return $this->save();
-	}
-
-	/**
-	*	Flip on horizontal axis
-	*	@return object
-	**/
-	function hflip() {
-		$tmp=imagecreatetruecolor(
-			$width=$this->width(),$height=$this->height());
-		imagesavealpha($tmp,TRUE);
-		imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
-		imagecopyresampled($tmp,$this->data,
-			0,0,$width-1,0,$width,$height,-$width,$height);
-		imagedestroy($this->data);
-		$this->data=$tmp;
-		return $this->save();
-	}
-
-	/**
-	*	Flip on vertical axis
-	*	@return object
-	**/
-	function vflip() {
-		$tmp=imagecreatetruecolor(
-			$width=$this->width(),$height=$this->height());
-		imagesavealpha($tmp,TRUE);
-		imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
-		imagecopyresampled($tmp,$this->data,
-			0,0,0,$height-1,$width,$height,$width,-$height);
-		imagedestroy($this->data);
-		$this->data=$tmp;
-		return $this->save();
-	}
-
-	/**
-	*	Crop the image
-	*	@return object
-	*	@param $x1 int
-	*	@param $y1 int
-	*	@param $x2 int
-	*	@param $y2 int
-	**/
-	function crop($x1,$y1,$x2,$y2) {
-		$tmp=imagecreatetruecolor($width=$x2-$x1+1,$height=$y2-$y1+1);
-		imagesavealpha($tmp,TRUE);
-		imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
-		imagecopyresampled($tmp,$this->data,
-			0,0,$x1,$y1,$width,$height,$width,$height);
-		imagedestroy($this->data);
-		$this->data=$tmp;
-		return $this->save();
-	}
-
-	/**
-	*	Resize image (Maintain aspect ratio); Crop relative to center
-	*	if flag is enabled; Enlargement allowed if flag is enabled
-	*	@return object
-	*	@param $width int
-	*	@param $height int
-	*	@param $crop bool
-	*	@param $enlarge bool
-	**/
-	function resize($width=NULL,$height=NULL,$crop=TRUE,$enlarge=TRUE) {
-		if (is_null($width) && is_null($height))
-			return $this;
-		$origw=$this->width();
-		$origh=$this->height();
-		if (is_null($width))
-			$width=round(($height/$origh)*$origw);
-		if (is_null($height))
-			$height=round(($width/$origw)*$origh);
-		// Adjust dimensions; retain aspect ratio
-		$ratio=$origw/$origh;
-		if (!$crop) {
-			if ($width/$ratio<=$height)
-				$height=round($width/$ratio);
-			else
-				$width=round($height*$ratio);
-		}
-		if (!$enlarge) {
-			$width=min($origw,$width);
-			$height=min($origh,$height);
-		}
-		// Create blank image
-		$tmp=imagecreatetruecolor($width,$height);
-		imagesavealpha($tmp,TRUE);
-		imagefill($tmp,0,0,IMG_COLOR_TRANSPARENT);
-		// Resize
-		if ($crop) {
-			if ($width/$ratio<=$height) {
-				$cropw=round($origh*$width/$height);
-				imagecopyresampled($tmp,$this->data,
-					0,0,($origw-$cropw)/2,0,$width,$height,$cropw,$origh);
-			}
-			else {
-				$croph=round($origw*$height/$width);
-				imagecopyresampled($tmp,$this->data,
-					0,0,0,($origh-$croph)/2,$width,$height,$origw,$croph);
-			}
-		}
-		else
-			imagecopyresampled($tmp,$this->data,
-				0,0,0,0,$width,$height,$origw,$origh);
-		imagedestroy($this->data);
-		$this->data=$tmp;
-		return $this->save();
-	}
-
-	/**
-	*	Rotate image
-	*	@return object
-	*	@param $angle int
-	**/
-	function rotate($angle) {
-		$this->data=imagerotate($this->data,$angle,
-			imagecolorallocatealpha($this->data,0,0,0,127));
-		imagesavealpha($this->data,TRUE);
-		return $this->save();
-	}
-
-	/**
-	*	Apply an image overlay
-	*	@return object
-	*	@param $img object
-	*	@param $align int|array
-	*	@param $alpha int
-	**/
-	function overlay(Image $img,$align=NULL,$alpha=100) {
-		if (is_null($align))
-			$align=self::POS_Right|self::POS_Bottom;
-		if (is_array($align)) {
-			list($posx,$posy)=$align;
-			$align = 0;
-		}
-		$ovr=imagecreatefromstring($img->dump());
-		imagesavealpha($ovr,TRUE);
-		$imgw=$this->width();
-		$imgh=$this->height();
-		$ovrw=imagesx($ovr);
-		$ovrh=imagesy($ovr);
-		if ($align & self::POS_Left)
-			$posx=0;
-		if ($align & self::POS_Center)
-			$posx=($imgw-$ovrw)/2;
-		if ($align & self::POS_Right)
-			$posx=$imgw-$ovrw;
-		if ($align & self::POS_Top)
-			$posy=0;
-		if ($align & self::POS_Middle)
-			$posy=($imgh-$ovrh)/2;
-		if ($align & self::POS_Bottom)
-			$posy=$imgh-$ovrh;
-		if (empty($posx))
-			$posx=0;
-		if (empty($posy))
-			$posy=0;
-		if ($alpha==100)
-			imagecopy($this->data,$ovr,$posx,$posy,0,0,$ovrw,$ovrh);
-		else {
-			$cut=imagecreatetruecolor($ovrw,$ovrh);
-			imagecopy($cut,$this->data,0,0,$posx,$posy,$ovrw,$ovrh);
-			imagecopy($cut,$ovr,0,0,0,0,$ovrw,$ovrh);
-			imagecopymerge($this->data,
-				$cut,$posx,$posy,0,0,$ovrw,$ovrh,$alpha);
-		}
-		return $this->save();
-	}
-
-	/**
-	*	Generate identicon
-	*	@return object
-	*	@param $str string
-	*	@param $size int
-	*	@param $blocks int
-	**/
-	function identicon($str,$size=64,$blocks=4) {
-		$sprites=[
-			[.5,1,1,0,1,1],
-			[.5,0,1,0,.5,1,0,1],
-			[.5,0,1,0,1,1,.5,1,1,.5],
-			[0,.5,.5,0,1,.5,.5,1,.5,.5],
-			[0,.5,1,0,1,1,0,1,1,.5],
-			[1,0,1,1,.5,1,1,.5,.5,.5],
-			[0,0,1,0,1,.5,0,0,.5,1,0,1],
-			[0,0,.5,0,1,.5,.5,1,0,1,.5,.5],
-			[.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5],
-			[0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1],
-			[0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1],
-			[.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25],
-			[0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1],
-			[0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25],
-			[0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1],
-			[0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1]
-		];
-		$hash=sha1($str);
-		$this->data=imagecreatetruecolor($size,$size);
-		list($r,$g,$b)=$this->rgb(hexdec(substr($hash,-3)));
-		$fg=imagecolorallocate($this->data,$r,$g,$b);
-		imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT);
-		$ctr=count($sprites);
-		$dim=$blocks*floor($size/$blocks)*2/$blocks;
-		for ($j=0,$y=ceil($blocks/2);$j<$y;$j++)
-			for ($i=$j,$x=$blocks-1-$j;$i<$x;$i++) {
-				$sprite=imagecreatetruecolor($dim,$dim);
-				imagefill($sprite,0,0,IMG_COLOR_TRANSPARENT);
-				$block=$sprites[hexdec($hash[($j*$blocks+$i)*2])%$ctr];
-				for ($k=0,$pts=count($block);$k<$pts;$k++)
-					$block[$k]*=$dim;
-				imagefilledpolygon($sprite,$block,$pts/2,$fg);
-				for ($k=0;$k<4;$k++) {
-					imagecopyresampled($this->data,$sprite,
-						$i*$dim/2,$j*$dim/2,0,0,$dim/2,$dim/2,$dim,$dim);
-					$this->data=imagerotate($this->data,90,
-						imagecolorallocatealpha($this->data,0,0,0,127));
-				}
-				imagedestroy($sprite);
-			}
-		imagesavealpha($this->data,TRUE);
-		return $this->save();
-	}
-
-	/**
-	*	Generate CAPTCHA image
-	*	@return object|FALSE
-	*	@param $font string
-	*	@param $size int
-	*	@param $len int
-	*	@param $key string
-	*	@param $path string
-	*	@param $fg int
-	*	@param $bg int
-	**/
-	function captcha($font,$size=24,$len=5,
-		$key=NULL,$path='',$fg=0xFFFFFF,$bg=0x000000) {
-		if ((!$ssl=extension_loaded('openssl')) && ($len<4 || $len>13)) {
-			user_error(sprintf(self::E_Length,$len),E_USER_ERROR);
-			return FALSE;
-		}
-		if (!function_exists('imagettftext')) {
-			user_error(self::E_TTF,E_USER_ERROR);
-			return FALSE;
-		}
-		$fw=Base::instance();
-		foreach ($fw->split($path?:$fw->UI.';./') as $dir)
-			if (is_file($path=$dir.$font)) {
-				$seed=strtoupper(substr(
-					$ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(),
-					-$len));
-				$block=$size*3;
-				$tmp=[];
-				for ($i=0,$width=0,$height=0;$i<$len;$i++) {
-					// Process at 2x magnification
-					$box=imagettfbbox($size*2,0,$path,$seed[$i]);
-					$w=$box[2]-$box[0];
-					$h=$box[1]-$box[5];
-					$char=imagecreatetruecolor($block,$block);
-					imagefill($char,0,0,$bg);
-					imagettftext($char,$size*2,0,
-						($block-$w)/2,$block-($block-$h)/2,
-						$fg,$path,$seed[$i]);
-					$char=imagerotate($char,mt_rand(-30,30),
-						imagecolorallocatealpha($char,0,0,0,127));
-					// Reduce to normal size
-					$tmp[$i]=imagecreatetruecolor(
-						($w=imagesx($char))/2,($h=imagesy($char))/2);
-					imagefill($tmp[$i],0,0,IMG_COLOR_TRANSPARENT);
-					imagecopyresampled($tmp[$i],
-						$char,0,0,0,0,$w/2,$h/2,$w,$h);
-					imagedestroy($char);
-					$width+=$i+1<$len?$block/2:$w/2;
-					$height=max($height,$h/2);
-				}
-				$this->data=imagecreatetruecolor($width,$height);
-				imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT);
-				for ($i=0;$i<$len;$i++) {
-					imagecopy($this->data,$tmp[$i],
-						$i*$block/2,($height-imagesy($tmp[$i]))/2,0,0,
-						imagesx($tmp[$i]),imagesy($tmp[$i]));
-					imagedestroy($tmp[$i]);
-				}
-				imagesavealpha($this->data,TRUE);
-				if ($key)
-					$fw->$key=$seed;
-				return $this->save();
-			}
-		user_error(self::E_Font,E_USER_ERROR);
-		return FALSE;
-	}
-
-	/**
-	*	Return image width
-	*	@return int
-	**/
-	function width() {
-		return imagesx($this->data);
-	}
-
-	/**
-	*	Return image height
-	*	@return int
-	**/
-	function height() {
-		return imagesy($this->data);
-	}
-
-	/**
-	*	Send image to HTTP client
-	*	@return NULL
-	**/
-	function render() {
-		$args=func_get_args();
-		$format=$args?array_shift($args):'png';
-		if (PHP_SAPI!='cli') {
-			header('Content-Type: image/'.$format);
-			header('X-Powered-By: '.Base::instance()->PACKAGE);
-		}
-		call_user_func_array(
-			'image'.$format,
-			array_merge([$this->data,NULL],$args)
-		);
-	}
-
-	/**
-	*	Return image as a string
-	*	@return string
-	**/
-	function dump() {
-		$args=func_get_args();
-		$format=$args?array_shift($args):'png';
-		ob_start();
-		call_user_func_array(
-			'image'.$format,
-			array_merge([$this->data,NULL],$args)
-		);
-		return ob_get_clean();
-	}
-
-	/**
-	*	Return image resource
-	*	@return resource
-	**/
-	function data() {
-		return $this->data;
-	}
-
-	/**
-	*	Save current state
-	*	@return object
-	**/
-	function save() {
-		$fw=Base::instance();
-		if ($this->flag) {
-			if (!is_dir($dir=$fw->TEMP))
-				mkdir($dir,Base::MODE,TRUE);
-			$this->count++;
-			$fw->write($dir.'/'.$fw->SEED.'.'.
-				$fw->hash($this->file).'-'.$this->count.'.png',
-				$this->dump());
-		}
-		return $this;
-	}
-
-	/**
-	*	Revert to specified state
-	*	@return object
-	*	@param $state int
-	**/
-	function restore($state=1) {
-		$fw=Base::instance();
-		if ($this->flag && is_file($file=($path=$fw->TEMP.
-			$fw->SEED.'.'.$fw->hash($this->file).'-').$state.'.png')) {
-			if (is_resource($this->data))
-				imagedestroy($this->data);
-			$this->data=imagecreatefromstring($fw->read($file));
-			imagesavealpha($this->data,TRUE);
-			foreach (glob($path.'*.png',GLOB_NOSORT) as $match)
-				if (preg_match('/-(\d+)\.png/',$match,$parts) &&
-					$parts[1]>$state)
-					@unlink($match);
-			$this->count=$state;
-		}
-		return $this;
-	}
-
-	/**
-	*	Undo most recently applied filter
-	*	@return object
-	**/
-	function undo() {
-		if ($this->flag) {
-			if ($this->count)
-				$this->count--;
-			return $this->restore($this->count);
-		}
-		return $this;
-	}
-
-	/**
-	*	Load string
-	*	@return object|FALSE
-	*	@param $str string
-	**/
-	function load($str) {
-		if (!$this->data=@imagecreatefromstring($str))
-			return FALSE;
-		imagesavealpha($this->data,TRUE);
-		$this->save();
-		return $this;
-	}
-
-	/**
-	*	Instantiate image
-	*	@param $file string
-	*	@param $flag bool
-	*	@param $path string
-	**/
-	function __construct($file=NULL,$flag=FALSE,$path=NULL) {
-		$this->flag=$flag;
-		if ($file) {
-			$fw=Base::instance();
-			// Create image from file
-			$this->file=$file;
-			if (!isset($path))
-				$path=$fw->UI.';./';
-			foreach ($fw->split($path,FALSE) as $dir)
-				if (is_file($dir.$file))
-					return $this->load($fw->read($dir.$file));
-			user_error(self::E_File,E_USER_ERROR);
-		}
-	}
-
-	/**
-	*	Wrap-up
-	*	@return NULL
-	**/
-	function __destruct() {
-		if (is_resource($this->data)) {
-			imagedestroy($this->data);
-			$fw=Base::instance();
-			$path=$fw->TEMP.$fw->SEED.'.'.$fw->hash($this->file);
-			if ($glob=@glob($path.'*.png',GLOB_NOSORT))
-				foreach ($glob as $match)
-					if (preg_match('/-(\d+)\.png/',$match))
-						@unlink($match);
-		}
-	}
-
-}

+ 0 - 71
lib/f3/log.php

@@ -1,71 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Custom logger
-class Log {
-
-	protected
-		//! File name
-		$file;
-
-	/**
-	*	Write specified text to log file
-	*	@return string
-	*	@param $text string
-	*	@param $format string
-	**/
-	function write($text,$format='r') {
-		$fw=Base::instance();
-		foreach (preg_split('/\r?\n|\r/',trim($text)) as $line)
-			$fw->write(
-				$this->file,
-				date($format).
-				(isset($_SERVER['REMOTE_ADDR'])?
-					(' ['.$_SERVER['REMOTE_ADDR'].
-					(($fwd=filter_var($fw->get('HEADERS.X-Forwarded-For'),
-						FILTER_VALIDATE_IP))?(' ('.$fwd.')'):'')
-					.']'):'').' '.
-				trim($line).PHP_EOL,
-				TRUE
-			);
-	}
-
-	/**
-	*	Erase log
-	*	@return NULL
-	**/
-	function erase() {
-		@unlink($this->file);
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $file string
-	**/
-	function __construct($file) {
-		$fw=Base::instance();
-		if (!is_dir($dir=$fw->LOGS))
-			mkdir($dir,Base::MODE,TRUE);
-		$this->file=$dir.$file;
-	}
-
-}

+ 0 - 139
lib/f3/magic.php

@@ -1,139 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! PHP magic wrapper
-abstract class Magic implements ArrayAccess {
-
-	/**
-	*	Return TRUE if key is not empty
-	*	@return bool
-	*	@param $key string
-	**/
-	abstract function exists($key);
-
-	/**
-	*	Bind value to key
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	abstract function set($key,$val);
-
-	/**
-	*	Retrieve contents of key
-	*	@return mixed
-	*	@param $key string
-	**/
-	abstract function &get($key);
-
-	/**
-	*	Unset key
-	*	@return NULL
-	*	@param $key string
-	**/
-	abstract function clear($key);
-
-	/**
-	*	Convenience method for checking property value
-	*	@return mixed
-	*	@param $key string
-	**/
-	function offsetexists($key) {
-		return Base::instance()->visible($this,$key)?
-			isset($this->$key):
-			($this->exists($key) && $this->get($key)!==NULL);
-	}
-
-	/**
-	*	Convenience method for assigning property value
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function offsetset($key,$val) {
-		return Base::instance()->visible($this,$key)?
-			($this->$key=$val):$this->set($key,$val);
-	}
-
-	/**
-	*	Convenience method for retrieving property value
-	*	@return mixed
-	*	@param $key string
-	**/
-	function &offsetget($key) {
-		if (Base::instance()->visible($this,$key))
-			$val=&$this->$key;
-		else
-			$val=&$this->get($key);
-		return $val;
-	}
-
-	/**
-	*	Convenience method for removing property value
-	*	@return NULL
-	*	@param $key string
-	**/
-	function offsetunset($key) {
-		if (Base::instance()->visible($this,$key))
-			unset($this->$key);
-		else
-			$this->clear($key);
-	}
-
-	/**
-	*	Alias for offsetexists()
-	*	@return mixed
-	*	@param $key string
-	**/
-	function __isset($key) {
-		return $this->offsetexists($key);
-	}
-
-	/**
-	*	Alias for offsetset()
-	*	@return mixed
-	*	@param $key string
-	*	@param $val mixed
-	**/
-	function __set($key,$val) {
-		return $this->offsetset($key,$val);
-	}
-
-	/**
-	*	Alias for offsetget()
-	*	@return mixed
-	*	@param $key string
-	**/
-	function &__get($key) {
-		$val=&$this->offsetget($key);
-		return $val;
-	}
-
-	/**
-	*	Alias for offsetunset()
-	*	@param $key string
-	**/
-	function __unset($key) {
-		$this->offsetunset($key);
-	}
-
-}

+ 0 - 569
lib/f3/markdown.php

@@ -1,569 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Markdown-to-HTML converter
-class Markdown extends Prefab {
-
-	protected
-		//! Parsing rules
-		$blocks,
-		//! Special characters
-		$special;
-
-	/**
-	*	Process blockquote
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _blockquote($str) {
-		$str=preg_replace('/(?<=^|\n)\h?>\h?(.*?(?:\n+|$))/','\1',$str);
-		return strlen($str)?
-			('<blockquote>'.$this->build($str).'</blockquote>'."\n\n"):'';
-	}
-
-	/**
-	*	Process whitespace-prefixed code block
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _pre($str) {
-		$str=preg_replace('/(?<=^|\n)(?: {4}|\t)(.+?(?:\n+|$))/','\1',
-			$this->esc($str));
-		return strlen($str)?
-			('<pre><code>'.
-				$this->esc($this->snip($str)).
-			'</code></pre>'."\n\n"):
-			'';
-	}
-
-	/**
-	*	Process fenced code block
-	*	@return string
-	*	@param $hint string
-	*	@param $str string
-	**/
-	protected function _fence($hint,$str) {
-		$str=$this->snip($str);
-		$fw=Base::instance();
-		if ($fw->HIGHLIGHT) {
-			switch (strtolower($hint)) {
-				case 'php':
-					$str=$fw->highlight($str);
-					break;
-				case 'apache':
-					preg_match_all('/(?<=^|\n)(\h*)'.
-						'(?:(<\/?)(\w+)((?:\h+[^>]+)*)(>)|'.
-						'(?:(\w+)(\h.+?)))(\h*(?:\n+|$))/',
-						$str,$matches,PREG_SET_ORDER);
-					$out='';
-					foreach ($matches as $match)
-						$out.=$match[1].
-							($match[3]?
-								('<span class="section">'.
-									$this->esc($match[2]).$match[3].
-								'</span>'.
-								($match[4]?
-									('<span class="data">'.
-										$this->esc($match[4]).
-									'</span>'):
-									'').
-								'<span class="section">'.
-									$this->esc($match[5]).
-								'</span>'):
-								('<span class="directive">'.
-									$match[6].
-								'</span>'.
-								'<span class="data">'.
-									$this->esc($match[7]).
-								'</span>')).
-							$match[8];
-					$str='<code>'.$out.'</code>';
-					break;
-				case 'html':
-					preg_match_all(
-						'/(?:(?:<(\/?)(\w+)'.
-						'((?:\h+(?:\w+\h*=\h*)?".+?"|[^>]+)*|'.
-						'\h+.+?)(\h*\/?)>)|(.+?))/s',
-						$str,$matches,PREG_SET_ORDER
-					);
-					$out='';
-					foreach ($matches as $match) {
-						if ($match[2]) {
-							$out.='<span class="xml_tag">&lt;'.
-								$match[1].$match[2].'</span>';
-							if ($match[3]) {
-								preg_match_all(
-									'/(?:\h+(?:(?:(\w+)\h*=\h*)?'.
-									'(".+?")|(.+)))/',
-									$match[3],$parts,PREG_SET_ORDER
-								);
-								foreach ($parts as $part)
-									$out.=' '.
-										(empty($part[3])?
-											((empty($part[1])?
-												'':
-												('<span class="xml_attr">'.
-													$part[1].'</span>=')).
-											'<span class="xml_data">'.
-												$part[2].'</span>'):
-											('<span class="xml_tag">'.
-												$part[3].'</span>'));
-							}
-							$out.='<span class="xml_tag">'.
-								$match[4].'&gt;</span>';
-						}
-						else
-							$out.=$this->esc($match[5]);
-					}
-					$str='<code>'.$out.'</code>';
-					break;
-				case 'ini':
-					preg_match_all(
-						'/(?<=^|\n)(?:'.
-						'(;[^\n]*)|(?:<\?php.+?\?>?)|'.
-						'(?:\[(.+?)\])|'.
-						'(.+?)(\h*=\h*)'.
-						'((?:\\\\\h*\r?\n|.+?)*)'.
-						')((?:\r?\n)+|$)/',
-						$str,$matches,PREG_SET_ORDER
-					);
-					$out='';
-					foreach ($matches as $match) {
-						if ($match[1])
-							$out.='<span class="comment">'.$match[1].
-								'</span>';
-						elseif ($match[2])
-							$out.='<span class="ini_section">['.$match[2].']'.
-								'</span>';
-						elseif ($match[3])
-							$out.='<span class="ini_key">'.$match[3].
-								'</span>'.$match[4].
-								($match[5]?
-									('<span class="ini_value">'.
-										$match[5].'</span>'):'');
-						else
-							$out.=$match[0];
-						if (isset($match[6]))
-							$out.=$match[6];
-					}
-					$str='<code>'.$out.'</code>';
-					break;
-				default:
-					$str='<code>'.$this->esc($str).'</code>';
-					break;
-			}
-		}
-		else
-			$str='<code>'.$this->esc($str).'</code>';
-		return '<pre>'.$str.'</pre>'."\n\n";
-	}
-
-	/**
-	*	Process horizontal rule
-	*	@return string
-	**/
-	protected function _hr() {
-		return '<hr />'."\n\n";
-	}
-
-	/**
-	*	Process atx-style heading
-	*	@return string
-	*	@param $type string
-	*	@param $str string
-	**/
-	protected function _atx($type,$str) {
-		$level=strlen($type);
-		return '<h'.$level.' id="'.Web::instance()->slug($str).'">'.
-			$this->scan($str).'</h'.$level.'>'."\n\n";
-	}
-
-	/**
-	*	Process setext-style heading
-	*	@return string
-	*	@param $str string
-	*	@param $type string
-	**/
-	protected function _setext($str,$type) {
-		$level=strpos('=-',$type)+1;
-		return '<h'.$level.' id="'.Web::instance()->slug($str).'">'.
-			$this->scan($str).'</h'.$level.'>'."\n\n";
-	}
-
-	/**
-	*	Process ordered/unordered list
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _li($str) {
-		// Initialize list parser
-		$len=strlen($str);
-		$ptr=0;
-		$dst='';
-		$first=TRUE;
-		$tight=TRUE;
-		$type='ul';
-		// Main loop
-		while ($ptr<$len) {
-			if (preg_match('/^\h*[*-](?:\h?[*-]){2,}(?:\n+|$)/',
-				substr($str,$ptr),$match)) {
-				$ptr+=strlen($match[0]);
-				// Embedded horizontal rule
-				return (strlen($dst)?
-					('<'.$type.'>'."\n".$dst.'</'.$type.'>'."\n\n"):'').
-					'<hr />'."\n\n".$this->build(substr($str,$ptr));
-			}
-			elseif (preg_match('/(?<=^|\n)([*+-]|\d+\.)\h'.
-				'(.+?(?:\n+|$))((?:(?: {4}|\t)+.+?(?:\n+|$))*)/s',
-				substr($str,$ptr),$match)) {
-				$match[3]=preg_replace('/(?<=^|\n)(?: {4}|\t)/','',$match[3]);
-				$found=FALSE;
-				foreach (array_slice($this->blocks,0,-1) as $regex)
-					if (preg_match($regex,$match[3])) {
-						$found=TRUE;
-						break;
-					}
-				// List
-				if ($first) {
-					// First pass
-					if (is_numeric($match[1]))
-						$type='ol';
-					if (preg_match('/\n{2,}$/',$match[2].
-						($found?'':$match[3])))
-						// Loose structure; Use paragraphs
-						$tight=FALSE;
-					$first=FALSE;
-				}
-				// Strip leading whitespaces
-				$ptr+=strlen($match[0]);
-				$tmp=$this->snip($match[2].$match[3]);
-				if ($tight) {
-					if ($found)
-						$tmp=$match[2].$this->build($this->snip($match[3]));
-				}
-				else
-					$tmp=$this->build($tmp);
-				$dst.='<li>'.$this->scan(trim($tmp)).'</li>'."\n";
-			}
-		}
-		return strlen($dst)?
-			('<'.$type.'>'."\n".$dst.'</'.$type.'>'."\n\n"):'';
-	}
-
-	/**
-	*	Ignore raw HTML
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _raw($str) {
-		return $str;
-	}
-
-	/**
-	*	Process paragraph
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _p($str) {
-		$str=trim($str);
-		if (strlen($str)) {
-			if (preg_match('/^(.+?\n)([>#].+)$/s',$str,$parts))
-				return $this->_p($parts[1]).$this->build($parts[2]);
-			$str=preg_replace_callback(
-				'/([^<>\[]+)?(<[\?%].+?[\?%]>|<.+?>|\[.+?\]\s*\(.+?\))|'.
-				'(.+)/s',
-				function($expr) {
-					$tmp='';
-					if (isset($expr[4]))
-						$tmp.=$this->esc($expr[4]);
-					else {
-						if (isset($expr[1]))
-							$tmp.=$this->esc($expr[1]);
-						$tmp.=$expr[2];
-						if (isset($expr[3]))
-							$tmp.=$this->esc($expr[3]);
-					}
-					return $tmp;
-				},
-				$str
-			);
-			$str=preg_replace('/\s{2}\r?\n/','<br />',$str);
-			return '<p>'.$this->scan($str).'</p>'."\n\n";
-		}
-		return '';
-	}
-
-	/**
-	*	Process strong/em/strikethrough spans
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _text($str) {
-		$tmp='';
-		while ($str!=$tmp)
-			$str=preg_replace_callback(
-				'/(?<=\s|^)(?<!\\\\)([*_])([*_]?)([*_]?)(.*?)(?!\\\\)\3\2\1(?=[\s[:punct:]]|$)/',
-				function($expr) {
-					if ($expr[3])
-						return '<strong><em>'.$expr[4].'</em></strong>';
-					if ($expr[2])
-						return '<strong>'.$expr[4].'</strong>';
-					return '<em>'.$expr[4].'</em>';
-				},
-				preg_replace(
-					'/(?<!\\\\)~~(.*?)(?!\\\\)~~(?=[\s[:punct:]]|$)/',
-					'<del>\1</del>',
-					$tmp=$str
-				)
-			);
-		return $str;
-	}
-
-	/**
-	*	Process image span
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _img($str) {
-		return preg_replace_callback(
-			'/!(?:\[(.+?)\])?\h*\(<?(.*?)>?(?:\h*"(.*?)"\h*)?\)/',
-			function($expr) {
-				return '<img src="'.$expr[2].'"'.
-					(empty($expr[1])?
-						'':
-						(' alt="'.$this->esc($expr[1]).'"')).
-					(empty($expr[3])?
-						'':
-						(' title="'.$this->esc($expr[3]).'"')).' />';
-			},
-			$str
-		);
-	}
-
-	/**
-	*	Process anchor span
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _a($str) {
-		return preg_replace_callback(
-			'/(?<!\\\\)\[(.+?)(?!\\\\)\]\h*\(<?(.*?)>?(?:\h*"(.*?)"\h*)?\)/',
-			function($expr) {
-				return '<a href="'.$this->esc($expr[2]).'"'.
-					(empty($expr[3])?
-						'':
-						(' title="'.$this->esc($expr[3]).'"')).
-					'>'.$this->scan($expr[1]).'</a>';
-			},
-			$str
-		);
-	}
-
-	/**
-	*	Auto-convert links
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _auto($str) {
-		return preg_replace_callback(
-			'/`.*?<(.+?)>.*?`|<(.+?)>/',
-			function($expr) {
-				if (empty($expr[1]) && parse_url($expr[2],PHP_URL_SCHEME)) {
-					$expr[2]=$this->esc($expr[2]);
-					return '<a href="'.$expr[2].'">'.$expr[2].'</a>';
-				}
-				return $expr[0];
-			},
-			$str
-		);
-	}
-
-	/**
-	*	Process code span
-	*	@return string
-	*	@param $str string
-	**/
-	protected function _code($str) {
-		return preg_replace_callback(
-			'/`` (.+?) ``|(?<!\\\\)`(.+?)(?!\\\\)`/',
-			function($expr) {
-				return '<code>'.
-					$this->esc(empty($expr[1])?$expr[2]:$expr[1]).'</code>';
-			},
-			$str
-		);
-	}
-
-	/**
-	*	Convert characters to HTML entities
-	*	@return string
-	*	@param $str string
-	**/
-	function esc($str) {
-		if (!$this->special)
-			$this->special=[
-				'...'=>'&hellip;',
-				'(tm)'=>'&trade;',
-				'(r)'=>'&reg;',
-				'(c)'=>'&copy;'
-			];
-		foreach ($this->special as $key=>$val)
-			$str=preg_replace('/'.preg_quote($key,'/').'/i',$val,$str);
-		return htmlspecialchars($str,ENT_COMPAT,
-			Base::instance()->ENCODING,FALSE);
-	}
-
-	/**
-	*	Reduce multiple line feeds
-	*	@return string
-	*	@param $str string
-	**/
-	protected function snip($str) {
-		return preg_replace('/(?:(?<=\n)\n+)|\n+$/',"\n",$str);
-	}
-
-	/**
-	*	Scan line for convertible spans
-	*	@return string
-	*	@param $str string
-	**/
-	function scan($str) {
-		$inline=['img','a','text','auto','code'];
-		foreach ($inline as $func)
-			$str=$this->{'_'.$func}($str);
-		return $str;
-	}
-
-	/**
-	*	Assemble blocks
-	*	@return string
-	*	@param $str string
-	**/
-	protected function build($str) {
-		if (!$this->blocks) {
-			// Regexes for capturing entire blocks
-			$this->blocks=[
-				'blockquote'=>'/^(?:\h?>\h?.*?(?:\n+|$))+/',
-				'pre'=>'/^(?:(?: {4}|\t).+?(?:\n+|$))+/',
-				'fence'=>'/^`{3}\h*(\w+)?.*?[^\n]*\n+(.+?)`{3}[^\n]*'.
-					'(?:\n+|$)/s',
-				'hr'=>'/^\h*[*_-](?:\h?[\*_-]){2,}\h*(?:\n+|$)/',
-				'atx'=>'/^\h*(#{1,6})\h?(.+?)\h*(?:#.*)?(?:\n+|$)/',
-				'setext'=>'/^\h*(.+?)\h*\n([=-])+\h*(?:\n+|$)/',
-				'li'=>'/^(?:(?:[*+-]|\d+\.)\h.+?(?:\n+|$)'.
-					'(?:(?: {4}|\t)+.+?(?:\n+|$))*)+/s',
-				'raw'=>'/^((?:<!--.+?-->|'.
-					'<(address|article|aside|audio|blockquote|canvas|dd|'.
-					'div|dl|fieldset|figcaption|figure|footer|form|h\d|'.
-					'header|hgroup|hr|noscript|object|ol|output|p|pre|'.
-					'section|table|tfoot|ul|video).*?'.
-					'(?:\/>|>(?:(?>[^><]+)|(?R))*<\/\2>))'.
-					'\h*(?:\n{2,}|\n*$)|<[\?%].+?[\?%]>\h*(?:\n?$|\n*))/s',
-				'p'=>'/^(.+?(?:\n{2,}|\n*$))/s'
-			];
-		}
-		// Treat lines with nothing but whitespaces as empty lines
-		$str=preg_replace('/\n\h+(?=\n)/',"\n",$str);
-		// Initialize block parser
-		$len=strlen($str);
-		$ptr=0;
-		$dst='';
-		// Main loop
-		while ($ptr<$len) {
-			if (preg_match('/^ {0,3}\[([^\[\]]+)\]:\s*<?(.*?)>?\s*'.
-				'(?:"([^\n]*)")?(?:\n+|$)/s',substr($str,$ptr),$match)) {
-				// Reference-style link; Backtrack
-				$ptr+=strlen($match[0]);
-				$tmp='';
-				// Catch line breaks in title attribute
-				$ref=preg_replace('/\h/','\s',preg_quote($match[1],'/'));
-				while ($dst!=$tmp) {
-					$dst=preg_replace_callback(
-						'/(?<!\\\\)\[('.$ref.')(?!\\\\)\]\s*\[\]|'.
-						'(!?)(?:\[([^\[\]]+)\]\s*)?'.
-						'(?<!\\\\)\[('.$ref.')(?!\\\\)\]/',
-						function($expr) use($match) {
-							return (empty($expr[2]))?
-								// Anchor
-								('<a href="'.$this->esc($match[2]).'"'.
-								(empty($match[3])?
-									'':
-									(' title="'.
-										$this->esc($match[3]).'"')).'>'.
-								// Link
-								$this->scan(
-									empty($expr[3])?
-										(empty($expr[1])?
-											$expr[4]:
-											$expr[1]):
-										$expr[3]
-								).'</a>'):
-								// Image
-								('<img src="'.$match[2].'"'.
-								(empty($expr[2])?
-									'':
-									(' alt="'.
-										$this->esc($expr[3]).'"')).
-								(empty($match[3])?
-									'':
-									(' title="'.
-										$this->esc($match[3]).'"')).
-								' />');
-						},
-						$tmp=$dst
-					);
-				}
-			}
-			else
-				foreach ($this->blocks as $func=>$regex)
-					if (preg_match($regex,substr($str,$ptr),$match)) {
-						$ptr+=strlen($match[0]);
-						$dst.=call_user_func_array(
-							[$this,'_'.$func],
-							count($match)>1?array_slice($match,1):$match
-						);
-						break;
-					}
-		}
-		return $dst;
-	}
-
-	/**
-	*	Render HTML equivalent of markdown
-	*	@return string
-	*	@param $txt string
-	**/
-	function convert($txt) {
-		$txt=preg_replace_callback(
-			'/(<code.*?>.+?<\/code>|'.
-			'<[^>\n]+>|\([^\n\)]+\)|"[^"\n]+")|'.
-			'\\\\(.)/s',
-			function($expr) {
-				// Process escaped characters
-				return empty($expr[1])?$expr[2]:$expr[1];
-			},
-			$this->build(preg_replace('/\r\n|\r/',"\n",$txt))
-		);
-		return $this->snip($txt);
-	}
-
-}

+ 0 - 139
lib/f3/matrix.php

@@ -1,139 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Generic array utilities
-class Matrix extends Prefab {
-
-	/**
-	*	Retrieve values from a specified column of a multi-dimensional
-	*	array variable
-	*	@return array
-	*	@param $var array
-	*	@param $col mixed
-	**/
-	function pick(array $var,$col) {
-		return array_map(
-			function($row) use($col) {
-				return $row[$col];
-			},
-			$var
-		);
-	}
-
-	/**
-	 * select a subset of fields from an input array
-	 * @param string|array $fields splittable string or array
-	 * @param string|array $data hive key or array
-	 * @return array
-	 */
-	function select($fields, $data) {
-		return array_intersect_key(is_array($data) ? $data : \Base::instance()->get($data),
-			array_flip(is_array($fields) ? $fields : \Base::instance()->split($fields)));
-	}
-
-	/**
-	 * walk with a callback function through a subset of fields from an input array
-	 * the callback receives the value, index-key and the full input array as parameters
-	 * set value parameter as reference and you're able to modify the data as well
-	 * @param string|array $fields splittable string or array of fields
-	 * @param string|array $data hive key or input array
-	 * @param callable $callback (mixed &$value, string $key, array $data)
-	 * @return array modified subset data
-	 */
-	function walk($fields, $data, $callback) {
-		$subset=$this->select($fields, $data);
-		array_walk($subset, $callback, $data);
-		return $subset;
-	}
-
-	/**
-	*	Rotate a two-dimensional array variable
-	*	@return NULL
-	*	@param $var array
-	**/
-	function transpose(array &$var) {
-		$out=[];
-		foreach ($var as $keyx=>$cols)
-			foreach ($cols as $keyy=>$valy)
-				$out[$keyy][$keyx]=$valy;
-		$var=$out;
-	}
-
-	/**
-	*	Sort a multi-dimensional array variable on a specified column
-	*	@return bool
-	*	@param $var array
-	*	@param $col mixed
-	*	@param $order int
-	**/
-	function sort(array &$var,$col,$order=SORT_ASC) {
-		uasort(
-			$var,
-			function($val1,$val2) use($col,$order) {
-				list($v1,$v2)=[$val1[$col],$val2[$col]];
-				$out=is_numeric($v1) && is_numeric($v2)?
-					Base::instance()->sign($v1-$v2):strcmp($v1,$v2);
-				if ($order==SORT_DESC)
-					$out=-$out;
-				return $out;
-			}
-		);
-		$var=array_values($var);
-	}
-
-	/**
-	*	Change the key of a two-dimensional array element
-	*	@return NULL
-	*	@param $var array
-	*	@param $old string
-	*	@param $new string
-	**/
-	function changekey(array &$var,$old,$new) {
-		$keys=array_keys($var);
-		$vals=array_values($var);
-		$keys[array_search($old,$keys)]=$new;
-		$var=array_combine($keys,$vals);
-	}
-
-	/**
-	*	Return month calendar of specified date, with optional setting for
-	*	first day of week (0 for Sunday)
-	*	@return array
-	*	@param $date string|int
-	*	@param $first int
-	**/
-	function calendar($date='now',$first=0) {
-		$out=FALSE;
-		if (extension_loaded('calendar')) {
-			if (is_string($date))
-				$date=strtotime($date);
-			$parts=getdate($date);
-			$days=cal_days_in_month(CAL_GREGORIAN,$parts['mon'],$parts['year']);
-			$ref=date('w',strtotime(date('Y-m',$parts[0]).'-01'))+(7-$first)%7;
-			$out=[];
-			for ($i=0;$i<$days;$i++)
-				$out[floor(($ref+$i)/7)][($ref+$i)%7]=$i+1;
-		}
-		return $out;
-	}
-
-}

+ 0 - 196
lib/f3/session.php

@@ -1,196 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Cache-based session handler
-class Session {
-
-	protected
-		//! Session ID
-		$sid,
-		//! Anti-CSRF token
-		$_csrf,
-		//! User agent
-		$_agent,
-		//! IP,
-		$_ip,
-		//! Suspect callback
-		$onsuspect,
-		//! Cache instance
-		$_cache;
-
-	/**
-	*	Open session
-	*	@return TRUE
-	*	@param $path string
-	*	@param $name string
-	**/
-	function open($path,$name) {
-		return TRUE;
-	}
-
-	/**
-	*	Close session
-	*	@return TRUE
-	**/
-	function close() {
-		$this->sid=NULL;
-		return TRUE;
-	}
-
-	/**
-	*	Return session data in serialized format
-	*	@return string
-	*	@param $id string
-	**/
-	function read($id) {
-		$this->sid=$id;
-		if (!$data=$this->_cache->get($id.'.@'))
-			return '';
-		if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) {
-			$fw=Base::instance();
-			if (!isset($this->onsuspect) ||
-				$fw->call($this->onsuspect,[$this,$id])===FALSE) {
-				//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
-				$this->destroy($id);
-				$this->close();
-				unset($fw->{'COOKIE.'.session_name()});
-				$fw->error(403);
-			}
-		}
-		return $data['data'];
-	}
-
-	/**
-	*	Write session data
-	*	@return TRUE
-	*	@param $id string
-	*	@param $data string
-	**/
-	function write($id,$data) {
-		$fw=Base::instance();
-		$jar=$fw->JAR;
-		$this->_cache->set($id.'.@',
-			[
-				'data'=>$data,
-				'ip'=>$this->_ip,
-				'agent'=>$this->_agent,
-				'stamp'=>time()
-			],
-			$jar['expire']
-		);
-		return TRUE;
-	}
-
-	/**
-	*	Destroy session
-	*	@return TRUE
-	*	@param $id string
-	**/
-	function destroy($id) {
-		$this->_cache->clear($id.'.@');
-		return TRUE;
-	}
-
-	/**
-	*	Garbage collector
-	*	@return TRUE
-	*	@param $max int
-	**/
-	function cleanup($max) {
-		$this->_cache->reset('.@',$max);
-		return TRUE;
-	}
-
-	/**
-	 *	Return session id (if session has started)
-	 *	@return string|NULL
-	 **/
-	function sid() {
-		return $this->sid;
-	}
-
-	/**
-	 *	Return anti-CSRF token
-	 *	@return string
-	 **/
-	function csrf() {
-		return $this->_csrf;
-	}
-
-	/**
-	 *	Return IP address
-	 *	@return string
-	 **/
-	function ip() {
-		return $this->_ip;
-	}
-
-	/**
-	 *	Return Unix timestamp
-	 *	@return string|FALSE
-	 **/
-	function stamp() {
-		if (!$this->sid)
-			session_start();
-		return $this->_cache->exists($this->sid.'.@',$data)?
-			$data['stamp']:FALSE;
-	}
-
-	/**
-	 *	Return HTTP user agent
-	 *	@return string
-	 **/
-	function agent() {
-		return $this->_agent;
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $onsuspect callback
-	*	@param $key string
-	**/
-	function __construct($onsuspect=NULL,$key=NULL,$cache=null) {
-		$this->onsuspect=$onsuspect;
-		$this->_cache=$cache?:Cache::instance();
-		session_set_save_handler(
-			[$this,'open'],
-			[$this,'close'],
-			[$this,'read'],
-			[$this,'write'],
-			[$this,'destroy'],
-			[$this,'cleanup']
-		);
-		register_shutdown_function('session_commit');
-		$fw=\Base::instance();
-		$headers=$fw->HEADERS;
-		$this->_csrf=$fw->hash($fw->SEED.
-			extension_loaded('openssl')?
-				implode(unpack('L',openssl_random_pseudo_bytes(4))):
-				mt_rand()
-			);
-		if ($key)
-			$fw->$key=$this->_csrf;
-		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		$this->_ip=$fw->IP;
-	}
-
-}

+ 0 - 360
lib/f3/smtp.php

@@ -1,360 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! SMTP plug-in
-class SMTP extends Magic {
-
-	//@{ Locale-specific error/exception messages
-	const
-		E_Header='%s: header is required',
-		E_Blank='Message must not be blank',
-		E_Attach='Attachment %s not found';
-	//@}
-
-	protected
-		//! Message properties
-		$headers,
-		//! E-mail attachments
-		$attachments,
-		//! SMTP host
-		$host,
-		//! SMTP port
-		$port,
-		//! TLS/SSL
-		$scheme,
-		//! User ID
-		$user,
-		//! Password
-		$pw,
-		//! TLS/SSL stream context
-		$context,
-		//! TCP/IP socket
-		$socket,
-		//! Server-client conversation
-		$log;
-
-	/**
-	*	Fix header
-	*	@return string
-	*	@param $key string
-	**/
-	protected function fixheader($key) {
-		return str_replace(' ','-',
-			ucwords(preg_replace('/[_-]/',' ',strtolower($key))));
-	}
-
-	/**
-	*	Return TRUE if header exists
-	*	@return bool
-	*	@param $key
-	**/
-	function exists($key) {
-		$key=$this->fixheader($key);
-		return isset($this->headers[$key]);
-	}
-
-	/**
-	*	Bind value to e-mail header
-	*	@return string
-	*	@param $key string
-	*	@param $val string
-	**/
-	function set($key,$val) {
-		$key=$this->fixheader($key);
-		return $this->headers[$key]=$val;
-	}
-
-	/**
-	*	Return value of e-mail header
-	*	@return string|NULL
-	*	@param $key string
-	**/
-	function &get($key) {
-		$key=$this->fixheader($key);
-		if (isset($this->headers[$key]))
-			$val=&$this->headers[$key];
-		else
-			$val=NULL;
-		return $val;
-	}
-
-	/**
-	*	Remove header
-	*	@return NULL
-	*	@param $key string
-	**/
-	function clear($key) {
-		$key=$this->fixheader($key);
-		unset($this->headers[$key]);
-	}
-
-	/**
-	*	Return client-server conversation history
-	*	@return string
-	**/
-	function log() {
-		return str_replace("\n",PHP_EOL,$this->log);
-	}
-
-	/**
-	*	Send SMTP command and record server response
-	*	@return string
-	*	@param $cmd string
-	*	@param $log bool|string
-	*	@param $mock bool
-	**/
-	protected function dialog($cmd=NULL,$log=TRUE,$mock=FALSE) {
-		$reply='';
-		if ($mock) {
-			$host=str_replace('ssl://','',$this->host);
-			switch ($cmd) {
-			case NULL:
-				$reply='220 '.$host.' ESMTP ready'."\n";
-				break;
-			case 'DATA':
-				$reply='354 Go ahead'."\n";
-				break;
-			case 'QUIT':
-				$reply='221 '.$host.' closing connection'."\n";
-				break;
-			default:
-				$reply='250 OK'."\n";
-				break;
-			}
-		}
-		else {
-			$socket=&$this->socket;
-			if ($cmd)
-				fputs($socket,$cmd."\r\n");
-			while (!feof($socket) && ($info=stream_get_meta_data($socket)) &&
-				!$info['timed_out'] && $str=fgets($socket,4096)) {
-				$reply.=$str;
-				if (preg_match('/(?:^|\n)\d{3} .+?\r\n/s',$reply))
-					break;
-			}
-		}
-		if ($log) {
-			if ($cmd)
-				$this->log.=$cmd."\n";
-			$this->log.=str_replace("\r",'',$reply);
-		}
-		return $reply;
-	}
-
-	/**
-	*	Add e-mail attachment
-	*	@return NULL
-	*	@param $file string
-	*	@param $alias string
-	*	@param $cid string
-	**/
-	function attach($file,$alias=NULL,$cid=NULL) {
-		if (!is_file($file))
-			user_error(sprintf(self::E_Attach,$file),E_USER_ERROR);
-		if ($alias)
-			$file=[$alias,$file];
-		$this->attachments[]=['filename'=>$file,'cid'=>$cid];
-	}
-
-	/**
-	*	Transmit message
-	*	@return bool
-	*	@param $message string
-	*	@param $log bool|string
-	*	@param $mock bool
-	**/
-	function send($message,$log=TRUE,$mock=FALSE) {
-		if ($this->scheme=='ssl' && !extension_loaded('openssl'))
-			return FALSE;
-		// Message should not be blank
-		if (!$message)
-			user_error(self::E_Blank,E_USER_ERROR);
-		$fw=Base::instance();
-		// Retrieve headers
-		$headers=$this->headers;
-		// Connect to the server
-		if (!$mock) {
-			$socket=&$this->socket;
-			$socket=@stream_socket_client($this->host.':'.$this->port,
-				$errno,$errstr,ini_get('default_socket_timeout'),
-				STREAM_CLIENT_CONNECT,$this->context);
-			if (!$socket) {
-				$fw->error(500,$errstr);
-				return FALSE;
-			}
-			stream_set_blocking($socket,TRUE);
-		}
-		// Get server's initial response
-		$this->dialog(NULL,$log,$mock);
-		// Announce presence
-		$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
-		if (strtolower($this->scheme)=='tls') {
-			$this->dialog('STARTTLS',$log,$mock);
-			if (!$mock) {
-				$method=STREAM_CRYPTO_METHOD_TLS_CLIENT;
-				if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
-					$method|=STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
-					$method|=STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
-				}
-				stream_socket_enable_crypto($socket,TRUE,$method);
-			}
-			$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
-		}
-		$message=wordwrap($message,998);
-		if (preg_match('/8BITMIME/',$reply))
-			$headers['Content-Transfer-Encoding']='8bit';
-		else {
-			$headers['Content-Transfer-Encoding']='quoted-printable';
-			$message=preg_replace('/^\.(.+)/m',
-				'..$1',quoted_printable_encode($message));
-		}
-		if ($this->user && $this->pw && preg_match('/AUTH/',$reply)) {
-			// Authenticate
-			$this->dialog('AUTH LOGIN',$log,$mock);
-			$this->dialog(base64_encode($this->user),$log,$mock);
-			$reply=$this->dialog(base64_encode($this->pw),$log,$mock);
-			if (!preg_match('/^235\s.*/',$reply)) {
-				$this->dialog('QUIT',$log,$mock);
-				if (!$mock && $socket)
-					fclose($socket);
-				return FALSE;
-			}
-		}
-		if (empty($headers['Message-Id']))
-			$headers['Message-Id']='<'.uniqid('',TRUE).'@'.$this->host.'>';
-		if (empty($headers['Date']))
-			$headers['Date']=date('r');
-		// Required headers
-		$reqd=['From','To','Subject'];
-		foreach ($reqd as $id)
-			if (empty($headers[$id]))
-				user_error(sprintf(self::E_Header,$id),E_USER_ERROR);
-		$eol="\r\n";
-		// Stringify headers
-		foreach ($headers as $key=>&$val) {
-			if (in_array($key,['From','To','Cc','Bcc'])) {
-				$email='';
-				preg_match_all('/(?:".+?" )?(?:<.+?>|[^ ,]+)/',
-					$val,$matches,PREG_SET_ORDER);
-				foreach ($matches as $raw)
-					$email.=($email?', ':'').
-						(preg_match('/<.+?>/',$raw[0])?
-							$raw[0]:
-							('<'.$raw[0].'>'));
-				$val=$email;
-			}
-			unset($val);
-		}
-		$from=isset($headers['Sender'])?$headers['Sender']:strstr($headers['From'],'<');
-		unset($headers['Sender']);
-		// Start message dialog
-		$this->dialog('MAIL FROM: '.$from,$log,$mock);
-		foreach ($fw->split($headers['To'].
-			(isset($headers['Cc'])?(';'.$headers['Cc']):'').
-			(isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst) {
-			$this->dialog('RCPT TO: '.strstr($dst,'<'),$log,$mock);
-		}
-		$this->dialog('DATA',$log,$mock);
-		if ($this->attachments) {
-			// Replace Content-Type
-			$type=$headers['Content-Type'];
-			unset($headers['Content-Type']);
-			$enc=$headers['Content-Transfer-Encoding'];
-			unset($headers['Content-Transfer-Encoding']);
-			$hash=uniqid(NULL,TRUE);
-			// Send mail headers
-			$out='Content-Type: multipart/mixed; boundary="'.$hash.'"'.$eol;
-			foreach ($headers as $key=>$val)
-				if ($key!='Bcc')
-					$out.=$key.': '.$val.$eol;
-			$out.=$eol;
-			$out.='This is a multi-part message in MIME format'.$eol;
-			$out.=$eol;
-			$out.='--'.$hash.$eol;
-			$out.='Content-Type: '.$type.$eol;
-			$out.='Content-Transfer-Encoding: '.$enc.$eol;
-			$out.=$eol;
-			$out.=$message.$eol;
-			foreach ($this->attachments as $attachment) {
-				if (is_array($attachment['filename']))
-					list($alias,$file)=$attachment['filename'];
-				else
-					$alias=basename($file=$attachment['filename']);
-				$out.='--'.$hash.$eol;
-				$out.='Content-Type: application/octet-stream'.$eol;
-				$out.='Content-Transfer-Encoding: base64'.$eol;
-				if ($attachment['cid'])
-					$out.='Content-Id: '.$attachment['cid'].$eol;
-				$out.='Content-Disposition: attachment; '.
-					'filename="'.$alias.'"'.$eol;
-				$out.=$eol;
-				$out.=chunk_split(base64_encode(
-					file_get_contents($file))).$eol;
-			}
-			$out.=$eol;
-			$out.='--'.$hash.'--'.$eol;
-			$out.='.';
-			$this->dialog($out,preg_match('/verbose/i',$log),$mock);
-		}
-		else {
-			// Send mail headers
-			$out='';
-			foreach ($headers as $key=>$val)
-				if ($key!='Bcc')
-					$out.=$key.': '.$val.$eol;
-			$out.=$eol;
-			$out.=$message.$eol;
-			$out.='.';
-			// Send message
-			$this->dialog($out,preg_match('/verbose/i',$log),$mock);
-		}
-		$this->dialog('QUIT',$log,$mock);
-		if (!$mock && $socket)
-			fclose($socket);
-		return TRUE;
-	}
-
-	/**
-	*	Instantiate class
-	*	@param $host string
-	*	@param $port int
-	*	@param $scheme string
-	*	@param $user string
-	*	@param $pw string
-	*	@param $ctx resource
-	**/
-	function __construct(
-		$host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL,$ctx=NULL) {
-		$this->headers=[
-			'MIME-Version'=>'1.0',
-			'Content-Type'=>'text/plain; '.
-				'charset='.Base::instance()->ENCODING
-		];
-		$this->host=strtolower((($this->scheme=strtolower($scheme))=='ssl'?
-			'ssl':'tcp').'://'.$host);
-		$this->port=$port;
-		$this->user=$user;
-		$this->pw=$pw;
-		$this->context=stream_context_create($ctx);
-	}
-
-}

+ 0 - 353
lib/f3/template.php

@@ -1,353 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! XML-style template engine
-class Template extends Preview {
-
-	//@{ Error messages
-	const
-		E_Method='Call to undefined method %s()';
-	//@}
-
-	protected
-		//! Template tags
-		$tags,
-		//! Custom tag handlers
-		$custom=[];
-
-	/**
-	*	Template -set- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _set(array $node) {
-		$out='';
-		foreach ($node['@attrib'] as $key=>$val)
-			$out.='$'.$key.'='.
-				(preg_match('/\{\{(.+?)\}\}/',$val)?
-					$this->token($val):
-					Base::instance()->stringify($val)).'; ';
-		return '<?php '.$out.'?>';
-	}
-
-	/**
-	*	Template -include- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _include(array $node) {
-		$attrib=$node['@attrib'];
-		$hive=isset($attrib['with']) &&
-			($attrib['with']=$this->token($attrib['with'])) &&
-			preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/',
-				$attrib['with'],$pairs,PREG_SET_ORDER)?
-					('['.implode(',',
-						array_map(function($pair) {
-							return '\''.$pair[1].'\'=>'.
-								(preg_match('/^\'.*\'$/',$pair[2]) ||
-									preg_match('/\$/',$pair[2])?
-									$pair[2]:Base::instance()->stringify(
-										Base::instance()->cast($pair[2])));
-						},$pairs)).']+get_defined_vars()'):
-					'get_defined_vars()';
-		$ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0;
-		return
-			'<?php '.(isset($attrib['if'])?
-				('if ('.$this->token($attrib['if']).') '):'').
-				('echo $this->render('.
-					(preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])?
-						$this->token($attrib['href']):
-						Base::instance()->stringify($attrib['href'])).','.
-					'NULL,'.$hive.','.$ttl.'); ?>');
-	}
-
-	/**
-	*	Template -exclude- tag handler
-	*	@return string
-	**/
-	protected function _exclude() {
-		return '';
-	}
-
-	/**
-	*	Template -ignore- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _ignore(array $node) {
-		return $node[0];
-	}
-
-	/**
-	*	Template -loop- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _loop(array $node) {
-		$attrib=$node['@attrib'];
-		unset($node['@attrib']);
-		return
-			'<?php for ('.
-				$this->token($attrib['from']).';'.
-				$this->token($attrib['to']).';'.
-				$this->token($attrib['step']).'): ?>'.
-				$this->build($node).
-			'<?php endfor; ?>';
-	}
-
-	/**
-	*	Template -repeat- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _repeat(array $node) {
-		$attrib=$node['@attrib'];
-		unset($node['@attrib']);
-		return
-			'<?php '.
-				(isset($attrib['counter'])?
-					(($ctr=$this->token($attrib['counter'])).'=0; '):'').
-				'foreach (('.
-				$this->token($attrib['group']).'?:[]) as '.
-				(isset($attrib['key'])?
-					($this->token($attrib['key']).'=>'):'').
-				$this->token($attrib['value']).'):'.
-				(isset($ctr)?(' '.$ctr.'++;'):'').' ?>'.
-				$this->build($node).
-			'<?php endforeach; ?>';
-	}
-
-	/**
-	*	Template -check- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _check(array $node) {
-		$attrib=$node['@attrib'];
-		unset($node['@attrib']);
-		// Grab <true> and <false> blocks
-		foreach ($node as $pos=>$block)
-			if (isset($block['true']))
-				$true=[$pos,$block];
-			elseif (isset($block['false']))
-				$false=[$pos,$block];
-		if (isset($true,$false) && $true[0]>$false[0])
-			// Reverse <true> and <false> blocks
-			list($node[$true[0]],$node[$false[0]])=[$false[1],$true[1]];
-		return
-			'<?php if ('.$this->token($attrib['if']).'): ?>'.
-				$this->build($node).
-			'<?php endif; ?>';
-	}
-
-	/**
-	*	Template -true- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _true(array $node) {
-		return $this->build($node);
-	}
-
-	/**
-	*	Template -false- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _false(array $node) {
-		return '<?php else: ?>'.$this->build($node);
-	}
-
-	/**
-	*	Template -switch- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _switch(array $node) {
-		$attrib=$node['@attrib'];
-		unset($node['@attrib']);
-		foreach ($node as $pos=>$block)
-			if (is_string($block) && !preg_replace('/\s+/','',$block))
-				unset($node[$pos]);
-		return
-			'<?php switch ('.$this->token($attrib['expr']).'): ?>'.
-				$this->build($node).
-			'<?php endswitch; ?>';
-	}
-
-	/**
-	*	Template -case- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _case(array $node) {
-		$attrib=$node['@attrib'];
-		unset($node['@attrib']);
-		return
-			'<?php case '.(preg_match('/\{\{(.+?)\}\}/',$attrib['value'])?
-				$this->token($attrib['value']):
-				Base::instance()->stringify($attrib['value'])).': ?>'.
-				$this->build($node).
-			'<?php '.(isset($attrib['break'])?
-				'if ('.$this->token($attrib['break']).') ':'').
-				'break; ?>';
-	}
-
-	/**
-	*	Template -default- tag handler
-	*	@return string
-	*	@param $node array
-	**/
-	protected function _default(array $node) {
-		return
-			'<?php default: ?>'.
-				$this->build($node).
-			'<?php break; ?>';
-	}
-
-	/**
-	*	Assemble markup
-	*	@return string
-	*	@param $node array|string
-	**/
-	function build($node) {
-		if (is_string($node))
-			return parent::build($node);
-		$out='';
-		foreach ($node as $key=>$val)
-			$out.=is_int($key)?$this->build($val):$this->{'_'.$key}($val);
-		return $out;
-	}
-
-	/**
-	*	Extend template with custom tag
-	*	@return NULL
-	*	@param $tag string
-	*	@param $func callback
-	**/
-	function extend($tag,$func) {
-		$this->tags.='|'.$tag;
-		$this->custom['_'.$tag]=$func;
-	}
-
-	/**
-	*	Call custom tag handler
-	*	@return string|FALSE
-	*	@param $func string
-	*	@param $args array
-	**/
-	function __call($func,array $args) {
-		if ($func[0]=='_')
-			return call_user_func_array($this->custom[$func],$args);
-		if (method_exists($this,$func))
-			return call_user_func_array([$this,$func],$args);
-		user_error(sprintf(self::E_Method,$func),E_USER_ERROR);
-	}
-
-	/**
-	*	Parse string for template directives and tokens
-	*	@return array
-	*	@param $text string
-	**/
-	function parse($text) {
-		$text=parent::parse($text);
-		// Build tree structure
-		for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;)
-			if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
-				'('.$this->tags.')\b((?:\s+[\w.:@!-]+'.
-				'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
-				'\h*\{\{.+?\}\})*)\h*(\/?)>/is',
-				substr($text,$ptr),$match)) {
-				if (strlen($tmp) || $match[1])
-					$tree[]=$tmp.$match[1];
-				// Element node
-				if ($match[2]) {
-					// Find matching start tag
-					$stack=[];
-					for($i=count($tree)-1;$i>=0;$i--) {
-						$item=$tree[$i];
-						if (is_array($item) &&
-							array_key_exists($match[3],$item) &&
-							!isset($item[$match[3]][0])) {
-							// Start tag found
-							$tree[$i][$match[3]]+=array_reverse($stack);
-							$tree=array_slice($tree,0,$i+1);
-							break;
-						}
-						else $stack[]=$item;
-					}
-				}
-				else {
-					// Start tag
-					$node=&$tree[][$match[3]];
-					$node=[];
-					if ($match[4]) {
-						// Process attributes
-						preg_match_all(
-							'/(?:(\{\{.+?\}\})|([^\s\/"\'=]+))'.
-							'\h*(?:=\h*(?:"(.*?)"|\'(.*?)\'))?/s',
-							$match[4],$attr,PREG_SET_ORDER);
-						foreach ($attr as $kv)
-							if (!empty($kv[1]) && !isset($kv[3]) && !isset($kv[4]))
-								$node['@attrib'][]=$kv[1];
-							else
-								$node['@attrib'][$kv[1]?:$kv[2]]=
-									(isset($kv[3]) && $kv[3]!==''?
-										$kv[3]:
-										(isset($kv[4]) && $kv[4]!==''?
-											$kv[4]:NULL));
-					}
-				}
-				$tmp='';
-				$ptr+=strlen($match[0]);
-				$w=5;
-			}
-			else {
-				// Text node
-				$tmp.=substr($text,$ptr,$w);
-				$ptr+=$w;
-				if ($w<50)
-					$w++;
-			}
-		if (strlen($tmp))
-			// Append trailing text
-			$tree[]=$tmp;
-		// Break references
-		unset($node);
-		return $tree;
-	}
-
-	/**
-	*	Class constructor
-	*	return object
-	**/
-	function __construct() {
-		$ref=new ReflectionClass(get_called_class());
-		$this->tags='';
-		foreach ($ref->getmethods() as $method)
-			if (preg_match('/^_(?=[[:alpha:]])/',$method->name))
-				$this->tags.=(strlen($this->tags)?'|':'').
-					substr($method->name,1);
-		parent::__construct();
-	}
-
-}

+ 0 - 98
lib/f3/test.php

@@ -1,98 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Unit test kit
-class Test {
-
-	//@{ Reporting level
-	const
-		FLAG_False=0,
-		FLAG_True=1,
-		FLAG_Both=2;
-	//@}
-
-	protected
-		//! Test results
-		$data=[],
-		//! Success indicator
-		$passed=TRUE,
-		//! Reporting level
-		$level;
-
-	/**
-	*	Return test results
-	*	@return array
-	**/
-	function results() {
-		return $this->data;
-	}
-
-	/**
-	*	Return FALSE if at least one test case fails
-	*	@return bool
-	**/
-	function passed() {
-		return $this->passed;
-	}
-
-	/**
-	*	Evaluate condition and save test result
-	*	@return object
-	*	@param $cond bool
-	*	@param $text string
-	**/
-	function expect($cond,$text=NULL) {
-		$out=(bool)$cond;
-		if ($this->level==$out || $this->level==self::FLAG_Both) {
-			$data=['status'=>$out,'text'=>$text,'source'=>NULL];
-			foreach (debug_backtrace() as $frame)
-				if (isset($frame['file'])) {
-					$data['source']=Base::instance()->
-						fixslashes($frame['file']).':'.$frame['line'];
-					break;
-				}
-			$this->data[]=$data;
-		}
-		if (!$out && $this->passed)
-			$this->passed=FALSE;
-		return $this;
-	}
-
-	/**
-	*	Append message to test results
-	*	@return NULL
-	*	@param $text string
-	**/
-	function message($text) {
-		$this->expect(TRUE,$text);
-	}
-
-	/**
-	*	Class constructor
-	*	@return NULL
-	*	@param $level int
-	**/
-	function __construct($level=self::FLAG_Both) {
-		$this->level=$level;
-	}
-
-}

+ 0 - 199
lib/f3/utf.php

@@ -1,199 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Unicode string manager
-class UTF extends Prefab {
-
-	/**
-	*	Get string length
-	*	@return int
-	*	@param $str string
-	**/
-	function strlen($str) {
-		preg_match_all('/./us',$str,$parts);
-		return count($parts[0]);
-	}
-
-	/**
-	*	Reverse a string
-	*	@return string
-	*	@param $str string
-	**/
-	function strrev($str) {
-		preg_match_all('/./us',$str,$parts);
-		return implode('',array_reverse($parts[0]));
-	}
-
-	/**
-	*	Find position of first occurrence of a string (case-insensitive)
-	*	@return int|FALSE
-	*	@param $stack string
-	*	@param $needle string
-	*	@param $ofs int
-	**/
-	function stripos($stack,$needle,$ofs=0) {
-		return $this->strpos($stack,$needle,$ofs,TRUE);
-	}
-
-	/**
-	*	Find position of first occurrence of a string
-	*	@return int|FALSE
-	*	@param $stack string
-	*	@param $needle string
-	*	@param $ofs int
-	*	@param $case bool
-	**/
-	function strpos($stack,$needle,$ofs=0,$case=FALSE) {
-		return preg_match('/^(.{'.$ofs.'}.*?)'.
-			preg_quote($needle,'/').'/us'.($case?'i':''),$stack,$match)?
-			$this->strlen($match[1]):FALSE;
-	}
-
-	/**
-	*	Returns part of haystack string from the first occurrence of
-	*	needle to the end of haystack (case-insensitive)
-	*	@return string|FALSE
-	*	@param $stack string
-	*	@param $needle string
-	*	@param $before bool
-	**/
-	function stristr($stack,$needle,$before=FALSE) {
-		return $this->strstr($stack,$needle,$before,TRUE);
-	}
-
-	/**
-	*	Returns part of haystack string from the first occurrence of
-	*	needle to the end of haystack
-	*	@return string|FALSE
-	*	@param $stack string
-	*	@param $needle string
-	*	@param $before bool
-	*	@param $case bool
-	**/
-	function strstr($stack,$needle,$before=FALSE,$case=FALSE) {
-		if (!$needle)
-			return FALSE;
-		preg_match('/^(.*?)'.preg_quote($needle,'/').'/us'.($case?'i':''),
-			$stack,$match);
-		return isset($match[1])?
-			($before?
-				$match[1]:
-				$this->substr($stack,$this->strlen($match[1]))):
-			FALSE;
-	}
-
-	/**
-	*	Return part of a string
-	*	@return string|FALSE
-	*	@param $str string
-	*	@param $start int
-	*	@param $len int
-	**/
-	function substr($str,$start,$len=0) {
-		if ($start<0)
-			$start=$this->strlen($str)+$start;
-		if (!$len)
-			$len=$this->strlen($str)-$start;
-		return preg_match('/^.{'.$start.'}(.{0,'.$len.'})/us',$str,$match)?
-			$match[1]:FALSE;
-	}
-
-	/**
-	*	Count the number of substring occurrences
-	*	@return int
-	*	@param $stack string
-	*	@param $needle string
-	**/
-	function substr_count($stack,$needle) {
-		preg_match_all('/'.preg_quote($needle,'/').'/us',$stack,
-			$matches,PREG_SET_ORDER);
-		return count($matches);
-	}
-
-	/**
-	*	Strip whitespaces from the beginning of a string
-	*	@return string
-	*	@param $str string
-	**/
-	function ltrim($str) {
-		return preg_replace('/^[\pZ\pC]+/u','',$str);
-	}
-
-	/**
-	*	Strip whitespaces from the end of a string
-	*	@return string
-	*	@param $str string
-	**/
-	function rtrim($str) {
-		return preg_replace('/[\pZ\pC]+$/u','',$str);
-	}
-
-	/**
-	*	Strip whitespaces from the beginning and end of a string
-	*	@return string
-	*	@param $str string
-	**/
-	function trim($str) {
-		return preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u','',$str);
-	}
-
-	/**
-	*	Return UTF-8 byte order mark
-	*	@return string
-	**/
-	function bom() {
-		return chr(0xef).chr(0xbb).chr(0xbf);
-	}
-
-	/**
-	*	Convert code points to Unicode symbols
-	*	@return string
-	*	@param $str string
-	**/
-	function translate($str) {
-		return html_entity_decode(
-			preg_replace('/\\\\u([[:xdigit:]]+)/i','&#x\1;',$str));
-	}
-
-	/**
-	*	Translate emoji tokens to Unicode font-supported symbols
-	*	@return string
-	*	@param $str string
-	**/
-	function emojify($str) {
-		$map=[
-			':('=>'\u2639', // frown
-			':)'=>'\u263a', // smile
-			'<3'=>'\u2665', // heart
-			':D'=>'\u1f603', // grin
-			'XD'=>'\u1f606', // laugh
-			';)'=>'\u1f609', // wink
-			':P'=>'\u1f60b', // tongue
-			':,'=>'\u1f60f', // think
-			':/'=>'\u1f623', // skeptic
-			'8O'=>'\u1f632', // oops
-		]+Base::instance()->EMOJI;
-		return $this->translate(str_replace(array_keys($map),
-			array_values($map),$str));
-	}
-
-}

+ 0 - 1017
lib/f3/web.php

@@ -1,1017 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-//! Wrapper for various HTTP utilities
-class Web extends Prefab {
-
-	//@{ Error messages
-	const
-		E_Request='No suitable HTTP request engine found';
-	//@}
-
-	protected
-		//! HTTP request engine
-		$wrapper;
-
-	/**
-	*	Detect MIME type using file extension or file inspection
-	*	@return string
-	*	@param $file string
-	*	@param $inspect bool
-	**/
-	function mime($file, $inspect=FALSE) {
-		if ($inspect) {
-			if (is_file($file) && is_readable($file)) {
-				// physical files
-				if (extension_loaded('fileinfo'))
-					$mime=mime_content_type($file);
-				elseif (preg_match('/Darwin/i',PHP_OS))
-					$mime=trim(exec('file -bI '.escapeshellarg($file)));
-				elseif (!preg_match('/^win/i',PHP_OS))
-					$mime=trim(exec('file -bi '.escapeshellarg($file)));
-				if (isset($mime) && !empty($mime)){
-					// cut charset information if any
-					$exp=explode(';',$mime,2);
-					$mime=$exp[0];
-				}
-			}
-			else {
-				// remote and stream files
-				if (ini_get('allow_url_fopen') && ($fhandle=fopen($file,'rb'))) {
-					// only get head bytes instead of whole file
-					$bytes=fread($fhandle,20);
-					fclose($fhandle);
-				}
-				elseif (($response=$this->request($file,['method' => 'HEAD']))
-					&& preg_grep('/HTTP\/[\d.]{1,3} 200/',$response['headers'])
-					&& ($type = preg_grep('/^Content-Type:/i',$response['headers']))) {
-					// get mime type directly from response header
-					return preg_replace('/^Content-Type:\s*/i','',array_pop($type));
-				}
-				else // load whole file
-					$bytes=file_get_contents($file);
-				if (extension_loaded('fileinfo')) {
-					// get mime from fileinfo
-					$finfo=finfo_open(FILEINFO_MIME_TYPE);
-					$mime=finfo_buffer($finfo,$bytes);
-				}
-				elseif ($bytes) {
-					// magic number header fallback
-					$map=[
-						'\x64\x6E\x73\x2E'=>'audio/basic',
-						'\x52\x49\x46\x46.{4}\x41\x56\x49\x20\x4C\x49\x53\x54'=>'video/avi',
-						'\x42\x4d'=>'image/bmp',
-						'\x42\x5A\x68'=>'application/x-bzip2',
-						'\x07\x64\x74\x32\x64\x64\x74\x64'=>'application/xml-dtd',
-						'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'=>'application/msword',
-						'\x50\x4B\x03\x04\x14\x00\x06\x00'=>'application/msword',
-						'\x0D\x44\x4F\x43'=>'application/msword',
-						'GIF\d+a'=>'image/gif',
-						'\x1F\x8B'=>'application/x-gzip',
-						'\xff\xd8\xff'=>'image/jpeg',
-						'\x49\x46\x00'=>'image/jpeg',
-						'\xFF\xFB'=>'audio/mpeg',
-						'\x49\x44\x33'=>'audio/mpeg',
-						'\x00\x00\x01\xBA'=>'video/mpeg',
-						'\x4F\x67\x67\x53\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00'=>'audio/vorbis',
-						'\x25\x50\x44\x46'=>'application/pdf',
-						'\x89PNG\x0d\x0a'=>'image/png',
-						'.{4}\x6D\x6F\x6F\x76\x'=>'video/quicktime',
-						'\x53\x49\x54\x21\x00'=>'application/x-stuffit',
-						'\x43\x57\x53'=>'application/x-shockwave-flash',
-						'\x1F\x8B\x08'=>'application/x-tar',
-						'\x49\x20\x49'=>'image/tiff',
-						'\x52\x49\x46\x46.{4}\x57\x41\x56\x45\x66\x6D\x74\x20'=>'audio/wav',
-						'\xFD\xFF\xFF\xFF\x20\x00\x00\x00'=>'application/vnd.ms-excel',
-						'\x50\x4B\x03\x04'=>'application/x-zip-compressed',
-						'[ -~]+$'=>'text/plain',
-					];
-					foreach ($map as $key=>$val)
-						if (preg_match('/^'.$key.'/',substr($bytes,0,128)))
-							return $val;
-				}
-			}
-			if (isset($mime) && !empty($mime))
-				return $mime;
-			// Fallback to file extension-based check if no mime was found yet
-		}
-		if (preg_match('/\w+$/',$file,$ext)) {
-			$map=[
-				'au'=>'audio/basic',
-				'avi'=>'video/avi',
-				'bmp'=>'image/bmp',
-				'bz2'=>'application/x-bzip2',
-				'css'=>'text/css',
-				'dtd'=>'application/xml-dtd',
-				'doc'=>'application/msword',
-				'gif'=>'image/gif',
-				'gz'=>'application/x-gzip',
-				'hqx'=>'application/mac-binhex40',
-				'html?'=>'text/html',
-				'jar'=>'application/java-archive',
-				'jpe?g|jfif?'=>'image/jpeg',
-				'js'=>'application/x-javascript',
-				'midi'=>'audio/x-midi',
-				'mp3'=>'audio/mpeg',
-				'mpe?g'=>'video/mpeg',
-				'ogg'=>'audio/vorbis',
-				'pdf'=>'application/pdf',
-				'png'=>'image/png',
-				'ppt'=>'application/vnd.ms-powerpoint',
-				'ps'=>'application/postscript',
-				'qt'=>'video/quicktime',
-				'ram?'=>'audio/x-pn-realaudio',
-				'rdf'=>'application/rdf',
-				'rtf'=>'application/rtf',
-				'sgml?'=>'text/sgml',
-				'sit'=>'application/x-stuffit',
-				'svg'=>'image/svg+xml',
-				'swf'=>'application/x-shockwave-flash',
-				'tgz'=>'application/x-tar',
-				'tiff'=>'image/tiff',
-				'txt'=>'text/plain',
-				'wav'=>'audio/wav',
-				'xls'=>'application/vnd.ms-excel',
-				'xml'=>'application/xml',
-				'zip'=>'application/x-zip-compressed'
-			];
-			foreach ($map as $key=>$val)
-				if (preg_match('/'.$key.'/',strtolower($ext[0])))
-					return $val;
-		}
-		return 'application/octet-stream';
-	}
-
-	/**
-	*	Return the MIME types stated in the HTTP Accept header as an array;
-	*	If a list of MIME types is specified, return the best match; or
-	*	FALSE if none found
-	*	@return array|string|FALSE
-	*	@param $list string|array
-	**/
-	function acceptable($list=NULL) {
-		$accept=[];
-		foreach (explode(',',str_replace(' ','',@$_SERVER['HTTP_ACCEPT']))
-			as $mime)
-			if (preg_match('/(.+?)(?:;q=([\d\.]+)|$)/',$mime,$parts))
-				$accept[$parts[1]]=isset($parts[2])?$parts[2]:1;
-		if (!$accept)
-			$accept['*/*']=1;
-		else {
-			krsort($accept);
-			arsort($accept);
-		}
-		if ($list) {
-			if (is_string($list))
-				$list=explode(',',$list);
-			foreach ($accept as $mime=>$q)
-				if ($q && $out=preg_grep('/'.
-					str_replace('\*','.*',preg_quote($mime,'/')).'/',$list))
-					return current($out);
-			return FALSE;
-		}
-		return $accept;
-	}
-
-	/**
-	*	Transmit file to HTTP client; Return file size if successful,
-	*	FALSE otherwise
-	*	@return int|FALSE
-	*	@param $file string
-	*	@param $mime string
-	*	@param $kbps int
-	*	@param $force bool
-	*	@param $name string
-	*	@param $flush bool
-	**/
-	function send($file,$mime=NULL,$kbps=0,$force=TRUE,$name=NULL,$flush=TRUE) {
-		if (!is_file($file))
-			return FALSE;
-		$size=filesize($file);
-		if (PHP_SAPI!='cli') {
-			header('Content-Type: '.($mime?:$this->mime($file)));
-			if ($force)
-				header('Content-Disposition: attachment; '.
-					'filename="'.($name!==NULL?$name:basename($file)).'"');
-			header('Accept-Ranges: bytes');
-			header('Content-Length: '.$size);
-			header('X-Powered-By: '.Base::instance()->PACKAGE);
-		}
-		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);
-		}
-		return $size;
-	}
-
-	/**
-	*	Receive file(s) from HTTP client
-	*	@return array|bool
-	*	@param $func callback
-	*	@param $overwrite bool
-	*	@param $slug callback|bool
-	**/
-	function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) {
-		$fw=Base::instance();
-		$dir=$fw->UPLOADS;
-		if (!is_dir($dir))
-			mkdir($dir,Base::MODE,TRUE);
-		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');
-				if (!$src || !$dst)
-					return FALSE;
-				while (!feof($src) &&
-					($info=stream_get_meta_data($src)) &&
-					!$info['timed_out'] && $str=fgets($src,4096))
-					fputs($dst,$str,strlen($str));
-				fclose($dst);
-				fclose($src);
-			}
-			$base=basename($fw->URI);
-			$file=[
-				'name'=>$dir.
-					($slug && preg_match('/(.+?)(\.\w+)?$/',$base,$parts)?
-						(is_callable($slug)?
-							$slug($base):
-							($this->slug($parts[1]).
-								(isset($parts[2])?$parts[2]:''))):
-						$base),
-				'tmp_name'=>$tmp,
-				'type'=>$this->mime($base),
-				'size'=>filesize($tmp)
-			];
-			return (!file_exists($file['name']) || $overwrite) &&
-				(!$func || $fw->call($func,[$file])!==FALSE) &&
-				rename($tmp,$file['name']);
-		}
-		$fetch=function($arr) use(&$fetch) {
-			if (!is_array($arr))
-				return [$arr];
-			$data=[];
-			foreach($arr as $k=>$sub)
-				$data=array_merge($data,$fetch($sub));
-			return $data;
-		};
-		$out=[];
-		foreach ($_FILES as $name=>$item) {
-			$files=[];
-			foreach ($item as $k=>$mix)
-				foreach ($fetch($mix) as $i=>$val)
-					$files[$i][$k]=$val;
-			foreach ($files as $file) {
-				if (empty($file['name']))
-					continue;
-				$base=basename($file['name']);
-				$file['name']=$dir.
-					($slug && preg_match('/(.+?)(\.\w+)?$/',$base,$parts)?
-						(is_callable($slug)?
-							$slug($base,$name):
-							($this->slug($parts[1]).
-								(isset($parts[2])?$parts[2]:''))):
-						$base);
-				$out[$file['name']]=!$file['error'] &&
-					(!file_exists($file['name']) || $overwrite) &&
-					(!$func || $fw->call($func,[$file,$name])!==FALSE) &&
-					move_uploaded_file($file['tmp_name'],$file['name']);
-			}
-		}
-		return $out;
-	}
-
-	/**
-	*	Return upload progress in bytes, FALSE on failure
-	*	@return int|FALSE
-	*	@param $id string
-	**/
-	function progress($id) {
-		// ID returned by session.upload_progress.name
-		return ini_get('session.upload_progress.enabled') &&
-			isset($_SESSION[$id]['bytes_processed'])?
-				$_SESSION[$id]['bytes_processed']:FALSE;
-	}
-
-	/**
-	*	HTTP request via cURL
-	*	@return array
-	*	@param $url string
-	*	@param $options array
-	**/
-	protected function _curl($url,$options) {
-		$curl=curl_init($url);
-		if (!$open_basedir=ini_get('open_basedir'))
-			curl_setopt($curl,CURLOPT_FOLLOWLOCATION,
-				$options['follow_location']);
-		curl_setopt($curl,CURLOPT_MAXREDIRS,
-			$options['max_redirects']);
-		curl_setopt($curl,CURLOPT_PROTOCOLS,CURLPROTO_HTTP|CURLPROTO_HTTPS);
-		curl_setopt($curl,CURLOPT_REDIR_PROTOCOLS,CURLPROTO_HTTP|CURLPROTO_HTTPS);
-		curl_setopt($curl,CURLOPT_CUSTOMREQUEST,$options['method']);
-		if (isset($options['header']))
-			curl_setopt($curl,CURLOPT_HTTPHEADER,$options['header']);
-		if (isset($options['content']))
-			curl_setopt($curl,CURLOPT_POSTFIELDS,$options['content']);
-		if (isset($options['proxy']))
-			curl_setopt($curl,CURLOPT_PROXY,$options['proxy']);
-		curl_setopt($curl,CURLOPT_ENCODING,'gzip,deflate');
-		$timeout=isset($options['timeout'])?
-			$options['timeout']:
-			ini_get('default_socket_timeout');
-		curl_setopt($curl,CURLOPT_CONNECTTIMEOUT,$timeout);
-		curl_setopt($curl,CURLOPT_TIMEOUT,$timeout);
-		$headers=[];
-		curl_setopt($curl,CURLOPT_HEADERFUNCTION,
-			// Callback for response headers
-			function($curl,$line) use(&$headers) {
-				if ($trim=trim($line))
-					$headers[]=$trim;
-				return strlen($line);
-			}
-		);
-		curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,2);
-		curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE);
-		ob_start();
-		curl_exec($curl);
-		$err=curl_error($curl);
-		curl_close($curl);
-		$body=ob_get_clean();
-		if (!$err &&
-			$options['follow_location'] && $open_basedir &&
-			preg_grep('/HTTP\/[\d.]{1,3} 3\d{2}/',$headers) &&
-			preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) {
-			$options['max_redirects']--;
-			if($loc[1][0] == '/') {
-				$parts=parse_url($url);
-				$loc[1]=$parts['scheme'].'://'.$parts['host'].
-					((isset($parts['port']) && !in_array($parts['port'],[80,443]))
-						?':'.$parts['port']:'').$loc[1];
-			}
-			return $this->request($loc[1],$options);
-		}
-		return [
-			'body'=>$body,
-			'headers'=>$headers,
-			'engine'=>'cURL',
-			'cached'=>FALSE,
-			'error'=>$err
-		];
-	}
-
-	/**
-	*	HTTP request via PHP stream wrapper
-	*	@return array
-	*	@param $url string
-	*	@param $options array
-	**/
-	protected function _stream($url,$options) {
-		$eol="\r\n";
-		if (isset($options['proxy'])) {
-			$options['proxy']=preg_replace('/https?/i','tcp',$options['proxy']);
-			$options['request_fulluri']=true;
-			if (preg_match('/socks4?/i',$options['proxy']))
-				return $this->_socket($url,$options);
-		}
-		$options['header']=implode($eol,$options['header']);
-		$body=@file_get_contents($url,FALSE,
-			stream_context_create(['http'=>$options]));
-		$headers=isset($http_response_header)?
-			$http_response_header:[];
-		$err='';
-		if (is_string($body)) {
-			$match=NULL;
-			foreach ($headers as $header)
-				if (preg_match('/Content-Encoding: (.+)/i',$header,$match))
-					break;
-			if ($match)
-				switch ($match[1]) {
-					case 'gzip':
-						$body=gzdecode($body);
-						break;
-					case 'deflate':
-						$body=gzuncompress($body);
-						break;
-				}
-		}
-		else {
-			$tmp=error_get_last();
-			$err=$tmp['message'];
-		}
-		return [
-			'body'=>$body,
-			'headers'=>$headers,
-			'engine'=>'stream',
-			'cached'=>FALSE,
-			'error'=>$err
-		];
-	}
-
-	/**
-	*	HTTP request via low-level TCP/IP socket
-	*	@return array
-	*	@param $url string
-	*	@param $options array
-	**/
-	protected function _socket($url,$options) {
-		$eol="\r\n";
-		$headers=[];
-		$body='';
-		$parts=parse_url($url);
-		$hostname=$parts['host'];
-		$proxy=false;
-		if ($parts['scheme']=='https')
-			$parts['host']='ssl://'.$parts['host'];
-		if (empty($parts['port']))
-			$parts['port']=$parts['scheme']=='https'?443:80;
-		if (empty($parts['path']))
-			$parts['path']='/';
-		if (empty($parts['query']))
-			$parts['query']='';
-		if (isset($options['proxy'])) {
-			$req=$url;
-			$pp=parse_url($options['proxy']);
-			$proxy=$pp['scheme'];
-			if ($pp['scheme']=='https')
-				$pp['host']='ssl://'.$pp['host'];
-			if (empty($pp['port']))
-				$pp['port']=$pp['scheme']=='https'?443:80;
-			$socket=@fsockopen($pp['host'],$pp['port'],$code,$err);
-		} else {
-			$req=$parts['path'].($parts['query']?('?'.$parts['query']):'');
-			$socket=@fsockopen($parts['host'],$parts['port'],$code,$err);
-		}
-		if ($socket) {
-			stream_set_blocking($socket,TRUE);
-			stream_set_timeout($socket,isset($options['timeout'])?
-				$options['timeout']:ini_get('default_socket_timeout'));
-			if ($proxy=='socks4') {
-				// SOCKS4; http://en.wikipedia.org/wiki/SOCKS#Protocol
-				$packet="\x04\x01".pack("n", $parts['port']).
-					pack("H*",dechex(ip2long(gethostbyname($hostname))))."\0";
-				fputs($socket, $packet, strlen($packet));
-				$response=fread($socket, 9);
-				if (strlen($response)==8 && (ord($response[0])==0 || ord($response[0])==4)
-					&& ord($response[1])==90) {
-					$options['header'][]='Host: '.$hostname;
-				} else
-					$err='Socket Status '.ord($response[1]);
-			}
-			fputs($socket,$options['method'].' '.$req.' HTTP/1.0'.$eol);
-			fputs($socket,implode($eol,$options['header']).$eol.$eol);
-			if (isset($options['content']))
-				fputs($socket,$options['content'].$eol);
-			// Get response
-			$content='';
-			while (!feof($socket) &&
-				($info=stream_get_meta_data($socket)) &&
-				!$info['timed_out'] && !connection_aborted() &&
-				$str=fgets($socket,4096))
-				$content.=$str;
-			fclose($socket);
-			$html=explode($eol.$eol,$content,2);
-			$body=isset($html[1])?$html[1]:'';
-			$headers=array_merge($headers,$current=explode($eol,$html[0]));
-			$match=NULL;
-			foreach ($current as $header)
-				if (preg_match('/Content-Encoding: (.+)/i',$header,$match))
-					break;
-			if ($match)
-				switch ($match[1]) {
-					case 'gzip':
-						$body=gzdecode($body);
-						break;
-					case 'deflate':
-						$body=gzuncompress($body);
-						break;
-				}
-			if ($options['follow_location'] &&
-				preg_grep('/HTTP\/[\d.]{1,3} 3\d{2}/',$headers) &&
-				preg_match('/Location: (.+?)'.preg_quote($eol).'/',
-				$html[0],$loc)) {
-				$options['max_redirects']--;
-				return $this->request($loc[1],$options);
-			}
-		}
-		return [
-			'body'=>$body,
-			'headers'=>$headers,
-			'engine'=>'socket',
-			'cached'=>FALSE,
-			'error'=>$err
-		];
-	}
-
-	/**
-	*	Specify the HTTP request engine to use; If not available,
-	*	fall back to an applicable substitute
-	*	@return string
-	*	@param $arg string
-	**/
-	function engine($arg='curl') {
-		$arg=strtolower($arg);
-		$flags=[
-			'curl'=>extension_loaded('curl'),
-			'stream'=>ini_get('allow_url_fopen'),
-			'socket'=>function_exists('fsockopen')
-		];
-		if ($flags[$arg])
-			return $this->wrapper=$arg;
-		foreach ($flags as $key=>$val)
-			if ($val)
-				return $this->wrapper=$key;
-		user_error(self::E_Request,E_USER_ERROR);
-	}
-
-	/**
-	*	Replace old headers with new elements
-	*	@return NULL
-	*	@param $old array
-	*	@param $new string|array
-	**/
-	function subst(array &$old,$new) {
-		if (is_string($new))
-			$new=[$new];
-		foreach ($new as $hdr) {
-			$old=preg_grep('/'.preg_quote(strstr($hdr,':',TRUE),'/').':.+/',
-				$old,PREG_GREP_INVERT);
-			array_push($old,$hdr);
-		}
-	}
-
-	/**
-	*	Submit HTTP request; Use HTTP context options (described in
-	*	http://www.php.net/manual/en/context.http.php) if specified;
-	*	Cache the page as instructed by remote server
-	*	@return array|FALSE
-	*	@param $url string
-	*	@param $options array
-	**/
-	function request($url,array $options=NULL) {
-		$fw=Base::instance();
-		$parts=parse_url($url);
-		if (empty($parts['scheme'])) {
-			// Local 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']))
-			return FALSE;
-		if (!is_array($options))
-			$options=[];
-		if (empty($options['header']))
-			$options['header']=[];
-		elseif (is_string($options['header']))
-			$options['header']=[$options['header']];
-		if (!$this->wrapper)
-			$this->engine();
-		if ($this->wrapper!='stream') {
-			// PHP streams can't cope with redirects when Host header is set
-			$this->subst($options['header'],'Host: '.$parts['host']);
-		}
-		$this->subst($options['header'],
-			[
-				'Accept-Encoding: gzip,deflate',
-				'User-Agent: '.(isset($options['user_agent'])?
-					$options['user_agent']:
-					'Mozilla/5.0 (compatible; '.php_uname('s').')'),
-				'Connection: close'
-			]
-		);
-		if (isset($options['content']) && is_string($options['content'])) {
-			if ($options['method']=='POST' &&
-				!preg_grep('/^Content-Type:/i',$options['header']))
-				$this->subst($options['header'],
-					'Content-Type: application/x-www-form-urlencoded');
-			$this->subst($options['header'],
-				'Content-Length: '.strlen($options['content']));
-		}
-		if (isset($parts['user'],$parts['pass']))
-			$this->subst($options['header'],
-				'Authorization: Basic '.
-					base64_encode($parts['user'].':'.$parts['pass'])
-			);
-		$options+=[
-			'method'=>'GET',
-			'header'=>$options['header'],
-			'follow_location'=>TRUE,
-			'max_redirects'=>20,
-			'ignore_errors'=>FALSE
-		];
-		$eol="\r\n";
-		if ($fw->CACHE &&
-			preg_match('/GET|HEAD/',$options['method'])) {
-			$cache=Cache::instance();
-			if ($cache->exists(
-				$hash=$fw->hash($options['method'].' '.$url).'.url',$data)) {
-				if (preg_match('/Last-Modified: (.+?)'.preg_quote($eol).'/',
-					implode($eol,$data['headers']),$mod))
-					$this->subst($options['header'],
-						'If-Modified-Since: '.$mod[1]);
-			}
-		}
-		$result=$this->{'_'.$this->wrapper}($url,$options);
-		if ($result && isset($cache)) {
-			if (preg_match('/HTTP\/[\d.]{1,3} 304/',
-				implode($eol,$result['headers']))) {
-				$result=$cache->get($hash);
-				$result['cached']=TRUE;
-			}
-			elseif (preg_match('/Cache-Control:(?:.*)max-age=(\d+)(?:,?.*'.
-				preg_quote($eol).')/i',implode($eol,$result['headers']),$exp))
-				$cache->set($hash,$result,$exp[1]);
-		}
-		$req=[$options['method'].' '.$url];
-		foreach ($options['header'] as $header)
-			array_push($req,$header);
-		return array_merge(['request'=>$req],$result);
-	}
-
-	/**
-	*	Strip Javascript/CSS files of extraneous whitespaces and comments;
-	*	Return combined output as a minified string
-	*	@return string
-	*	@param $files string|array
-	*	@param $mime string
-	*	@param $header bool
-	*	@param $path string
-	**/
-	function minify($files,$mime=NULL,$header=TRUE,$path=NULL) {
-		$fw=Base::instance();
-		if (is_string($files))
-			$files=$fw->split($files);
-		if (!$mime)
-			$mime=$this->mime($files[0]);
-		preg_match('/\w+$/',$files[0],$ext);
-		$cache=Cache::instance();
-		$dst='';
-		if (!isset($path))
-			$path=$fw->UI.';./';
-		foreach (array_unique($fw->split($path,FALSE)) as $dir)
-			foreach ($files as $i=>$file)
-				if (is_file($save=$fw->fixslashes($dir.$file)) &&
-					is_bool(strpos($save,'../')) &&
-					preg_match('/\.(css|js)$/i',$file)) {
-					unset($files[$i]);
-					if ($fw->CACHE &&
-						($cached=$cache->exists(
-							$hash=$fw->hash($save).'.'.$ext[0],$data)) &&
-						$cached[0]>filemtime($save))
-						$dst.=$data;
-					else {
-						$data='';
-						$src=$fw->read($save);
-						for ($ptr=0,$len=strlen($src);$ptr<$len;) {
-							if (preg_match('/^@import\h+url'.
-								'\(\h*([\'"])((?!(?:https?:)?\/\/).+?)\1\h*\)[^;]*;/',
-								substr($src,$ptr),$parts)) {
-								$path=dirname($file);
-								$data.=$this->minify(
-									($path?($path.'/'):'').$parts[2],
-									$mime,$header
-								);
-								$ptr+=strlen($parts[0]);
-								continue;
-							}
-							if ($ext[0]=='css'&&preg_match('/^url\(([^\'"].*?[^\'"])\)/i',
-									substr($src,$ptr),$parts)) {
-								$data.=$parts[0];
-								$ptr+=strlen($parts[0]);
-								continue;
-							}
-							if ($src[$ptr]=='/') {
-								if ($src[$ptr+1]=='*') {
-									// Multiline comment
-									$str=strstr(
-										substr($src,$ptr+2),'*/',TRUE);
-									$ptr+=strlen($str)+4;
-								}
-								elseif ($src[$ptr+1]=='/') {
-									// Single-line comment
-									$str=strstr(
-										substr($src,$ptr+2),"\n",TRUE);
-									$ptr+=(empty($str))?
-										strlen(substr($src,$ptr)):strlen($str)+2;
-								}
-								else {
-									// Presume it's a regex pattern
-									$regex=TRUE;
-									// Backtrack and validate
-									for ($ofs=$ptr;$ofs;$ofs--) {
-										// Pattern should be preceded by
-										// open parenthesis, colon,
-										// object property or operator
-										if (preg_match(
-											'/(return|[(:=!+\-*&|])$/',
-											substr($src,0,$ofs))) {
-											$data.='/';
-											$ptr++;
-											while ($ptr<$len) {
-												$data.=$src[$ptr];
-												$ptr++;
-												if ($src[$ptr-1]=='\\') {
-													$data.=$src[$ptr];
-													$ptr++;
-												}
-												elseif ($src[$ptr-1]=='/')
-													break;
-											}
-											break;
-										}
-										elseif (!ctype_space($src[$ofs-1])) {
-											// Not a regex pattern
-											$regex=FALSE;
-											break;
-										}
-									}
-									if (!$regex) {
-										// Division operator
-										$data.=$src[$ptr];
-										$ptr++;
-									}
-								}
-								continue;
-							}
-							if (in_array($src[$ptr],['\'','"','`'])) {
-								$match=$src[$ptr];
-								$data.=$match;
-								$ptr++;
-								// String literal
-								while ($ptr<$len) {
-									$data.=$src[$ptr];
-									$ptr++;
-									if ($src[$ptr-1]=='\\') {
-										$data.=$src[$ptr];
-										$ptr++;
-									}
-									elseif ($src[$ptr-1]==$match)
-										break;
-								}
-								continue;
-							}
-							if (ctype_space($src[$ptr])) {
-								if ($ptr+1<strlen($src) &&
-									preg_match('/[\w'.($ext[0]=='css'?
-										'#\.%+\-*()\[\]':'\$').']{2}|'.
-										'[+\-]{2}/',
-										substr($data,-1).$src[$ptr+1]) ||
-									($ext[0]=='css' && $ptr+2<strlen($src) &&
-									preg_match('/:\w/',substr($src,$ptr+1,2))))
-									$data.=' ';
-								$ptr++;
-								continue;
-							}
-							$data.=$src[$ptr];
-							$ptr++;
-						}
-						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->ENCODING);
-		return $dst;
-	}
-
-	/**
-	*	Retrieve RSS feed and return as an array
-	*	@return array|FALSE
-	*	@param $url string
-	*	@param $max int
-	*	@param $tags string
-	**/
-	function rss($url,$max=10,$tags=NULL) {
-		if (!$data=$this->request($url))
-			return FALSE;
-		// Suppress errors caused by invalid XML structures
-		libxml_use_internal_errors(TRUE);
-		$xml=simplexml_load_string($data['body'],
-			NULL,LIBXML_NOBLANKS|LIBXML_NOERROR);
-		if (!is_object($xml))
-			return FALSE;
-		$out=[];
-		if (isset($xml->channel)) {
-			$out['source']=(string)$xml->channel->title;
-			$max=min($max,count($xml->channel->item));
-			for ($i=0;$i<$max;$i++) {
-				$item=$xml->channel->item[$i];
-				$list=[''=>NULL]+$item->getnamespaces(TRUE);
-				$fields=[];
-				foreach ($list as $ns=>$uri)
-					foreach ($item->children($uri) as $key=>$val)
-						$fields[$ns.($ns?':':'').$key]=(string)$val;
-				$out['feed'][]=$fields;
-			}
-		}
-		else
-			return FALSE;
-		Base::instance()->scrub($out,$tags);
-		return $out;
-	}
-
-	/**
-	*	Retrieve information from whois server
-	*	@return string|FALSE
-	*	@param $addr string
-	*	@param $server string
-	**/
-	function whois($addr,$server='whois.internic.net') {
-		$socket=@fsockopen($server,43,$errno,$errstr);
-		if (!$socket)
-			// Can't establish connection
-			return FALSE;
-		// Set connection timeout parameters
-		stream_set_blocking($socket,FALSE);
-		stream_set_timeout($socket,ini_get('default_socket_timeout'));
-		// Send request
-		fputs($socket,$addr."\r\n");
-		$info=stream_get_meta_data($socket);
-		// Get response
-		$response='';
-		while (!feof($socket) && !$info['timed_out']) {
-			$response.=fgets($socket,4096); // MDFK97
-			$info=stream_get_meta_data($socket);
-		}
-		fclose($socket);
-		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','ь'=>'','ъ'=>'',
-			'њ'=>'nj','љ'=>'lj','ђ'=>'dj','џ'=>'dz','ћ'=>'c','ј'=>'j',
-			'\''=>'',
-		];
-	}
-
-	/**
-	*	Return a URL/filesystem-friendly version of string
-	*	@return string
-	*	@param $text string
-	**/
-	function slug($text) {
-		return trim(strtolower(preg_replace('/([^\pL\pN])+/u','-',
-			trim(strtr($text,Base::instance()->DIACRITICS+$this->diacritics())))),'-');
-	}
-
-	/**
-	*	Return chunk of text from standard Lorem Ipsum passage
-	*	@return string
-	*	@param $count int
-	*	@param $max int
-	*	@param $std bool
-	**/
-	function filler($count=1,$max=20,$std=TRUE) {
-		$out='';
-		if ($std)
-			$out='Lorem ipsum dolor sit amet, consectetur adipisicing elit, '.
-				'sed do eiusmod tempor incididunt ut labore et dolore magna '.
-				'aliqua.';
-		$rnd=explode(' ',
-			'a ab ad accusamus adipisci alias aliquam amet animi aperiam '.
-			'architecto asperiores aspernatur assumenda at atque aut beatae '.
-			'blanditiis cillum commodi consequatur corporis corrupti culpa '.
-			'cum cupiditate debitis delectus deleniti deserunt dicta '.
-			'dignissimos distinctio dolor ducimus duis ea eaque earum eius '.
-			'eligendi enim eos error esse est eum eveniet ex excepteur '.
-			'exercitationem expedita explicabo facere facilis fugiat harum '.
-			'hic id illum impedit in incidunt ipsa iste itaque iure iusto '.
-			'laborum laudantium libero magnam maiores maxime minim minus '.
-			'modi molestiae mollitia nam natus necessitatibus nemo neque '.
-			'nesciunt nihil nisi nobis non nostrum nulla numquam occaecati '.
-			'odio officia omnis optio pariatur perferendis perspiciatis '.
-			'placeat porro possimus praesentium proident quae quia quibus '.
-			'quo ratione recusandae reiciendis rem repellat reprehenderit '.
-			'repudiandae rerum saepe sapiente sequi similique sint soluta '.
-			'suscipit tempora tenetur totam ut ullam unde vel veniam vero '.
-			'vitae voluptas');
-		for ($i=0,$add=$count-(int)$std;$i<$add;$i++) {
-			shuffle($rnd);
-			$words=array_slice($rnd,0,mt_rand(3,$max));
-			$out.=(!$std&&$i==0?'':' ').ucfirst(implode(' ',$words)).'.';
-		}
-		return $out;
-	}
-
-}
-
-if (!function_exists('gzdecode')) {
-
-	/**
-	*	Decode gzip-compressed string
-	*	@param $str string
-	*	@return string
-	**/
-	function gzdecode($str) {
-		$fw=Base::instance();
-		if (!is_dir($tmp=$fw->TEMP))
-			mkdir($tmp,Base::MODE,TRUE);
-		file_put_contents($file=$tmp.'/'.$fw->SEED.'.'.
-			$fw->hash(uniqid(NULL,TRUE)).'.gz',$str,LOCK_EX);
-		ob_start();
-		readgzfile($file);
-		$out=ob_get_clean();
-		@unlink($file);
-		return $out;
-	}
-
-}

+ 0 - 111
lib/f3/web/geo.php

@@ -1,111 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace Web;
-
-//! Geo plug-in
-class Geo extends \Prefab {
-
-	/**
-	*	Return information about specified Unix time zone
-	*	@return array
-	*	@param $zone string
-	**/
-	function tzinfo($zone) {
-		$ref=new \DateTimeZone($zone);
-		$loc=$ref->getLocation();
-		$trn=$ref->getTransitions($now=time(),$now);
-		$out=[
-			'offset'=>$ref->
-				getOffset(new \DateTime('now',new \DateTimeZone('UTC')))/3600,
-			'country'=>$loc['country_code'],
-			'latitude'=>$loc['latitude'],
-			'longitude'=>$loc['longitude'],
-			'dst'=>$trn[0]['isdst']
-		];
-		unset($ref);
-		return $out;
-	}
-
-	/**
-	*	Return geolocation data based on specified/auto-detected IP address
-	*	@return array|FALSE
-	*	@param $ip string
-	**/
-	function location($ip=NULL) {
-		$fw=\Base::instance();
-		$web=\Web::instance();
-		if (!$ip)
-			$ip=$fw->IP;
-		$public=filter_var($ip,FILTER_VALIDATE_IP,
-			FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|
-			FILTER_FLAG_NO_RES_RANGE|FILTER_FLAG_NO_PRIV_RANGE);
-		if (function_exists('geoip_db_avail') &&
-			geoip_db_avail(GEOIP_CITY_EDITION_REV1) &&
-			$out=@geoip_record_by_name($ip)) {
-			$out['request']=$ip;
-			$out['region_code']=$out['region'];
-			$out['region_name']='';
-			if (!empty($out['country_code']) && !empty($out['region']))
-				$out['region_name']=geoip_region_name_by_code(
-					$out['country_code'],$out['region']
-				);
-			unset($out['country_code3'],$out['region'],$out['postal_code']);
-			return $out;
-		}
-		if (($req=$web->request('http://www.geoplugin.net/json.gp'.
-			($public?('?ip='.$ip):''))) &&
-			$data=json_decode($req['body'],TRUE)) {
-			$out=[];
-			foreach ($data as $key=>$val)
-				if (!strpos($key,'currency') && $key!=='geoplugin_status'
-					&& $key!=='geoplugin_region')
-					$out[$fw->snakecase(substr($key, 10))]=$val;
-			return $out;
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Return weather data based on specified latitude/longitude
-	*	@return array|FALSE
-	*	@param $latitude float
-	*	@param $longitude float
-	*	@param $key string
-	**/
-	function weather($latitude,$longitude,$key) {
-		$fw=\Base::instance();
-		$web=\Web::instance();
-		$query=[
-			'lat'=>$latitude,
-			'lon'=>$longitude,
-			'APPID'=>$key,
-			'units'=>'metric'
-		];
-		return ($req=$web->request(
-			'http://api.openweathermap.org/data/2.5/weather?'.
-				http_build_query($query)))?
-			json_decode($req['body'],TRUE):
-			FALSE;
-	}
-
-}

+ 0 - 58
lib/f3/web/google/recaptcha.php

@@ -1,58 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace Web\Google;
-
-//! Google ReCAPTCHA v2 plug-in
-class Recaptcha {
-
-	const
-		//! API URL
-		URL_Recaptcha='https://www.google.com/recaptcha/api/siteverify';
-
-	/**
-	 *	Verify reCAPTCHA response
-	 *	@param string $secret
-	 *	@param string $response
-	 *	@return bool
-	 **/
-	static function verify($secret,$response=NULL) {
-		$fw=\Base::instance();
-		if (!isset($response))
-			$response=$fw->{'POST.g-recaptcha-response'};
-		if (!$response)
-			return FALSE;
-		$web=\Web::instance();
-		$out=$web->request(self::URL_Recaptcha,[
-			'method'=>'POST',
-			'content'=>http_build_query([
-				'secret'=>$secret,
-				'response'=>$response,
-				'remoteip'=>$fw->IP
-			]),
-		]);
-		return isset($out['body']) &&
-			($json=json_decode($out['body'],TRUE)) &&
-			isset($json['success']) && $json['success'];
-	}
-
-}

+ 0 - 65
lib/f3/web/google/staticmap.php

@@ -1,65 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace Web\Google;
-
-//! Google Static Maps API v2 plug-in
-class StaticMap {
-
-	const
-		//! API URL
-		URL_Static='http://maps.googleapis.com/maps/api/staticmap';
-
-	protected
-		//! Query arguments
-		$query=array();
-
-	/**
-	*	Specify API key-value pair via magic call
-	*	@return object
-	*	@param $func string
-	*	@param $args array
-	**/
-	function __call($func,array $args) {
-		$this->query[]=array($func,$args[0]);
-		return $this;
-	}
-
-	/**
-	*	Generate map
-	*	@return string
-	**/
-	function dump() {
-		$fw=\Base::instance();
-		$web=\Web::instance();
-		$out='';
-		return ($req=$web->request(
-			self::URL_Static.'?'.array_reduce(
-				$this->query,
-				function($out,$item) {
-					return ($out.=($out?'&':'').
-						urlencode($item[0]).'='.urlencode($item[1]));
-				}
-			))) && $req['body']?$req['body']:FALSE;
-	}
-
-}

+ 0 - 163
lib/f3/web/oauth2.php

@@ -1,163 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace Web;
-
-//! Lightweight OAuth2 client
-class OAuth2 extends \Magic {
-
-	protected
-		//! Scopes and claims
-		$args=[],
-		//! Encoding
-		$enc_type = PHP_QUERY_RFC1738;
-
-	/**
-	*	Return OAuth2 authentication URI
-	*	@return string
-	*	@param $endpoint string
-	*	@param $query bool
-	**/
-	function uri($endpoint,$query=TRUE) {
-		return $endpoint.($query?('?'.
-				http_build_query($this->args,null,'&',$this->enc_type)):'');
-	}
-
-	/**
-	*	Send request to API/token endpoint
-	*	@return string|FALSE
-	*	@param $uri string
-	*	@param $method string
-	*	@param $token array
-	**/
-	function request($uri,$method,$token=NULL) {
-		$web=\Web::instance();
-		$options=[
-			'method'=>$method,
-			'content'=>http_build_query($this->args,null,'&',$this->enc_type),
-			'header'=>['Accept: application/json']
-		];
-		if ($token)
-			array_push($options['header'],'Authorization: Bearer '.$token);
-		elseif ($method=='POST' && isset($this->args['client_id']))
-			array_push($options['header'],'Authorization: Basic '.
-				base64_encode(
-					$this->args['client_id'].':'.
-					$this->args['client_secret']
-				)
-			);
-		$response=$web->request($uri,$options);
-		if ($response['error'])
-			user_error($response['error'],E_USER_ERROR);
-		if (isset($response['body'])) {
-			if (preg_grep('/^Content-Type:.*application\/json/i',
-				$response['headers'])) {
-				$token=json_decode($response['body'],TRUE);
-				if (isset($token['error_description']))
-					user_error($token['error_description'],E_USER_ERROR);
-				if (isset($token['error']))
-					user_error($token['error'],E_USER_ERROR);
-				return $token;
-			}
-			else
-				return $response['body'];
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Parse JSON Web token
-	*	@return array
-	*	@param $token string
-	**/
-	function jwt($token) {
-		return json_decode(
-			base64_decode(
-				str_replace(['-','_'],['+','/'],explode('.',$token)[1])
-			),
-			TRUE
-		);
-	}
-
-	/**
-	 * change default url encoding type, i.E. PHP_QUERY_RFC3986
-	 * @param $type
-	 */
-	function setEncoding($type) {
-		$this->enc_type = $type;
-	}
-
-	/**
-	*	URL-safe base64 encoding
-	*	@return array
-	*	@param $data string
-	**/
-	function b64url($data) {
-		return trim(strtr(base64_encode($data),'+/','-_'),'=');
-	}
-
-	/**
-	*	Return TRUE if scope/claim exists
-	*	@return bool
-	*	@param $key string
-	**/
-	function exists($key) {
-		return isset($this->args[$key]);
-	}
-
-	/**
-	*	Bind value to scope/claim
-	*	@return string
-	*	@param $key string
-	*	@param $val string
-	**/
-	function set($key,$val) {
-		return $this->args[$key]=$val;
-	}
-
-	/**
-	*	Return value of scope/claim
-	*	@return mixed
-	*	@param $key string
-	**/
-	function &get($key) {
-		if (isset($this->args[$key]))
-			$val=&$this->args[$key];
-		else
-			$val=NULL;
-		return $val;
-	}
-
-	/**
-	*	Remove scope/claim
-	*	@return NULL
-	*	@param $key string
-	**/
-	function clear($key=NULL) {
-		if ($key)
-			unset($this->args[$key]);
-		else
-			$this->args=[];
-	}
-
-}
-

+ 0 - 248
lib/f3/web/openid.php

@@ -1,248 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace Web;
-
-//! OpenID consumer
-class OpenID extends \Magic {
-
-	protected
-		//! OpenID provider endpoint URL
-		$url,
-		//! HTTP request parameters
-		$args=[];
-
-	/**
-	*	Determine OpenID provider
-	*	@return string|FALSE
-	*	@param $proxy string
-	**/
-	protected function discover($proxy) {
-		// Normalize
-		if (!preg_match('/https?:\/\//i',$this->args['endpoint']))
-			$this->args['endpoint']='http://'.$this->args['endpoint'];
-		$url=parse_url($this->args['endpoint']);
-		// Remove fragment; reconnect parts
-		$this->args['endpoint']=$url['scheme'].'://'.
-			(isset($url['user'])?
-				($url['user'].
-				(isset($url['pass'])?(':'.$url['pass']):'').'@'):'').
-			strtolower($url['host']).(isset($url['path'])?$url['path']:'/').
-			(isset($url['query'])?('?'.$url['query']):'');
-		// HTML-based discovery of OpenID provider
-		$req=\Web::instance()->
-			request($this->args['endpoint'],['proxy'=>$proxy]);
-		if (!$req)
-			return FALSE;
-		$type=array_values(preg_grep('/Content-Type:/',$req['headers']));
-		if ($type &&
-			preg_match('/application\/xrds\+xml|text\/xml/',$type[0]) &&
-			($sxml=simplexml_load_string($req['body'])) &&
-			($xrds=json_decode(json_encode($sxml),TRUE)) &&
-			isset($xrds['XRD'])) {
-			// XRDS document
-			$svc=$xrds['XRD']['Service'];
-			if (isset($svc[0]))
-				$svc=$svc[0];
-			$svc_type=is_array($svc['Type'])?$svc['Type']:array($svc['Type']);
-			if (preg_grep('/http:\/\/specs\.openid\.net\/auth\/2.0\/'.
-					'(?:server|signon)/',$svc_type)) {
-				$this->args['provider']=$svc['URI'];
-				if (isset($svc['LocalID']))
-					$this->args['localidentity']=$svc['LocalID'];
-				elseif (isset($svc['CanonicalID']))
-					$this->args['localidentity']=$svc['CanonicalID'];
-			}
-			$this->args['server']=$svc['URI'];
-			if (isset($svc['Delegate']))
-				$this->args['delegate']=$svc['Delegate'];
-		}
-		else {
-			$len=strlen($req['body']);
-			$ptr=0;
-			// Parse document
-			while ($ptr<$len)
-				if (preg_match(
-					'/^<link\b((?:\h+\w+\h*=\h*'.
-					'(?:"(?:.+?)"|\'(?:.+?)\'))*)\h*\/?>/is',
-					substr($req['body'],$ptr),$parts)) {
-					if ($parts[1] &&
-						// Process attributes
-						preg_match_all('/\b(rel|href)\h*=\h*'.
-							'(?:"(.+?)"|\'(.+?)\')/s',$parts[1],$attr,
-							PREG_SET_ORDER)) {
-						$node=[];
-						foreach ($attr as $kv)
-							$node[$kv[1]]=isset($kv[2])?$kv[2]:$kv[3];
-						if (isset($node['rel']) &&
-							preg_match('/openid2?\.(\w+)/',
-								$node['rel'],$var) &&
-							isset($node['href']))
-							$this->args[$var[1]]=$node['href'];
-
-					}
-					$ptr+=strlen($parts[0]);
-				}
-				else
-					$ptr++;
-		}
-		// Get OpenID provider's endpoint URL
-		if (isset($this->args['provider'])) {
-			// OpenID 2.0
-			$this->args['ns']='http://specs.openid.net/auth/2.0';
-			if (isset($this->args['localidentity']))
-				$this->args['identity']=$this->args['localidentity'];
-			if (isset($this->args['trust_root']))
-				$this->args['realm']=$this->args['trust_root'];
-		}
-		elseif (isset($this->args['server'])) {
-			// OpenID 1.1
-			$this->args['ns']='http://openid.net/signon/1.1';
-			if (isset($this->args['delegate']))
-				$this->args['identity']=$this->args['delegate'];
-		}
-		if (isset($this->args['provider'])) {
-			// OpenID 2.0
-			if (empty($this->args['claimed_id']))
-				$this->args['claimed_id']=$this->args['identity'];
-			return $this->args['provider'];
-		}
-		elseif (isset($this->args['server']))
-			// OpenID 1.1
-			return $this->args['server'];
-		else
-			return FALSE;
-	}
-
-	/**
-	*	Initiate OpenID authentication sequence; Return FALSE on failure
-	*	or redirect to OpenID provider URL
-	*	@return bool
-	*	@param $proxy string
-	*	@param $attr array
-	*	@param $reqd string|array
-	**/
-	function auth($proxy=NULL,$attr=[],array $reqd=NULL) {
-		$fw=\Base::instance();
-		$root=$fw->SCHEME.'://'.$fw->HOST;
-		if (empty($this->args['trust_root']))
-			$this->args['trust_root']=$root.$fw->BASE.'/';
-		if (empty($this->args['return_to']))
-			$this->args['return_to']=$root.$_SERVER['REQUEST_URI'];
-		$this->args['mode']='checkid_setup';
-		if ($this->url=$this->discover($proxy)) {
-			if ($attr) {
-				$this->args['ns.ax']='http://openid.net/srv/ax/1.0';
-				$this->args['ax.mode']='fetch_request';
-				foreach ($attr as $key=>$val)
-					$this->args['ax.type.'.$key]=$val;
-				$this->args['ax.required']=is_string($reqd)?
-					$reqd:implode(',',$reqd);
-			}
-			$var=[];
-			foreach ($this->args as $key=>$val)
-				$var['openid.'.$key]=$val;
-			$fw->reroute($this->url.'?'.http_build_query($var));
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Return TRUE if OpenID verification was successful
-	*	@return bool
-	*	@param $proxy string
-	**/
-	function verified($proxy=NULL) {
-		preg_match_all('/(?<=^|&)openid\.([^=]+)=([^&]+)/',
-			$_SERVER['QUERY_STRING'],$matches,PREG_SET_ORDER);
-		foreach ($matches as $match)
-			$this->args[$match[1]]=urldecode($match[2]);
-		if (isset($this->args['mode']) &&
-			$this->args['mode']!='error' &&
-			$this->url=$this->discover($proxy)) {
-			$this->args['mode']='check_authentication';
-			$var=[];
-			foreach ($this->args as $key=>$val)
-				$var['openid.'.$key]=$val;
-			$req=\Web::instance()->request(
-				$this->url,
-				[
-					'method'=>'POST',
-					'content'=>http_build_query($var),
-					'proxy'=>$proxy
-				]
-			);
-			return (bool)preg_match('/is_valid:true/i',$req['body']);
-		}
-		return FALSE;
-	}
-
-	/**
-	*	Return OpenID response fields
-	*	@return array
-	**/
-	function response() {
-		return $this->args;
-	}
-
-	/**
-	*	Return TRUE if OpenID request parameter exists
-	*	@return bool
-	*	@param $key string
-	**/
-	function exists($key) {
-		return isset($this->args[$key]);
-	}
-
-	/**
-	*	Bind value to OpenID request parameter
-	*	@return string
-	*	@param $key string
-	*	@param $val string
-	**/
-	function set($key,$val) {
-		return $this->args[$key]=$val;
-	}
-
-	/**
-	*	Return value of OpenID request parameter
-	*	@return mixed
-	*	@param $key string
-	**/
-	function &get($key) {
-		if (isset($this->args[$key]))
-			$val=&$this->args[$key];
-		else
-			$val=NULL;
-		return $val;
-	}
-
-	/**
-	*	Remove OpenID request parameter
-	*	@return NULL
-	*	@param $key
-	**/
-	function clear($key) {
-		unset($this->args[$key]);
-	}
-
-}

+ 0 - 176
lib/f3/web/pingback.php

@@ -1,176 +0,0 @@
-<?php
-
-/*
-
-	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
-
-	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
-
-	This is free software: you can redistribute it and/or modify it under the
-	terms of the GNU General Public License as published by the Free Software
-	Foundation, either version 3 of the License, or later.
-
-	Fat-Free Framework is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-	General Public License for more details.
-
-	You should have received a copy of the GNU General Public License along
-	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-namespace Web;
-
-//! Pingback 1.0 protocol (client and server) implementation
-class Pingback extends \Prefab {
-
-	protected
-		//! Transaction history
-		$log;
-
-	/**
-	*	Return TRUE if URL points to a pingback-enabled resource
-	*	@return bool
-	*	@param $url
-	**/
-	protected function enabled($url) {
-		$web=\Web::instance();
-		$req=$web->request($url);
-		$found=FALSE;
-		if ($req['body']) {
-			// Look for pingback header
-			foreach ($req['headers'] as $header)
-				if (preg_match('/^X-Pingback:\h*(.+)/',$header,$href)) {
-					$found=$href[1];
-					break;
-				}
-			if (!$found &&
-				// Scan page for pingback link tag
-				preg_match('/<link\h+(.+?)\h*\/?>/i',$req['body'],$parts) &&
-				preg_match('/rel\h*=\h*"pingback"/i',$parts[1]) &&
-				preg_match('/href\h*=\h*"\h*(.+?)\h*"/i',$parts[1],$href))
-				$found=$href[1];
-		}
-		return $found;
-	}
-
-	/**
-	*	Load local page contents, parse HTML anchor tags, find permalinks,
-	*	and send XML-RPC calls to corresponding pingback servers
-	*	@return NULL
-	*	@param $source string
-	**/
-	function inspect($source) {
-		$fw=\Base::instance();
-		$web=\Web::instance();
-		$parts=parse_url($source);
-		if (empty($parts['scheme']) || empty($parts['host']) ||
-			$parts['host']==$fw->HOST) {
-			$req=$web->request($source);
-			$doc=new \DOMDocument('1.0',$fw->ENCODING);
-			$doc->stricterrorchecking=FALSE;
-			$doc->recover=TRUE;
-			if (@$doc->loadhtml($req['body'])) {
-				// Parse anchor tags
-				$links=$doc->getelementsbytagname('a');
-				foreach ($links as $link) {
-					$permalink=$link->getattribute('href');
-					// Find pingback-enabled resources
-					if ($permalink && $found=$this->enabled($permalink)) {
-						$req=$web->request($found,
-							[
-								'method'=>'POST',
-								'header'=>'Content-Type: application/xml',
-								'content'=>xmlrpc_encode_request(
-									'pingback.ping',
-									[$source,$permalink],
-									['encoding'=>$fw->ENCODING]
-								)
-							]
-						);
-						if ($req['body'])
-							$this->log.=date('r').' '.
-								$permalink.' [permalink:'.$found.']'.PHP_EOL.
-								$req['body'].PHP_EOL;
-					}
-				}
-			}
-			unset($doc);
-		}
-	}
-
-	/**
-	*	Receive ping, check if local page is pingback-enabled, verify
-	*	source contents, and return XML-RPC response
-	*	@return string
-	*	@param $func callback
-	*	@param $path string
-	**/
-	function listen($func,$path=NULL) {
-		$fw=\Base::instance();
-		if (PHP_SAPI!='cli') {
-			header('X-Powered-By: '.$fw->PACKAGE);
-			header('Content-Type: application/xml; '.
-				'charset='.$charset=$fw->ENCODING);
-		}
-		if (!$path)
-			$path=$fw->BASE;
-		$web=\Web::instance();
-		$args=xmlrpc_decode_request($fw->BODY,$method,$charset);
-		$options=['encoding'=>$charset];
-		if ($method=='pingback.ping' && isset($args[0],$args[1])) {
-			list($source,$permalink)=$args;
-			$doc=new \DOMDocument('1.0',$fw->ENCODING);
-			// Check local page if pingback-enabled
-			$parts=parse_url($permalink);
-			if ((empty($parts['scheme']) ||
-				$parts['host']==$fw->HOST) &&
-				preg_match('/^'.preg_quote($path,'/').'/'.
-					($fw->CASELESS?'i':''),$parts['path']) &&
-				$this->enabled($permalink)) {
-				// Check source
-				$parts=parse_url($source);
-				if ((empty($parts['scheme']) ||
-					$parts['host']==$fw->HOST) &&
-					($req=$web->request($source)) &&
-					$doc->loadhtml($req['body'])) {
-					$links=$doc->getelementsbytagname('a');
-					foreach ($links as $link) {
-						if ($link->getattribute('href')==$permalink) {
-							call_user_func_array($func,[$source,$req['body']]);
-							// Success
-							die(xmlrpc_encode_request(NULL,$source,$options));
-						}
-					}
-					// No link to local page
-					die(xmlrpc_encode_request(NULL,0x11,$options));
-				}
-				// Source failure
-				die(xmlrpc_encode_request(NULL,0x10,$options));
-			}
-			// Doesn't exist (or not pingback-enabled)
-			die(xmlrpc_encode_request(NULL,0x21,$options));
-		}
-		// Access denied
-		die(xmlrpc_encode_request(NULL,0x31,$options));
-	}
-
-	/**
-	*	Return transaction history
-	*	@return string
-	**/
-	function log() {
-		return $this->log;
-	}
-
-	/**
-	*	Instantiate class
-	*	@return object
-	**/
-	function __construct() {
-		// Suppress errors caused by invalid HTML structures
-		libxml_use_internal_errors(TRUE);
-	}
-
-}

+ 0 - 254
src/Modules/restful/restful.php

@@ -1,254 +0,0 @@
-<?php
-/**
- * Created by PhpStorm.
- * User: Giancarlo
- * Date: 11/10/2018
- * Time: 14:57
- */
-
-namespace Modules\restful;
-
-use Base;
-use OpenMind\FindBox\FindBoxCoreSystem;
-use OpenMind\FindBox\QueueCommand;
-use OpenMind\FindBox\SmartESL;
-use OpenMind\System\GDSupport;
-use OpenMind\System\PluginStrategy;
-use OpenMind\System\QueueSystem;
-use Template;
-
-define('API_VERSION', '1.10 beta test only');
-
-class restful extends PluginStrategy
-{
-	/** @var FindBoxCoreSystem $findBoxCore */
-	private $findBoxCore;
-
-	public function __construct()
-	{
-		global $f3;
-
-		$f3->AJAX = true;
-
-		$this->AddRoute("GET /api.ws", __NAMESPACE__ . '\restful->RESTful_ListAPIS');
-
-		$this->AddRoute("POST /api.ws.bind/@labelid", __NAMESPACE__ . '\restful->RESTful_bind');
-		$this->AddRoute("POST /api.ws.blinkon/@labelid/@color", __NAMESPACE__ . '\restful->RESTful_blinkon');
-		$this->AddRoute("POST /api.ws.blinkoff/@labelid", __NAMESPACE__ . '\restful->RESTful_blinkoff');
-		$this->AddRoute("POST /api.ws.setdisplay/@labelid/@templateid", __NAMESPACE__ . '\restful->RESTful_SetDisplayTemplate');
-		$this->AddRoute("POST /api.ws.saveproduct/@labelid", __NAMESPACE__ . '\restful->RESTful_SaveProduct');
-		$this->AddRoute("POST /api.ws.getstatus/@labelid", __NAMESPACE__ . '\restful->RESTful_GetStatus');
-		$this->AddRoute("POST /api.ws.getlabelsfromap/@ap_id", __NAMESPACE__ . '\restful->RESTful_GetLabelsFromAP');
-
-		// TODO NEW OMLINK TPL GENERATOR
-		$this->AddRoute("GET /api.ws.gentpl", __NAMESPACE__ . '\restful->RESTful_gentpl');
-
-		$this->findBoxCore = new FindBoxCoreSystem(...array_values($f3->get('CORE.fb')));
-	}
-
-	public function __destruct()
-	{
-		global $f3;
-		$f3->clear('SESSION');
-	}
-
-	public function RESTful_gentpl($f3)
-	{
-		$font = '"C:\\\\Users\\\\Giancarlo\\\\AppData\\\\Local\\\\Microsoft\\\\Windows\\\\Fonts\\\\Lato-Black.ttf"';
-		$fontSize = 16;
-
-		$demo1 = json_decode('[{"BODY":{"w":264,"h":176,"bgcolor":"#FFFFFF"}},{"LINE":{"x1":0,"y1":55,"x2":263,"y2":55,"color":"#000000"}},{"LINE":{"x1":0,"y1":110,"x2":263,"y2":110,"color":"#000000"}},{"TEXT":{"x":10,"y":22,"size":' . $fontSize . ',"color":"#000000","value":"ZSS 123456 DX","font":' . $font . '}},{"TEXT":{"x":10,"y":47,"size":' . $fontSize . ',"color":"#000000","value":"50","font":' . $font . '}},{"TEXT":{"x":45,"y":47,"size":' . $fontSize . ',"color":"#000000","value":"-0.75","font":' . $font . '}},{"TEXT":{"x":100,"y":47,"size":' . $fontSize . ',"color":"#000000","value":"+0.25","font":' . $font . '}},{"TEXT":{"x":170,"y":47,"size":' . $fontSize . ',"color":"#000000","value":"B-2-1","font":' . $font . '}},{"TEXT":{"x":10,"y":77,"size":' . $fontSize . ',"color":"#000000","value":"ZSS 123456 eee","font":' . $font . '}},{"TEXT":{"x":10,"y":102,"size":' . $fontSize . ',"color":"#000000","value":"70","font":' . $font . '}},{"TEXT":{"x":45,"y":102,"size":' . $fontSize . ',"color":"#000000","value":"-0.50","font":' . $font . '}},{"TEXT":{"x":100,"y":102,"size":' . $fontSize . ',"color":"#000000","value":"+0.25","font":' . $font . '}},{"TEXT":{"x":170,"y":102,"size":' . $fontSize . ',"color":"#000000","value":"A-123-56","font":' . $font . '}},{"BARCODE":{"x":10,"y":120,"widthFactor":2,"h":55,"type":"I25","value":"01234567"}},{"RECT":{"x1":170,"y1":120,"x2":263,"y2":175,"color":"#FF0000","filled":1}},{"TEXT":{"x":178,"y":161,"size":26,"color":"#FFFFFF","value":"COC","font":' . $font . '}}]', true);
-
-		$demo2 = json_decode('
-[
- {"BODY":{"w":264,"h":176,"bgcolor":"#FFFFFF"}},
- {"RECT":{"x1":5,"y1":5,"x2":100,"y2":90,"color":"#FF0000","filled":1}},
- {"TEXTFIT":{"x":8,"y":40,"w":95,"color":"#FFFFFF","value":"MAXMARA","font":' . $font . '}},
- {"RECT":{"x1":105,"y1":95,"x2":200,"y2":176,"color":"#FF0000","filled":1}},
- {"TEXTFIT":{"x":107,"y":140,"w":95,"color":"#FFFFFF","value":"ZEISS","font":' . $font . '}}
-]', true);
-
-		$gd = new GDSupport();
-		$gd->Render($demo2, 'f:\test_tpl.png');
-		$imageBin = $gd->Render($demo2);
-
-//		$esl = new SmartESL($this->findBoxCore, ['0108A338']);
-//		$esl = new SmartESL($this->findBoxCore, ['0109BDC8']);
-		$esl = new SmartESL($this->findBoxCore, ['01097D82']);
-		$esl->SetPageImage($imageBin, 1);
-//		$esl->SwitchPage(1);
-
-	}
-
-	public function RESTful_ListAPIS($f3)
-	{
-		$apiList = array_filter(array_keys($f3->ROUTES), function ($item) {
-			return (strpos($item, '/api.ws.') === 0);
-		});
-
-		$f3->APILIST = $apiList;
-		$f3->SITENAME = 'OM-LINK API List';
-		$f3->CONTENT = '../../src/Modules/restful/restful.tpl';
-		$f3->API_VERSION = API_VERSION;
-		echo Template::instance()->render('masterpage.tpl');
-	}
-
-	public function RESTful_blinkon($f3, $args)
-	{
-		$msgBlink = new QueueCommand([$args['labelid']]);
-
-		$msgBlink->setLeds(2, 1, 500, 10, $args['color']);
-
-		$qm = new QueueSystem();
-		$qm->send($msgBlink);
-
-		global $frameworkUtils;
-		$frameworkUtils->getLogger()->write(__FUNCTION__ . " request blink for label {$args['labelid']}");
-	}
-
-	public function RESTful_blinkoff($f3, $args)
-	{
-		$msgBlink = new QueueCommand([$args['labelid']]);
-
-		$msgBlink->setToBlinkOff();
-
-		$qm = new QueueSystem();
-		$qm->send($msgBlink);
-
-		global $frameworkUtils;
-		$frameworkUtils->getLogger()->write(__FUNCTION__ . " request blink off for label {$args['labelid']}");
-	}
-
-	public function RESTful_SetDisplayTemplate($f3, $args)
-	{
-		$msgBlink = new QueueCommand([$args['labelid']]);
-
-		$msgBlink->setDisplay($args['templateid'], $args['labelid']);
-
-		$qm = new QueueSystem();
-		$qm->send($msgBlink);
-
-		global $frameworkUtils;
-		$frameworkUtils->getLogger()->write(__FUNCTION__ . " request set display to default for label {$args['labelid']}");
-	}
-
-	/**
-	 * @param Base $f3
-	 * @param $args
-	 */
-	public function RESTful_SaveProduct($f3, $args)
-	{
-		global $frameworkUtils;
-
-		$post = $f3->get('BODY');
-
-		if (empty($post)) {
-			echo $this->returnCode(1, 'POST data format incorrect');
-			return;
-		}
-
-		$dataFields = json_decode($post, true);
-
-		if ($dataFields == null) {
-			echo $this->returnCode(1, 'POST data format incorrect');
-			return;
-		}
-
-		$msgSave = new QueueCommand([$args['labelid']]);
-		$msgSave->saveProducts('', $dataFields);
-
-		$qm = new QueueSystem();
-		$qm->send($msgSave);
-
-		$frameworkUtils->getLogger()->write(__FUNCTION__ . " save product for label {$args['labelid']} fields: {$post}");
-
-		echo $this->returnCode(0, 'OK');
-	}
-
-	/**
-	 * This private function return the REST response code
-	 *
-	 * @param int $code
-	 * @param string $text
-	 * @return false|string
-	 */
-	private function returnCode(int $code, string $text)
-	{
-		return json_encode(["code" => $code, "text" => $text]);
-	}
-
-	public function RESTful_GetStatus($f3, $args)
-	{
-		$esl = new SmartESL($this->findBoxCore, [$args['labelid']]);
-		$status = $esl->GetStatus();
-
-		$labelStatus = $status[$args['labelid']];
-
-		echo json_encode($this->stripStatusFields($labelStatus));
-	}
-
-	/**
-	 * @param $status
-	 * @return array
-	 */
-	private function stripStatusFields($status): array
-	{
-		return [
-			'rssi' => $status['status']['rssi'],
-			'bat_level' => $status['status']['bat_level'],
-			'esl_state' => $status['data']['rssi'],
-			'esl_state_msg' => $status['data']['esl_state_msg'],
-			'rssi_ap' => $status['rssi']
-		];
-	}
-
-	public function RESTful_GetLabelsFromAP($f3, $args)
-	{
-		$esl = new SmartESL($this->findBoxCore, []);
-		$status = $esl->GetStatus();
-
-		if ($status == null) {
-			echo $this->returnCode(0, 'can\'t get label status = null');
-			return;
-		}
-
-		array_walk($status, function (&$item) use ($args) {
-			$rssi = array_intersect_key($item['rssi'], [$args['ap_id'] => '']);
-
-			$newLabelObj = $this->stripStatusFields($item);
-			$newLabelObj['rssi_ap'] = $rssi;
-
-			$item = $newLabelObj;
-		});
-
-		$result = array_filter($status, function ($item) use ($args) {
-			return isset($item['rssi_ap'][$args['ap_id']]);
-		});
-
-
-		echo $this->returnCode(0, json_encode($result));
-	}
-
-	/**
-	 * @param Base $f3
-	 * @param $args
-	 */
-	public function RESTful_bind($f3, $args)
-	{
-		global $frameworkUtils;
-
-		$this->findBoxCore->Bind($args['labelid'], $args['labelid']);
-
-		$frameworkUtils->getLogger()->write(__FUNCTION__ . " label {$args['labelid']} binded");
-
-		/** @var SQL $db */
-		$db = $f3->get('DB');
-
-		$exists = $db->exec("SELECT label_id FROM label_bindings WHERE `label_id`='{$args['label_id']}' LIMIT 1");
-
-		if (count($exists) == 0)
-			$db->exec("INSERT INTO label_bindings(label_id, barcode) VALUES('{$args['labelid']}', '{$args['labelid']}')");
-
-		echo $this->returnCode(0, 'OK');
-	}
-}

+ 0 - 6
src/Modules/restful/restful.tpl

@@ -1,6 +0,0 @@
-<ul class="list-group">
-	<li class="list-group-item active text-center font-weight-bold">API List <i>({{@API_VERSION}})</i></li>
-	<repeat group="{{@APILIST}}" value="{{@API}}">
-		<li class="list-group-item text-dark">{{@API}}</li>
-	</repeat>
-</ul>

+ 0 - 188
src/OpenMind/DataManager/CSVImporter.php

@@ -1,188 +0,0 @@
-<?php
-/**
- * Created by PhpStorm.
- * User: Giancarlo
- * Date: 24/07/2018
- * Time: 17:36
- */
-
-namespace OpenMind\DataManager;
-
-use DB\SQL\Mapper;
-use F3;
-use PDOException;
-
-class ENUM_DIMATEC_CSV
-{
-	public const MXORDI = 7;
-	public const MXARTI = 10;
-	public const MXDESC = 12;
-	public const MXQTMI = 14;
-	public const MXUBIC = 16;
-}
-
-
-class CSVImporter
-{
-	const LAMBORGHINI_DISTINTA_COLONNE = 20;
-	const LAMBORGHINI_ARTICLES_COLONNE = 15;
-	const DIMATEC_LISTA_MOVIMENTI_AS400 = 32;
-
-	private $folderToWatch;
-	private $dbMysql;
-	private $output;
-	private $user;
-
-	public function __construct(string $folderPath)
-	{
-		$this->user = (int)F3::get('USER')->id;
-		$this->folderToWatch = $folderPath;
-		$this->dbMysql = F3::get('DB');
-	}
-
-	/**
-	 * @return mixed
-	 */
-	public function getOutput()
-	{
-		return $this->output;
-	}
-
-	public function ScanAndStoreData()
-	{
-		foreach(glob("{$this->folderToWatch}/*.csv") as $fname) {
-			if (is_file($fname)) {
-				$allData = $this->loadCSV($fname);
-
-				// Esce se il file non è una distinta, lo riconosce solamente dal numero di colonne del file CSV
-				if ($allData == NULL)
-					return;
-
-				switch (count($allData[0])) {
-					case CSVImporter::LAMBORGHINI_DISTINTA_COLONNE:
-						$this->importOnDatabase($fname, $allData);
-						break;
-					case CSVImporter::LAMBORGHINI_ARTICLES_COLONNE:
-						$this->mysqlLoadCSV(addslashes(realpath($fname)), 'articles');
-						break;
-					case CSVImporter::DIMATEC_LISTA_MOVIMENTI_AS400:
-						//$tmpTableName = $this->createTmpTableFrom('distinte');
-						$this->dynamicImporter($fname, $allData, function ($data) {
-							if ($data[0] == "MXIDDM")
-								return;
-							try {
-								$distinta = new Mapper($this->dbMysql, 'distinte');
-								$distinta->idUtente = $this->user;
-								$distinta->commessa = $data[ENUM_DIMATEC_CSV::MXORDI];
-								$distinta->barcode = $data[ENUM_DIMATEC_CSV::MXARTI];
-								$distinta->descrizione = $data[ENUM_DIMATEC_CSV::MXDESC];
-								$distinta->qta_prelevare = $data[ENUM_DIMATEC_CSV::MXQTMI];
-								$distinta->save();
-							} catch (PDOException $e) {
-								$this->output .= $e->getMessage();
-							}
-						});
-						/*            $this->dbMysql->exec(
-													"INSERT INTO `distinta_esplosa` (commessa, barcode, descrizione, qta_prelevare) " .
-													"SELECT commessa, barcode, descrizione, SUM(qta_prelevare) as qta_prelevare " .
-													"FROM `distinte` group by barcode order by commessa"
-												);*/
-						$this->markFileAsDone($fname);
-						break;
-					default:
-						$this->output .= "Numero di colonne non riconosciute ne come DISTINTA e ne come LISTA ARTICOLI per il file '{$fname}'";
-				}
-			}
-		}
-	}
-
-	private function loadCSV(string $path): ?array
-	{
-		$fh = fopen($path, "r");
-		$csvData = [];
-		while (($d = fgetcsv($fh, 0, ';'))) {
-			if ($d)
-				$csvData[] = $d;
-		}
-		fclose($fh);
-		return $csvData;
-	}
-
-	/**
-	 * @param string $fileName
-	 * @param array $data
-	 * @deprecated from 24/07/2018
-	 */
-	private function importOnDatabase(string $fileName, array $data)
-	{
-		global $f3;
-		$this->dbMysql->query("TRUNCATE TABLE `distinte`");
-		foreach($data as $dataRow) {
-			$descr = addslashes($dataRow[10]);
-			$sql = <<<EOL
-INSERT INTO distinte (
-origine, consegna, commessa, ordine_produzione, data_creazione, time_creazione, destinazione_merci, barcode, descrizione, qta_prelevare, qta_mancante
-) VALUES (
-'$dataRow[16]', '$dataRow[0]', '$dataRow[2]', '$dataRow[3]', str_to_date('$dataRow[6]','%d/%m/%Y'), '$dataRow[7]', '$dataRow[15]','$dataRow[9]',
-'$descr', '$dataRow[11]', 0
-);
-EOL;
-			if (!$this->dbMysql->query($sql)) {
-				echo "Error on query: {$sql}" . PHP_EOL;
-				echo $this->dbMysql->error . PHP_EOL;
-				return;
-			}
-		}
-		echo "{$fileName} successfully imported" . PHP_EOL;
-		$this->markFileAsDone($fileName);
-	}
-
-	private function markFileAsDone(string $csvPath)
-	{
-		rename($csvPath, "{$csvPath}_imported");
-	}
-
-	private function mysqlLoadCSV(string $CSVPath, string $tableName)
-	{
-		$this->dbMysql->query("TRUNCATE TABLE `{$tableName}`");
-		$sql = <<<EOF
-LOAD DATA INFILE '$CSVPath' INTO TABLE `$tableName`
-FIELDS TERMINATED BY ';' ENCLOSED BY '' LINES TERMINATED BY '\\n';
-EOF;
-		if (!$this->dbMysql->query($sql)) {
-			echo "Error on query: {$sql}" . PHP_EOL;
-			echo $this->dbMysql->error . PHP_EOL;
-			return;
-		}
-		echo "{$CSVPath} successfully imported" . PHP_EOL;
-		$this->markFileAsDone($CSVPath);
-	}
-
-	/**
-	 * Iterate on $data and call $func to make operations on rows
-	 *
-	 * @param string $fileName
-	 * @param array $data
-	 * @param function(array $dataRow) $func
-	 */
-	private function dynamicImporter(string $fileName, array $data, $func)
-	{
-		foreach($data as $dataRow) {
-			$func($dataRow);
-		}
-		$this->output .= "{$fileName} successfully imported<br/>";
-	}
-
-	/**
-	 * Create MySQL temp table for smart operations
-	 *
-	 * @param string $table
-	 * @return string temporary name
-	 */
-	private function createTmpTableFrom(string $table)
-	{
-		$tempTableName = 'tmp' . rand(10, 200);
-		$this->dbMysql->exec("CREATE TEMPORARY TABLE IF NOT EXISTS `{$tempTableName}` AS (SELECT * FROM `{$table}`)");
-		return $tempTableName;
-	}
-}

+ 0 - 20
src/OpenMind/FindBox/FindBox.php

@@ -1,20 +0,0 @@
-<?php
-/**
- * Created by PhpStorm.
- * User: Giancarlo
- * Date: 09/08/2018
- * Time: 09:57
- */
-
-namespace OpenMind\FindBox;
-
-use OpenMind\System\PluginStrategy;
-
-class FindBox extends PluginStrategy
-{
-	public function __construct()
-	{
-		// CLI scripts
-		$this->AddRoute('GET /cli.fb.service', __NAMESPACE__ . '\FindBoxService->Main');
-	}
-}

+ 0 - 414
src/OpenMind/FindBox/FindBoxCoreSystem.php

@@ -1,414 +0,0 @@
-<?php
-/**
- * @param array $array
- * @param array $keysToExtract
- * @return array
- * @author Giancarlo Voci
- * @date 28/03/2018
- * @time 09:13
- */
-
-namespace OpenMind\FindBox;
-
-use DOMDocument;
-use Exception;
-use Generator;
-use OpenMind\System\System;
-use ZipArchive;
-
-function array_filter_keys(array $array, array $keysToExtract)
-{
-	return array_intersect_key($array, array_flip($keysToExtract));
-}
-
-class FindBoxCoreSystem
-{
-	/** @var string $Host */
-	private $Host;
-
-	/** @var int $Port */
-	private $Port;
-
-	/** @var string $Key */
-	private $Key;
-
-	/** @var string $Pass */
-	private $Pass;
-
-	/** @var FALSE|mixed coreDetails */
-	private $coreDetails;
-
-	/**
-	 * CoreLabel constructor.
-	 *
-	 * @param string $host hostname of FindBox OS system
-	 * @param int $port
-	 * @param string $APIKey
-	 * @param string $Password
-	 * @throws Exception
-	 */
-	public function __construct(string $host, int $port, string $APIKey, string $Password)
-	{
-		$this->Host = $host;
-		$this->Port = $port;
-		$this->Key = $APIKey;
-		$this->Pass = $Password;
-
-		Global $System;
-		/** @var System $System */
-		$Cache = $System->getCache();
-
-		if (!$Cache->exists('FindBoxCoreSystem')) {
-			$URI = '/1.0.0/index.php/getstatus/1.0.0/';
-			$ret = $this->CallAPI($URI, "");
-
-			if ($ret['err'] != 0)
-				throw new Exception("Can't connect to FindBox server:<br/><br/>{$ret['value']}");
-
-			if ($ret['err'] == 0 && $ret['value']['code'] == 0) {
-				$details = $ret['value']['value'];
-				$this->coreDetails = array_filter_keys($details, ['core.box_id', 'core.version']);
-			}
-			$Cache->set('FindBoxCoreSystem', $this->coreDetails, 3600);
-		} else {
-			$this->coreDetails = $Cache->get('FindBoxCoreSystem');
-		}
-	}
-
-	/**
-	 * CallGetAPI make HTTP GET requests
-	 * @param string $URI full path of api url, for example "/1.0.0/index.php/smartesl.blink_special/1.0.0/"
-	 * @param string $Parameters
-	 * @param bool $skipResponse for faster communications, if response not need, you can skip this step
-	 * @return array converted from JSON response of request if present
-	 */
-	public function CallAPI(string $URI, string $Parameters, bool $skipResponse = false): array
-	{
-		global $frameworkUtils;
-
-		$bodyJSON = null;
-		$auth = "Authorization: Basic " . base64_encode("{$this->Key}:{$this->Pass}") . "\r\n";
-
-		$streamfbOS = @stream_socket_client("tcp://{$this->Host}:{$this->Port}", $errno, $errstr, 1);
-		if (!$streamfbOS) {
-			$frameworkUtils->getLogger()->write("CallAPI::stream_socket_client error code: [{$errno}]-'{$errstr}'", 'd/m/Y H:i:s');
-			return $this->returnCode($errno, $errstr);
-		} else {
-			//$requestText = "{$method} {$URI}{$Parameters} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nConnection: close\r\n\r\n";
-			$requestBodyLength = strlen($Parameters);
-			$requestText = "POST {$URI} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nContent-Type: application/json\r\nContent-Length: {$requestBodyLength}\r\n\r\n{$Parameters}\r\n";
-			fwrite($streamfbOS, $requestText);
-			if (!$skipResponse) {
-				$r = stream_get_contents($streamfbOS);
-				list(, $bodyJSON) = explode("\r\n\r\n", $r, 2);
-			}
-			fclose($streamfbOS);
-		}
-
-		$bodyJSON = ($bodyJSON == null) ? "{}" : $bodyJSON;
-
-		return $this->returnCode(0, $bodyJSON);
-	}
-
-	/**
-	 * Helper for make JSON return code
-	 *
-	 * @param int $errorCode
-	 * @param string $value
-	 * @return array
-	 */
-	private function returnCode(int $errorCode, string $value): array
-	{
-		$val = json_decode($value, true) ?? $value;
-		return array(
-			"err" => $errorCode,
-			"value" => $val);
-	}
-
-	/**
-	 * @param string $URI
-	 * @param string $Parameters
-	 * @param bool $skipResponse
-	 * @return Generator
-	 */
-	public function AsyncCallAPI(string $URI, string $Parameters, bool $skipResponse = false): Generator
-	{
-		global $f3, $frameworkUtils;
-
-		$bodyJSON = null;
-		$auth = "Authorization: Basic " . base64_encode("{$this->Key}:{$this->Pass}") . "\r\n";
-
-		if ($f3->get('DEBUG') > 2)
-			$frameworkUtils->getLogger()->write("AsyncCallAPI::OpenSocket: $Parameters", 'd/m/Y H:i:s');
-
-		$streamfbOS = @stream_socket_client("tcp://{$this->Host}:{$this->Port}", $errno, $errstr, 1);
-		if (!$streamfbOS) {
-			$frameworkUtils->getLogger()->write("AsyncCallAPI::stream_socket_client error code: [{$errno}]-'{$errstr}'", 'd/m/Y H:i:s');
-			return $this->returnCode($errno, $errstr);
-		} else {
-			stream_set_blocking($streamfbOS, false);
-			//$requestText = "{$method} {$URI}{$Parameters} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nConnection: close\r\n\r\n";
-			$requestBodyLength = strlen($Parameters);
-			$requestText = "POST {$URI} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nContent-Type: application/json\r\nContent-Length: {$requestBodyLength}\r\n\r\n{$Parameters}\r\n";
-			fwrite($streamfbOS, $requestText);
-			if (!$skipResponse) {
-				while ('' == $r = stream_get_contents($streamfbOS))
-					// Send(true) interrupt the app
-					if ($reason = (yield $r)) {
-						$r = "\r\n\r\n{\"value\": \"{$reason}\"}";
-						break;
-					}
-				list(, $bodyJSON) = explode("\r\n\r\n", $r, 2);
-			}
-			if ($f3->get('DEBUG') > 2)
-				$frameworkUtils->getLogger()->write("AsyncCallAPI::CloseSocket: $Parameters", 'd/m/Y H:i:s');
-
-			fclose($streamfbOS);
-		}
-
-		$bodyJSON = ($bodyJSON == null) ? "{}" : $bodyJSON;
-
-		yield $this->returnCode(0, $bodyJSON);
-	}
-
-	public function BlinkUnassigned()
-	{
-		$URI = "/1.0.0/index.php/smartesl.blink_unassigned/2.0.0/";
-		$this->CallAPI($URI, "", true);
-	}
-
-	/**
-	 * Save products into findbox database
-	 * @param string $barcode
-	 * @param string $name
-	 * @param array $dataFields Custom fields
-	 * @return array
-	 */
-	public function SaveProducts(string $barcode, string $name, array $dataFields): array
-	{
-		$product = json_encode([[[
-			"barcode" => $barcode,
-			"name1" => $name,
-			"datafields" => $dataFields
-		]]]);
-
-		$URI = "/1.0.0/index.php/saveproducts/1.2.0/";
-
-		return $this->CallAPI($URI, $product);
-	}
-
-	/**
-	 * Add label into findbox system and Bind it with a barcode
-	 *
-	 * @param string $labelId
-	 * @param string $barcode
-	 * @param int $groupId (default 1)
-	 * @return array
-	 */
-	public function Bind(string $labelId, string $barcode, int $groupId = 1): array
-	{
-		$URI = "/1.0.0/index.php/smartesl.bind/1.1.0/";
-
-		return $this->CallAPI($URI, "[\"{$labelId}\",\"{$barcode}\",false,$groupId]");
-	}
-
-	/**
-	 * Remove label from findbox system
-	 *
-	 * @param string $labelId
-	 * @return array
-	 */
-	public function UnBind(string $labelId): array
-	{
-		// Retrieve barcode associated to the label
-		$labelDetails = $this->CallAPI("/1.0.0/index.php/smartesl.getstatus/4.0.0/", "[[\"{$labelId}\"]]");
-		// If RSSI exist, label it is binded to system
-		if (isset($labelDetails['value']['value'][$labelId]['rssi'])) {
-			$barcode = $labelDetails['value']['value'][$labelId]['data']['barcode'];
-
-			$ret = $this->CallAPI("/1.0.0/index.php/smartesl.unbind/1.0.0/", "[\"{$labelId}\"]");
-			if ($ret['err'] != 0)
-				return $this->returnCode(1, 'Error on unbind');
-
-			$ret = $this->CallAPI("/1.0.0/index.php/deleteproducts/1.0.0/", "[\"{$barcode}\"]");
-			if ($ret['err'] != 0)
-				return $this->returnCode(1, 'Error on deleteproducts');
-
-			// All ok
-			return $this->returnCode(0, '');
-		} else
-			return $this->returnCode(1, 'No label exist on Findbox');
-	}
-
-	/**
-	 * Request all label status to FindBox core.
-	 * If request is forced may require large amount of time to retrieve status directly from labels
-	 *
-	 * @param bool $force true = ask status directly to ESL and not from FindBox Core Cache
-	 *
-	 * @return null|array return array with labelID => status_struct
-	 */
-	public function GetAllESLStatus($force = false): ?array
-	{
-		global $frameworkUtils;
-		// Retrieve all labels
-		$URI = '/1.0.0/index.php/smartesl.getstatus/4.0.0/[[],false]';
-		$ret = $this->CallAPI($URI, "");
-		if ($ret['err'] != 0)
-			return null;
-
-		$fbOSResponse = $this->getLabelsFromResponse($ret);
-		if (!$fbOSResponse) {
-			$frameworkUtils->getLogger()->write('GetAllESLStatus: ' . $ret['value']['value'], 'd/m/Y H:i:s');
-			return null;
-		}
-
-		$labels = $fbOSResponse;
-
-		$this->setTemplatesName($labels);
-
-		if (!$force)
-			return $labels;
-
-		// Request status directly to ESL
-		$eslStatus = [];
-		foreach($labels as $labelID => $status) {
-			// Filter for ONLINE labels only
-			$labelStatus = strtoupper($status['data']['esl_state']);
-			if ($force && in_array($labelStatus, ['OFFLINE', 'BLOCKED,FC']))
-				continue;
-
-			// Request status to ESL
-			$URI = "/1.0.0/index.php/smartesl.getstatus/4.0.0/[[\"{$labelID}\"],true]";
-			$fbOSResponse = $this->getLabelsFromResponse($this->CallAPI($URI, ""));
-			if ($fbOSResponse != null) {
-
-				$eslStatus = array_merge($eslStatus, $fbOSResponse);
-			}
-		}
-
-		return $eslStatus;
-	}
-
-	/**
-	 * Check the response of FindBoxOS, return an array if no error, else NULL
-	 *
-	 * @param array $fbOS_Response
-	 * @return NULL|array
-	 */
-	public function getLabelsFromResponse(array $fbOS_Response): ?array
-	{
-		// Check if response have field 'value' that is an array
-		$haveArray = isset($fbOS_Response['value']['value']) && is_array($fbOS_Response['value']['value']);
-
-		if (!$haveArray)
-			return NULL;
-
-		return $fbOS_Response['value']['value'];
-	}
-
-	/**
-	 * Add 'data.template_name' to the structure returned by GetStatus
-	 *
-	 * @param array $status
-	 */
-	private function setTemplatesName(&$status)
-	{
-		$tplNameList = [];
-
-		$fb_template = $this->CallAPI('/1.0.0/index.php/smartesl.gettemplates/1.0.0/', '[]');
-		$templates = $this->getLabelsFromResponse($fb_template);
-
-		if ($templates != null) {
-			foreach($templates as $item) {
-				$tplNameList[$item['template_id']] = $item['name'];
-			}
-		}
-
-		foreach($status as &$esl) {
-			if (count($tplNameList) == 0) {
-				$esl['data']['template_name'] = '';
-				continue;
-			}
-
-			$tplName = $tplNameList[$esl['data']['template_id']];
-
-			$esl['data']['template_name'] = $tplName ?? '{NO_NAME}';
-		}
-	}
-
-	/**
-	 * Retrieve the SVG file of template
-	 *
-	 * @param string|int $templateID name or id of findbox template
-	 * @return string|null
-	 */
-	public function GetTemplateImage($templateID)
-	{
-		$quote = !is_numeric($templateID) ? '"' : '';
-		$urlAPI = "/1.0.0/index.php/smartesl.templates.gettemplate/1.0.0/[{$quote}{$templateID}{$quote}]";
-		$ret = $this->CallAPI($urlAPI, "");
-
-		if ($ret['err'] == 0 && $ret['value']['code'] == 0) {
-			// Update current properties
-			$status = $ret['value']['value'];
-
-			$binZIP = @base64_decode($status['base64_zip']);
-
-			if ($binZIP == null)
-				return null;
-
-			$zipName = "fbtpl/{$templateID}.zip";
-
-			@mkdir('fbtpl', 0777, true);
-
-			file_put_contents($zipName, $binZIP);
-
-			$zip = new ZipArchive();
-			if ($zip->open($zipName) === TRUE) {
-				$dest = "fbtpl/{$templateID}.svg";
-				$zip->extractTo('fbtpl/', 'source.svg');
-				$zip->close();
-				rename('fbtpl/source.svg', $dest);
-				unlink("fbtpl/{$templateID}.zip");
-				$this->fixFindBoxSVG($dest);
-				return $dest;
-			}
-		}
-		return NULL;
-	}
-
-	private function fixFindBoxSVG($svgPath)
-	{
-		global $f3;
-
-		$svgTemplate = new DOMDocument();
-		$svgTemplate->load($svgPath);
-		$xlink = $svgTemplate->getElementsByTagName('image');
-		if ($xlink->length > 0) {
-			$href = $xlink[0]->getAttribute('xlink:href');
-
-			$url = parse_url($href);
-			$url['host'] = $f3->get('CORE.fb.host');
-			$imgUrl = "{$url['scheme']}://{$url['host']}{$url['path']}";
-
-			$xlink[0]->setAttribute('xlink:href', $imgUrl);
-		}
-
-		$svgNode = $svgTemplate->getElementsByTagName('svg');
-		if ($svgNode->length > 0) {
-			$svgNode[0]->setAttribute('class', 'w-100'); // Just fit everywhere
-			$svgNode[0]->removeAttribute('transform');
-		}
-
-		$svgTemplate->save($svgPath);
-	}
-
-	public function getJobStatus(array $id)
-	{
-		$ids = join(',', $id);
-		$URI = "/1.0.0/index.php/smartesl.getjobstatus/1.0.0/[[{$ids}]]";
-
-		return $this->CallAPI($URI, '');
-	}
-}

+ 0 - 571
src/OpenMind/FindBox/FindBoxService.php

@@ -1,571 +0,0 @@
-<?php
-/**
- * Created by PhpStorm.
- * User: Giancarlo
- * Date: 09/08/2018
- * Time: 11:18
- */
-
-namespace OpenMind\FindBox;
-
-use Base;
-use DB\SQL;
-use Exception;
-use Generator;
-use Log;
-use OpenMind\System\QueueSystem;
-
-class JobManager
-{
-	/** @var AsyncJobClass[] $jobs */
-	private $jobs;
-
-	/** @var Log $ServiceLogger */
-	private $ServiceLogger;
-
-	public function __construct($ServiceLogger)
-	{
-		$this->ServiceLogger = $ServiceLogger;
-		$this->Reset();
-	}
-
-	/*
-	 * Reset Job Manager and clear active queue
-	 */
-	public function Reset()
-	{
-		$this->jobs = [];
-	}
-
-	public function AggregateBlinkByParams()
-	{
-		global $f3;
-
-		if (count($this->jobs) == 0)
-			return;
-
-		$uniq = [];
-		$freeJobs = array_slice($this->jobs, $f3->get('FINDBOXSERVICE.MAX_JOBS'), null, true);
-
-		if (count($freeJobs) == 0)
-			return;
-		// Get unique jobs with same params
-		foreach($freeJobs as $k => $jobToFind) {
-
-			$parmExists = array_filter($uniq, function ($job) use ($jobToFind) {
-				return (count($this->array_diff_symmetric($job->getLedParams(), $jobToFind->getLedParams())) === 0);
-			});
-			if (count($parmExists) == 0)
-				$uniq[$k] = $jobToFind;
-		}
-
-		foreach($uniq as $jobUniq) {
-			array_filter($freeJobs, function ($job) use (&$jobUniq) {
-
-				// aggregate ids with same params
-				if (count($this->array_diff_symmetric($jobUniq->getLedParams(), $job->getLedParams())) === 0) {
-
-					if (count($this->array_diff_symmetric($job->getLabels(), $jobUniq->getLabels())) === 0)
-						return;
-
-					$labels = array_unique(array_merge($job->getLabels(), $jobUniq->getLabels()));
-					$jobUniq->setLabels($labels);
-				}
-			});
-		}
-
-		$this->jobs = array_merge(
-			array_slice($this->jobs, 0, 1, true),
-			$uniq
-		);
-	}
-
-	private function array_diff_symmetric(array $ar1, array $ar2): array
-	{
-		return array_merge(array_diff($ar1, $ar2), array_diff($ar2, $ar1));
-	}
-
-	public function count()
-	{
-		return count($this->jobs);
-	}
-
-	/**
-	 * In callBack need to call GetCurrentValue to get the Generator value
-	 *
-	 * @param callable $callBack function(AsyncJobClass), return TRUE to remove job from queue
-	 */
-	public function Run(callable $callBack)
-	{
-		global $f3;
-
-		if (count($this->jobs) == 0)
-			return;
-
-		$jobList = array_slice($this->jobs, 0, $f3->get('FINDBOXSERVICE.MAX_JOBS'), TRUE);
-
-		foreach($jobList as $k => $job) {
-
-			if ($callBack($job) === TRUE) {
-				unset($this->jobs[$k]);
-			} else
-				$job->Run();
-
-		}
-	}
-
-	public function add(AsyncJobClass $job): void
-	{
-		$this->jobs[] = $job;
-	}
-}
-
-class AsyncJobClass
-{
-	public $Opened;
-	/** @var integer $retryForFault */
-	public $retryForFault;
-	/** @var Generator $blinkGenerator */
-	private $blinkGenerator = null;
-	/** @var array|null ledParams */
-	private $ledParams;
-	/** @var array labels */
-	private $labels;
-	/** @var FindBoxCoreSystem $fb */
-	private $fb;
-
-	/**
-	 * AsyncJobClass constructor.
-	 *
-	 * @param FindBoxCoreSystem $fb
-	 * @param array $labels
-	 * @param array|null $ledParams if null, the blink become blinkOFF
-	 */
-	public function __construct(FindBoxCoreSystem $fb, array $labels, array $ledParams = null)
-	{
-		global $f3;
-
-		$this->fb = $fb;
-		$this->labels = $labels;
-		$this->ledParams = $ledParams;
-		$this->retryForFault = $f3->get('MAX_RETRY_FOR_FAULT');
-		$this->Opened = false;
-	}
-
-	public function __destruct()
-	{
-		if (!$this->blinkGenerator)
-			return;
-
-		$this->Stop('Class Disposed');
-	}
-
-	public function Stop($reason)
-	{
-		$this->Opened = false;
-		return $this->blinkGenerator->send($reason);
-	}
-
-	public function RemoveLabels(array $labelsToFind): int
-	{
-		$diff = array_diff($this->labels, $labelsToFind);
-		$this->labels = $diff;
-		return count($diff);
-	}
-
-	/**
-	 * @return array|string
-	 */
-	public function getLabels()
-	{
-		return $this->labels;
-	}
-
-	/**
-	 * @param array $labels
-	 */
-	public function setLabels(array $labels): void
-	{
-		$this->labels = $labels;
-	}
-
-	public function BlinkON()
-	{
-		$esl = new SmartESL($this->fb, $this->labels);
-
-		$this->blinkGenerator =
-			$esl->AsyncBlinkSpecial(
-				$this->ledParams['pattern'],
-				$this->ledParams['repeat'],
-				$this->ledParams['iter_time'],
-				$this->ledParams['iterations'],
-				$this->ledParams['color']
-			);
-	}
-
-	public function BlinkOFF()
-	{
-		$esl = new SmartESL($this->fb, $this->labels);
-		$this->blinkGenerator = $esl->AsyncBlinkOff();
-	}
-
-	/**
-	 * @return bool
-	 */
-	public function isBlinkoff(): bool
-	{
-		return $this->ledParams == null;
-	}
-
-	public function GetCurrentValue()
-	{
-		$value = $this->blinkGenerator->current();
-		$this->Opened = !is_array($value);
-		return $value;
-	}
-
-	public function Run()
-	{
-		$this->blinkGenerator->next();
-	}
-
-	public function Finished()
-	{
-		return !$this->blinkGenerator->valid();
-	}
-
-	/**
-	 * @return array
-	 */
-	public function getLedParams(): array
-	{
-		return ($this->ledParams == null) ? [] : $this->ledParams;
-	}
-}
-
-class FindBoxService
-{
-	/** @var SQL $dbHandle */
-	private $dbHandle;
-
-	/** @var FindBoxCoreSystem|null findBoxCore */
-	private $findBoxCore = null;
-
-	/** @var Log $ServiceLogger */
-	private $ServiceLogger;
-
-	/** @var array $labelToBlink */
-	private $labelToBlink;
-
-	/** @var QueueSystem $qs_Server */
-	private $qs_Server;
-
-	/** @var JobManager $jobManager */
-	private $jobManager;
-
-	private $ts_ms;
-
-	public function __construct()
-	{
-		global $f3;
-
-		$this->ServiceLogger = new Log('ServiceLogger.log');
-
-		$this->dbHandle = $f3->get('DB');
-		while ($this->findBoxCore == null)
-			try {
-				$this->findBoxCore = new FindBoxCoreSystem(...array_values($f3->get('CORE.fb')));
-			} catch (Exception $e) {
-				$this->ServiceLogger->write("Can't find FindBoxCore, retry in 1 second..", 'd/m/Y H:i:s');
-				sleep(1);
-			}
-		$this->labelToBlink = [];
-
-		$this->ts_ms = NULL;
-
-		$this->qs_Server = new QueueSystem();
-
-		$this->qs_Server->CreateService();
-	}
-
-	/**
-	 * Called from php spawn process.
-	 *
-	 * @param Base $f3
-	 */
-	public function Main($f3)
-	{
-		$this->ServiceLogger->write("Starting FindBoxService main loop", 'd/m/Y H:i:s');
-		$this->ServiceLogger->write("Opening socket listen {$f3->get('FINDBOXSERVICE.PORT')}", 'd/m/Y H:i:s');
-
-		$this->jobManager = new JobManager($this->ServiceLogger);
-
-		while (true) {
-			$this->getCurrentQueue();
-
-			$isTimeToAggregateCommands = ($this->now_ms() - $this->ts_ms) > $f3->AGGREGATE_TIMEOUT;
-
-			if ($isTimeToAggregateCommands && (count($this->labelToBlink) > 0)) {
-
-				$this->SplitBeforeAggregation();
-
-				foreach($this->labelToBlink as $commandID => &$label) {
-					switch ($label->operation) {
-
-						case QueueCommand::$Q_BLINK_OFF:
-							$labelJ = join(', ', $label->id);
-							$this->ServiceLogger->write("BlinkOff for label(s) {$labelJ} requested", 'd/m/Y H:i:s');
-
-							// Rimuove il JOB se esiste
-							$asyncBlinkOFF = new AsyncJobClass($this->findBoxCore, $label->id);
-							$this->jobManager->add($asyncBlinkOFF);
-
-							unset($this->labelToBlink[$commandID]);
-
-							break;
-
-						case QueueCommand::$Q_BLINK_ON:
-							$labelJ = join(', ', $label->id);
-							$this->ServiceLogger->write("BlinkOn for label(s) {$labelJ} requested", 'd/m/Y H:i:s');
-
-							$asyncBlink = new AsyncJobClass($this->findBoxCore, $label->id, $label->params);
-							$this->jobManager->add($asyncBlink);
-
-							unset($this->labelToBlink[$commandID]);
-
-							break;
-					}
-				}
-				$this->ts_ms = NULL;
-			}
-
-			$this->jobManager->AggregateBlinkByParams();
-
-			// Async check on JOBS
-			$this->jobManager->Run(function (AsyncJobClass $asyncBlink) {
-
-				// Start the JOB
-				if (!$asyncBlink->Opened) {
-					if ($asyncBlink->isBlinkoff())
-						$asyncBlink->BlinkOFF();
-					else
-						$asyncBlink->BlinkON();
-				}
-
-				$err = $asyncBlink->GetCurrentValue();  // Fa scattare il generator e ricava il valore
-
-				// Response RESTful ricevuta da Findbox Core?
-				if (is_array($err)) {
-					// TODO da controllare perchè sembra che si accumulino array quadrupli
-					$this->ServiceLogger->write('JobManager->Run: '
-						. json_encode($asyncBlink->getLabels())
-						. json_encode($asyncBlink->getLedParams())
-						. ' err ' . json_encode($err['value']['value']), 'd/m/Y H:i:s');
-
-					if ($this->ManageBlinkFaults($asyncBlink, $err)) {
-						// Remove the JOB
-						return TRUE;
-					} else {
-						$labelJ = join(', ', $asyncBlink->getLabels());
-						$this->ServiceLogger->write("Restart BlinkOn/Off for label(s) {$labelJ} requested", 'd/m/Y H:i:s');
-
-						// Restart JOB if any errors
-						if ($asyncBlink->isBlinkoff())
-							$asyncBlink->BlinkOFF();
-						else
-							$asyncBlink->BlinkON();
-					}
-				}
-				return FALSE;
-			});
-		}
-		return;
-	}
-
-	private function getCurrentQueue()
-	{
-		/** @var QueueCommand $qCommand */
-		$this->qs_Server->ServerCheckClientCommands(
-			function ($clientCommands) {
-
-				if ($clientCommands->operation == QueueCommand::$Q_BLINK_ON || $clientCommands->operation == QueueCommand::$Q_BLINK_OFF) {
-
-					if ($clientCommands->id == null) {
-						$this->ServiceLogger->write("No labels passed to BLINK_OFF", 'd/m/Y H:i:s');
-						return NULL;
-					}
-					// Remove NULL label_id if present
-					$clientCommands->id = array_filter($clientCommands->id);
-
-					// Start aggregation timer only when first blink on/off is received
-					if ($this->ts_ms == NULL)
-						$this->ts_ms = $this->now_ms();
-
-					$this->RemoveDuplicateLabels($clientCommands->id);
-
-				}
-
-				switch ($clientCommands->operation) {
-
-					case QueueCommand::$Q_GETJOBS:
-						return ['jobCount' => $this->jobManager->count()];
-						break;
-
-					case QueueCommand::$Q_CLEARQUEUE:
-						$this->ServiceLogger->write("Clear jobs requested", 'd/m/Y H:i:s');
-						$this->jobManager->Reset();
-						return ['jobClear' => true];
-						break;
-
-					case QueueCommand::$Q_SAVEPRODUCT:
-						$this->saveProduct($clientCommands);
-						break;
-
-					case QueueCommand::$Q_BLINK_OFF:
-						// Split blink list by 9 items each array
-						$this->SplitArrayIn9Parts($clientCommands);
-
-						break;
-
-					case QueueCommand::$Q_BLINK_ON:
-						// filter for blinkon and regroup for jobs that has same blink parameters
-						$flt = array_filter($this->labelToBlink, function (&$item) use ($clientCommands) {
-							if ($item->operation != QueueCommand::$Q_BLINK_ON)
-								return false;
-							return count(array_diff($item->params, $clientCommands->params)) === 0;
-						});
-
-						if (count($flt) > 0) {
-							$key = array_keys($flt)[0]; // get first array key name
-							$flt[$key]->id = array_merge($flt[$key]->id, $clientCommands->id);  //merge all label id of all jobs removing duplicates too
-						} else {
-							// Split blink list by 9 items each array
-							$this->SplitArrayIn9Parts($clientCommands);
-						}
-						break;
-
-					case QueueCommand::$Q_DISPLAY:
-						$this->ServiceLogger->write("Change display for label {$clientCommands->id[0]} requested", 'd/m/Y H:i:s');
-						$esl = new SmartESL($this->findBoxCore, $clientCommands->id);
-						$esl->Display($clientCommands->params['templateId'], $clientCommands->params['barcode']);
-						break;
-				}
-				return NULL;
-			}
-		);
-	}
-
-	/**
-	 * Return time in milliseconds
-	 *
-	 * @return float
-	 */
-	private function now_ms()
-	{
-		return round(microtime(true) * 1000);
-	}
-
-	/**
-	 * Remove the label already present in blink_on lists
-	 *
-	 * @param array $labelsToRemove
-	 */
-	private function RemoveDuplicateLabels(array $labelsToRemove)
-	{
-		$this->labelToBlink = array_filter($this->labelToBlink, function (&$item) use ($labelsToRemove) {
-			if (count(array_intersect($item->id, $labelsToRemove)) > 0) {
-				$item->id = array_diff($item->id, $labelsToRemove);
-			}
-			return count($item->id) > 0; // need to clean empty jobs due to empty label_id
-		});
-	}
-
-	/**
-	 * Send SaveProduct to Label
-	 *
-	 * @param QueueCommand $label
-	 * @return bool
-	 */
-	public function saveProduct($label)
-	{
-		if ($label->operation == QueueCommand::$Q_SAVEPRODUCT) {
-			$retSave = $this->findBoxCore->SaveProducts($label->id[0], $label->description, $label->params['dataFields']);
-			$labelsJ = join(', ', $label->id);
-			$this->ServiceLogger->write("Save product {$labelsJ} RET_CODE= {$retSave['value']['code']} RET_VALUE= {$retSave['value']['value']}", 'd/m/Y H:i:s');
-			return true;
-		}
-		return false;
-	}
-
-	/**
-	 * If a command come with more than 9 label ids, it split command in multiple commands
-	 *
-	 * @param QueueCommand $command
-	 */
-	private function SplitArrayIn9Parts(QueueCommand $command)
-	{
-		// Split blink list by 9 items each array
-		$labelsSplitted = array_chunk($command->id, 9);
-		foreach($labelsSplitted as $labels) {
-			$cmd = clone $command;
-			$cmd->id = $labels;
-			$this->labelToBlink[rand(0, 60000)] = $cmd;
-		}
-	}
-
-	private function SplitBeforeAggregation()
-	{
-		$newLabelToBlink = [];
-		foreach($this->labelToBlink as $commandID => &$label) {
-			$labelsSplitted = array_chunk($label->id, 9);
-			foreach($labelsSplitted as $labels) {
-				$cmd = clone $label;
-				$cmd->id = $labels;
-				$newLabelToBlink[rand(0, 60000)] = $cmd;
-			}
-		}
-		$this->labelToBlink = $newLabelToBlink;
-	}
-
-	/**
-	 * @param AsyncJobClass $job
-	 * @param array $labels
-	 * @param $fbError
-	 * @return bool true if no error, false if error
-	 */
-	private function ManageBlinkFaults(AsyncJobClass &$job, $fbError): bool
-	{
-		$labelResponses = $this->findBoxCore->getLabelsFromResponse($fbError);
-		if ($labelResponses === NULL) {
-			$labelJ = join(', ', $job->getLabels());
-			$this->ServiceLogger->write("Label(s) {$labelJ} error removed from queue: {$fbError['value']['value']}", 'd/m/Y H:i:s');
-			return true;
-		}
-
-		// Filter labels with errors
-		$fail = array_filter($labelResponses, function ($v) {
-			return !$v;
-		});
-		$fail = array_keys($fail);
-
-		// TODO fare il controllo con "isset($labelResponses[$labelId]) && $labelResponses[$labelId]"
-		if ($labelResponses && count($fail) == 0) { // Label exists and is true, mean that's all OK, no error
-			return true;
-		}
-
-		// Ho finito il numero di tentativi massimi?
-		if ($job->retryForFault < 0) {  // Rimuovo dalla coda l'etichetta
-			$labelJ = join(', ', $job->getLabels());
-			$this->ServiceLogger->write("Label(s) {$labelJ} fatal error. Removing from queue", 'd/m/Y H:i:s');
-			return true;
-		} else {                        // Riprovo al prossimo ciclo
-			$labelJ = join(', ', $fail);
-			$this->ServiceLogger->write("Label(s) {$labelJ} blink error. Try next cycle...", 'd/m/Y H:i:s');
-			$job->retryForFault--;
-			// Set failed labels into JOB
-			$job->setLabels($fail);
-		}
-
-		return false; // Default return value
-	}
-}

+ 0 - 115
src/OpenMind/FindBox/QueueCommand.php

@@ -1,115 +0,0 @@
-<?php
-/**
- * Created by PhpStorm.
- * User: Giancarlo
- * Date: 09/08/2018
- * Time: 14:42
- */
-
-namespace OpenMind\FindBox;
-
-class QueueCommand
-{
-	static public $Q_BLINK_ON = 1;
-	static public $Q_BLINK_OFF = 2;
-	static public $Q_SAVEPRODUCT = 3;
-	static public $Q_DISPLAY = 4;
-	static public $Q_GETJOBS = 5;
-	static public $Q_CLEARQUEUE = 6;
-
-	public $id;
-	public $operation;
-	public $description;
-	public $params;
-
-	/**
-	 * QueueCommand constructor.
-	 * @param array|null $labels
-	 */
-	public function __construct(array $labels = null)
-	{
-		$this->id = $labels;
-		$this->params = null;
-	}
-
-	/**
-	 * Get custom property
-	 *
-	 * @param $name
-	 * @return mixed
-	 */
-	public function __get($name)
-	{
-		return $this->{$name};
-	}
-
-	/**
-	 * Add custom property
-	 *
-	 * @param $name
-	 * @param $value
-	 */
-	public function __set($name, $value)
-	{
-		$this->{$name} = $value;
-	}
-
-	public function setLeds(int $pattern, int $repeat, int $iter_time, int $iterations, int $color)
-	{
-		$this->operation = self::$Q_BLINK_ON;
-		list(
-			$this->params['pattern'],
-			$this->params['repeat'],
-			$this->params['iter_time'],
-			$this->params['iterations'],
-			$this->params['color']
-			) = func_get_args();
-	}
-
-	public function saveProducts(string $description, array $dataFields)
-	{
-		$this->operation = self::$Q_SAVEPRODUCT;
-		$this->description = $description;
-		$this->params['dataFields'] = $dataFields;
-	}
-
-	public function setToBlinkOff()
-	{
-		$this->operation = self::$Q_BLINK_OFF;
-	}
-
-	/**
-	 * Calculate blink duration in seconds for patterns_type=2
-	 *
-	 * @return int
-	 */
-	public function getBlinkDuration(): int
-	{
-		return ((($this->params['iter_time'] / 1000) * ($this->params['iterations'])) * $this->params['repeat']);
-	}
-
-	/**
-	 * Set Display ID
-	 *
-	 * @param string $templateId
-	 * @param string $barcode
-	 */
-	public function setDisplay(string $templateId, string $barcode)
-	{
-		$this->operation = self::$Q_DISPLAY;
-		list(
-			$this->params['templateId'],
-			$this->params['barcode']
-			) = func_get_args();
-	}
-
-	public function getJobs()
-	{
-		$this->operation = self::$Q_GETJOBS;
-	}
-
-	public function clearQueue()
-	{
-		$this->operation = self::$Q_CLEARQUEUE;
-	}
-}

+ 0 - 187
src/OpenMind/FindBox/SmartESL.php

@@ -1,187 +0,0 @@
-<?php
-
-/**
- * @author Giancarlo Voci
- * @date 28/03/2018
- * @time 09:09
- */
-
-namespace OpenMind\FindBox;
-
-use Generator;
-
-class SmartESL
-{
-  /** @var FindBoxCoreSystem $core */
-  protected $core;
-
-  /** @var array $labels */
-  protected $labels;
-
-  /** @var array $labelProperties */
-  protected $labelProperties;
-
-  public function __construct(FindBoxCoreSystem $coreL, array $LabelID)
-  {
-    $this->core = $coreL;
-    $this->labels = array_map('strtoupper', $LabelID);
-  }
-
-  /**
-   * Request to label(s) its properties
-   *
-   * @param bool $force request directly to ESL
-   * @return null|array Details
-   */
-  public function GetStatus(bool $force = false)
-  {
-    $labels = json_encode($this->labels);
-    //$URI = "/1.0.0/index.php/smartesl.getstatus/3.1.0/";
-    $forceStr = var_export($force, true);
-    $URI = "/1.0.0/index.php/smartesl.getstatus/4.0.0/[{$labels},{$forceStr}]";
-    $ret = $this->core->CallAPI($URI, '');
-    if ($ret['err'] == 0 && $ret['value']['code'] == 0) {
-      // Update current properties
-      $this->labelProperties = $ret['value']['value'];
-      return $this->labelProperties;
-    }
-    return null;
-  }
-
-  /**
-   * Get label version splitted into the three components major, minor, patch
-   *
-   * @return array
-   */
-  public function GetVersion()
-  {
-    $softVersion = $this->labelProperties[array_keys($this->labelProperties)[0]]['status'];
-    return array_values(array_filter_keys($softVersion, ['soft_major', 'soft_minor', 'soft_patch']));
-  }
-
-  public function AsyncBlinkOff(): Generator
-  {
-    global $f3;
-    return $this->AsyncBlinkSpecial(2, 1, 1000, 1, $f3->BLINKOFF_COLOR);
-  }
-
-
-  /**
-   * Blink the label with parameters.
-   *
-   * @param int $pattern
-   * @param int $repeat
-   * @param int $iter_time
-   * @param int $iterations
-   * @param int|0 $ledColor
-   * @return Generator
-   */
-  public function AsyncBlinkSpecial(int $pattern, int $repeat, int $iter_time, int $iterations, int $ledColor = 0): Generator
-  {
-    $uriParams = json_encode(array_merge(
-      array($this->labels),
-      array($pattern, $ledColor, $repeat, $iter_time, $iterations, 1)
-    ));
-
-    $URI = "/1.0.0/index.php/smartesl.blink_special/2.0.0/";
-    return $this->core->AsyncCallAPI($URI, $uriParams);
-  }
-
-  public function getData()
-  {
-    return $this->labelProperties[$this->labels[0]]['data'];
-  }
-
-  /**
-   * Turn off led(s)
-   */
-  public function BlinkOff()
-  {
-    global $f3;
-    return $this->BlinkSpecial(2, 1, 1000, 1, $f3->BLINKOFF_COLOR);
-  }
-
-  /**
-   * Blink the label with parameters.
-   *
-   * @param int $pattern
-   * @param int $repeat
-   * @param int $iter_time
-   * @param int $iterations
-   * @param int|0 $ledColor
-   * @return array
-   */
-  public function BlinkSpecial(int $pattern, int $repeat, int $iter_time, int $iterations, int $ledColor = 0)
-  {
-    $uriParams = json_encode(array_merge(
-      array($this->labels),
-      array($pattern, $ledColor, $repeat, $iter_time, $iterations, 1)
-    ));
-
-    $URI = "/1.0.0/index.php/smartesl.blink_special/2.0.0/";
-    return $this->core->CallAPI($URI, $uriParams);
-  }
-
-  public function Display(string $templateId, string $barcode)
-  {
-    $URI = "/1.0.0/index.php/smartesl.display/2.3.0/";
-    $params = json_encode(array(
-      $this->labels[0],
-      $barcode,
-      [],
-      $templateId,
-      1, 1
-    ));
-
-    return $this->core->CallAPI($URI, $params);
-  }
-
-  /**
-   * Set ESL display using displayimage CORE API
-   *
-   * @param string $binaryImage
-   * @param int $pageNum
-   * @return array
-   */
-  public function SetPageImage(string $binaryImage, int $pageNum = 1)
-  {
-    $imageb64 = base64_encode($binaryImage);
-    $URI = "/1.0.0/index.php/smartesl.displayimage/1.1.1/";
-
-    $params = json_encode([array_map(function ($label) use ($pageNum, $imageb64) {
-      return [
-        'label_id' => $label,
-        'image' => $imageb64,
-        'page' => $pageNum
-      ];
-    }, $this->labels)]);
-
-    return $this->core->CallAPI($URI, $params);
-  }
-
-  /**
-   * Switch to ESL page $pageNum
-   *
-   * @param int $pageNum
-   * @return array
-   */
-  public function SwitchPage(int $pageNum = 1)
-  {
-    $URI = "/1.0.0/index.php/smartesl.switchpage/1.1.0/";
-
-    $params = json_encode([array_map(function ($label) use ($pageNum) {
-      return [
-        'label_id' => $label,
-        'page' => $pageNum
-      ];
-    }, $this->labels)]);
-
-    return $this->core->CallAPI($URI, $params);
-  }
-
-  public function GetLabelMatch()
-  {
-    $URI = "/1.0.0/index.php/smartesl.getlabelmatch/1.2.0/";
-    return $this->core->CallAPI($URI, json_encode(array($this->labels)));
-  }
-}

+ 0 - 145
src/OpenMind/System/GDSupport.php

@@ -1,145 +0,0 @@
-<?php
-
-/**
- * @author Giancarlo Voci
- * @date 21/09/2019
- * @time 22:32
- */
-
-namespace OpenMind\System;
-
-use GdImage;
-use Picqer\Barcode\BarcodeGeneratorPNG;
-use Picqer\Barcode\Exceptions\BarcodeException;
-
-/**
- * Class GDSupport
- * Facility for draw images using GD php library
- * Accept an array of elements:
- * ['gd_function_name' => ['property_function_1' => value_function_1, 'property_function_2' => value_function_2, etc]];
- *
- * @package OpenMind\System
- */
-class GDSupport
-{
-  private $image;
-
-  public function __construct()
-  {
-    $this->image = new GdImage();
-  }
-
-  /**
-   * @param array $drawings
-   * @param string|null $outputPathFile
-   * @return string if outputPathFile is null, return the image instead to write to file
-   * @throws BarcodeException
-   */
-  public function Render(array $drawings, $outputPathFile = NULL): string
-  {
-    foreach ($drawings as $gdElement) {
-      $name = key($gdElement);
-      $prop = $gdElement[$name];
-      $color = isset($prop['color']) ? $this->getColor($prop['color']) : NULL;
-      switch ($name) {
-
-        case 'BODY':
-          $this->image = imagecreatetruecolor($prop['w'], $prop['h']);
-          $bgColor = isset($prop['bgcolor']) ? $this->getColor($prop['bgcolor']) : NULL;
-          imageantialias($this->image, false);
-          imagefill($this->image, 0, 0, $bgColor);
-          break;
-
-        case 'TEXT':
-          imagettftext($this->image, $prop['size'], 0, $prop['x'], $prop['y'], $color, $prop['font'], $prop['value']);
-          break;
-
-        case 'TEXTFIT':
-          $fontSizeCalulated = $this->fixTextRect($prop['value'], $prop['font'], $prop['w']);
-          imagettftext($this->image, $fontSizeCalulated, 0, $prop['x'], $prop['y'], $color, $prop['font'], $prop['value']);
-          break;
-
-        case 'FILL':
-          imagefill($this->image, $prop['x'], $prop['y'], $color);
-          break;
-
-        case 'RECT':
-          if (isset($prop['filled']))
-            imagefilledrectangle($this->image, $prop['x1'], $prop['y1'], $prop['x2'], $prop['y2'], $color);
-          else
-            imagerectangle($this->image, $prop['x1'], $prop['y1'], $prop['x2'], $prop['y2'], $color);
-          break;
-
-        case 'LINE':
-          if (isset($prop['thickness']))
-            imagesetthickness($this->image, $prop['thickness']);
-
-          imageline($this->image, $prop['x1'], $prop['y1'], $prop['x2'], $prop['y2'], $color);
-
-          if (isset($prop['thickness']))  // Restore default tickness
-            imagesetthickness($this->image, 1);
-          break;
-
-        case 'PIXEL':
-          imagesetpixel($this->image, $prop['x'], $prop['y'], $color);
-          break;
-
-        case 'ELLIPSE':
-          imagefilledellipse($this->image, $prop['x'], $prop['y'], $prop['w'], $prop['h'], $color);
-          break;
-
-        case 'BARCODE':
-          $bcGen = new BarcodeGeneratorPNG();
-          $bcImg = $bcGen->getBarcode($prop['value'], $prop['type'], $prop['widthFactor'], $prop['h']);
-          $gdBcImg = imagecreatefromstring($bcImg);
-          imagecopy($this->image, $gdBcImg, $prop['x'], $prop['y'], 0, 0, imagesx($gdBcImg), imagesy($gdBcImg));
-          break;
-      }
-    }
-
-    // ESL color fix
-    imagetruecolortopalette($this->image, false, 3);
-    imagefilter($this->image, IMG_FILTER_CONTRAST, -100);
-
-    ob_start();
-    imagepng($this->image, $outputPathFile);
-    $data = ob_get_contents();
-    ob_end_clean();
-    return $data;
-  }
-
-  /**
-   * @param string $colorStr
-   * @return false|int
-   */
-  private function getColor(string $colorStr)
-  {
-    list($r, $g, $b) = sscanf($colorStr, "#%02x%02x%02x");
-    return imagecolorallocate($this->image, $r, $g, $b);
-  }
-
-  private function fixTextRect(string $text, string $font, int $maxWidth): int
-  {
-    $font_size = 0;
-    do {
-      $font_size += 0.1;
-      $p = imagettfbbox($font_size, 0, $font, $text);
-      $txt_width = $p[2] - $p[0];
-    } while ($txt_width <= $maxWidth);
-
-    return $font_size;
-  }
-
-  public function __destruct()
-  {
-    $this->Destroy();
-  }
-
-  public function Destroy()
-  {
-    if ($this->image) {
-      imagedestroy($this->image);
-      $this->image = NULL;
-    }
-  }
-}

+ 0 - 120
src/OpenMind/System/QueueSystem.php

@@ -1,120 +0,0 @@
-<?php
-/**
- * Created by PhpStorm..
- * User: Giancarlo
- * Date: 09/08/2018
- * Time: 11:27
- */
-
-namespace OpenMind\System;
-
-class QueueSystem
-{
-	// Used internally
-	private $sckFindBoxService;
-	private $serverPort;
-
-	public function __construct()
-	{
-		global $f3;
-		$this->serverPort = $f3->get('FINDBOXSERVICE.PORT');
-	}
-
-	public function CreateService()
-	{
-		$this->sckFindBoxService = @socket_create_listen($this->serverPort);
-		socket_set_nonblock($this->sckFindBoxService);
-	}
-
-	/**
-	 * Send object to <$serviceName>.
-	 * send() serialize $obj before send to queue
-	 *
-	 * @param $obj
-	 * @param bool $waitResponse
-	 * @return true = connection successful else the JSON decoded server response
-	 */
-	public function send($obj, bool $waitResponse = false)
-	{
-		$serializedObject = serialize($obj);
-		$command = base64_encode($serializedObject);
-
-		$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
-
-		if ($connectOK = @socket_connect($socket, 'localhost', $this->serverPort))
-			socket_write($socket, $command, strlen($command));
-
-		if ($waitResponse) {
-			$cmdResponse = "";
-			while ($resp = @socket_read($socket, 60 * 1024, PHP_BINARY_READ)) {
-				$cmdResponse .= $resp;
-			}
-			if ($cmdResponse)
-				$cmdResponse = json_decode($cmdResponse);
-		}
-
-		socket_close($socket);
-
-		/** @noinspection PhpUndefinedVariableInspection */
-		return ($waitResponse ? $cmdResponse : $connectOK);
-	}
-
-	/**
-	 * Retrieve messages
-	 *
-	 * @param callable $callback (QueueCommand) possible return code:<BR/>
-	 *  <strong>NULL</strong>: do nothing<BR/>
-	 *  <strong>ARRAY</strong>: returning array, ServerCheckClientCommands will json_encode and send to client this data<BR/>
-	 */
-	public function ServerCheckClientCommands(callable $callback): void
-	{
-		$client = $this->SocketClientAccept($this->sckFindBoxService);
-		if ($client) {
-			$clientRead = "";
-
-			while ($resp = socket_read($client, 60 * 1024, PHP_BINARY_READ))
-				$clientRead .= $resp;
-
-			$dataClient = $this->decodeClientData($clientRead);
-
-			$serverResponse = $callback($dataClient);
-			if ($serverResponse !== NULL)
-				socket_write($client, json_encode($serverResponse));
-
-			socket_close($client);
-		}
-	}
-
-	/**
-	 * Check if client connected and send data else return NULL
-	 *
-	 * @param $serverSocket
-	 * @return mixed|null
-	 */
-	private function SocketClientAccept($serverSocket)
-	{
-		$null = NULL;
-		$read = [$serverSocket];
-		$which = socket_select($read, $null, $null, 0, 5);
-
-		if ($which == 0)
-			return NULL;
-
-		$whichSocket = $read[$which - 1];
-		return socket_accept($whichSocket);
-	}
-
-	/**
-	 * Retrieve and unserialize current object
-	 *
-	 * @param $clientData
-	 * @return mixed
-	 */
-	private function decodeClientData($clientData)
-	{
-		$decodedObj = base64_decode($clientData);
-		$obj = unserialize($decodedObj);
-		return $obj;
-	}
-
-}

+ 0 - 2911
src/Picqer/Barcode/BarcodeGenerator.php

@@ -1,2911 +0,0 @@
-<?php
-
-/**
- * General PHP Barcode Generator
- *
- * @author Casper Bakker - picqer.com
- * Based on TCPDF Barcode Generator
- */
-
-// Copyright (C) 2002-2015 Nicola Asuni - Tecnick.com LTD
-//
-// This file is part of TCPDF software library.
-//
-// TCPDF is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Lesser General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// TCPDF is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-// See the GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the License
-// along with TCPDF. If not, see
-// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
-//
-// See LICENSE.TXT file for more information.
-
-namespace Picqer\Barcode;
-
-use Picqer\Barcode\Exceptions\BarcodeException;
-use Picqer\Barcode\Exceptions\InvalidCharacterException;
-use Picqer\Barcode\Exceptions\InvalidCheckDigitException;
-use Picqer\Barcode\Exceptions\InvalidFormatException;
-use Picqer\Barcode\Exceptions\InvalidLengthException;
-use Picqer\Barcode\Exceptions\UnknownTypeException;
-
-abstract class BarcodeGenerator
-{
-	const TYPE_CODE_39 = 'C39';
-	const TYPE_CODE_39_CHECKSUM = 'C39+';
-	const TYPE_CODE_39E = 'C39E';
-	const TYPE_CODE_39E_CHECKSUM = 'C39E+';
-	const TYPE_CODE_93 = 'C93';
-	const TYPE_STANDARD_2_5 = 'S25';
-	const TYPE_STANDARD_2_5_CHECKSUM = 'S25+';
-	const TYPE_INTERLEAVED_2_5 = 'I25';
-	const TYPE_INTERLEAVED_2_5_CHECKSUM = 'I25+';
-	const TYPE_CODE_128 = 'C128';
-	const TYPE_CODE_128_A = 'C128A';
-	const TYPE_CODE_128_B = 'C128B';
-	const TYPE_CODE_128_C = 'C128C';
-	const TYPE_EAN_2 = 'EAN2';
-	const TYPE_EAN_5 = 'EAN5';
-	const TYPE_EAN_8 = 'EAN8';
-	const TYPE_EAN_13 = 'EAN13';
-	const TYPE_UPC_A = 'UPCA';
-	const TYPE_UPC_E = 'UPCE';
-	const TYPE_MSI = 'MSI';
-	const TYPE_MSI_CHECKSUM = 'MSI+';
-	const TYPE_POSTNET = 'POSTNET';
-	const TYPE_PLANET = 'PLANET';
-	const TYPE_RMS4CC = 'RMS4CC';
-	const TYPE_KIX = 'KIX';
-	const TYPE_IMB = 'IMB';
-	const TYPE_CODABAR = 'CODABAR';
-	const TYPE_CODE_11 = 'CODE11';
-	const TYPE_PHARMA_CODE = 'PHARMA';
-	const TYPE_PHARMA_CODE_TWO_TRACKS = 'PHARMA2T';
-
-	/**
-	 * Get the barcode data
-	 *
-	 * @param string $code code to print
-	 * @param string $type type of barcode
-	 * @return array barcode array
-	 * @public
-	 */
-	protected function getBarcodeData($code, $type)
-	{
-		switch (strtoupper($type)) {
-			case self::TYPE_CODE_39:
-				{ // CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
-					$arrcode = $this->barcode_code39($code, false, false);
-					break;
-				}
-			case self::TYPE_CODE_39_CHECKSUM:
-				{ // CODE 39 with checksum
-					$arrcode = $this->barcode_code39($code, false, true);
-					break;
-				}
-			case self::TYPE_CODE_39E:
-				{ // CODE 39 EXTENDED
-					$arrcode = $this->barcode_code39($code, true, false);
-					break;
-				}
-			case self::TYPE_CODE_39E_CHECKSUM:
-				{ // CODE 39 EXTENDED + CHECKSUM
-					$arrcode = $this->barcode_code39($code, true, true);
-					break;
-				}
-			case self::TYPE_CODE_93:
-				{ // CODE 93 - USS-93
-					$arrcode = $this->barcode_code93($code);
-					break;
-				}
-			case self::TYPE_STANDARD_2_5:
-				{ // Standard 2 of 5
-					$arrcode = $this->barcode_s25($code, false);
-					break;
-				}
-			case self::TYPE_STANDARD_2_5_CHECKSUM:
-				{ // Standard 2 of 5 + CHECKSUM
-					$arrcode = $this->barcode_s25($code, true);
-					break;
-				}
-			case self::TYPE_INTERLEAVED_2_5:
-				{ // Interleaved 2 of 5
-					$arrcode = $this->barcode_i25($code, false);
-					break;
-				}
-			case self::TYPE_INTERLEAVED_2_5_CHECKSUM:
-				{ // Interleaved 2 of 5 + CHECKSUM
-					$arrcode = $this->barcode_i25($code, true);
-					break;
-				}
-			case self::TYPE_CODE_128:
-				{ // CODE 128
-					$arrcode = $this->barcode_c128($code, '');
-					break;
-				}
-			case self::TYPE_CODE_128_A:
-				{ // CODE 128 A
-					$arrcode = $this->barcode_c128($code, 'A');
-					break;
-				}
-			case self::TYPE_CODE_128_B:
-				{ // CODE 128 B
-					$arrcode = $this->barcode_c128($code, 'B');
-					break;
-				}
-			case self::TYPE_CODE_128_C:
-				{ // CODE 128 C
-					$arrcode = $this->barcode_c128($code, 'C');
-					break;
-				}
-			case self::TYPE_EAN_2:
-				{ // 2-Digits UPC-Based Extention
-					$arrcode = $this->barcode_eanext($code, 2);
-					break;
-				}
-			case self::TYPE_EAN_5:
-				{ // 5-Digits UPC-Based Extention
-					$arrcode = $this->barcode_eanext($code, 5);
-					break;
-				}
-			case self::TYPE_EAN_8:
-				{ // EAN 8
-					$arrcode = $this->barcode_eanupc($code, 8);
-					break;
-				}
-			case self::TYPE_EAN_13:
-				{ // EAN 13
-					$arrcode = $this->barcode_eanupc($code, 13);
-					break;
-				}
-			case self::TYPE_UPC_A:
-				{ // UPC-A
-					$arrcode = $this->barcode_eanupc($code, 12);
-					break;
-				}
-			case self::TYPE_UPC_E:
-				{ // UPC-E
-					$arrcode = $this->barcode_eanupc($code, 6);
-					break;
-				}
-			case self::TYPE_MSI:
-				{ // MSI (Variation of Plessey code)
-					$arrcode = $this->barcode_msi($code, false);
-					break;
-				}
-			case self::TYPE_MSI_CHECKSUM:
-				{ // MSI + CHECKSUM (modulo 11)
-					$arrcode = $this->barcode_msi($code, true);
-					break;
-				}
-			case self::TYPE_POSTNET:
-				{ // POSTNET
-					$arrcode = $this->barcode_postnet($code, false);
-					break;
-				}
-			case self::TYPE_PLANET:
-				{ // PLANET
-					$arrcode = $this->barcode_postnet($code, true);
-					break;
-				}
-			case self::TYPE_RMS4CC:
-				{ // RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
-					$arrcode = $this->barcode_rms4cc($code, false);
-					break;
-				}
-			case self::TYPE_KIX:
-				{ // KIX (Klant index - Customer index)
-					$arrcode = $this->barcode_rms4cc($code, true);
-					break;
-				}
-			case self::TYPE_IMB:
-				{ // IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
-					$arrcode = $this->barcode_imb($code);
-					break;
-				}
-			case self::TYPE_CODABAR:
-				{ // CODABAR
-					$arrcode = $this->barcode_codabar($code);
-					break;
-				}
-			case self::TYPE_CODE_11:
-				{ // CODE 11
-					$arrcode = $this->barcode_code11($code);
-					break;
-				}
-			case self::TYPE_PHARMA_CODE:
-				{ // PHARMACODE
-					$arrcode = $this->barcode_pharmacode($code);
-					break;
-				}
-			case self::TYPE_PHARMA_CODE_TWO_TRACKS:
-				{ // PHARMACODE TWO-TRACKS
-					$arrcode = $this->barcode_pharmacode2t($code);
-					break;
-				}
-			default:
-				{
-					throw new UnknownTypeException();
-					break;
-				}
-		}
-
-		if (!isset($arrcode['maxWidth'])) {
-			$arrcode = $this->convertBarcodeArrayToNewStyle($arrcode);
-		}
-
-		return $arrcode;
-	}
-
-	/**
-	 * CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
-	 * General-purpose code in very wide use world-wide
-	 *
-	 * @param $code (string) code to represent.
-	 * @param $extended (boolean) if true uses the extended mode.
-	 * @param $checksum (boolean) if true add a checksum to the code.
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_code39($code, $extended = false, $checksum = false)
-	{
-		$chr = [];
-		$chr['0'] = '111331311';
-		$chr['1'] = '311311113';
-		$chr['2'] = '113311113';
-		$chr['3'] = '313311111';
-		$chr['4'] = '111331113';
-		$chr['5'] = '311331111';
-		$chr['6'] = '113331111';
-		$chr['7'] = '111311313';
-		$chr['8'] = '311311311';
-		$chr['9'] = '113311311';
-		$chr['A'] = '311113113';
-		$chr['B'] = '113113113';
-		$chr['C'] = '313113111';
-		$chr['D'] = '111133113';
-		$chr['E'] = '311133111';
-		$chr['F'] = '113133111';
-		$chr['G'] = '111113313';
-		$chr['H'] = '311113311';
-		$chr['I'] = '113113311';
-		$chr['J'] = '111133311';
-		$chr['K'] = '311111133';
-		$chr['L'] = '113111133';
-		$chr['M'] = '313111131';
-		$chr['N'] = '111131133';
-		$chr['O'] = '311131131';
-		$chr['P'] = '113131131';
-		$chr['Q'] = '111111333';
-		$chr['R'] = '311111331';
-		$chr['S'] = '113111331';
-		$chr['T'] = '111131331';
-		$chr['U'] = '331111113';
-		$chr['V'] = '133111113';
-		$chr['W'] = '333111111';
-		$chr['X'] = '131131113';
-		$chr['Y'] = '331131111';
-		$chr['Z'] = '133131111';
-		$chr['-'] = '131111313';
-		$chr['.'] = '331111311';
-		$chr[' '] = '133111311';
-		$chr['$'] = '131313111';
-		$chr['/'] = '131311131';
-		$chr['+'] = '131113131';
-		$chr['%'] = '111313131';
-		$chr['*'] = '131131311';
-
-		$code = strtoupper($code);
-
-		if ($extended) {
-			// extended mode
-			$code = $this->encode_code39_ext($code);
-		}
-
-		if ($checksum) {
-			// checksum
-			$code .= $this->checksum_code39($code);
-		}
-
-		// add start and stop codes
-		$code = '*' . $code . '*';
-
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-		$k = 0;
-		$clen = strlen($code);
-		for($i = 0; $i < $clen; ++$i) {
-			$char = $code{$i};
-			if (!isset($chr[$char])) {
-				throw new InvalidCharacterException('Char ' . $char . ' is unsupported');
-			}
-			for($j = 0; $j < 9; ++$j) {
-				if (($j % 2) == 0) {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$w = $chr[$char]{$j};
-				$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-				++$k;
-			}
-			// intercharacter gap
-			$bararray['bcode'][$k] = array('t' => false, 'w' => 1, 'h' => 1, 'p' => 0);
-			$bararray['maxw'] += 1;
-			++$k;
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * Encode a string to be used for CODE 39 Extended mode.
-	 *
-	 * @param string $code code to represent.
-	 * @return bool|string encoded string.
-	 * @protected
-	 */
-	protected function encode_code39_ext($code)
-	{
-		$encode = array(
-			chr(0) => '%U',
-			chr(1) => '$A',
-			chr(2) => '$B',
-			chr(3) => '$C',
-			chr(4) => '$D',
-			chr(5) => '$E',
-			chr(6) => '$F',
-			chr(7) => '$G',
-			chr(8) => '$H',
-			chr(9) => '$I',
-			chr(10) => '$J',
-			chr(11) => '£K',
-			chr(12) => '$L',
-			chr(13) => '$M',
-			chr(14) => '$N',
-			chr(15) => '$O',
-			chr(16) => '$P',
-			chr(17) => '$Q',
-			chr(18) => '$R',
-			chr(19) => '$S',
-			chr(20) => '$T',
-			chr(21) => '$U',
-			chr(22) => '$V',
-			chr(23) => '$W',
-			chr(24) => '$X',
-			chr(25) => '$Y',
-			chr(26) => '$Z',
-			chr(27) => '%A',
-			chr(28) => '%B',
-			chr(29) => '%C',
-			chr(30) => '%D',
-			chr(31) => '%E',
-			chr(32) => ' ',
-			chr(33) => '/A',
-			chr(34) => '/B',
-			chr(35) => '/C',
-			chr(36) => '/D',
-			chr(37) => '/E',
-			chr(38) => '/F',
-			chr(39) => '/G',
-			chr(40) => '/H',
-			chr(41) => '/I',
-			chr(42) => '/J',
-			chr(43) => '/K',
-			chr(44) => '/L',
-			chr(45) => '-',
-			chr(46) => '.',
-			chr(47) => '/O',
-			chr(48) => '0',
-			chr(49) => '1',
-			chr(50) => '2',
-			chr(51) => '3',
-			chr(52) => '4',
-			chr(53) => '5',
-			chr(54) => '6',
-			chr(55) => '7',
-			chr(56) => '8',
-			chr(57) => '9',
-			chr(58) => '/Z',
-			chr(59) => '%F',
-			chr(60) => '%G',
-			chr(61) => '%H',
-			chr(62) => '%I',
-			chr(63) => '%J',
-			chr(64) => '%V',
-			chr(65) => 'A',
-			chr(66) => 'B',
-			chr(67) => 'C',
-			chr(68) => 'D',
-			chr(69) => 'E',
-			chr(70) => 'F',
-			chr(71) => 'G',
-			chr(72) => 'H',
-			chr(73) => 'I',
-			chr(74) => 'J',
-			chr(75) => 'K',
-			chr(76) => 'L',
-			chr(77) => 'M',
-			chr(78) => 'N',
-			chr(79) => 'O',
-			chr(80) => 'P',
-			chr(81) => 'Q',
-			chr(82) => 'R',
-			chr(83) => 'S',
-			chr(84) => 'T',
-			chr(85) => 'U',
-			chr(86) => 'V',
-			chr(87) => 'W',
-			chr(88) => 'X',
-			chr(89) => 'Y',
-			chr(90) => 'Z',
-			chr(91) => '%K',
-			chr(92) => '%L',
-			chr(93) => '%M',
-			chr(94) => '%N',
-			chr(95) => '%O',
-			chr(96) => '%W',
-			chr(97) => '+A',
-			chr(98) => '+B',
-			chr(99) => '+C',
-			chr(100) => '+D',
-			chr(101) => '+E',
-			chr(102) => '+F',
-			chr(103) => '+G',
-			chr(104) => '+H',
-			chr(105) => '+I',
-			chr(106) => '+J',
-			chr(107) => '+K',
-			chr(108) => '+L',
-			chr(109) => '+M',
-			chr(110) => '+N',
-			chr(111) => '+O',
-			chr(112) => '+P',
-			chr(113) => '+Q',
-			chr(114) => '+R',
-			chr(115) => '+S',
-			chr(116) => '+T',
-			chr(117) => '+U',
-			chr(118) => '+V',
-			chr(119) => '+W',
-			chr(120) => '+X',
-			chr(121) => '+Y',
-			chr(122) => '+Z',
-			chr(123) => '%P',
-			chr(124) => '%Q',
-			chr(125) => '%R',
-			chr(126) => '%S',
-			chr(127) => '%T'
-		);
-		$code_ext = '';
-		$clen = strlen($code);
-		for($i = 0; $i < $clen; ++$i) {
-			if (ord($code{$i}) > 127) {
-				throw new InvalidCharacterException('Only supports till char 127');
-			}
-			$code_ext .= $encode[$code{$i}];
-		}
-
-		return $code_ext;
-	}
-
-	/**
-	 * Calculate CODE 39 checksum (modulo 43).
-	 *
-	 * @param string $code code to represent.
-	 * @return string char checksum.
-	 * @protected
-	 */
-	protected function checksum_code39($code)
-	{
-		$chars = array(
-			'0',
-			'1',
-			'2',
-			'3',
-			'4',
-			'5',
-			'6',
-			'7',
-			'8',
-			'9',
-			'A',
-			'B',
-			'C',
-			'D',
-			'E',
-			'F',
-			'G',
-			'H',
-			'I',
-			'J',
-			'K',
-			'L',
-			'M',
-			'N',
-			'O',
-			'P',
-			'Q',
-			'R',
-			'S',
-			'T',
-			'U',
-			'V',
-			'W',
-			'X',
-			'Y',
-			'Z',
-			'-',
-			'.',
-			' ',
-			'$',
-			'/',
-			'+',
-			'%'
-		);
-		$sum = 0;
-		$codelength = strlen($code);
-		for($i = 0; $i < $codelength; ++$i) {
-			$k = array_keys($chars, $code{$i});
-			$sum += $k[0];
-		}
-		$j = ($sum % 43);
-
-		return $chars[$j];
-	}
-
-	/**
-	 * CODE 93 - USS-93
-	 * Compact code similar to Code 39
-	 *
-	 * @param $code (string) code to represent.
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_code93($code)
-	{
-		$chr = [];
-		$chr[48] = '131112'; // 0
-		$chr[49] = '111213'; // 1
-		$chr[50] = '111312'; // 2
-		$chr[51] = '111411'; // 3
-		$chr[52] = '121113'; // 4
-		$chr[53] = '121212'; // 5
-		$chr[54] = '121311'; // 6
-		$chr[55] = '111114'; // 7
-		$chr[56] = '131211'; // 8
-		$chr[57] = '141111'; // 9
-		$chr[65] = '211113'; // A
-		$chr[66] = '211212'; // B
-		$chr[67] = '211311'; // C
-		$chr[68] = '221112'; // D
-		$chr[69] = '221211'; // E
-		$chr[70] = '231111'; // F
-		$chr[71] = '112113'; // G
-		$chr[72] = '112212'; // H
-		$chr[73] = '112311'; // I
-		$chr[74] = '122112'; // J
-		$chr[75] = '132111'; // K
-		$chr[76] = '111123'; // L
-		$chr[77] = '111222'; // M
-		$chr[78] = '111321'; // N
-		$chr[79] = '121122'; // O
-		$chr[80] = '131121'; // P
-		$chr[81] = '212112'; // Q
-		$chr[82] = '212211'; // R
-		$chr[83] = '211122'; // S
-		$chr[84] = '211221'; // T
-		$chr[85] = '221121'; // U
-		$chr[86] = '222111'; // V
-		$chr[87] = '112122'; // W
-		$chr[88] = '112221'; // X
-		$chr[89] = '122121'; // Y
-		$chr[90] = '123111'; // Z
-		$chr[45] = '121131'; // -
-		$chr[46] = '311112'; // .
-		$chr[32] = '311211'; //
-		$chr[36] = '321111'; // $
-		$chr[47] = '112131'; // /
-		$chr[43] = '113121'; // +
-		$chr[37] = '211131'; // %
-		$chr[128] = '121221'; // ($)
-		$chr[129] = '311121'; // (/)
-		$chr[130] = '122211'; // (+)
-		$chr[131] = '312111'; // (%)
-		$chr[42] = '111141'; // start-stop
-		$code = strtoupper($code);
-		$encode = array(
-			chr(0) => chr(131) . 'U',
-			chr(1) => chr(128) . 'A',
-			chr(2) => chr(128) . 'B',
-			chr(3) => chr(128) . 'C',
-			chr(4) => chr(128) . 'D',
-			chr(5) => chr(128) . 'E',
-			chr(6) => chr(128) . 'F',
-			chr(7) => chr(128) . 'G',
-			chr(8) => chr(128) . 'H',
-			chr(9) => chr(128) . 'I',
-			chr(10) => chr(128) . 'J',
-			chr(11) => '£K',
-			chr(12) => chr(128) . 'L',
-			chr(13) => chr(128) . 'M',
-			chr(14) => chr(128) . 'N',
-			chr(15) => chr(128) . 'O',
-			chr(16) => chr(128) . 'P',
-			chr(17) => chr(128) . 'Q',
-			chr(18) => chr(128) . 'R',
-			chr(19) => chr(128) . 'S',
-			chr(20) => chr(128) . 'T',
-			chr(21) => chr(128) . 'U',
-			chr(22) => chr(128) . 'V',
-			chr(23) => chr(128) . 'W',
-			chr(24) => chr(128) . 'X',
-			chr(25) => chr(128) . 'Y',
-			chr(26) => chr(128) . 'Z',
-			chr(27) => chr(131) . 'A',
-			chr(28) => chr(131) . 'B',
-			chr(29) => chr(131) . 'C',
-			chr(30) => chr(131) . 'D',
-			chr(31) => chr(131) . 'E',
-			chr(32) => ' ',
-			chr(33) => chr(129) . 'A',
-			chr(34) => chr(129) . 'B',
-			chr(35) => chr(129) . 'C',
-			chr(36) => chr(129) . 'D',
-			chr(37) => chr(129) . 'E',
-			chr(38) => chr(129) . 'F',
-			chr(39) => chr(129) . 'G',
-			chr(40) => chr(129) . 'H',
-			chr(41) => chr(129) . 'I',
-			chr(42) => chr(129) . 'J',
-			chr(43) => chr(129) . 'K',
-			chr(44) => chr(129) . 'L',
-			chr(45) => '-',
-			chr(46) => '.',
-			chr(47) => chr(129) . 'O',
-			chr(48) => '0',
-			chr(49) => '1',
-			chr(50) => '2',
-			chr(51) => '3',
-			chr(52) => '4',
-			chr(53) => '5',
-			chr(54) => '6',
-			chr(55) => '7',
-			chr(56) => '8',
-			chr(57) => '9',
-			chr(58) => chr(129) . 'Z',
-			chr(59) => chr(131) . 'F',
-			chr(60) => chr(131) . 'G',
-			chr(61) => chr(131) . 'H',
-			chr(62) => chr(131) . 'I',
-			chr(63) => chr(131) . 'J',
-			chr(64) => chr(131) . 'V',
-			chr(65) => 'A',
-			chr(66) => 'B',
-			chr(67) => 'C',
-			chr(68) => 'D',
-			chr(69) => 'E',
-			chr(70) => 'F',
-			chr(71) => 'G',
-			chr(72) => 'H',
-			chr(73) => 'I',
-			chr(74) => 'J',
-			chr(75) => 'K',
-			chr(76) => 'L',
-			chr(77) => 'M',
-			chr(78) => 'N',
-			chr(79) => 'O',
-			chr(80) => 'P',
-			chr(81) => 'Q',
-			chr(82) => 'R',
-			chr(83) => 'S',
-			chr(84) => 'T',
-			chr(85) => 'U',
-			chr(86) => 'V',
-			chr(87) => 'W',
-			chr(88) => 'X',
-			chr(89) => 'Y',
-			chr(90) => 'Z',
-			chr(91) => chr(131) . 'K',
-			chr(92) => chr(131) . 'L',
-			chr(93) => chr(131) . 'M',
-			chr(94) => chr(131) . 'N',
-			chr(95) => chr(131) . 'O',
-			chr(96) => chr(131) . 'W',
-			chr(97) => chr(130) . 'A',
-			chr(98) => chr(130) . 'B',
-			chr(99) => chr(130) . 'C',
-			chr(100) => chr(130) . 'D',
-			chr(101) => chr(130) . 'E',
-			chr(102) => chr(130) . 'F',
-			chr(103) => chr(130) . 'G',
-			chr(104) => chr(130) . 'H',
-			chr(105) => chr(130) . 'I',
-			chr(106) => chr(130) . 'J',
-			chr(107) => chr(130) . 'K',
-			chr(108) => chr(130) . 'L',
-			chr(109) => chr(130) . 'M',
-			chr(110) => chr(130) . 'N',
-			chr(111) => chr(130) . 'O',
-			chr(112) => chr(130) . 'P',
-			chr(113) => chr(130) . 'Q',
-			chr(114) => chr(130) . 'R',
-			chr(115) => chr(130) . 'S',
-			chr(116) => chr(130) . 'T',
-			chr(117) => chr(130) . 'U',
-			chr(118) => chr(130) . 'V',
-			chr(119) => chr(130) . 'W',
-			chr(120) => chr(130) . 'X',
-			chr(121) => chr(130) . 'Y',
-			chr(122) => chr(130) . 'Z',
-			chr(123) => chr(131) . 'P',
-			chr(124) => chr(131) . 'Q',
-			chr(125) => chr(131) . 'R',
-			chr(126) => chr(131) . 'S',
-			chr(127) => chr(131) . 'T'
-		);
-		$code_ext = '';
-		$clen = strlen($code);
-		for($i = 0; $i < $clen; ++$i) {
-			if (ord($code{$i}) > 127) {
-				throw new InvalidCharacterException('Only supports till char 127');
-			}
-			$code_ext .= $encode[$code{$i}];
-		}
-		// checksum
-		$code_ext .= $this->checksum_code93($code_ext);
-		// add start and stop codes
-		$code = '*' . $code_ext . '*';
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-		$k = 0;
-		$clen = strlen($code);
-		for($i = 0; $i < $clen; ++$i) {
-			$char = ord($code{$i});
-			if (!isset($chr[$char])) {
-				throw new InvalidCharacterException('Char ' . $char . ' is unsupported');
-			}
-			for($j = 0; $j < 6; ++$j) {
-				if (($j % 2) == 0) {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$w = $chr[$char]{$j};
-				$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-				++$k;
-			}
-		}
-		$bararray['bcode'][$k] = array('t' => true, 'w' => 1, 'h' => 1, 'p' => 0);
-		$bararray['maxw'] += 1;
-
-		return $bararray;
-	}
-
-	/**
-	 * Calculate CODE 93 checksum (modulo 47).
-	 *
-	 * @param $code (string) code to represent.
-	 * @return string checksum code.
-	 * @protected
-	 */
-	protected function checksum_code93($code)
-	{
-		$chars = array(
-			'0',
-			'1',
-			'2',
-			'3',
-			'4',
-			'5',
-			'6',
-			'7',
-			'8',
-			'9',
-			'A',
-			'B',
-			'C',
-			'D',
-			'E',
-			'F',
-			'G',
-			'H',
-			'I',
-			'J',
-			'K',
-			'L',
-			'M',
-			'N',
-			'O',
-			'P',
-			'Q',
-			'R',
-			'S',
-			'T',
-			'U',
-			'V',
-			'W',
-			'X',
-			'Y',
-			'Z',
-			'-',
-			'.',
-			' ',
-			'$',
-			'/',
-			'+',
-			'%',
-			'<',
-			'=',
-			'>',
-			'?'
-		);
-		// translate special characters
-		$code = strtr($code, chr(128) . chr(131) . chr(129) . chr(130), '<=>?');
-		$len = strlen($code);
-		// calculate check digit C
-		$p = 1;
-		$check = 0;
-		for($i = ($len - 1); $i >= 0; --$i) {
-			$k = array_keys($chars, $code{$i});
-			$check += ($k[0] * $p);
-			++$p;
-			if ($p > 20) {
-				$p = 1;
-			}
-		}
-		$check %= 47;
-		$c = $chars[$check];
-		$code .= $c;
-		// calculate check digit K
-		$p = 1;
-		$check = 0;
-		for($i = $len; $i >= 0; --$i) {
-			$k = array_keys($chars, $code{$i});
-			$check += ($k[0] * $p);
-			++$p;
-			if ($p > 15) {
-				$p = 1;
-			}
-		}
-		$check %= 47;
-		$k = $chars[$check];
-		$checksum = $c . $k;
-		// resto respecial characters
-		$checksum = strtr($checksum, '<=>?', chr(128) . chr(131) . chr(129) . chr(130));
-
-		return $checksum;
-	}
-
-	/**
-	 * Standard 2 of 5 barcodes.
-	 * Used in airline ticket marking, photofinishing
-	 * Contains digits (0 to 9) and encodes the data only in the width of bars.
-	 *
-	 * @param $code (string) code to represent.
-	 * @param $checksum (boolean) if true add a checksum to the code
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_s25($code, $checksum = false)
-	{
-		$chr['0'] = '10101110111010';
-		$chr['1'] = '11101010101110';
-		$chr['2'] = '10111010101110';
-		$chr['3'] = '11101110101010';
-		$chr['4'] = '10101110101110';
-		$chr['5'] = '11101011101010';
-		$chr['6'] = '10111011101010';
-		$chr['7'] = '10101011101110';
-		$chr['8'] = '10101110111010';
-		$chr['9'] = '10111010111010';
-		if ($checksum) {
-			// add checksum
-			$code .= $this->checksum_s25($code);
-		}
-		if ((strlen($code) % 2) != 0) {
-			// add leading zero if code-length is odd
-			$code = '0' . $code;
-		}
-		$seq = '11011010';
-		$clen = strlen($code);
-		for($i = 0; $i < $clen; ++$i) {
-			$digit = $code{$i};
-			if (!isset($chr[$digit])) {
-				throw new InvalidCharacterException('Char ' . $digit . ' is unsupported');
-			}
-			$seq .= $chr[$digit];
-		}
-		$seq .= '1101011';
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-
-		return $this->binseq_to_array($seq, $bararray);
-	}
-
-	/**
-	 * Checksum for standard 2 of 5 barcodes.
-	 *
-	 * @param $code (string) code to process.
-	 * @return int checksum.
-	 * @protected
-	 */
-	protected function checksum_s25($code)
-	{
-		$len = strlen($code);
-		$sum = 0;
-		for($i = 0; $i < $len; $i += 2) {
-			$sum += $code{$i};
-		}
-		$sum *= 3;
-		for($i = 1; $i < $len; $i += 2) {
-			$sum += ($code{$i});
-		}
-		$r = $sum % 10;
-		if ($r > 0) {
-			$r = (10 - $r);
-		}
-
-		return $r;
-	}
-
-	/**
-	 * Convert binary barcode sequence to TCPDF barcode array.
-	 *
-	 * @param $seq (string) barcode as binary sequence.
-	 * @param $bararray (array) barcode array.
-	 * òparam array $bararray TCPDF barcode array to fill up
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function binseq_to_array($seq, $bararray)
-	{
-		$len = strlen($seq);
-		$w = 0;
-		$k = 0;
-		for($i = 0; $i < $len; ++$i) {
-			$w += 1;
-			if (($i == ($len - 1)) OR (($i < ($len - 1)) AND ($seq{$i} != $seq{($i + 1)}))) {
-				if ($seq{$i} == '1') {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-				++$k;
-				$w = 0;
-			}
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * Interleaved 2 of 5 barcodes.
-	 * Compact numeric code, widely used in industry, air cargo
-	 * Contains digits (0 to 9) and encodes the data in the width of both bars and spaces.
-	 *
-	 * @param $code (string) code to represent.
-	 * @param $checksum (boolean) if true add a checksum to the code
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_i25($code, $checksum = false)
-	{
-		$chr['0'] = '11221';
-		$chr['1'] = '21112';
-		$chr['2'] = '12112';
-		$chr['3'] = '22111';
-		$chr['4'] = '11212';
-		$chr['5'] = '21211';
-		$chr['6'] = '12211';
-		$chr['7'] = '11122';
-		$chr['8'] = '21121';
-		$chr['9'] = '12121';
-		$chr['A'] = '11';
-		$chr['Z'] = '21';
-		if ($checksum) {
-			// add checksum
-			$code .= $this->checksum_s25($code);
-		}
-		if ((strlen($code) % 2) != 0) {
-			// add leading zero if code-length is odd
-			$code = '0' . $code;
-		}
-		// add start and stop codes
-		$code = 'AA' . strtolower($code) . 'ZA';
-
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-		$k = 0;
-		$clen = strlen($code);
-		for($i = 0; $i < $clen; $i = ($i + 2)) {
-			$char_bar = $code{$i};
-			$char_space = $code{$i + 1};
-			if (!isset($chr[$char_bar]) || !isset($chr[$char_space])) {
-				throw new InvalidCharacterException();
-			}
-			// create a bar-space sequence
-			$seq = '';
-			$chrlen = strlen($chr[$char_bar]);
-			for($s = 0; $s < $chrlen; $s++) {
-				$seq .= $chr[$char_bar]{$s} . $chr[$char_space]{$s};
-			}
-			$seqlen = strlen($seq);
-			for($j = 0; $j < $seqlen; ++$j) {
-				if (($j % 2) == 0) {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$w = $seq{$j};
-				$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-				++$k;
-			}
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * C128 barcodes.
-	 * Very capable code, excellent density, high reliability; in very wide use world-wide
-	 *
-	 * @param $code (string) code to represent.
-	 * @param $type (string) barcode type: A, B, C or empty for automatic switch (AUTO mode)
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_c128($code, $type = '')
-	{
-		$chr = array(
-			'212222', /* 00 */
-			'222122', /* 01 */
-			'222221', /* 02 */
-			'121223', /* 03 */
-			'121322', /* 04 */
-			'131222', /* 05 */
-			'122213', /* 06 */
-			'122312', /* 07 */
-			'132212', /* 08 */
-			'221213', /* 09 */
-			'221312', /* 10 */
-			'231212', /* 11 */
-			'112232', /* 12 */
-			'122132', /* 13 */
-			'122231', /* 14 */
-			'113222', /* 15 */
-			'123122', /* 16 */
-			'123221', /* 17 */
-			'223211', /* 18 */
-			'221132', /* 19 */
-			'221231', /* 20 */
-			'213212', /* 21 */
-			'223112', /* 22 */
-			'312131', /* 23 */
-			'311222', /* 24 */
-			'321122', /* 25 */
-			'321221', /* 26 */
-			'312212', /* 27 */
-			'322112', /* 28 */
-			'322211', /* 29 */
-			'212123', /* 30 */
-			'212321', /* 31 */
-			'232121', /* 32 */
-			'111323', /* 33 */
-			'131123', /* 34 */
-			'131321', /* 35 */
-			'112313', /* 36 */
-			'132113', /* 37 */
-			'132311', /* 38 */
-			'211313', /* 39 */
-			'231113', /* 40 */
-			'231311', /* 41 */
-			'112133', /* 42 */
-			'112331', /* 43 */
-			'132131', /* 44 */
-			'113123', /* 45 */
-			'113321', /* 46 */
-			'133121', /* 47 */
-			'313121', /* 48 */
-			'211331', /* 49 */
-			'231131', /* 50 */
-			'213113', /* 51 */
-			'213311', /* 52 */
-			'213131', /* 53 */
-			'311123', /* 54 */
-			'311321', /* 55 */
-			'331121', /* 56 */
-			'312113', /* 57 */
-			'312311', /* 58 */
-			'332111', /* 59 */
-			'314111', /* 60 */
-			'221411', /* 61 */
-			'431111', /* 62 */
-			'111224', /* 63 */
-			'111422', /* 64 */
-			'121124', /* 65 */
-			'121421', /* 66 */
-			'141122', /* 67 */
-			'141221', /* 68 */
-			'112214', /* 69 */
-			'112412', /* 70 */
-			'122114', /* 71 */
-			'122411', /* 72 */
-			'142112', /* 73 */
-			'142211', /* 74 */
-			'241211', /* 75 */
-			'221114', /* 76 */
-			'413111', /* 77 */
-			'241112', /* 78 */
-			'134111', /* 79 */
-			'111242', /* 80 */
-			'121142', /* 81 */
-			'121241', /* 82 */
-			'114212', /* 83 */
-			'124112', /* 84 */
-			'124211', /* 85 */
-			'411212', /* 86 */
-			'421112', /* 87 */
-			'421211', /* 88 */
-			'212141', /* 89 */
-			'214121', /* 90 */
-			'412121', /* 91 */
-			'111143', /* 92 */
-			'111341', /* 93 */
-			'131141', /* 94 */
-			'114113', /* 95 */
-			'114311', /* 96 */
-			'411113', /* 97 */
-			'411311', /* 98 */
-			'113141', /* 99 */
-			'114131', /* 100 */
-			'311141', /* 101 */
-			'411131', /* 102 */
-			'211412', /* 103 START A */
-			'211214', /* 104 START B */
-			'211232', /* 105 START C */
-			'233111', /* STOP */
-			'200000'  /* END */
-		);
-		// ASCII characters for code A (ASCII 00 - 95)
-		$keys_a = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_';
-		$keys_a .= chr(0) . chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8) . chr(9);
-		$keys_a .= chr(10) . chr(11) . chr(12) . chr(13) . chr(14) . chr(15) . chr(16) . chr(17) . chr(18) . chr(19);
-		$keys_a .= chr(20) . chr(21) . chr(22) . chr(23) . chr(24) . chr(25) . chr(26) . chr(27) . chr(28) . chr(29);
-		$keys_a .= chr(30) . chr(31);
-		// ASCII characters for code B (ASCII 32 - 127)
-		$keys_b = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' . chr(127);
-		// special codes
-		$fnc_a = array(241 => 102, 242 => 97, 243 => 96, 244 => 101);
-		$fnc_b = array(241 => 102, 242 => 97, 243 => 96, 244 => 100);
-		// array of symbols
-		$code_data = array();
-		// length of the code
-		$len = strlen($code);
-		switch (strtoupper($type)) {
-			case 'A':
-				{ // MODE A
-					$startid = 103;
-					for($i = 0; $i < $len; ++$i) {
-						$char = $code{$i};
-						$char_id = ord($char);
-						if (($char_id >= 241) AND ($char_id <= 244)) {
-							$code_data[] = $fnc_a[$char_id];
-						} elseif (($char_id >= 0) AND ($char_id <= 95)) {
-							$code_data[] = strpos($keys_a, $char);
-						} else {
-							throw new InvalidCharacterException('Char ' . $char . ' is unsupported');
-						}
-					}
-					break;
-				}
-			case 'B':
-				{ // MODE B
-					$startid = 104;
-					for($i = 0; $i < $len; ++$i) {
-						$char = $code{$i};
-						$char_id = ord($char);
-						if (($char_id >= 241) AND ($char_id <= 244)) {
-							$code_data[] = $fnc_b[$char_id];
-						} elseif (($char_id >= 32) AND ($char_id <= 127)) {
-							$code_data[] = strpos($keys_b, $char);
-						} else {
-							throw new InvalidCharacterException('Char ' . $char . ' is unsupported');
-						}
-					}
-					break;
-				}
-			case 'C':
-				{ // MODE C
-					$startid = 105;
-					if (ord($code[0]) == 241) {
-						$code_data[] = 102;
-						$code = substr($code, 1);
-						--$len;
-					}
-					if (($len % 2) != 0) {
-						throw new InvalidLengthException('Length must be even');
-					}
-					for($i = 0; $i < $len; $i += 2) {
-						$chrnum = $code{$i} . $code{$i + 1};
-						if (preg_match('/([0-9]{2})/', $chrnum) > 0) {
-							$code_data[] = intval($chrnum);
-						} else {
-							throw new InvalidCharacterException();
-						}
-					}
-					break;
-				}
-			default:
-				{ // MODE AUTO
-					// split code into sequences
-					$sequence = array();
-					// get numeric sequences (if any)
-					$numseq = array();
-					preg_match_all('/([0-9]{4,})/', $code, $numseq, PREG_OFFSET_CAPTURE);
-					if (isset($numseq[1]) AND !empty($numseq[1])) {
-						$end_offset = 0;
-						foreach($numseq[1] as $val) {
-							$offset = $val[1];
-							if ($offset > $end_offset) {
-								// non numeric sequence
-								$sequence = array_merge($sequence,
-									$this->get128ABsequence(substr($code, $end_offset, ($offset - $end_offset))));
-							}
-							// numeric sequence
-							$slen = strlen($val[0]);
-							if (($slen % 2) != 0) {
-								// the length must be even
-								--$slen;
-							}
-							$sequence[] = array('C', substr($code, $offset, $slen), $slen);
-							$end_offset = $offset + $slen;
-						}
-						if ($end_offset < $len) {
-							$sequence = array_merge($sequence, $this->get128ABsequence(substr($code, $end_offset)));
-						}
-					} else {
-						// text code (non C mode)
-						$sequence = array_merge($sequence, $this->get128ABsequence($code));
-					}
-					// process the sequence
-					foreach($sequence as $key => $seq) {
-						switch ($seq[0]) {
-							case 'A':
-								{
-									if ($key == 0) {
-										$startid = 103;
-									} elseif ($sequence[($key - 1)][0] != 'A') {
-										if (($seq[2] == 1) AND ($key > 0) AND ($sequence[($key - 1)][0] == 'B') AND (!isset($sequence[($key - 1)][3]))) {
-											// single character shift
-											$code_data[] = 98;
-											// mark shift
-											$sequence[$key][3] = true;
-										} elseif (!isset($sequence[($key - 1)][3])) {
-											$code_data[] = 101;
-										}
-									}
-									for($i = 0; $i < $seq[2]; ++$i) {
-										$char = $seq[1]{$i};
-										$char_id = ord($char);
-										if (($char_id >= 241) AND ($char_id <= 244)) {
-											$code_data[] = $fnc_a[$char_id];
-										} else {
-											$code_data[] = strpos($keys_a, $char);
-										}
-									}
-									break;
-								}
-							case 'B':
-								{
-									if ($key == 0) {
-										$tmpchr = ord($seq[1][0]);
-										if (($seq[2] == 1) AND ($tmpchr >= 241) AND ($tmpchr <= 244) AND isset($sequence[($key + 1)]) AND ($sequence[($key + 1)][0] != 'B')) {
-											switch ($sequence[($key + 1)][0]) {
-												case 'A':
-													{
-														$startid = 103;
-														$sequence[$key][0] = 'A';
-														$code_data[] = $fnc_a[$tmpchr];
-														break;
-													}
-												case 'C':
-													{
-														$startid = 105;
-														$sequence[$key][0] = 'C';
-														$code_data[] = $fnc_a[$tmpchr];
-														break;
-													}
-											}
-											break;
-										} else {
-											$startid = 104;
-										}
-									} elseif ($sequence[($key - 1)][0] != 'B') {
-										if (($seq[2] == 1) AND ($key > 0) AND ($sequence[($key - 1)][0] == 'A') AND (!isset($sequence[($key - 1)][3]))) {
-											// single character shift
-											$code_data[] = 98;
-											// mark shift
-											$sequence[$key][3] = true;
-										} elseif (!isset($sequence[($key - 1)][3])) {
-											$code_data[] = 100;
-										}
-									}
-									for($i = 0; $i < $seq[2]; ++$i) {
-										$char = $seq[1]{$i};
-										$char_id = ord($char);
-										if (($char_id >= 241) AND ($char_id <= 244)) {
-											$code_data[] = $fnc_b[$char_id];
-										} else {
-											$code_data[] = strpos($keys_b, $char);
-										}
-									}
-									break;
-								}
-							case 'C':
-								{
-									if ($key == 0) {
-										$startid = 105;
-									} elseif ($sequence[($key - 1)][0] != 'C') {
-										$code_data[] = 99;
-									}
-									for($i = 0; $i < $seq[2]; $i += 2) {
-										$chrnum = $seq[1]{$i} . $seq[1]{$i + 1};
-										$code_data[] = intval($chrnum);
-									}
-									break;
-								}
-						}
-					}
-				}
-		}
-		// calculate check character
-		$sum = $startid;
-		foreach($code_data as $key => $val) {
-			$sum += ($val * ($key + 1));
-		}
-		// add check character
-		$code_data[] = ($sum % 103);
-		// add stop sequence
-		$code_data[] = 106;
-		$code_data[] = 107;
-		// add start code at the beginning
-		array_unshift($code_data, $startid);
-		// build barcode array
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-		foreach($code_data as $val) {
-			$seq = $chr[$val];
-			for($j = 0; $j < 6; ++$j) {
-				if (($j % 2) == 0) {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$w = $seq{$j};
-				$bararray['bcode'][] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-			}
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * Split text code in A/B sequence for 128 code
-	 *
-	 * @param $code (string) code to split.
-	 * @return array sequence
-	 * @protected
-	 */
-	protected function get128ABsequence($code)
-	{
-		$len = strlen($code);
-		$sequence = array();
-		// get A sequences (if any)
-		$numseq = array();
-		preg_match_all('/([\x00-\x1f])/', $code, $numseq, PREG_OFFSET_CAPTURE);
-		if (isset($numseq[1]) AND !empty($numseq[1])) {
-			$end_offset = 0;
-			foreach($numseq[1] as $val) {
-				$offset = $val[1];
-				if ($offset > $end_offset) {
-					// B sequence
-					$sequence[] = array(
-						'B',
-						substr($code, $end_offset, ($offset - $end_offset)),
-						($offset - $end_offset)
-					);
-				}
-				// A sequence
-				$slen = strlen($val[0]);
-				$sequence[] = array('A', substr($code, $offset, $slen), $slen);
-				$end_offset = $offset + $slen;
-			}
-			if ($end_offset < $len) {
-				$sequence[] = array('B', substr($code, $end_offset), ($len - $end_offset));
-			}
-		} else {
-			// only B sequence
-			$sequence[] = array('B', $code, $len);
-		}
-
-		return $sequence;
-	}
-
-	/**
-	 * UPC-Based Extensions
-	 * 2-Digit Ext.: Used to indicate magazines and newspaper issue numbers
-	 * 5-Digit Ext.: Used to mark suggested retail price of books
-	 *
-	 * @param $code (string) code to represent.
-	 * @param $len (string) barcode type: 2 = 2-Digit, 5 = 5-Digit
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_eanext($code, $len = 5)
-	{
-		//Padding
-		$code = str_pad($code, $len, '0', STR_PAD_LEFT);
-		// calculate check digit
-		if ($len == 2) {
-			$r = $code % 4;
-		} elseif ($len == 5) {
-			$r = (3 * ($code[0] + $code[2] + $code[4])) + (9 * ($code[1] + $code[3]));
-			$r %= 10;
-		} else {
-			throw new InvalidCheckDigitException();
-		}
-		//Convert digits to bars
-		$codes = array(
-			'A' => array( // left odd parity
-				'0' => '0001101',
-				'1' => '0011001',
-				'2' => '0010011',
-				'3' => '0111101',
-				'4' => '0100011',
-				'5' => '0110001',
-				'6' => '0101111',
-				'7' => '0111011',
-				'8' => '0110111',
-				'9' => '0001011'
-			),
-			'B' => array( // left even parity
-				'0' => '0100111',
-				'1' => '0110011',
-				'2' => '0011011',
-				'3' => '0100001',
-				'4' => '0011101',
-				'5' => '0111001',
-				'6' => '0000101',
-				'7' => '0010001',
-				'8' => '0001001',
-				'9' => '0010111'
-			)
-		);
-		$parities = array();
-		$parities[2] = array(
-			'0' => array('A', 'A'),
-			'1' => array('A', 'B'),
-			'2' => array('B', 'A'),
-			'3' => array('B', 'B')
-		);
-		$parities[5] = array(
-			'0' => array('B', 'B', 'A', 'A', 'A'),
-			'1' => array('B', 'A', 'B', 'A', 'A'),
-			'2' => array('B', 'A', 'A', 'B', 'A'),
-			'3' => array('B', 'A', 'A', 'A', 'B'),
-			'4' => array('A', 'B', 'B', 'A', 'A'),
-			'5' => array('A', 'A', 'B', 'B', 'A'),
-			'6' => array('A', 'A', 'A', 'B', 'B'),
-			'7' => array('A', 'B', 'A', 'B', 'A'),
-			'8' => array('A', 'B', 'A', 'A', 'B'),
-			'9' => array('A', 'A', 'B', 'A', 'B')
-		);
-		$p = $parities[$len][$r];
-		$seq = '1011'; // left guard bar
-		$seq .= $codes[$p[0]][$code[0]];
-		for($i = 1; $i < $len; ++$i) {
-			$seq .= '01'; // separator
-			$seq .= $codes[$p[$i]][$code{$i}];
-		}
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-
-		return $this->binseq_to_array($seq, $bararray);
-	}
-
-	/**
-	 * EAN13 and UPC-A barcodes.
-	 * EAN13: European Article Numbering international retail product code
-	 * UPC-A: Universal product code seen on almost all retail products in the USA and Canada
-	 * UPC-E: Short version of UPC symbol
-	 *
-	 * @param $code (string) code to represent.
-	 * @param $len (string) barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_eanupc($code, $len = 13)
-	{
-		$upce = false;
-		if ($len == 6) {
-			$len = 12; // UPC-A
-			$upce = true; // UPC-E mode
-		}
-		$data_len = $len - 1;
-		//Padding
-		$code = str_pad($code, $data_len, '0', STR_PAD_LEFT);
-		$code_len = strlen($code);
-		// calculate check digit
-		$sum_a = 0;
-		for($i = 1; $i < $data_len; $i += 2) {
-			$sum_a += $code{$i};
-		}
-		if ($len > 12) {
-			$sum_a *= 3;
-		}
-		$sum_b = 0;
-		for($i = 0; $i < $data_len; $i += 2) {
-			$sum_b += ($code{$i});
-		}
-		if ($len < 13) {
-			$sum_b *= 3;
-		}
-		$r = ($sum_a + $sum_b) % 10;
-		if ($r > 0) {
-			$r = (10 - $r);
-		}
-		if ($code_len == $data_len) {
-			// add check digit
-			$code .= $r;
-		} elseif ($r !== intval($code{$data_len})) {
-			throw new InvalidCheckDigitException();
-		}
-		if ($len == 12) {
-			// UPC-A
-			$code = '0' . $code;
-			++$len;
-		}
-		if ($upce) {
-			// convert UPC-A to UPC-E
-			$tmp = substr($code, 4, 3);
-			if (($tmp == '000') OR ($tmp == '100') OR ($tmp == '200')) {
-				// manufacturer code ends in 000, 100, or 200
-				$upce_code = substr($code, 2, 2) . substr($code, 9, 3) . substr($code, 4, 1);
-			} else {
-				$tmp = substr($code, 5, 2);
-				if ($tmp == '00') {
-					// manufacturer code ends in 00
-					$upce_code = substr($code, 2, 3) . substr($code, 10, 2) . '3';
-				} else {
-					$tmp = substr($code, 6, 1);
-					if ($tmp == '0') {
-						// manufacturer code ends in 0
-						$upce_code = substr($code, 2, 4) . substr($code, 11, 1) . '4';
-					} else {
-						// manufacturer code does not end in zero
-						$upce_code = substr($code, 2, 5) . substr($code, 11, 1);
-					}
-				}
-			}
-		}
-		//Convert digits to bars
-		$codes = array(
-			'A' => array( // left odd parity
-				'0' => '0001101',
-				'1' => '0011001',
-				'2' => '0010011',
-				'3' => '0111101',
-				'4' => '0100011',
-				'5' => '0110001',
-				'6' => '0101111',
-				'7' => '0111011',
-				'8' => '0110111',
-				'9' => '0001011'
-			),
-			'B' => array( // left even parity
-				'0' => '0100111',
-				'1' => '0110011',
-				'2' => '0011011',
-				'3' => '0100001',
-				'4' => '0011101',
-				'5' => '0111001',
-				'6' => '0000101',
-				'7' => '0010001',
-				'8' => '0001001',
-				'9' => '0010111'
-			),
-			'C' => array( // right
-				'0' => '1110010',
-				'1' => '1100110',
-				'2' => '1101100',
-				'3' => '1000010',
-				'4' => '1011100',
-				'5' => '1001110',
-				'6' => '1010000',
-				'7' => '1000100',
-				'8' => '1001000',
-				'9' => '1110100'
-			)
-		);
-		$parities = array(
-			'0' => array('A', 'A', 'A', 'A', 'A', 'A'),
-			'1' => array('A', 'A', 'B', 'A', 'B', 'B'),
-			'2' => array('A', 'A', 'B', 'B', 'A', 'B'),
-			'3' => array('A', 'A', 'B', 'B', 'B', 'A'),
-			'4' => array('A', 'B', 'A', 'A', 'B', 'B'),
-			'5' => array('A', 'B', 'B', 'A', 'A', 'B'),
-			'6' => array('A', 'B', 'B', 'B', 'A', 'A'),
-			'7' => array('A', 'B', 'A', 'B', 'A', 'B'),
-			'8' => array('A', 'B', 'A', 'B', 'B', 'A'),
-			'9' => array('A', 'B', 'B', 'A', 'B', 'A')
-		);
-		$upce_parities = array();
-		$upce_parities[0] = array(
-			'0' => array('B', 'B', 'B', 'A', 'A', 'A'),
-			'1' => array('B', 'B', 'A', 'B', 'A', 'A'),
-			'2' => array('B', 'B', 'A', 'A', 'B', 'A'),
-			'3' => array('B', 'B', 'A', 'A', 'A', 'B'),
-			'4' => array('B', 'A', 'B', 'B', 'A', 'A'),
-			'5' => array('B', 'A', 'A', 'B', 'B', 'A'),
-			'6' => array('B', 'A', 'A', 'A', 'B', 'B'),
-			'7' => array('B', 'A', 'B', 'A', 'B', 'A'),
-			'8' => array('B', 'A', 'B', 'A', 'A', 'B'),
-			'9' => array('B', 'A', 'A', 'B', 'A', 'B')
-		);
-		$upce_parities[1] = array(
-			'0' => array('A', 'A', 'A', 'B', 'B', 'B'),
-			'1' => array('A', 'A', 'B', 'A', 'B', 'B'),
-			'2' => array('A', 'A', 'B', 'B', 'A', 'B'),
-			'3' => array('A', 'A', 'B', 'B', 'B', 'A'),
-			'4' => array('A', 'B', 'A', 'A', 'B', 'B'),
-			'5' => array('A', 'B', 'B', 'A', 'A', 'B'),
-			'6' => array('A', 'B', 'B', 'B', 'A', 'A'),
-			'7' => array('A', 'B', 'A', 'B', 'A', 'B'),
-			'8' => array('A', 'B', 'A', 'B', 'B', 'A'),
-			'9' => array('A', 'B', 'B', 'A', 'B', 'A')
-		);
-		$k = 0;
-		$seq = '101'; // left guard bar
-		if ($upce) {
-			$bararray = array('code' => $upce_code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-			$p = $upce_parities[$code[1]][$r];
-			for($i = 0; $i < 6; ++$i) {
-				$seq .= $codes[$p[$i]][$upce_code{$i}];
-			}
-			$seq .= '010101'; // right guard bar
-		} else {
-			$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-			$half_len = intval(ceil($len / 2));
-			if ($len == 8) {
-				for($i = 0; $i < $half_len; ++$i) {
-					$seq .= $codes['A'][$code{$i}];
-				}
-			} else {
-				$p = $parities[$code[0]];
-				for($i = 1; $i < $half_len; ++$i) {
-					$seq .= $codes[$p[$i - 1]][$code{$i}];
-				}
-			}
-			$seq .= '01010'; // center guard bar
-			for($i = $half_len; $i < $len; ++$i) {
-				if (!isset($codes['C'][$code{$i}])) {
-					throw new InvalidCharacterException('Char ' . $code{$i} . ' not allowed');
-				}
-				$seq .= $codes['C'][$code{$i}];
-			}
-			$seq .= '101'; // right guard bar
-		}
-		$clen = strlen($seq);
-		$w = 0;
-		for($i = 0; $i < $clen; ++$i) {
-			$w += 1;
-			if (($i == ($clen - 1)) OR (($i < ($clen - 1)) AND ($seq{$i} != $seq{($i + 1)}))) {
-				if ($seq{$i} == '1') {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-				++$k;
-				$w = 0;
-			}
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * MSI.
-	 * Variation of Plessey code, with similar applications
-	 * Contains digits (0 to 9) and encodes the data only in the width of bars.
-	 *
-	 * @param $code (string) code to represent.
-	 * @param $checksum (boolean) if true add a checksum to the code (modulo 11)
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_msi($code, $checksum = false)
-	{
-		$chr['0'] = '100100100100';
-		$chr['1'] = '100100100110';
-		$chr['2'] = '100100110100';
-		$chr['3'] = '100100110110';
-		$chr['4'] = '100110100100';
-		$chr['5'] = '100110100110';
-		$chr['6'] = '100110110100';
-		$chr['7'] = '100110110110';
-		$chr['8'] = '110100100100';
-		$chr['9'] = '110100100110';
-		$chr['A'] = '110100110100';
-		$chr['B'] = '110100110110';
-		$chr['C'] = '110110100100';
-		$chr['D'] = '110110100110';
-		$chr['E'] = '110110110100';
-		$chr['F'] = '110110110110';
-		if ($checksum) {
-			// add checksum
-			$clen = strlen($code);
-			$p = 2;
-			$check = 0;
-			for($i = ($clen - 1); $i >= 0; --$i) {
-				$check += (hexdec($code{$i}) * $p);
-				++$p;
-				if ($p > 7) {
-					$p = 2;
-				}
-			}
-			$check %= 11;
-			if ($check > 0) {
-				$check = 11 - $check;
-			}
-			$code .= $check;
-		}
-		$seq = '110'; // left guard
-		$clen = strlen($code);
-		for($i = 0; $i < $clen; ++$i) {
-			$digit = $code{$i};
-			if (!isset($chr[$digit])) {
-				throw new InvalidCharacterException('Char ' . $digit . ' is unsupported');
-			}
-			$seq .= $chr[$digit];
-		}
-		$seq .= '1001'; // right guard
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-
-		return $this->binseq_to_array($seq, $bararray);
-	}
-
-	/**
-	 * POSTNET and PLANET barcodes.
-	 * Used by U.S. Postal Service for automated mail sorting
-	 *
-	 * @param $code (string) zip code to represent. Must be a string containing a zip code of the form DDDDD or
-	 *     DDDDD-DDDD.
-	 * @param $planet (boolean) if true print the PLANET barcode, otherwise print POSTNET
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_postnet($code, $planet = false)
-	{
-		// bar length
-		if ($planet) {
-			$barlen = Array(
-				0 => Array(1, 1, 2, 2, 2),
-				1 => Array(2, 2, 2, 1, 1),
-				2 => Array(2, 2, 1, 2, 1),
-				3 => Array(2, 2, 1, 1, 2),
-				4 => Array(2, 1, 2, 2, 1),
-				5 => Array(2, 1, 2, 1, 2),
-				6 => Array(2, 1, 1, 2, 2),
-				7 => Array(1, 2, 2, 2, 1),
-				8 => Array(1, 2, 2, 1, 2),
-				9 => Array(1, 2, 1, 2, 2)
-			);
-		} else {
-			$barlen = Array(
-				0 => Array(2, 2, 1, 1, 1),
-				1 => Array(1, 1, 1, 2, 2),
-				2 => Array(1, 1, 2, 1, 2),
-				3 => Array(1, 1, 2, 2, 1),
-				4 => Array(1, 2, 1, 1, 2),
-				5 => Array(1, 2, 1, 2, 1),
-				6 => Array(1, 2, 2, 1, 1),
-				7 => Array(2, 1, 1, 1, 2),
-				8 => Array(2, 1, 1, 2, 1),
-				9 => Array(2, 1, 2, 1, 1)
-			);
-		}
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 2, 'bcode' => array());
-		$k = 0;
-		$code = str_replace('-', '', $code);
-		$code = str_replace(' ', '', $code);
-		$len = strlen($code);
-		// calculate checksum
-		$sum = 0;
-		for($i = 0; $i < $len; ++$i) {
-			$sum += intval($code{$i});
-		}
-		$chkd = ($sum % 10);
-		if ($chkd > 0) {
-			$chkd = (10 - $chkd);
-		}
-		$code .= $chkd;
-		$len = strlen($code);
-		// start bar
-		$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => 2, 'p' => 0);
-		$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
-		$bararray['maxw'] += 2;
-		for($i = 0; $i < $len; ++$i) {
-			for($j = 0; $j < 5; ++$j) {
-				$h = $barlen[$code{$i}][$j];
-				$p = floor(1 / $h);
-				$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => $h, 'p' => $p);
-				$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
-				$bararray['maxw'] += 2;
-			}
-		}
-		// end bar
-		$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => 2, 'p' => 0);
-		$bararray['maxw'] += 1;
-
-		return $bararray;
-	}
-
-	/**
-	 * RMS4CC - CBC - KIX
-	 * RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code) - KIX (Klant index - Customer index)
-	 * RM4SCC is the name of the barcode symbology used by the Royal Mail for its Cleanmail service.
-	 *
-	 * @param $code (string) code to print
-	 * @param $kix (boolean) if true prints the KIX variation (doesn't use the start and end symbols, and the checksum)
-	 *     - in this case the house number must be sufficed with an X and placed at the end of the code.
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_rms4cc($code, $kix = false)
-	{
-		$notkix = !$kix;
-		// bar mode
-		// 1 = pos 1, length 2
-		// 2 = pos 1, length 3
-		// 3 = pos 2, length 1
-		// 4 = pos 2, length 2
-		$barmode = array(
-			'0' => array(3, 3, 2, 2),
-			'1' => array(3, 4, 1, 2),
-			'2' => array(3, 4, 2, 1),
-			'3' => array(4, 3, 1, 2),
-			'4' => array(4, 3, 2, 1),
-			'5' => array(4, 4, 1, 1),
-			'6' => array(3, 1, 4, 2),
-			'7' => array(3, 2, 3, 2),
-			'8' => array(3, 2, 4, 1),
-			'9' => array(4, 1, 3, 2),
-			'A' => array(4, 1, 4, 1),
-			'B' => array(4, 2, 3, 1),
-			'C' => array(3, 1, 2, 4),
-			'D' => array(3, 2, 1, 4),
-			'E' => array(3, 2, 2, 3),
-			'F' => array(4, 1, 1, 4),
-			'G' => array(4, 1, 2, 3),
-			'H' => array(4, 2, 1, 3),
-			'I' => array(1, 3, 4, 2),
-			'J' => array(1, 4, 3, 2),
-			'K' => array(1, 4, 4, 1),
-			'L' => array(2, 3, 3, 2),
-			'M' => array(2, 3, 4, 1),
-			'N' => array(2, 4, 3, 1),
-			'O' => array(1, 3, 2, 4),
-			'P' => array(1, 4, 1, 4),
-			'Q' => array(1, 4, 2, 3),
-			'R' => array(2, 3, 1, 4),
-			'S' => array(2, 3, 2, 3),
-			'T' => array(2, 4, 1, 3),
-			'U' => array(1, 1, 4, 4),
-			'V' => array(1, 2, 3, 4),
-			'W' => array(1, 2, 4, 3),
-			'X' => array(2, 1, 3, 4),
-			'Y' => array(2, 1, 4, 3),
-			'Z' => array(2, 2, 3, 3)
-		);
-		$code = strtoupper($code);
-		$len = strlen($code);
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 3, 'bcode' => array());
-		if ($notkix) {
-			// table for checksum calculation (row,col)
-			$checktable = array(
-				'0' => array(1, 1),
-				'1' => array(1, 2),
-				'2' => array(1, 3),
-				'3' => array(1, 4),
-				'4' => array(1, 5),
-				'5' => array(1, 0),
-				'6' => array(2, 1),
-				'7' => array(2, 2),
-				'8' => array(2, 3),
-				'9' => array(2, 4),
-				'A' => array(2, 5),
-				'B' => array(2, 0),
-				'C' => array(3, 1),
-				'D' => array(3, 2),
-				'E' => array(3, 3),
-				'F' => array(3, 4),
-				'G' => array(3, 5),
-				'H' => array(3, 0),
-				'I' => array(4, 1),
-				'J' => array(4, 2),
-				'K' => array(4, 3),
-				'L' => array(4, 4),
-				'M' => array(4, 5),
-				'N' => array(4, 0),
-				'O' => array(5, 1),
-				'P' => array(5, 2),
-				'Q' => array(5, 3),
-				'R' => array(5, 4),
-				'S' => array(5, 5),
-				'T' => array(5, 0),
-				'U' => array(0, 1),
-				'V' => array(0, 2),
-				'W' => array(0, 3),
-				'X' => array(0, 4),
-				'Y' => array(0, 5),
-				'Z' => array(0, 0)
-			);
-			$row = 0;
-			$col = 0;
-			for($i = 0; $i < $len; ++$i) {
-				$row += $checktable[$code{$i}][0];
-				$col += $checktable[$code{$i}][1];
-			}
-			$row %= 6;
-			$col %= 6;
-			$chk = array_keys($checktable, array($row, $col));
-			$code .= $chk[0];
-			++$len;
-		}
-		$k = 0;
-		if ($notkix) {
-			// start bar
-			$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => 2, 'p' => 0);
-			$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
-			$bararray['maxw'] += 2;
-		}
-		for($i = 0; $i < $len; ++$i) {
-			for($j = 0; $j < 4; ++$j) {
-				switch ($barmode[$code{$i}][$j]) {
-					case 1:
-						{
-							$p = 0;
-							$h = 2;
-							break;
-						}
-					case 2:
-						{
-							$p = 0;
-							$h = 3;
-							break;
-						}
-					case 3:
-						{
-							$p = 1;
-							$h = 1;
-							break;
-						}
-					case 4:
-						{
-							$p = 1;
-							$h = 2;
-							break;
-						}
-				}
-				$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => $h, 'p' => $p);
-				$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
-				$bararray['maxw'] += 2;
-			}
-		}
-		if ($notkix) {
-			// stop bar
-			$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => 3, 'p' => 0);
-			$bararray['maxw'] += 1;
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
-	 * (requires PHP bcmath extension)
-	 * Intelligent Mail barcode is a 65-bar code for use on mail in the United States.
-	 * The fields are described as follows:<ul><li>The Barcode Identifier shall be assigned by USPS to encode the
-	 * presort identification that is currently printed in human readable form on the optional endorsement line (OEL)
-	 * as well as for future USPS use. This shall be two digits, with the second digit in the range of 0–4. The
-	 * allowable encoding ranges shall be 00–04, 10–14, 20–24, 30–34, 40–44, 50–54, 60–64, 70–74, 80–84, and
-	 * 90–94.</li><li>The Service Type Identifier shall be assigned by USPS for any combination of services requested
-	 * on the mailpiece. The allowable encoding range shall be 000http://it2.php.net/manual/en/function.dechex.php–999.
-	 * Each 3-digit value shall correspond to a particular mail class with a particular combination of service(s). Each
-	 * service program, such as OneCode Confirm and OneCode ACS, shall provide the list of Service Type Identifier
-	 * values.</li><li>The Mailer or Customer Identifier shall be assigned by USPS as a unique, 6 or 9 digit number
-	 * that identifies a business entity. The allowable encoding range for the 6 digit Mailer ID shall be 000000-
-	 * 899999, while the allowable encoding range for the 9 digit Mailer ID shall be 900000000-999999999.</li><li>The
-	 * Serial or Sequence Number shall be assigned by the mailer for uniquely identifying and tracking mailpieces. The
-	 * allowable encoding range shall be 000000000–999999999 when used with a 6 digit Mailer ID and 000000-999999 when
-	 * used with a 9 digit Mailer ID. e. The Delivery Point ZIP Code shall be assigned by the mailer for routing the
-	 * mailpiece. This shall replace POSTNET for routing the mailpiece to its final delivery point. The length may be
-	 * 0, 5, 9, or 11 digits. The allowable encoding ranges shall be no ZIP Code, 00000–99999,  000000000–999999999,
-	 * and 00000000000–99999999999.</li></ul>
-	 *
-	 * @param $code (string) code to print, separate the ZIP (routing code) from the rest using a minus char '-'
-	 *     (BarcodeID_ServiceTypeID_MailerID_SerialNumber-RoutingCode)
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_imb($code)
-	{
-		$asc_chr = array(
-			4,
-			0,
-			2,
-			6,
-			3,
-			5,
-			1,
-			9,
-			8,
-			7,
-			1,
-			2,
-			0,
-			6,
-			4,
-			8,
-			2,
-			9,
-			5,
-			3,
-			0,
-			1,
-			3,
-			7,
-			4,
-			6,
-			8,
-			9,
-			2,
-			0,
-			5,
-			1,
-			9,
-			4,
-			3,
-			8,
-			6,
-			7,
-			1,
-			2,
-			4,
-			3,
-			9,
-			5,
-			7,
-			8,
-			3,
-			0,
-			2,
-			1,
-			4,
-			0,
-			9,
-			1,
-			7,
-			0,
-			2,
-			4,
-			6,
-			3,
-			7,
-			1,
-			9,
-			5,
-			8
-		);
-		$dsc_chr = array(
-			7,
-			1,
-			9,
-			5,
-			8,
-			0,
-			2,
-			4,
-			6,
-			3,
-			5,
-			8,
-			9,
-			7,
-			3,
-			0,
-			6,
-			1,
-			7,
-			4,
-			6,
-			8,
-			9,
-			2,
-			5,
-			1,
-			7,
-			5,
-			4,
-			3,
-			8,
-			7,
-			6,
-			0,
-			2,
-			5,
-			4,
-			9,
-			3,
-			0,
-			1,
-			6,
-			8,
-			2,
-			0,
-			4,
-			5,
-			9,
-			6,
-			7,
-			5,
-			2,
-			6,
-			3,
-			8,
-			5,
-			1,
-			9,
-			8,
-			7,
-			4,
-			0,
-			2,
-			6,
-			3
-		);
-		$asc_pos = array(
-			3,
-			0,
-			8,
-			11,
-			1,
-			12,
-			8,
-			11,
-			10,
-			6,
-			4,
-			12,
-			2,
-			7,
-			9,
-			6,
-			7,
-			9,
-			2,
-			8,
-			4,
-			0,
-			12,
-			7,
-			10,
-			9,
-			0,
-			7,
-			10,
-			5,
-			7,
-			9,
-			6,
-			8,
-			2,
-			12,
-			1,
-			4,
-			2,
-			0,
-			1,
-			5,
-			4,
-			6,
-			12,
-			1,
-			0,
-			9,
-			4,
-			7,
-			5,
-			10,
-			2,
-			6,
-			9,
-			11,
-			2,
-			12,
-			6,
-			7,
-			5,
-			11,
-			0,
-			3,
-			2
-		);
-		$dsc_pos = array(
-			2,
-			10,
-			12,
-			5,
-			9,
-			1,
-			5,
-			4,
-			3,
-			9,
-			11,
-			5,
-			10,
-			1,
-			6,
-			3,
-			4,
-			1,
-			10,
-			0,
-			2,
-			11,
-			8,
-			6,
-			1,
-			12,
-			3,
-			8,
-			6,
-			4,
-			4,
-			11,
-			0,
-			6,
-			1,
-			9,
-			11,
-			5,
-			3,
-			7,
-			3,
-			10,
-			7,
-			11,
-			8,
-			2,
-			10,
-			3,
-			5,
-			8,
-			0,
-			3,
-			12,
-			11,
-			8,
-			4,
-			5,
-			1,
-			3,
-			0,
-			7,
-			12,
-			9,
-			8,
-			10
-		);
-		$code_arr = explode('-', $code);
-		$tracking_number = $code_arr[0];
-		if (isset($code_arr[1])) {
-			$routing_code = $code_arr[1];
-		} else {
-			$routing_code = '';
-		}
-		// Conversion of Routing Code
-		switch (strlen($routing_code)) {
-			case 0:
-				{
-					$binary_code = 0;
-					break;
-				}
-			case 5:
-				{
-					$binary_code = bcadd($routing_code, '1');
-					break;
-				}
-			case 9:
-				{
-					$binary_code = bcadd($routing_code, '100001');
-					break;
-				}
-			case 11:
-				{
-					$binary_code = bcadd($routing_code, '1000100001');
-					break;
-				}
-			default:
-				{
-					throw new BarcodeException('Routing code unknown');
-					break;
-				}
-		}
-		$binary_code = bcmul($binary_code, 10);
-		$binary_code = bcadd($binary_code, $tracking_number[0]);
-		$binary_code = bcmul($binary_code, 5);
-		$binary_code = bcadd($binary_code, $tracking_number[1]);
-		$binary_code .= substr($tracking_number, 2, 18);
-		// convert to hexadecimal
-		$binary_code = $this->dec_to_hex($binary_code);
-		// pad to get 13 bytes
-		$binary_code = str_pad($binary_code, 26, '0', STR_PAD_LEFT);
-		// convert string to array of bytes
-		$binary_code_arr = chunk_split($binary_code, 2, "\r");
-		$binary_code_arr = substr($binary_code_arr, 0, -1);
-		$binary_code_arr = explode("\r", $binary_code_arr);
-		// calculate frame check sequence
-		$fcs = $this->imb_crc11fcs($binary_code_arr);
-		// exclude first 2 bits from first byte
-		$first_byte = sprintf('%2s', dechex((hexdec($binary_code_arr[0]) << 2) >> 2));
-		$binary_code_102bit = $first_byte . substr($binary_code, 2);
-		// convert binary data to codewords
-		$codewords = array();
-		$data = $this->hex_to_dec($binary_code_102bit);
-		$codewords[0] = bcmod($data, 636) * 2;
-		$data = bcdiv($data, 636);
-		for($i = 1; $i < 9; ++$i) {
-			$codewords[$i] = bcmod($data, 1365);
-			$data = bcdiv($data, 1365);
-		}
-		$codewords[9] = $data;
-		if (($fcs >> 10) == 1) {
-			$codewords[9] += 659;
-		}
-		// generate lookup tables
-		$table2of13 = $this->imb_tables(2, 78);
-		$table5of13 = $this->imb_tables(5, 1287);
-		// convert codewords to characters
-		$characters = array();
-		$bitmask = 512;
-		foreach($codewords as $k => $val) {
-			if ($val <= 1286) {
-				$chrcode = $table5of13[$val];
-			} else {
-				$chrcode = $table2of13[($val - 1287)];
-			}
-			if (($fcs & $bitmask) > 0) {
-				// bitwise invert
-				$chrcode = ((~$chrcode) & 8191);
-			}
-			$characters[] = $chrcode;
-			$bitmask /= 2;
-		}
-		$characters = array_reverse($characters);
-		// build bars
-		$k = 0;
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 3, 'bcode' => array());
-		for($i = 0; $i < 65; ++$i) {
-			$asc = (($characters[$asc_chr[$i]] & pow(2, $asc_pos[$i])) > 0);
-			$dsc = (($characters[$dsc_chr[$i]] & pow(2, $dsc_pos[$i])) > 0);
-			if ($asc AND $dsc) {
-				// full bar (F)
-				$p = 0;
-				$h = 3;
-			} elseif ($asc) {
-				// ascender (A)
-				$p = 0;
-				$h = 2;
-			} elseif ($dsc) {
-				// descender (D)
-				$p = 1;
-				$h = 2;
-			} else {
-				// tracker (T)
-				$p = 1;
-				$h = 1;
-			}
-			$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => $h, 'p' => $p);
-			$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
-			$bararray['maxw'] += 2;
-		}
-		unset($bararray['bcode'][($k - 1)]);
-		--$bararray['maxw'];
-
-		return $bararray;
-	}
-
-	/**
-	 * Convert large integer number to hexadecimal representation.
-	 * (requires PHP bcmath extension)
-	 *
-	 * @param $number (string) number to convert specified as a string
-	 * @return string hexadecimal representation
-	 */
-	protected function dec_to_hex($number)
-	{
-		if ($number == 0) {
-			return '00';
-		}
-
-		$hex = [];
-
-		while ($number > 0) {
-			array_push($hex, strtoupper(dechex(bcmod($number, '16'))));
-			$number = bcdiv($number, '16', 0);
-		}
-		$hex = array_reverse($hex);
-
-		return implode($hex);
-	}
-
-	/**
-	 * Intelligent Mail Barcode calculation of Frame Check Sequence
-	 *
-	 * @param $code_arr (string) array of hexadecimal values (13 bytes holding 102 bits right justified).
-	 * @return int 11 bit Frame Check Sequence as integer (decimal base)
-	 * @protected
-	 */
-	protected function imb_crc11fcs($code_arr)
-	{
-		$genpoly = 0x0F35; // generator polynomial
-		$fcs = 0x07FF; // Frame Check Sequence
-		// do most significant byte skipping the 2 most significant bits
-		$data = hexdec($code_arr[0]) << 5;
-		for($bit = 2; $bit < 8; ++$bit) {
-			if (($fcs ^ $data) & 0x400) {
-				$fcs = ($fcs << 1) ^ $genpoly;
-			} else {
-				$fcs = ($fcs << 1);
-			}
-			$fcs &= 0x7FF;
-			$data <<= 1;
-		}
-		// do rest of bytes
-		for($byte = 1; $byte < 13; ++$byte) {
-			$data = hexdec($code_arr[$byte]) << 3;
-			for($bit = 0; $bit < 8; ++$bit) {
-				if (($fcs ^ $data) & 0x400) {
-					$fcs = ($fcs << 1) ^ $genpoly;
-				} else {
-					$fcs = ($fcs << 1);
-				}
-				$fcs &= 0x7FF;
-				$data <<= 1;
-			}
-		}
-
-		return $fcs;
-	}
-
-	/**
-	 * Convert large hexadecimal number to decimal representation (string).
-	 * (requires PHP bcmath extension)
-	 *
-	 * @param $hex (string) hexadecimal number to convert specified as a string
-	 * @return string hexadecimal representation
-	 */
-	protected function hex_to_dec($hex)
-	{
-		$dec = 0;
-		$bitval = 1;
-		$len = strlen($hex);
-		for($pos = ($len - 1); $pos >= 0; --$pos) {
-			$dec = bcadd($dec, bcmul(hexdec($hex{$pos}), $bitval));
-			$bitval = bcmul($bitval, 16);
-		}
-
-		return $dec;
-	}
-
-	/**
-	 * generate Nof13 tables used for Intelligent Mail Barcode
-	 *
-	 * @param $n (int) is the type of table: 2 for 2of13 table, 5 for 5of13table
-	 * @param $size (int) size of table (78 for n=2 and 1287 for n=5)
-	 * @return array requested table
-	 * @protected
-	 */
-	protected function imb_tables($n, $size)
-	{
-		$table = array();
-		$lli = 0; // LUT lower index
-		$lui = $size - 1; // LUT upper index
-		for($count = 0; $count < 8192; ++$count) {
-			$bit_count = 0;
-			for($bit_index = 0; $bit_index < 13; ++$bit_index) {
-				$bit_count += intval(($count & (1 << $bit_index)) != 0);
-			}
-			// if we don't have the right number of bits on, go on to the next value
-			if ($bit_count == $n) {
-				$reverse = ($this->imb_reverse_us($count) >> 3);
-				// if the reverse is less than count, we have already visited this pair before
-				if ($reverse >= $count) {
-					// If count is symmetric, place it at the first free slot from the end of the list.
-					// Otherwise, place it at the first free slot from the beginning of the list AND place $reverse ath the next free slot from the beginning of the list
-					if ($reverse == $count) {
-						$table[$lui] = $count;
-						--$lui;
-					} else {
-						$table[$lli] = $count;
-						++$lli;
-						$table[$lli] = $reverse;
-						++$lli;
-					}
-				}
-			}
-		}
-
-		return $table;
-	}
-
-	/**
-	 * Reverse unsigned short value
-	 *
-	 * @param $num (int) value to reversr
-	 * @return int reversed value
-	 * @protected
-	 */
-	protected function imb_reverse_us($num)
-	{
-		$rev = 0;
-		for($i = 0; $i < 16; ++$i) {
-			$rev <<= 1;
-			$rev |= ($num & 1);
-			$num >>= 1;
-		}
-
-		return $rev;
-	}
-
-	/**
-	 * CODABAR barcodes.
-	 * Older code often used in library systems, sometimes in blood banks
-	 *
-	 * @param $code (string) code to represent.
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_codabar($code)
-	{
-		$chr = array(
-			'0' => '11111221',
-			'1' => '11112211',
-			'2' => '11121121',
-			'3' => '22111111',
-			'4' => '11211211',
-			'5' => '21111211',
-			'6' => '12111121',
-			'7' => '12112111',
-			'8' => '12211111',
-			'9' => '21121111',
-			'-' => '11122111',
-			'$' => '11221111',
-			':' => '21112121',
-			'/' => '21211121',
-			'.' => '21212111',
-			'+' => '11222221',
-			'A' => '11221211',
-			'B' => '12121121',
-			'C' => '11121221',
-			'D' => '11122211'
-		);
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-		$k = 0;
-		$w = 0;
-		$seq = '';
-		$code = 'A' . strtoupper($code) . 'A';
-		$len = strlen($code);
-		for($i = 0; $i < $len; ++$i) {
-			if (!isset($chr[$code{$i}])) {
-				throw new InvalidCharacterException('Char ' . $code{$i} . ' is unsupported');
-			}
-			$seq = $chr[$code{$i}];
-			for($j = 0; $j < 8; ++$j) {
-				if (($j % 2) == 0) {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$w = $seq{$j};
-				$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-				++$k;
-			}
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * CODE11 barcodes.
-	 * Used primarily for labeling telecommunications equipment
-	 *
-	 * @param $code (string) code to represent.
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_code11($code)
-	{
-		$chr = array(
-			'0' => '111121',
-			'1' => '211121',
-			'2' => '121121',
-			'3' => '221111',
-			'4' => '112121',
-			'5' => '212111',
-			'6' => '122111',
-			'7' => '111221',
-			'8' => '211211',
-			'9' => '211111',
-			'-' => '112111',
-			'S' => '112211'
-		);
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-		$k = 0;
-		$w = 0;
-		$seq = '';
-		$len = strlen($code);
-		// calculate check digit C
-		$p = 1;
-		$check = 0;
-		for($i = ($len - 1); $i >= 0; --$i) {
-			$digit = $code{$i};
-			if ($digit == '-') {
-				$dval = 10;
-			} else {
-				$dval = intval($digit);
-			}
-			$check += ($dval * $p);
-			++$p;
-			if ($p > 10) {
-				$p = 1;
-			}
-		}
-		$check %= 11;
-		if ($check == 10) {
-			$check = '-';
-		}
-		$code .= $check;
-		if ($len > 10) {
-			// calculate check digit K
-			$p = 1;
-			$check = 0;
-			for($i = $len; $i >= 0; --$i) {
-				$digit = $code{$i};
-				if ($digit == '-') {
-					$dval = 10;
-				} else {
-					$dval = intval($digit);
-				}
-				$check += ($dval * $p);
-				++$p;
-				if ($p > 9) {
-					$p = 1;
-				}
-			}
-			$check %= 11;
-			$code .= $check;
-			++$len;
-		}
-		$code = 'S' . $code . 'S';
-		$len += 3;
-		for($i = 0; $i < $len; ++$i) {
-			if (!isset($chr[$code{$i}])) {
-				throw new InvalidCharacterException('Char ' . $code{$i} . ' is unsupported');
-			}
-			$seq = $chr[$code{$i}];
-			for($j = 0; $j < 6; ++$j) {
-				if (($j % 2) == 0) {
-					$t = true; // bar
-				} else {
-					$t = false; // space
-				}
-				$w = $seq{$j};
-				$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
-				$bararray['maxw'] += $w;
-				++$k;
-			}
-		}
-
-		return $bararray;
-	}
-
-	/**
-	 * Pharmacode
-	 * Contains digits (0 to 9)
-	 *
-	 * @param $code (string) code to represent.
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_pharmacode($code)
-	{
-		$seq = '';
-		$code = intval($code);
-		while ($code > 0) {
-			if (($code % 2) == 0) {
-				$seq .= '11100';
-				$code -= 2;
-			} else {
-				$seq .= '100';
-				$code -= 1;
-			}
-			$code /= 2;
-		}
-		$seq = substr($seq, 0, -2);
-		$seq = strrev($seq);
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
-
-		return $this->binseq_to_array($seq, $bararray);
-	}
-
-	/**
-	 * Pharmacode two-track
-	 * Contains digits (0 to 9)
-	 *
-	 * @param $code (string) code to represent.
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_pharmacode2t($code)
-	{
-		$seq = '';
-		$code = intval($code);
-		do {
-			switch ($code % 3) {
-				case 0:
-					{
-						$seq .= '3';
-						$code = ($code - 3) / 3;
-						break;
-					}
-				case 1:
-					{
-						$seq .= '1';
-						$code = ($code - 1) / 3;
-						break;
-					}
-				case 2:
-					{
-						$seq .= '2';
-						$code = ($code - 2) / 3;
-						break;
-					}
-			}
-		} while ($code != 0);
-		$seq = strrev($seq);
-		$k = 0;
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 2, 'bcode' => array());
-		$len = strlen($seq);
-		for($i = 0; $i < $len; ++$i) {
-			switch ($seq{$i}) {
-				case '1':
-					{
-						$p = 1;
-						$h = 1;
-						break;
-					}
-				case '2':
-					{
-						$p = 0;
-						$h = 1;
-						break;
-					}
-				case '3':
-					{
-						$p = 0;
-						$h = 2;
-						break;
-					}
-			}
-			$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => $h, 'p' => $p);
-			$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
-			$bararray['maxw'] += 2;
-		}
-		unset($bararray['bcode'][($k - 1)]);
-		--$bararray['maxw'];
-
-		return $bararray;
-	}
-
-	protected function convertBarcodeArrayToNewStyle($oldBarcodeArray)
-	{
-		$newBarcodeArray = [];
-		$newBarcodeArray['code'] = $oldBarcodeArray['code'];
-		$newBarcodeArray['maxWidth'] = $oldBarcodeArray['maxw'];
-		$newBarcodeArray['maxHeight'] = $oldBarcodeArray['maxh'];
-		$newBarcodeArray['bars'] = [];
-		foreach($oldBarcodeArray['bcode'] as $oldbar) {
-			$newBar = [];
-			$newBar['width'] = $oldbar['w'];
-			$newBar['height'] = $oldbar['h'];
-			$newBar['positionVertical'] = $oldbar['p'];
-			$newBar['drawBar'] = $oldbar['t'];
-			$newBar['drawSpacing'] = !$oldbar['t'];
-
-			$newBarcodeArray['bars'][] = $newBar;
-		}
-
-		return $newBarcodeArray;
-	}
-
-	/**
-	 * IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
-	 *
-	 * @param $code (string) pre-formatted IMB barcode (65 chars "FADT")
-	 * @return array barcode representation.
-	 * @protected
-	 */
-	protected function barcode_imb_pre($code)
-	{
-		if (!preg_match('/^[fadtFADT]{65}$/', $code) == 1) {
-			throw new InvalidFormatException();
-		}
-		$characters = str_split(strtolower($code), 1);
-		// build bars
-		$k = 0;
-		$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 3, 'bcode' => array());
-		for($i = 0; $i < 65; ++$i) {
-			switch ($characters[$i]) {
-				case 'f':
-					{
-						// full bar
-						$p = 0;
-						$h = 3;
-						break;
-					}
-				case 'a':
-					{
-						// ascender
-						$p = 0;
-						$h = 2;
-						break;
-					}
-				case 'd':
-					{
-						// descender
-						$p = 1;
-						$h = 2;
-						break;
-					}
-				case 't':
-					{
-						// tracker (short)
-						$p = 1;
-						$h = 1;
-						break;
-					}
-			}
-			$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => $h, 'p' => $p);
-			$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
-			$bararray['maxw'] += 2;
-		}
-		unset($bararray['bcode'][($k - 1)]);
-		--$bararray['maxw'];
-
-		return $bararray;
-	}
-}

+ 0 - 43
src/Picqer/Barcode/BarcodeGeneratorHTML.php

@@ -1,43 +0,0 @@
-<?php
-
-namespace Picqer\Barcode;
-
-class BarcodeGeneratorHTML extends BarcodeGenerator
-{
-
-	/**
-	 * Return an HTML representation of barcode.
-	 *
-	 * @param string $code code to print
-	 * @param string $type type of barcode
-	 * @param int $widthFactor Width of a single bar element in pixels.
-	 * @param int $totalHeight Height of a single bar element in pixels.
-	 * @param int|string $color Foreground color for bar elements (background is transparent).
-	 * @return string HTML code.
-	 * @public
-	 */
-	public function getBarcode($code, $type, $widthFactor = 2, $totalHeight = 30, $color = 'black')
-	{
-		$barcodeData = $this->getBarcodeData($code, $type);
-
-		$html = '<div style="font-size:0;position:relative;width:' . ($barcodeData['maxWidth'] * $widthFactor) . 'px;height:' . ($totalHeight) . 'px;">' . "\n";
-
-		$positionHorizontal = 0;
-		foreach($barcodeData['bars'] as $bar) {
-			$barWidth = round(($bar['width'] * $widthFactor), 3);
-			$barHeight = round(($bar['height'] * $totalHeight / $barcodeData['maxHeight']), 3);
-
-			if ($bar['drawBar']) {
-				$positionVertical = round(($bar['positionVertical'] * $totalHeight / $barcodeData['maxHeight']), 3);
-				// draw a vertical bar
-				$html .= '<div style="background-color:' . $color . ';width:' . $barWidth . 'px;height:' . $barHeight . 'px;position:absolute;left:' . $positionHorizontal . 'px;top:' . $positionVertical . 'px;">&nbsp;</div>' . "\n";
-			}
-
-			$positionHorizontal += $barWidth;
-		}
-
-		$html .= '</div>' . "\n";
-
-		return $html;
-	}
-}

+ 0 - 77
src/Picqer/Barcode/BarcodeGeneratorJPG.php

@@ -1,77 +0,0 @@
-<?php
-
-namespace Picqer\Barcode;
-
-use Picqer\Barcode\Exceptions\BarcodeException;
-
-class BarcodeGeneratorJPG extends BarcodeGenerator
-{
-
-	/**
-	 * Return a JPG image representation of barcode (requires GD or Imagick library).
-	 *
-	 * @param string $code code to print
-	 * @param string $type type of barcode:
-	 * @param int $widthFactor Width of a single bar element in pixels.
-	 * @param int $totalHeight Height of a single bar element in pixels.
-	 * @param array $color RGB (0-255) foreground color for bar elements (background is transparent).
-	 * @return string image data or false in case of error.
-	 * @public
-	 * @throws BarcodeException
-	 */
-	public function getBarcode($code, $type, $widthFactor = 2, $totalHeight = 30, $color = array(0, 0, 0))
-	{
-		$barcodeData = $this->getBarcodeData($code, $type);
-
-		// calculate image size
-		$width = ($barcodeData['maxWidth'] * $widthFactor);
-		$height = $totalHeight;
-
-		if (function_exists('imagecreate')) {
-			// GD library
-			$imagick = false;
-			$jpg = imagecreate($width, $height);
-			$colorBackground = imagecolorallocate($jpg, 255, 255, 255);
-			imagecolortransparent($jpg, $colorBackground);
-			$colorForeground = imagecolorallocate($jpg, $color[0], $color[1], $color[2]);
-		} elseif (extension_loaded('imagick')) {
-			$imagick = true;
-			$colorForeground = new \imagickpixel('rgb(' . $color[0] . ',' . $color[1] . ',' . $color[2] . ')');
-			$jpg = new \Imagick();
-			$jpg->newImage($width, $height, 'none', 'jpg');
-			$imageMagickObject = new \imagickdraw();
-			$imageMagickObject->setFillColor($colorForeground);
-		} else {
-			throw new BarcodeException('Neither gd-lib or imagick are installed!');
-		}
-
-		// print bars
-		$positionHorizontal = 0;
-		foreach($barcodeData['bars'] as $bar) {
-			$bw = round(($bar['width'] * $widthFactor), 3);
-			$bh = round(($bar['height'] * $totalHeight / $barcodeData['maxHeight']), 3);
-			if ($bar['drawBar']) {
-				$y = round(($bar['positionVertical'] * $totalHeight / $barcodeData['maxHeight']), 3);
-				// draw a vertical bar
-				if ($imagick && isset($imageMagickObject)) {
-					$imageMagickObject->rectangle($positionHorizontal, $y, ($positionHorizontal + $bw), ($y + $bh));
-				} else {
-					imagefilledrectangle($jpg, $positionHorizontal, $y, ($positionHorizontal + $bw) - 1, ($y + $bh),
-						$colorForeground);
-				}
-			}
-			$positionHorizontal += $bw;
-		}
-		ob_start();
-		if ($imagick && isset($imageMagickObject)) {
-			$jpg->drawImage($imageMagickObject);
-			echo $jpg;
-		} else {
-			imagejpeg($jpg);
-			imagedestroy($jpg);
-		}
-		$image = ob_get_clean();
-
-		return $image;
-	}
-}

+ 0 - 76
src/Picqer/Barcode/BarcodeGeneratorPNG.php

@@ -1,76 +0,0 @@
-<?php
-
-namespace Picqer\Barcode;
-
-use Picqer\Barcode\Exceptions\BarcodeException;
-
-class BarcodeGeneratorPNG extends BarcodeGenerator
-{
-
-	/**
-	 * Return a PNG image representation of barcode (requires GD or Imagick library).
-	 *
-	 * @param string $code code to print
-	 * @param string $type type of barcode:
-	 * @param int $widthFactor Width of a single bar element in pixels.
-	 * @param int $totalHeight Height of a single bar element in pixels.
-	 * @param array $color RGB (0-255) foreground color for bar elements (background is transparent).
-	 * @return string image data or false in case of error.
-	 * @public
-	 */
-	public function getBarcode($code, $type, $widthFactor = 2, $totalHeight = 30, $color = array(0, 0, 0))
-	{
-		$barcodeData = $this->getBarcodeData($code, $type);
-
-		// calculate image size
-		$width = ($barcodeData['maxWidth'] * $widthFactor);
-		$height = $totalHeight;
-
-		if (function_exists('imagecreate')) {
-			// GD library
-			$imagick = false;
-			$png = imagecreate($width, $height);
-			$colorBackground = imagecolorallocate($png, 255, 255, 255);
-			imagecolortransparent($png, $colorBackground);
-			$colorForeground = imagecolorallocate($png, $color[0], $color[1], $color[2]);
-		} elseif (extension_loaded('imagick')) {
-			$imagick = true;
-			$colorForeground = new \imagickpixel('rgb(' . $color[0] . ',' . $color[1] . ',' . $color[2] . ')');
-			$png = new \Imagick();
-			$png->newImage($width, $height, 'none', 'png');
-			$imageMagickObject = new \imagickdraw();
-			$imageMagickObject->setFillColor($colorForeground);
-		} else {
-			throw new BarcodeException('Neither gd-lib or imagick are installed!');
-		}
-
-		// print bars
-		$positionHorizontal = 0;
-		foreach($barcodeData['bars'] as $bar) {
-			$bw = round(($bar['width'] * $widthFactor), 3);
-			$bh = round(($bar['height'] * $totalHeight / $barcodeData['maxHeight']), 3);
-			if ($bar['drawBar']) {
-				$y = round(($bar['positionVertical'] * $totalHeight / $barcodeData['maxHeight']), 3);
-				// draw a vertical bar
-				if ($imagick && isset($imageMagickObject)) {
-					$imageMagickObject->rectangle($positionHorizontal, $y, ($positionHorizontal + $bw), ($y + $bh));
-				} else {
-					imagefilledrectangle($png, $positionHorizontal, $y, ($positionHorizontal + $bw) - 1, ($y + $bh),
-						$colorForeground);
-				}
-			}
-			$positionHorizontal += $bw;
-		}
-		ob_start();
-		if ($imagick && isset($imageMagickObject)) {
-			$png->drawImage($imageMagickObject);
-			echo $png;
-		} else {
-			imagepng($png);
-			imagedestroy($png);
-		}
-		$image = ob_get_clean();
-
-		return $image;
-	}
-}

+ 0 - 49
src/Picqer/Barcode/BarcodeGeneratorSVG.php

@@ -1,49 +0,0 @@
-<?php
-
-namespace Picqer\Barcode;
-
-class BarcodeGeneratorSVG extends BarcodeGenerator
-{
-
-	/**
-	 * Return a SVG string representation of barcode.
-	 *
-	 * @param $code (string) code to print
-	 * @param $type (const) type of barcode
-	 * @param $widthFactor (int) Minimum width of a single bar in user units.
-	 * @param $totalHeight (int) Height of barcode in user units.
-	 * @param $color (string) Foreground color (in SVG format) for bar elements (background is transparent).
-	 * @return string SVG code.
-	 * @public
-	 */
-	public function getBarcode($code, $type, $widthFactor = 2, $totalHeight = 30, $color = 'black')
-	{
-		$barcodeData = $this->getBarcodeData($code, $type);
-
-		// replace table for special characters
-		$repstr = array("\0" => '', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;');
-
-		$svg = '<?xml version="1.0" standalone="no" ?>' . "\n";
-		$svg .= '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' . "\n";
-		$svg .= '<svg width="' . round(($barcodeData['maxWidth'] * $widthFactor),
-				3) . '" height="' . $totalHeight . '" version="1.1" xmlns="http://www.w3.org/2000/svg">' . "\n";
-		$svg .= "\t" . '<desc>' . strtr($barcodeData['code'], $repstr) . '</desc>' . "\n";
-		$svg .= "\t" . '<g id="bars" fill="' . $color . '" stroke="none">' . "\n";
-		// print bars
-		$positionHorizontal = 0;
-		foreach($barcodeData['bars'] as $bar) {
-			$barWidth = round(($bar['width'] * $widthFactor), 3);
-			$barHeight = round(($bar['height'] * $totalHeight / $barcodeData['maxHeight']), 3);
-			if ($bar['drawBar']) {
-				$positionVertical = round(($bar['positionVertical'] * $totalHeight / $barcodeData['maxHeight']), 3);
-				// draw a vertical bar
-				$svg .= "\t\t" . '<rect x="' . $positionHorizontal . '" y="' . $positionVertical . '" width="' . $barWidth . '" height="' . $barHeight . '" />' . "\n";
-			}
-			$positionHorizontal += $barWidth;
-		}
-		$svg .= "\t" . '</g>' . "\n";
-		$svg .= '</svg>' . "\n";
-
-		return $svg;
-	}
-}

+ 0 - 7
src/Picqer/Barcode/Exceptions/BarcodeException.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Picqer\Barcode\Exceptions;
-
-class BarcodeException extends \Exception
-{
-}

+ 0 - 7
src/Picqer/Barcode/Exceptions/InvalidCharacterException.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Picqer\Barcode\Exceptions;
-
-class InvalidCharacterException extends BarcodeException
-{
-}

+ 0 - 7
src/Picqer/Barcode/Exceptions/InvalidCheckDigitException.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Picqer\Barcode\Exceptions;
-
-class InvalidCheckDigitException extends BarcodeException
-{
-}

+ 0 - 7
src/Picqer/Barcode/Exceptions/InvalidFormatException.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Picqer\Barcode\Exceptions;
-
-class InvalidFormatException extends BarcodeException
-{
-}

+ 0 - 7
src/Picqer/Barcode/Exceptions/InvalidLengthException.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Picqer\Barcode\Exceptions;
-
-class InvalidLengthException extends BarcodeException
-{
-}

+ 0 - 7
src/Picqer/Barcode/Exceptions/UnknownTypeException.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Picqer\Barcode\Exceptions;
-
-class UnknownTypeException extends BarcodeException
-{
-}

+ 0 - 99
ui/admin/assets/js/ModalEslDetails.js

@@ -1,99 +0,0 @@
-import {AjaxReq} from "./OMGUtils";
-
-// Private Methods
-function clearStatusModalFields() {
-	$('#esl_rssi_list').html('');
-	$('#svgtpl').html('');
-
-	$('#cardbox_esl_status span').each((i, o) => {
-		$('#cardbox_esl_status #' + o.id).html('');
-	});
-}
-
-function fillESLDetails(ESLstatus) {
-	clearStatusModalFields();
-
-	$('#cardbox_esl_status span').each((i, o) => {
-		let value = (ESLstatus.value.status[o.id]);
-		$('#cardbox_esl_status #' + o.id).html(value);
-	});
-
-	for (let [ap, rssi] of Object.entries(ESLstatus.value.rssi)) {
-		// language=HTML
-		let ap_rssi = `
-<tr>
-	<td>
-		<span class="badge badge-primary">${ap}</span>
-	</td>
-	<td>
-		<span class="badge text-inverse badge-warning">${rssi}</span>
-	</td>
-</tr>
-`;
-		$('#esl_rssi_list').append(ap_rssi);
-	}
-
-	if (ESLstatus.svgPath != null) {
-		// Retrieve SVG from server
-		$.get(BASEURL + '/' + ESLstatus.svgPath, data => {
-			$('#svgtpl').html(data);
-		}, "text");
-	}
-}
-
-// MAIN CLASS
-export class ModalEslDetails {
-	constructor(modalClass) {
-		let self = this;
-
-		this.abort = null;
-		this.modalClass = modalClass;
-		this.timer = null;
-
-
-		$(modalClass).on('show.bs.modal', function () {
-			let labelID = $(`${modalClass} #labelID`).html();
-			self.RefreshESLStatus(labelID);
-		});
-
-		$(modalClass).on('hide.bs.modal', function () {
-			self.abort();
-			clearTimeout(self.timer);
-		});
-	}
-
-	OpenESLDetails(labelID) {
-
-		clearStatusModalFields();
-
-		$(`${this.modalClass} #labelID`).html(labelID);
-		$(`${this.modalClass}`).modal('show');
-	}
-
-	RefreshESLStatus(labelID) {
-		let self = this;
-
-		$('#spinnerloader').removeClass('d-none');
-
-		let req = AjaxReq(`${BASEURL}/admin/config`, {
-			method: 'POST',
-			body: JSON.stringify({"method": "geteslstatus", "label_id": labelID})
-		});
-
-		req.fetchPromise()
-			.then(json => {
-				fillESLDetails(json);
-				toastr.success(json.err);
-				if ($(this.modalClass).is(':visible'))
-					self.timer = setTimeout(function () {
-						self.RefreshESLStatus(labelID);
-					}, 3000);
-			})
-			.catch(err => console.log)
-			.finally(function () {
-				$('#spinnerloader').addClass('d-none');
-			});
-
-		this.abort = req.abort;
-	}
-}

+ 0 - 100
ui/admin/assets/js/ModalTemplateSelector.js

@@ -1,100 +0,0 @@
-import {AjaxReq} from "./OMGUtils";
-
-// PRIVATE METHODS
-const COL_ID_IDX = 1;
-const COL_RESOLUTION_IDX = 7;
-
-// MAIN CLASS
-export class ModalTemplateSelector {
-	constructor(modalClass) {
-		let self = this;
-
-		this.modalClass = modalClass;
-		this.timer = null;
-		this.AbortLoadTemplate = null;
-
-		$(modalClass).on('hide.bs.modal', function () {
-			self.AbortLoadTemplate();
-			self.ClearModal();
-		});
-	}
-
-	ClearModal() {
-		$(`${this.modalClass} .modal-body`).html('Loading Templates...');
-	}
-
-	/**
-	 * Change display of ESL labels
-	 *
-	 * @param arrLabels
-	 * @param idTemplate
-	 */
-	setTemplate(arrLabels, idTemplate) {
-		AjaxReq(`${BASEURL}/admin/config`, {
-			method: 'POST',
-			body: JSON.stringify({
-				"method": "SetDisplay",
-				"labels": arrLabels,
-				"id": idTemplate
-			})
-		}).fetchPromise()
-			.then(json => {
-				$(`${this.modalClass}`).modal('hide');
-				toastr.success(json.err);
-			})
-			.catch(err => toastr.error(err.url, `${err.status} (${err.statusText})`));
-	}
-
-	Open(clearCache) {
-		let self = this;
-
-		let selectedRows = $('#datatable').DataTable().rows({selected: true}).data().toArray();
-		let selectedLabels = selectedRows.map(v => v[COL_ID_IDX]);
-		let resolutions = selectedRows.map(v => v[COL_RESOLUTION_IDX]);
-
-		if (selectedRows.length === 0) {
-			toastr.warning('Nessuna label selezionata');
-			return;
-		}
-
-		$(`${this.modalClass}`).modal('show');
-
-		$('#spinnerloader').removeClass('d-none');
-
-		let req = AjaxReq(`${BASEURL}/admin/config`, {
-			method: 'POST',
-			body: JSON.stringify({
-				"method": 'TemplateSelector',
-				"clearCache": clearCache,
-				"resolution": resolutions
-			})
-		});
-
-		this.AbortLoadTemplate = req.abort;
-
-		req.fetchPromise()
-			.then(json => {
-				if (json.err === 0) {
-					$(`${self.modalClass} .modal-body`).html(json.value);
-
-					// Add onlick event to all template thumbs
-					$(`${self.modalClass} a`).on('click', function () {
-						self.setTemplate(selectedLabels, this.id);
-					});
-
-				} else
-					toastr.error(json.value);
-			})
-			.catch(err => {
-				if (err.name === 'AbortError')
-					return;
-				if (err.url !== undefined)
-					toastr.error(err.url, `${err.status} (${err.statusText})`);
-				else
-					toastr.error(err);
-			})
-			.finally(function () {
-				$('#spinnerloader').addClass('d-none')
-			});
-	}
-}

+ 0 - 153
ui/admin/labelstatus.tpl

@@ -1,153 +0,0 @@
-<div class="col-xl-12">
-	<div class="card-box">
-		<div class="dropdown pull-right">
-			<a href="#" class="dropdown-toggle arrow-none card-drop" data-toggle="dropdown" aria-expanded="false">
-				<i class="mdi mdi-dots-vertical"></i>
-			</a>
-			<div class="dropdown-menu dropdown-menu-right">
-				<!-- item-->
-				<a href="javascript:ToggleSelectAll('#datatable', true);" class="dropdown-item">Select All</a>
-				<a href="javascript:ToggleSelectAll('#datatable', false);" class="dropdown-item">Deselect All</a>
-				<!-- item-->
-				<div class="dropdown-divider"></div>
-				<!-- item-->
-				<a href="javascript:ESLMultiOperations('resetfailcounter');" class="dropdown-item">Reset Fail Counter</a>
-				<!-- item-->
-				<a href="javascript:ESLMultiOperations('blinklabels')" class="dropdown-item">Blink</a>
-				<!-- item-->
-				<a href="javascript:ESLMultiOperations('unbind');" class="dropdown-item">Unbind</a>
-				<!-- item-->
-				<div class="dropdown-divider"></div>
-				<!-- item-->
-
-				<a href="javascript:TemplateSelector(false);" class="dropdown-item">Change Template</a>
-				<a href="javascript:TemplateSelector(true);" class="dropdown-item">Change Template (reload)</a>
-			</div>
-		</div>
-
-		<h4 class="header-title mt-0 m-b-30">ESL Status</h4>
-
-		<div class="table-responsive-sm">
-			<table id="datatable" class="table table-sm dt-responsive responsive table-bordered mb-0 table-striped">
-				<thead>
-				<tr>
-					<th></th>
-					<th>Label ID</th>
-					<th>Bat Level</th>
-					<th>Status</th>
-					<th>Template</th>
-					<th>Device Name</th>
-					<th>DT type</th>
-					<th>Display Resolution</th>
-					<th>Leds Count</th>
-					<th>rssi</th>
-					<th class="text-center">OPS</th>
-				</tr>
-				</thead>
-				<tbody>
-				<repeat group="{{@ESL_STATUS}}" key="{{@LABEL_ID}}" value="{{@STATUS}}">
-
-					<tr>
-						<td class="text-center"></td>
-						<td class="text-white font-weight-bold">{{@LABEL_ID}}</td>
-						<td data-filter="{{@STATUS['status']['bat_level']}}" class="text-center"><i class="text-warning fa fa-battery-{{@BATLEV_ICONS[@STATUS['status']['bat_level']]}}"></i></td>
-						<td class="text-center"><span class="badge badge-{{@STATUS_BADGE_COLOR[@STATUS['data']['esl_state']]??'primary'}}">{{@STATUS['data']['esl_state']}}</span></td>
-						<td>[{{@STATUS['data']['template_id']}}] {{@STATUS['data']['template_name']}}</td>
-						<td>{{@STATUS['data']['devicename']}}</td>
-						<td>{{@STATUS['data']['devtype_id']}}</td>
-						<td>{{@STATUS['data']['resolution']}}</td>
-						<td>{{@STATUS['data']['led']}}</td>
-						<td><span class="text-danger">{{@STATUS['status']['rssi']}}</span></td>
-						<td class="text-center">
-							<button onclick="OpenESLDetails('{{@LABEL_ID}}')" class="btn btn-sm btn-primary">Details</button>
-						</td>
-					</tr>
-
-				</repeat>
-				</tbody>
-			</table>
-		</div>
-	</div>
-</div>
-
-{* Modal Status *}
-<div class="modal fade statusmodal" tabindex="-1" role="dialog" aria-labelledby="statusmodal" aria-hidden="true" style="display: none;">
-	<div class="modal-dialog modal-dialog-centered">
-		<div class="modal-content">
-			<div class="modal-header">
-				<h4 class="modal-title mt-0" id="statusmodal">Status label <span id="labelID"></span></h4>
-				<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-			</div>
-			<div class="modal-body">
-				<div class="row">
-
-					<div class="col-6">
-						<div class="card" id="status_values">
-							<h5 class="card-header">RSSI <span class="badge badge-warning text-inverse" id="esl_rssi"></span></h5>
-							<div class="card-body">
-								<table id="esl_rssi_list" class="table table-borderless table-sm">
-								</table>
-							</div>
-						</div>
-					</div>
-
-					<div class="col-6">
-						<div class="card">
-							<h5 class="card-header">Template</h5>
-							<div id="svgtpl" class="card-body"></div>
-						</div>
-					</div>
-
-				</div>
-				<div class="row">
-
-					<div class="col-6 mt-2">
-						<div class="card">
-							<h5 class="card-header">Label info</h5>
-							<div class="card-body">
-								<table class="table table-borderless table-sm table" id="cardbox_esl_status">
-									<tr>
-										<td>Hello period</td>
-										<td><span class="badge badge-success" id="hello_period"></span></td>
-									</tr>
-									<tr>
-										<td>Blink count</td>
-										<td><span class="badge badge-success" id="blink_count"></span></td>
-									</tr>
-									<tr>
-										<td>Hardware Version</td>
-										<td>
-											<div class="badge badge-success"><span id="hard_major"></span>.<span id="hard_minor"></span>.<span id="hard_patch"></span></div>
-										</td>
-									</tr>
-									<tr>
-										<td>Software Version</td>
-										<td>
-											<div class="badge badge-success"><span id="soft_major"></span>.<span id="soft_minor"></span>.<span id="soft_patch"></span></div>
-										</td>
-									</tr>
-								</table>
-							</div>
-						</div>
-					</div>
-
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
-
-{* Modal Template Selector*}
-<div class="modal fade templateselector" tabindex="-1" role="dialog" aria-labelledby="templateselector" aria-hidden="true" style="display: none;">
-	<div class="modal-dialog modal-full modal-dialog-centered">
-		<div class="modal-content">
-
-			<div class="modal-header">
-				<h4 class="modal-title mt-0" id="templateselector">Template Selector</h4>
-				<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-			</div>
-
-			<div class="modal-body"></div>
-		</div>
-	</div>
-</div>

+ 0 - 13
ui/admin/templates.tpl

@@ -1,13 +0,0 @@
-<div class="d-flex flex-wrap justify-content-center">
-
-	<repeat group="{{@TPLOBJ}}" value="{{@TPL}}">
-
-		<div class="card-box text-white bg-secondary m-1">
-
-			<h4 class="header-title mt-0 m-b-30">[{{@TPL["id"]}}] "{{@TPL["title"]}}" ({{@TPL["resolution"]}})</h4>
-			<a id="{{@TPL['name']}}" href="#">{{@TPL["svg"]|raw}}</a>
-		</div>
-
-	</repeat>
-
-</div>