diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml new file mode 100644 index 00000000..10c46f9a --- /dev/null +++ b/.idea/dataSources.local.xml @@ -0,0 +1,10 @@ + + + + + root + eve_phoebe.* pathfinder.* + eve_phoebe.* pathfinder.* + + + \ No newline at end of file diff --git a/.idea/dictionaries/exodus4d.xml b/.idea/dictionaries/exodus4d.xml index d55b1265..d41eb6ec 100644 --- a/.idea/dictionaries/exodus4d.xml +++ b/.idea/dictionaries/exodus4d.xml @@ -8,6 +8,7 @@ contextmenu cronjob crosshairs + curviness cytaclysmic dashstyle datacache diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml index b38f8e97..5a1a766a 100644 --- a/.idea/jsLibraryMappings.xml +++ b/.idea/jsLibraryMappings.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/.idea/pathfinder.iml b/.idea/pathfinder.iml index 5a3ea793..9caffe92 100644 --- a/.idea/pathfinder.iml +++ b/.idea/pathfinder.iml @@ -14,9 +14,11 @@ + + diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml index 746abdc9..37585f35 100644 --- a/.idea/watcherTasks.xml +++ b/.idea/watcherTasks.xml @@ -1,6 +1,6 @@ - + diff --git a/.idea/webResources.xml b/.idea/webResources.xml new file mode 100644 index 00000000..785fb6e1 --- /dev/null +++ b/.idea/webResources.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 90b2534e..5715d006 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,14 +5,19 @@ - + - - - - + + + + + + + + + @@ -33,6 +38,9 @@ + + + @@ -149,65 +157,103 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + + - - + + + + + + + + + + + + + + + - - - + + - + + - - - + + + + + + - + + - - - + + + + + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + - + - + - - + + - + - - - - - - - - - - - - + + @@ -283,8 +319,8 @@ - - + + @@ -294,17 +330,7 @@ - - - - - - - - - - - + @@ -313,38 +339,97 @@ - - + + - - + + - + - - + + - + - - + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -353,14 +438,14 @@ - + - - + + @@ -370,17 +455,72 @@ - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -388,147 +528,43 @@ - - - - - - - - - - - - + + - + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -538,11 +574,11 @@ - - + + - - + + @@ -550,97 +586,97 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - + + - - + + - - - - - - - - - - - - + + - + @@ -668,57 +704,57 @@ @@ -736,12 +772,12 @@ - + - @@ -750,13 +786,45 @@ + + CSS + General + + GeneralJavaScript + + + GeneralPHP + + + HTML + + + JavaScript + + + Node.jsJavaScript + + + PHP + + + SQL + + + UndefinedPHP + XPath + + + Blade files + + @@ -804,20 +872,6 @@ @@ -1420,7 +1334,7 @@ - + @@ -1428,21 +1342,23 @@ + + - - - - - + + + + + + + + - - - @@ -1679,7 +1595,13 @@ @@ -1703,25 +1625,25 @@ - + + - + - + - - - - + + + + - @@ -1759,433 +1681,435 @@ - - - - + - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + - + - - - + + + + + - + - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - + - - + + + + + + + + + + + + + + - + - - + + - + - - - - - + + + - + - - - - - + + + - + - - - - - + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + diff --git a/app/config.cfg b/app/config.ini similarity index 95% rename from app/config.cfg rename to app/config.ini index b64c2f57..6b193ff9 100644 --- a/app/config.cfg +++ b/app/config.ini @@ -69,6 +69,16 @@ MAX_SHARED_USER = 10 MAX_SHARED_CORPORATION = 3 MAX_SHARED_ALLIANCE = 2 +; Lifetime for map types +[PATHFINDER.MAP.PRIVATE] +LIFETIME = 2 + +[PATHFINDER.MAP.CORPORATION] +LIFETIME = 99999 + +[PATHFINDER.MAP.ALLIANCE] +ALLIANCE = 99999 + ; ====================================================================================================== [PATHFINDER.TIMER] diff --git a/app/cron.ini b/app/cron.ini new file mode 100644 index 00000000..c3962710 --- /dev/null +++ b/app/cron.ini @@ -0,0 +1,24 @@ +[CRON] +log = TRUE +cli = TRUE +web = FALSE + +[CRON.presets] +; run every minute +instant = * * * * * + +; run in downtime 11:00 GMT/UTC +downtime = 0 11 * * * + +[CRON.jobs] +; import system data (jump, kill,..) from CCP API +importSystemData = Cron\CcpSystemsUpdate->importSystemData, @hourly + +; disable outdated maps +deactivateMapData = Cron\MapUpdate->deactivateMapData, @hourly + +; delete disabled maps +deleteMapData = Cron\MapUpdate->deleteMapData, @downtime + +; delete character log data +deleteLogData = Cron\CharacterUpdate->deleteLogData, @downtime \ No newline at end of file diff --git a/app/lib/CHANGELOG b/app/lib/CHANGELOG new file mode 100644 index 00000000..d7179d10 --- /dev/null +++ b/app/lib/CHANGELOG @@ -0,0 +1,577 @@ +CHANGELOG + +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 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 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 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 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 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: and 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 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 diff --git a/app/lib/COPYING b/app/lib/COPYING new file mode 100644 index 00000000..3c7236c8 --- /dev/null +++ b/app/lib/COPYING @@ -0,0 +1,621 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +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 diff --git a/app/lib/audit.php b/app/lib/audit.php index e2d334ae..1338ca8c 100644 --- a/app/lib/audit.php +++ b/app/lib/audit.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -19,7 +25,7 @@ class Audit extends Prefab { //@{ User agents const - UA_Mobile='android|blackberry|iphone|ipod|palm|windows\s+ce', + 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'; //@} @@ -98,28 +104,34 @@ class Audit extends Prefab { /** * Return TRUE if user agent is a desktop browser * @return bool + * @param $agent string **/ - function isdesktop() { - $agent=Base::instance()->get('AGENT'); + function isdesktop($agent=NULL) { + if (!isset($agent)) + $agent=Base::instance()->get('AGENT'); return (bool)preg_match('/('.self::UA_Desktop.')/i',$agent) && - !$this->ismobile(); + !$this->ismobile($agent); } /** * Return TRUE if user agent is a mobile device * @return bool + * @param $agent string **/ - function ismobile() { - $agent=Base::instance()->get('AGENT'); + function ismobile($agent=NULL) { + if (!isset($agent)) + $agent=Base::instance()->get('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=Base::instance()->get('AGENT'); + function isbot($agent=NULL) { + if (!isset($agent)) + $agent=Base::instance()->get('AGENT'); return (bool)preg_match('/('.self::UA_Bot.')/i',$agent); } diff --git a/app/lib/auth.php b/app/lib/auth.php index adc26e0f..5f7e0508 100644 --- a/app/lib/auth.php +++ b/app/lib/auth.php @@ -10,10 +10,17 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ + //! Authorization/authentication plug-in class Auth { @@ -122,7 +129,7 @@ class Auth { @ldap_close($dc)) { return $info[0]['uid'][0]==$id; } - user_error(self::E_LDAP); + user_error(self::E_LDAP,E_USER_ERROR); } /** @@ -169,7 +176,7 @@ class Auth { fclose($socket); return (bool)preg_match('/^235 /',$reply); } - user_error(self::E_SMTP); + user_error(self::E_SMTP,E_USER_ERROR); } /** diff --git a/app/lib/base.php b/app/lib/base.php index 330b202c..0855f9d1 100644 --- a/app/lib/base.php +++ b/app/lib/base.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -39,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess { //@{ Framework details const PACKAGE='Fat-Free Framework', - VERSION='3.4.0-Release'; + VERSION='3.5.0-Release'; //@} //@{ HTTP status codes (RFC 2616) @@ -158,12 +164,16 @@ final class Base extends Prefab implements ArrayAccess { } else { $i=0; - $url=preg_replace_callback('/@(\w+)|\*/',function($m)use(&$i,$params){ - $i++; - if (isset($m[1]) && array_key_exists($m[1],$params)) - return $params[$m[1]]; - return array_key_exists($i,$params)?$params[$i]:$m[0]; - },$url); + $url=preg_replace_callback('/@(\w+)|\*/', + function($match) use(&$i,$params) { + $i++; + if (isset($match[1]) && + array_key_exists($match[1],$params)) + return $params[$match[1]]; + return array_key_exists($i,$params)? + $params[$i]: + $match[0]; + },$url); } return $url; } @@ -172,12 +182,13 @@ final class Base extends Prefab implements ArrayAccess { * assemble url from alias name * @return NULL * @param $name string - * @param $params string + * @param $params array|string **/ - function alias($name,$params=null) { - $params=$params?$this->parse($params):array(); + function alias($name,$params=array()) { + if (!is_array($params)) + $params=$this->parse($params); if (empty($this->hive['ALIASES'][$name])) - user_error(sprintf(self::E_Named,$name)); + user_error(sprintf(self::E_Named,$name),E_USER_ERROR); $url=$this->build($this->hive['ALIASES'][$name],$params); return $url; } @@ -242,7 +253,8 @@ final class Base extends Prefab implements ArrayAccess { $this->sync('SESSION'); } elseif (!preg_match('/^\w+$/',$parts[0])) - user_error(sprintf(self::E_Hive,$this->stringify($key))); + user_error(sprintf(self::E_Hive,$this->stringify($key)), + E_USER_ERROR); if ($add) $var=&$this->hive; else @@ -518,7 +530,7 @@ final class Base extends Prefab implements ArrayAccess { **/ function push($key,$val) { $ref=&$this->ref($key); - array_push($ref,$val); + $ref[] = $val; return $val; } @@ -578,10 +590,11 @@ final class Base extends Prefab implements ArrayAccess { * Split comma-, semi-colon, or pipe-separated string * @return array * @param $str string + * @param $noempty bool **/ - function split($str) { + function split($str,$noempty=TRUE) { return array_map('trim', - preg_split('/[,;|]/',$str,0,PREG_SPLIT_NO_EMPTY)); + preg_split('/[,;|]/',$str,0,$noempty?PREG_SPLIT_NO_EMPTY:0)); } /** @@ -666,6 +679,24 @@ final class Base extends Prefab implements ArrayAccess { return $num?($num/abs($num)):0; } + /** + * Convert class constants to array + * @return array + * @param $class object|string + * @param $prefix string + **/ + function constants($class,$prefix='') { + $ref=new ReflectionClass($class); + $out=array(); + foreach (preg_grep('/^'.$prefix.'/',array_keys($ref->getconstants())) + as $val) { + $out[$key=substr($val,strlen($prefix))]= + constant((is_object($class)?get_class($class):$class).'::'.$prefix.$key); + } + unset($ref); + return $out; + } + /** * Generate 64bit/base36 hash * @return string @@ -692,7 +723,7 @@ final class Base extends Prefab implements ArrayAccess { * @param $str string **/ function encode($str) { - return @htmlentities($str,$this->hive['BITMASK'], + return @htmlspecialchars($str,$this->hive['BITMASK'], $this->hive['ENCODING'])?:$this->scrub($str); } @@ -702,8 +733,7 @@ final class Base extends Prefab implements ArrayAccess { * @param $str string **/ function decode($str) { - return html_entity_decode($str,$this->hive['BITMASK'], - $this->hive['ENCODING']); + return htmlspecialchars_decode($str,$this->hive['BITMASK']); } /** @@ -986,14 +1016,14 @@ final class Base extends Prefab implements ArrayAccess { } /** - * Send HTTP/1.1 status header; Return text equivalent of status code + * Send HTTP status header; Return text equivalent of status code * @return string * @param $code int **/ function status($code) { $reason=@constant('self::HTTP_'.$code); if (PHP_SAPI!='cli') - header('HTTP/1.1 '.$code.' '.$reason); + header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$reason); return $reason; } @@ -1014,12 +1044,6 @@ final class Base extends Prefab implements ArrayAccess { header('Expires: '.gmdate('r',$time+$secs)); header('Cache-Control: max-age='.$secs); header('Last-Modified: '.gmdate('r')); - $headers=$this->hive['HEADERS']; - if (isset($headers['If-Modified-Since']) && - strtotime($headers['If-Modified-Since'])+$secs>$time) { - $this->status(304); - die; - } } else header('Cache-Control: no-cache, no-store, must-revalidate'); @@ -1065,21 +1089,11 @@ final class Base extends Prefab implements ArrayAccess { } /** - * Log error; Execute ONERROR handler if defined, else display - * default error page (HTML for synchronous requests, JSON string - * for AJAX requests) - * @return NULL - * @param $code int - * @param $text string - * @param $trace array + * Return formatted stack trace + * @return string + * @param $trace array|NULL **/ - function error($code,$text='',array $trace=NULL) { - $prior=$this->hive['ERROR']; - $header=$this->status($code); - $req=$this->hive['VERB'].' '.$this->hive['PATH']; - if (!$text) - $text='HTTP '.$code.' ('.$req.')'; - error_log($text); + function trace(array $trace=NULL) { if (!$trace) { $trace=debug_backtrace(FALSE); $frame=$trace[0]; @@ -1097,8 +1111,6 @@ final class Base extends Prefab implements ArrayAccess { '__call|call_user_func)/',$frame['function'])); } ); - $highlight=PHP_SAPI!='cli' && - $this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS); $out=''; $eol="\n"; // Analyze stack trace @@ -1111,12 +1123,35 @@ final class Base extends Prefab implements ArrayAccess { ($debug>2 && isset($frame['args'])? $this->csv($frame['args']):'').')'; $src=$this->fixslashes(str_replace($_SERVER['DOCUMENT_ROOT']. - '/','',$frame['file'])).':'.$frame['line'].' '; - error_log('- '.$src.$line); - $out.='• '.($highlight? - ($this->highlight($src).' '.$this->highlight($line)): - ($src.$line)).$eol; + '/','',$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) + * @return NULL + * @param $code int + * @param $text string + * @param $trace array + **/ + function error($code,$text='',array $trace=NULL) { + $prior=$this->hive['ERROR']; + $header=$this->status($code); + $req=$this->hive['VERB'].' '.$this->hive['PATH']; + if (!$text) + $text='HTTP '.$code.' ('.$req.')'; + error_log($text); + $trace=$this->trace($trace); + foreach (explode("\n",$trace) as $nexus) + if ($nexus) + error_log($nexus); + if ($highlight=PHP_SAPI!='cli' && !$this->hive['AJAX'] && + $this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS)) + $trace=$this->highlight($trace); $this->hive['ERROR']=array( 'status'=>$header, 'code'=>$code, @@ -1125,8 +1160,9 @@ final class Base extends Prefab implements ArrayAccess { ); $handler=$this->hive['ONERROR']; $this->hive['ONERROR']=NULL; + $eol="\n"; if ((!$handler || - $this->call($handler,array($this,$this->get('PARAMS')), + $this->call($handler,array($this,$this->hive['PARAMS']), 'beforeroute,afterroute')===FALSE) && !$prior && PHP_SAPI!='cli' && !$this->hive['QUIET']) echo $this->hive['AJAX']? @@ -1141,7 +1177,7 @@ final class Base extends Prefab implements ArrayAccess { ''.$eol. '

'.$header.'

'.$eol. '

'.$this->encode($text?:$req).'

'.$eol. - ($debug?('
'.$out.'
'.$eol):''). + ($this->hive['DEBUG']?('
'.$trace.'
'.$eol):''). ''.$eol. ''); if ($this->hive['HALT']) @@ -1166,13 +1202,13 @@ final class Base extends Prefab implements ArrayAccess { $verb=strtoupper($parts[1]); if ($parts[2]) { if (empty($this->hive['ALIASES'][$parts[2]])) - user_error(sprintf(self::E_Named,$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]):array()); } if (empty($parts[4])) - user_error(sprintf(self::E_Pattern,$pattern)); + 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)) @@ -1215,11 +1251,11 @@ final class Base extends Prefab implements ArrayAccess { $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])); + 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)); + user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR); $type=empty($parts[5])? self::REQ_SYNC|self::REQ_AJAX: constant('self::REQ_'.strtoupper($parts[5])); @@ -1237,24 +1273,22 @@ final class Base extends Prefab implements ArrayAccess { * @param $url string * @param $permanent bool **/ - function reroute($url,$permanent=FALSE) { + function reroute($url=NULL,$permanent=FALSE) { + if (!$url) + $url=$this->hive['REALM']; + if (preg_match('/^(?:@(\w+)(?:(\(.+?)\))*)/',$url,$parts)) { + if (empty($this->hive['ALIASES'][$parts[1]])) + user_error(sprintf(self::E_Named,$parts[1]),E_USER_ERROR); + $url=$this->hive['ALIASES'][$parts[1]]; + } + $url=$this->build($url, + isset($parts[2])?$this->parse($parts[2]):array()); if (($handler=$this->hive['ONREROUTE']) && $this->call($handler,array($url,$permanent))!==FALSE) return; + if ($url[0]=='/') + $url=$this->hive['BASE'].$url; if (PHP_SAPI!='cli') { - if (preg_match('/^(?:@(\w+)(?:(\(.+?)\))*|https?:\/\/)/', - $url,$parts)) { - if (isset($parts[1])) { - if (empty($this->hive['ALIASES'][$parts[1]])) - user_error(sprintf(self::E_Named,$parts[1])); - $url=$this->hive['BASE']. - $this->hive['ALIASES'][$parts[1]]; - $url=$this->build($url, - isset($parts[2])?$this->parse($parts[2]):array()); - } - } - else - $url=$this->hive['BASE'].$url; header('Location: '.$url); $this->status($permanent?301:302); die; @@ -1266,7 +1300,7 @@ final class Base extends Prefab implements ArrayAccess { * Provide ReST interface by mapping HTTP verb to class method * @return NULL * @param $url string - * @param $class string + * @param $class string|object * @param $ttl int * @param $kbps int **/ @@ -1277,8 +1311,10 @@ final class Base extends Prefab implements ArrayAccess { return; } foreach (explode('|',self::VERBS) as $method) - $this->route($method.' '.$url, - $class.'->'.strtolower($method),$ttl,$kbps); + $this->route($method.' '.$url,is_string($class)? + $class.'->'.$this->hive['PREMAP'].strtolower($method): + array($class,$this->hive['PREMAP'].strtolower($method)), + $ttl,$kbps); } /** @@ -1286,15 +1322,16 @@ final class Base extends Prefab implements ArrayAccess { * @return NULL * @param $pattern string|array * @param $url string + * @param $permanent bool */ - function redirect($pattern,$url) { + function redirect($pattern,$url,$permanent=TRUE) { if (is_array($pattern)) { foreach ($pattern as $item) - $this->redirect($item,$url); + $this->redirect($item,$url,$permanent); return; } - $this->route($pattern,function($this) use($url) { - $this->reroute($url); + $this->route($pattern,function($fw) use($url,$permanent) { + $fw->reroute($url,$permanent); }); } @@ -1321,6 +1358,23 @@ final class Base extends Prefab implements ArrayAccess { 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':''; + preg_match('/^'. + preg_replace('/@(\w+\b)/','(?P<\1>[^\/\?]+)', + str_replace('\*','([^\?]+)',preg_quote($pattern,'/'))). + '\/?(?:\?.*)?$/'.$case.'um',$url,$args); + return $args; + } + /** * Match routes against incoming URI * @return mixed @@ -1331,31 +1385,32 @@ final class Base extends Prefab implements ArrayAccess { $this->error(403); if (!$this->hive['ROUTES']) // No routes defined - user_error(self::E_Routes); + user_error(self::E_Routes,E_USER_ERROR); // Match specific routes first $paths=array(); foreach ($keys=array_keys($this->hive['ROUTES']) as $key) - $paths[]=str_replace('@',"\x00".'@',$key); + $paths[]=str_replace('@','*@',$key); $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=preg_replace( - '/^'.preg_quote($this->hive['BASE'],'/').'(\/.*|$)/','\1', - $this->hive['URI'] - ); + $req=$this->rel($this->hive['URI']); + if ($cors=(isset($this->hive['HEADERS']['Origin']) && + $this->hive['CORS']['origin'])) { + $cors=$this->hive['CORS']; + header('Access-Control-Allow-Origin: '.$cors['origin']); + header('Access-Control-Allow-Credentials: '. + ($cors['credentials']?'true':'false')); + } $allowed=array(); - $case=$this->hive['CASELESS']?'i':''; - foreach ($this->hive['ROUTES'] as $url=>$routes) { - if (!preg_match('/^'. - preg_replace('/@(\w+\b)/','(?P<\1>[^\/\?]+)', - str_replace('\*','([^\?]*)',preg_quote($url,'/'))). - '\/?(?:\?.*)?$/'.$case.'um',$req,$args)) + foreach ($this->hive['ROUTES'] as $pattern=>$routes) { + if (!$args=$this->mask($pattern,$req)) continue; ksort($args); $route=NULL; - if (isset($routes[$this->hive['AJAX']+1])) - $route=$routes[$this->hive['AJAX']+1]; + if (isset( + $routes[$ptr=$this->hive['AJAX']+1][$this->hive['VERB']])) + $route=$routes[$ptr]; elseif (isset($routes[self::REQ_SYNC|self::REQ_AJAX])) $route=$routes[self::REQ_SYNC|self::REQ_AJAX]; if (!$route) @@ -1368,10 +1423,18 @@ final class Base extends Prefab implements ArrayAccess { $this->reroute(substr($parts['path'],0,-1). (isset($parts['query'])?('?'.$parts['query']):'')); list($handler,$ttl,$kbps,$alias)=$route[$this->hive['VERB']]; - if (is_bool(strpos($url,'/*'))) + if (is_bool(strpos($pattern,'/*'))) foreach (array_keys($args) as $key) if (is_numeric($key) && $key) unset($args[$key]); + // Capture values of route pattern tokens + $this->hive['PARAMS']=$args=array_map('urldecode',$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)/', @@ -1384,11 +1447,6 @@ final class Base extends Prefab implements ArrayAccess { !class_exists($match[1])) $this->error(404); } - // Capture values of route pattern tokens - $this->hive['PARAMS']=$args=array_map('urldecode',$args); - // Save matching route - $this->hive['ALIAS']=$alias; - $this->hive['PATTERN']=$url; // Process request $result=NULL; $body=''; @@ -1401,6 +1459,12 @@ final class Base extends Prefab implements ArrayAccess { $hash=$this->hash($this->hive['VERB'].' '. $this->hive['URI']).'.url',$data); if ($cached && $cached[0]+$ttl>$now) { + 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 (PHP_SAPI!='cli') @@ -1421,10 +1485,13 @@ final class Base extends Prefab implements ArrayAccess { $result=$this->call($handler,array($this,$args), 'beforeroute,afterroute'); $body=ob_get_clean(); - if (isset($cache) && !error_get_last()) + if (isset($cache) && !error_get_last()) { // Save to cache backend - $cache->set($hash, - array(headers_list(),$body,$result),$ttl); + $cache->set($hash,array( + // Remove cookies + preg_grep('/Set-Cookie\:/',headers_list(), + PREG_GREP_INVERT),$body,$result),$ttl); + } } $this->hive['RESPONSE']=$body; if (!$this->hive['QUIET']) { @@ -1444,21 +1511,106 @@ final class Base extends Prefab implements ArrayAccess { } return $result; } - $allowed=array_keys($route); - break; + $allowed=array_merge($allowed,array_keys($route)); } if (!$allowed) // URL doesn't match any route $this->error(404); elseif (PHP_SAPI!='cli') { // Unhandled HTTP method - header('Allow: '.implode(',',$allowed)); + header('Allow: '.implode(',',array_unique($allowed))); + if ($cors) { + header('Access-Control-Allow-Methods: OPTIONS,'.implode(',',$allowed)); + if ($cors['headers']) + 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=array(); + $time=time(); + $limit=max(0,min($timeout,$max=ini_get('max_execution_time')-1)); + $out=''; + $flag=FALSE; + // Not for the weak of heart + while ( + // Still alive? + !connection_aborted() && + // Got time left? + (time()-$time+1<$limit) && + // Restart session + $flag=@session_start() && + // CAUTION: Callback will kill host if it never becomes truthy! + !($out=$this->call($func,$args))) { + session_commit(); + ob_flush(); + flush(); + // Hush down + sleep(1); + } + if ($flag) { + session_commit(); + ob_flush(); + flush(); + } + return $out; + } + + /** + * Disconnect HTTP client + **/ + function abort() { + @session_start(); + session_commit(); + header('Content-Length: 0'); + while (ob_get_level()) + ob_end_clean(); + 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'); + else { + $ref=new ReflectionClass($parts[1]); + $parts[1]=method_exists($parts[1],'__construct')? + $ref->newinstanceargs($args): + $ref->newinstance(); + } + } + $func=array($parts[1],$parts[3]); + } + return $func; + } + /** * Execute callback/hooks (supports 'class->method' format) * @return mixed|FALSE @@ -1469,25 +1621,17 @@ final class Base extends Prefab implements ArrayAccess { function call($func,$args=NULL,$hooks='') { if (!is_array($args)) $args=array($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_string($func) && - 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])); - if ($parts[2]=='->') - $parts[1]=is_subclass_of($parts[1],'Prefab')? - call_user_func($parts[1].'::instance'): - new $parts[1]($this); - $func=array($parts[1],$parts[3]); - } if (!is_callable($func)) // No route handler if ($hooks=='beforeroute,afterroute') { - $allowed=''; - if (isset($parts[1])) + $allowed=array(); + if (is_array($func)) $allowed=array_intersect( - array_map('strtoupper',get_class_methods($parts[1])), + array_map('strtoupper',get_class_methods($func[0])), explode('|',self::VERBS) ); header('Allow: '.implode(',',$allowed)); @@ -1495,7 +1639,8 @@ final class Base extends Prefab implements ArrayAccess { } else user_error(sprintf(self::E_Method, - is_string($func)?$func:$this->stringify($func))); + is_string($func)?$func:$this->stringify($func)), + E_USER_ERROR); $obj=FALSE; if (is_array($func)) { $hooks=$this->split($hooks); @@ -1546,65 +1691,83 @@ final class Base extends Prefab implements ArrayAccess { } /** - * Configure framework according to .ini-style file settings - * @return NULL + * Configure framework according to .ini-style file settings; + * If optional 2nd arg is provided, template strings are interpreted + * @return object * @param $file string + * @param $allow bool **/ - function config($file) { + function config($file,$allow=FALSE) { preg_match_all( '/(?<=^|\n)(?:'. '\[(?
.+?)\]|'. '(?[^\h\r\n;].*?)\h*=\h*'. '(?(?:\\\\\h*\r?\n|.+?)*)'. ')(?=\r?\n|$)/', - $this->read($file),$matches,PREG_SET_ORDER); + $this->read($file), + $matches,PREG_SET_ORDER); if ($matches) { $sec='globals'; foreach ($matches as $match) { - if ($match['section']) + if ($match['section']) { $sec=$match['section']; - elseif (in_array($sec,array('routes','maps','redirects'))) { - call_user_func_array( - array($this,rtrim($sec,'s')), - array_merge(array($match['lval']), - str_getcsv($match['rval']))); + if (preg_match('/^(?!(?:global|config|route|map|redirect)s\b)'. + '((?:\.?\w)+)/i',$sec,$msec) && !$this->exists($msec[0])) + $this->set($msec[0],NULL); } else { - $args=array_map( - function($val) { - if (is_numeric($val)) - return $val+0; - $val=ltrim($val); - if (preg_match('/^\w+$/i',$val) && defined($val)) - return constant($val); - return preg_replace( - array('/\\\\"/','/\\\\\h*(\r?\n)/'), - array('"','\1'),$val); - }, - // Mark quoted strings with 0x00 whitespace - str_getcsv(preg_replace('/(?[^:]+)(?:\:(?.+))?/', - $sec,$parts); - $func=isset($parts['func'])?$parts['func']:NULL; - $custom=($parts['section']!='globals'); - if ($func) - $args=array($this->call($func, - count($args)>1?array($args):$args)); - call_user_func_array( - array($this,'set'), - array_merge( - array( - ($custom?($parts['section'].'.'):''). - $match['lval'] - ), - count($args)>1?array($args):$args - ) - ); + if ($allow) { + $match['lval']=Preview::instance()-> + resolve($match['lval']); + $match['rval']=Preview::instance()-> + resolve($match['rval']); + } + if (preg_match('/^(config|route|map|redirect)s\b/i', + $sec,$cmd)) { + call_user_func_array( + array($this,$cmd[1]), + array_merge(array($match['lval']), + str_getcsv($match['rval']))); + } + else { + $args=array_map( + function($val) { + if (is_numeric($val)) + return $val+0; + $val=ltrim($val); + if (preg_match('/^\w+$/i',$val) && + defined($val)) + return constant($val); + return trim(preg_replace( + array('/\\\\"/','/\\\\\h*(\r?\n)/'), + array('"','\1'),$val)); + }, + // Mark quoted strings with 0x00 whitespace + str_getcsv(preg_replace('/(?[^:]+)(?:\:(?.+))?/', + $sec,$parts); + $func=isset($parts['func'])?$parts['func']:NULL; + $custom=(strtolower($parts['section'])!='globals'); + if ($func) + $args=array($this->call($func, + count($args)>1?array($args):$args)); + call_user_func_array( + array($this,'set'), + array_merge( + array( + ($custom?($parts['section'].'.'):''). + $match['lval'] + ), + count($args)>1?array($args):$args + ) + ); + } } } } + return $this; } /** @@ -1691,13 +1854,13 @@ final class Base extends Prefab implements ArrayAccess { } /** - * Return path relative to the base directory + * 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'],'/').'/','',rtrim($url,'/')); + return preg_replace('/^(?:https?:\/\/)?'. + preg_quote($this->hive['BASE'],'/').'(\/.*|$)/','\1',$url); } /** @@ -1838,7 +2001,7 @@ final class Base extends Prefab implements ArrayAccess { @ini_set('magic_quotes_gpc',0); @ini_set('register_globals',0); // Intercept errors/exceptions; PHP5.3-compatible - error_reporting((E_ALL|E_STRICT)&~E_NOTICE); + error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE)); $fw=$this; set_exception_handler( function($obj) use($fw) { @@ -1848,7 +2011,7 @@ final class Base extends Prefab implements ArrayAccess { ); set_error_handler( function($code,$text) use($fw) { - if (error_reporting()) + if ($code & error_reporting()) $fw->error(500,$text); } ); @@ -1918,6 +2081,12 @@ final class Base extends Prefab implements ArrayAccess { 'CACHE'=>FALSE, 'CASELESS'=>TRUE, 'CONFIG'=>NULL, + 'CORS'=>array( + 'headers'=>'', + 'origin'=>false, + 'credentials'=>false, + 'expose'=>false, + 'ttl'=>0), 'DEBUG'=>0, 'DIACRITICS'=>array(), 'DNSBL'=>'', @@ -1948,6 +2117,7 @@ final class Base extends Prefab implements ArrayAccess { 'PLUGINS'=>$this->fixslashes(__DIR__).'/', 'PORT'=>$port, 'PREFIX'=>NULL, + 'PREMAP'=>'', 'QUERY'=>isset($uri['query'])?$uri['query']:'', 'QUIET'=>FALSE, 'RAW'=>FALSE, @@ -2137,15 +2307,17 @@ class Cache extends Prefab { $parts=explode('=',$this->dsn,2); switch ($parts[0]) { case 'apc': - $key='info'; case 'apcu': - if (empty($key)) - $key='key'; $info=apc_cache_info('user'); - foreach ($info['cache_list'] as $item) - if (preg_match($regex,$item[$key]) && - $item['mtime']+$lifetimealias($key,$arg); - } - /** * Create sandbox for template execution * @return string @@ -2308,7 +2470,7 @@ class View extends Prefab { $this->level++; $fw=Base::instance(); $implicit=false; - if (!$hive) { + if ($hive === null) { $implicit=true; $hive=$fw->hive(); } @@ -2318,9 +2480,9 @@ class View extends Prefab { if (isset($hive['ALIASES'])) $hive['ALIASES']=$fw->build($hive['ALIASES']); } + unset($fw, $implicit); extract($hive); unset($hive); - unset($fw); ob_start(); require($this->view); $this->level--; @@ -2346,7 +2508,7 @@ class View extends Prefab { if (isset($_COOKIE[session_name()])) @session_start(); $fw->sync('SESSION'); - if ($mime && PHP_SAPI!='cli') + if ($mime && PHP_SAPI!='cli' && !headers_sent()) header('Content-Type: '.$mime.'; '. 'charset='.$fw->get('ENCODING')); $data=$this->sandbox($hive); @@ -2357,7 +2519,7 @@ class View extends Prefab { $cache->set($hash,$data); return $data; } - user_error(sprintf(Base::E_Open,$file)); + user_error(sprintf(Base::E_Open,$file),E_USER_ERROR); } /** @@ -2375,7 +2537,14 @@ class Preview extends View { protected //! MIME type - $mime; + $mime, + //! token filter + $filter = array( + 'esc'=>'$this->esc', + 'raw'=>'$this->raw', + 'alias'=>'\Base::instance()->alias', + 'format'=>'\Base::instance()->format' + ); /** * Convert token to variable @@ -2387,6 +2556,20 @@ class Preview extends View { Base::instance()->compile($str))); } + /** + * register token filter + * @param string $key + * @param string $func + * @return array + */ + function filter($key=NULL,$func=NULL) { + if (!$key) + return array_keys($this->filter); + if (!$func) + return $this->filter[$key]; + $this->filter[$key]=$func; + } + /** * Assemble markup * @return string @@ -2404,11 +2587,10 @@ class Preview extends View { $str,$parts)) { $str=$parts[1]; foreach (Base::instance()->split($parts[2]) as $func) - $str=(($func=='format')?'\Base::instance()':'$this'). - '->'.$func.'('.$str.')'; + $str=$self->filter($func).'('.$str.')'; } return ''. - (isset($expr[3])?$expr[3]:''); + (isset($expr[3])?$expr[3]."\n":''); }, preg_replace_callback( '/\{~(.+?)~\}/s', @@ -2469,7 +2651,7 @@ class Preview extends View { if (isset($_COOKIE[session_name()])) @session_start(); $fw->sync('SESSION'); - if ($mime && PHP_SAPI!='cli') + if ($mime && PHP_SAPI!='cli' && !headers_sent()) header('Content-Type: '.($this->mime=$mime).'; '. 'charset='.$fw->get('ENCODING')); $data=$this->sandbox($hive); @@ -2481,7 +2663,7 @@ class Preview extends View { return $data; } } - user_error(sprintf(Base::E_Open,$file)); + user_error(sprintf(Base::E_Open,$file),E_USER_ERROR); } } @@ -2829,29 +3011,12 @@ class ISO extends Prefab { LC_zh='Chinese'; //@} - /** - * Convert class constants to array - * @return array - * @param $prefix string - **/ - protected function constants($prefix) { - $ref=new ReflectionClass($this); - $out=array(); - foreach (preg_grep('/^'.$prefix.'/',array_keys($ref->getconstants())) - as $val) { - $out[$key=substr($val,strlen($prefix))]= - constant('static::'.$prefix.$key); - } - unset($ref); - return $out; - } - /** * Return list of languages indexed by ISO 639-1 language code * @return array **/ function languages() { - return $this->constants('LC_'); + return \Base::instance()->constants($this,'LC_'); } /** @@ -2859,7 +3024,7 @@ class ISO extends Prefab { * @return array **/ function countries() { - return $this->constants('CC_'); + return \Base::instance()->constants($this,'CC_'); } } diff --git a/app/lib/basket.php b/app/lib/basket.php index dd8a1004..7445e143 100644 --- a/app/lib/basket.php +++ b/app/lib/basket.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -59,7 +65,7 @@ class Basket extends Magic { return $this->id; if (array_key_exists($key,$this->item)) return $this->item[$key]; - user_error(sprintf(self::E_Field,$key)); + user_error(sprintf(self::E_Field,$key),E_USER_ERROR); return FALSE; } @@ -183,10 +189,12 @@ class Basket extends Magic { /** * Hydrate item using hive array variable * @return NULL - * @param $key string + * @param $var array|string **/ - function copyfrom($key) { - foreach (\Base::instance()->get($key) as $key=>$val) + function copyfrom($var) { + if (is_string($var)) + $var=\Base::instance()->get($var); + foreach ($var as $key=>$val) $this->item[$key]=$val; } diff --git a/app/lib/bcrypt.php b/app/lib/bcrypt.php index 709db1de..6ecd61e4 100644 --- a/app/lib/bcrypt.php +++ b/app/lib/bcrypt.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -36,11 +42,11 @@ class Bcrypt extends Prefab { **/ function hash($pw,$salt=NULL,$cost=self::COST) { if ($cost<4 || $cost>31) - user_error(self::E_CostArg); + user_error(self::E_CostArg,E_USER_ERROR); $len=22; if ($salt) { if (!preg_match('/^[[:alnum:]\.\/]{'.$len.',}$/',$salt)) - user_error(self::E_SaltArg); + user_error(self::E_SaltArg,E_USER_ERROR); } else { $raw=16; diff --git a/app/lib/cron.php b/app/lib/cron.php new file mode 100644 index 00000000..28a619b6 --- /dev/null +++ b/app/lib/cron.php @@ -0,0 +1,228 @@ +'0 0 1 1 *', + 'annually'=>'0 0 1 1 *', + 'monthly'=>'0 0 1 * *', + 'weekly'=>'0 0 * * 0', + 'daily'=>'0 0 * * *', + 'hourly'=>'0 * * * *', + ); + + /** + * Schedule a job + * @param string $job + * @param string $handler + * @param string $expr + */ + function set($job,$handler,$expr) { + if (!preg_match('/^[\w\-]+$/',$job)) + user_error(sprintf(self::E_Invalid,$job),E_USER_ERROR); + $this->jobs[$job]=array($handler,$expr); + } + + /** + * Define a schedule preset + * @param string $name + * @param string $expr + */ + function preset($name,$expr) { + $this->presets[$name]=$expr; + } + + /** + * Returns TRUE if the requested job is due at the given time + * @param string $job + * @param int $time + * @return bool + */ + function isDue($job,$time) { + if (!isset($this->jobs[$job]) || !$parts=$this->parseExpr($this->jobs[$job][1])) + return FALSE; + + foreach($this->parseTimestamp($time) as $i=>$k) + if (!in_array($k,$parts[$i])) + return FALSE; + return TRUE; + } + + /** + * Execute a job + * @param string $job + * @param bool $async + */ + function execute($job,$async=TRUE) { + if (!isset($this->jobs[$job])) + return; + $f3=\Base::instance(); + if (is_string($func=$this->jobs[$job][0])){ + $func=$f3->grab($func); + } + + if (!is_callable($func)) + return; + if ($async && $this->async) { + // PHP docs: If a program is started with this function, in order for it to continue running in the background, + // the output of the program must be redirected to a file or another output stream. + // Failing to do so will cause PHP to hang until the execution of the program ends. + $dir=''; + $file='index.php'; + if ($this->clipath) { + $dir=dirname($this->clipath); + $file=basename($this->clipath); + } + if (@$dir[0]!='/') + $dir=getcwd().'/'.$dir; + exec(sprintf('cd "%s";php %s /cron/%s > /dev/null 2>/dev/null &',$dir,$file,$job)); + } else { + $start=microtime(TRUE); + call_user_func_array($func,array($f3)); + if ($this->log) { + $log=new Log('cron.log'); + $log->write(sprintf(self::L_Execution,$job,microtime(TRUE)-$start)); + } + } + } + + /** + * Run scheduler, i.e executes all due jobs at a given time + * @param int $time + * @param bool $async + */ + function run($time=NULL,$async=TRUE) { + if (!isset($time)) + $time=time(); + foreach(array_keys($this->jobs) as $job) + if ($this->isDue($job,$time)) + $this->execute($job,$async); + } + + /** + * Route controller code + * @param \Base $f3 + * @param array $params + */ + function route($f3,$params) { + + if (PHP_SAPI=='cli'?!$this->cli:!$this->web) + $f3->error(404); + if (isset($params['job'])) + $this->execute($params['job'],FALSE); + else{ + // IMPORTANT! async does not work on Windows + // -> my development environment is Windows :(( + $async = FALSE; + $this->run(NULL, $async); + } + } + + /** + * Parse a timestamp + * @param int $time + * @return array + */ + function parseTimestamp($time) { + return array( + (int)date('i',$time),//minute + (int)date('H',$time),//hour + (int)date('d',$time),//day of month + (int)date('m',$time),//month + (int)date('w',$time),//day of week + ); + } + + /** + * Parse a cron expression + * @param string $expr + * @return array|FALSE + */ + function parseExpr($expr) { + $parts=array(); + if (preg_match('/^@(\w+)$/',$expr,$m)) { + if (!isset($this->presets[$m[1]])) + return FALSE; + $expr=$this->presets[$m[1]]; + } + $expr=preg_split('/\s+/',$expr,-1,PREG_SPLIT_NO_EMPTY); + $ranges=array( + 0=>59,//minute + 1=>23,//hour + 2=>31,//day of month + 3=>12,//month + 4=>6,//day of week + ); + foreach($ranges as $i=>$max) + if (isset($expr[$i]) && preg_match_all('/(?<=,|^)\h*(?:(\d+)(?:-(\d+))?|(\*))(?:\/(\d+))?\h*(?=,|$)/', + $expr[$i],$matches,PREG_SET_ORDER)) { + $parts[$i]=array(); + foreach($matches as $m) { + if (!$range=@range(@$m[3]?0:$m[1],@$m[3]?$max:(@$m[2]?:$m[1]),@$m[4]?:1)) + return FALSE;//step exceeds specified range + $parts[$i]=array_merge($parts[$i],$range); + } + } else + return FALSE; + return $parts; + } + + //! Read-only public properties + function __get($name) { + if (in_array($name,array('jobs','async','presets'))) + return $this->$name; + trigger_error(sprintf(self::E_Undefined,__CLASS__,$name)); + } + + //! Constructor + function __construct() { + $f3=\Base::instance(); + $config=(array)$f3->get('CRON'); + + foreach(array('log','cli','web') as $k) + if (isset($config[$k])) + $this->$k=(bool)$config[$k]; + foreach(array('clipath') as $k) + if (isset($config[$k])) + $this->$k=(string)$config[$k]; + if (isset($config['jobs'])) + foreach($config['jobs'] as $job=>$arr) { + $handler=array_shift($arr); + $this->set($job,$handler,implode(',',$arr)); + } + if (isset($config['presets'])) + foreach($config['presets'] as $name=>$expr) + $this->preset($name,is_array($expr)?implode(',',$expr):$expr); + if (function_exists('exec') && exec('php -r "echo 1+3;"')=='4') + $this->async=TRUE; + if ($this->cli || $this->web) + $f3->route(array('GET /cron','GET /cron/@job'),array($this,'route')); + } + +} \ No newline at end of file diff --git a/app/lib/db/cursor.php b/app/lib/db/cursor.php index 7ae1cae4..c218fa47 100644 --- a/app/lib/db/cursor.php +++ b/app/lib/db/cursor.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -83,10 +89,10 @@ abstract class Cursor extends \Magic implements \IteratorAggregate { /** * Hydrate mapper object using hive array variable * @return NULL - * @param $key string + * @param $var array|string * @param $func callback **/ - abstract function copyfrom($key,$func=NULL); + abstract function copyfrom($var,$func=NULL); /** * Populate hive array variable with mapper fields @@ -100,7 +106,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate { * Causes a fatal error in PHP 5.3.5if uncommented * return ArrayIterator **/ - //abstract function getiterator(); + abstract function getiterator(); /** @@ -113,12 +119,16 @@ abstract class Cursor extends \Magic implements \IteratorAggregate { /** * Return first record (mapper object) that matches criteria - * @return object|FALSE + * @return \DB\Cursor|FALSE * @param $filter string|array * @param $options array * @param $ttl int **/ function findone($filter=NULL,array $options=NULL,$ttl=0) { + if (!$options) + $options=array(); + // Override limit + $options['limit']=1; return ($data=$this->find($filter,$options,$ttl))?$data[0]:FALSE; } @@ -238,7 +248,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate { function erase() { $this->query=array_slice($this->query,0,$this->ptr,TRUE)+ array_slice($this->query,$this->ptr,NULL,TRUE); - $this->ptr=0; + $this->skip(0); } /** diff --git a/app/lib/db/jig.php b/app/lib/db/jig.php index 811e0b79..736735c4 100644 --- a/app/lib/db/jig.php +++ b/app/lib/db/jig.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -42,12 +48,13 @@ class Jig { * @return array * @param $file string **/ - function read($file) { - if (!$this->dir) - return isset($this->data[$file])?$this->data[$file]:array(); + function &read($file) { + if (!$this->dir || !is_file($dst=$this->dir.$file)) { + if (!isset($this->data[$file])) + $this->data[$file]=array(); + return $this->data[$file]; + } $fw=\Base::instance(); - if (!is_file($dst=$this->dir.$file)) - return array(); $raw=$fw->read($dst); switch ($this->format) { case self::FORMAT_JSON: @@ -57,7 +64,8 @@ class Jig { $data=$fw->unserialize($raw); break; } - return $data; + $this->data[$file] = $data; + return $this->data[$file]; } /** diff --git a/app/lib/db/jig/mapper.php b/app/lib/db/jig/mapper.php index 0808cfbe..e5b007c6 100644 --- a/app/lib/db/jig/mapper.php +++ b/app/lib/db/jig/mapper.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -66,7 +72,7 @@ class Mapper extends \DB\Cursor { return $this->id; if (array_key_exists($key,$this->document)) return $this->document[$key]; - user_error(sprintf(self::E_Field,$key)); + user_error(sprintf(self::E_Field,$key),E_USER_ERROR); } /** @@ -143,7 +149,7 @@ class Mapper extends \DB\Cursor { /** * Return records that match criteria - * @return array|FALSE + * @return \DB\JIG\Mapper[]|FALSE * @param $filter array * @param $options array * @param $ttl int @@ -314,15 +320,16 @@ class Mapper extends \DB\Cursor { $db=$this->db; $now=microtime(TRUE); while (($id=uniqid(NULL,TRUE)) && - ($data=$db->read($this->file)) && isset($data[$id]) && + ($data=&$db->read($this->file)) && isset($data[$id]) && !connection_aborted()) usleep(mt_rand(0,100)); $this->id=$id; - $data[$id]=$this->document; $pkey=array('_id'=>$this->id); - if (isset($this->trigger['beforeinsert'])) + if (isset($this->trigger['beforeinsert']) && \Base::instance()->call($this->trigger['beforeinsert'], - array($this,$pkey)); + array($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)); @@ -340,11 +347,12 @@ class Mapper extends \DB\Cursor { function update() { $db=$this->db; $now=microtime(TRUE); - $data=$db->read($this->file); - $data[$this->id]=$this->document; - if (isset($this->trigger['beforeupdate'])) + $data=&$db->read($this->file); + if (isset($this->trigger['beforeupdate']) && \Base::instance()->call($this->trigger['beforeupdate'], - array($this,array('_id'=>$this->id))); + array($this,array('_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)); @@ -362,7 +370,7 @@ class Mapper extends \DB\Cursor { function erase($filter=NULL) { $db=$this->db; $now=microtime(TRUE); - $data=$db->read($this->file); + $data=&$db->read($this->file); $pkey=array('_id'=>$this->id); if ($filter) { foreach ($this->find($filter,NULL,FALSE) as $mapper) @@ -373,13 +381,13 @@ class Mapper extends \DB\Cursor { elseif (isset($this->id)) { unset($data[$this->id]); parent::erase(); - $this->skip(0); } else return FALSE; - if (isset($this->trigger['beforeerase'])) + if (isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], - array($this,$pkey)); + array($this,$pkey))===FALSE) + return FALSE; $db->write($this->file,$data); if ($filter) { $args=isset($filter[1]) && is_array($filter[1])? @@ -414,11 +422,12 @@ class Mapper extends \DB\Cursor { /** * Hydrate mapper object using hive array variable * @return NULL - * @param $key string + * @param $var array|string * @param $func callback **/ - function copyfrom($key,$func=NULL) { - $var=\Base::instance()->get($key); + function copyfrom($var,$func=NULL) { + if (is_string($var)) + $var=\Base::instance()->get($var); if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) diff --git a/app/lib/db/jig/session.php b/app/lib/db/jig/session.php index 5be319c7..7f9a6ffa 100644 --- a/app/lib/db/jig/session.php +++ b/app/lib/db/jig/session.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -135,10 +141,11 @@ class Session extends Mapper { /** * Instantiate class * @param $db object - * @param $table string + * @param $file string + * @param $onsuspect callback **/ - function __construct(\DB\Jig $db,$table='sessions') { - parent::__construct($db,'sessions'); + function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL) { + parent::__construct($db,$file); session_set_save_handler( array($this,'open'), array($this,'close'), @@ -155,8 +162,12 @@ class Session extends Mapper { ($agent=$this->agent()) && (!isset($headers['User-Agent']) || $agent!=$headers['User-Agent'])) { - session_destroy(); - $fw->error(403); + if (isset($onsuspect)) + $fw->call($onsuspect,array($this)); + else { + session_destroy(); + $fw->error(403); + } } $csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. $fw->hash(mt_rand()); diff --git a/app/lib/db/mongo.php b/app/lib/db/mongo.php index 4dfef583..ffe772e0 100644 --- a/app/lib/db/mongo.php +++ b/app/lib/db/mongo.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/db/mongo/mapper.php b/app/lib/db/mongo/mapper.php index 82ba94c7..f4ef5170 100644 --- a/app/lib/db/mongo/mapper.php +++ b/app/lib/db/mongo/mapper.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -64,7 +70,7 @@ class Mapper extends \DB\Cursor { function &get($key) { if ($this->exists($key)) return $this->document[$key]; - user_error(sprintf(self::E_Field,$key)); + user_error(sprintf(self::E_Field,$key),E_USER_ERROR); } /** @@ -78,7 +84,7 @@ class Mapper extends \DB\Cursor { /** * Convert array to mapper object - * @return object + * @return \DB\Mongo\Mapper * @param $row array **/ protected function factory($row) { @@ -105,7 +111,7 @@ class Mapper extends \DB\Cursor { /** * Build query and execute - * @return array + * @return \DB\Mongo\Mapper[] * @param $fields string * @param $filter array * @param $options array @@ -171,7 +177,7 @@ class Mapper extends \DB\Cursor { /** * Return records that match criteria - * @return array + * @return \DB\Mongo\Mapper[] * @param $filter array * @param $options array * @param $ttl int @@ -200,7 +206,7 @@ class Mapper extends \DB\Cursor { if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify( array($filter))).'.mongo',$result)) || !$ttl || $cached[0]+$ttlcollection->count($filter); + $result=$this->collection->count($filter?:array()); if ($fw->get('CACHE') && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); @@ -228,9 +234,10 @@ class Mapper extends \DB\Cursor { function insert() { if (isset($this->document['_id'])) return $this->update(); - if (isset($this->trigger['beforeinsert'])) + if (isset($this->trigger['beforeinsert']) && \Base::instance()->call($this->trigger['beforeinsert'], - array($this,array('_id'=>$this->document['_id']))); + array($this,array('_id'=>$this->document['_id'])))===FALSE) + return $this->document; $this->collection->insert($this->document); $pkey=array('_id'=>$this->document['_id']); if (isset($this->trigger['afterinsert'])) @@ -246,9 +253,10 @@ class Mapper extends \DB\Cursor { **/ function update() { $pkey=array('_id'=>$this->document['_id']); - if (isset($this->trigger['beforeupdate'])) + if (isset($this->trigger['beforeupdate']) && \Base::instance()->call($this->trigger['beforeupdate'], - array($this,$pkey)); + array($this,$pkey))===FALSE) + return $this->document; $this->collection->update( $pkey,$this->document,array('upsert'=>TRUE)); if (isset($this->trigger['afterupdate'])) @@ -266,13 +274,13 @@ class Mapper extends \DB\Cursor { if ($filter) return $this->collection->remove($filter); $pkey=array('_id'=>$this->document['_id']); - if (isset($this->trigger['beforeerase'])) + if (isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], - array($this,$pkey)); + array($this,$pkey))===FALSE) + return FALSE; $result=$this->collection-> remove(array('_id'=>$this->document['_id'])); parent::erase(); - $this->skip(0); if (isset($this->trigger['aftererase'])) \Base::instance()->call($this->trigger['aftererase'], array($this,$pkey)); @@ -291,11 +299,12 @@ class Mapper extends \DB\Cursor { /** * Hydrate mapper object using hive array variable * @return NULL - * @param $key string + * @param $var array|string * @param $func callback **/ - function copyfrom($key,$func=NULL) { - $var=\Base::instance()->get($key); + function copyfrom($var,$func=NULL) { + if (is_string($var)) + $var=\Base::instance()->get($var); if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) diff --git a/app/lib/db/mongo/session.php b/app/lib/db/mongo/session.php index e547cb14..3d7e1d26 100644 --- a/app/lib/db/mongo/session.php +++ b/app/lib/db/mongo/session.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -74,12 +80,6 @@ class Session extends Mapper { isset($headers['User-Agent'])?$headers['User-Agent']:''); $this->set('stamp',time()); $this->save(); - if (!$sent) { - if (isset($_COOKIE['_'])) - setcookie('_','',strtotime('-1 year')); - call_user_func_array('setcookie', - array('_',$csrf)+$fw->get('JAR')); - } return TRUE; } @@ -142,8 +142,9 @@ class Session extends Mapper { * Instantiate class * @param $db object * @param $table string + * @param $onsuspect callback **/ - function __construct(\DB\Mongo $db,$table='sessions') { + function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL) { parent::__construct($db,$table); session_set_save_handler( array($this,'open'), @@ -161,8 +162,12 @@ class Session extends Mapper { ($agent=$this->agent()) && (!isset($headers['User-Agent']) || $agent!=$headers['User-Agent'])) { - session_destroy(); - $fw->error(403); + if (isset($onsuspect)) + $fw->call($onsuspect,array($this)); + else { + session_destroy(); + $fw->error(403); + } } $csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. $fw->hash(mt_rand()); diff --git a/app/lib/db/sql.php b/app/lib/db/sql.php index efa43e41..f4734c3c 100644 --- a/app/lib/db/sql.php +++ b/app/lib/db/sql.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -19,6 +25,11 @@ namespace DB; //! PDO wrapper class SQL { + //@{ Error messages + const + E_PKey='Table %s does not have a primary key'; + //@} + protected //! UUID $uuid, @@ -151,6 +162,12 @@ class SQL { $keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key). '/'; } + if ($log) + $this->log.=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) { @@ -168,17 +185,23 @@ class SQL { $keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key). '/'; } + if ($log) + $this->log.=date('r').' ('. + sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. + preg_replace($keys,$vals, + str_replace('?',chr(0).'?',$cmd),1).PHP_EOL; $query->execute(); $error=$query->errorinfo(); if ($error[0]!=\PDO::ERR_NONE) { // Statement-level error occurred if ($this->trans) $this->rollback(); - user_error('PDOStatement: '.$error[2]); + user_error('PDOStatement: '.$error[2],E_USER_ERROR); } if (preg_match('/^\s*'. - '(?:CALL|EXPLAIN|SELECT|PRAGMA|SHOW|RETURNING|EXEC)\b/is', - $cmd)) { + '(?: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)) @@ -204,15 +227,9 @@ class SQL { // PDO-level error occurred if ($this->trans) $this->rollback(); - user_error('PDO: '.$error[2]); + user_error('PDO: '.$error[2],E_USER_ERROR); } } - if ($log) - $this->log.=date('r').' ('. - sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. - (empty($cached)?'':'[CACHED] '). - preg_replace($keys,$vals, - str_replace('?',chr(0).'?',$cmd),1).PHP_EOL; } if ($this->trans && $auto) $this->commit(); @@ -243,6 +260,8 @@ class SQL { * @param $ttl int **/ function schema($table,$fields=NULL,$ttl=0) { + if (strpos($table,'.')) + list($schema,$table)=explode('.',$table); // Supported engines $cmd=array( 'sqlite2?'=>array( @@ -310,11 +329,11 @@ class SQL { $rows[$row[$val[1]]]=array( 'type'=>$row[$val[2]], 'pdo_type'=> - preg_match('/int\b|int(?=eger)|bool/i', - $row[$val[2]],$parts)? - constant('\PDO::PARAM_'. - strtoupper($parts[0])): - \PDO::PARAM_STR, + preg_match('/int\b|integer/i',$row[$val[2]])? + \PDO::PARAM_INT: + (preg_match('/bool/i',$row[$val[2]])? + \PDO::PARAM_BOOL: + \PDO::PARAM_STR), 'default'=>is_string($row[$val[3]])? preg_replace('/^\s*([\'"])(.*)\1\s*/','\2', $row[$val[3]]):$row[$val[3]], @@ -324,6 +343,7 @@ class SQL { } return $rows; } + user_error(sprintf(self::E_PKey,$table),E_USER_ERROR); return FALSE; } @@ -351,7 +371,7 @@ class SQL { /** * Return parent object - * @return object + * @return \PDO **/ function pdo() { return $this->pdo; @@ -387,15 +407,18 @@ class SQL { * @param $key **/ function quotekey($key) { - if ($this->engine=='mysql') - $key="`".implode('`.`',explode('.',$key))."`"; - elseif (preg_match('/sybase|dblib/',$this->engine)) - $key="'".implode("'.'",explode('.',$key))."'"; - elseif (preg_match('/sqlite2?|pgsql|oci/',$this->engine)) - $key='"'.implode('"."',explode('.',$key)).'"'; - elseif (preg_match('/mssql|sqlsrv|odbc/',$this->engine)) - $key="[".implode('].[',explode('.',$key))."]"; - return $key; + $delims=array( + 'mysql'=>'``', + 'sqlite2?|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].implode($use[1].'.'.$use[0],explode('.',$key)).$use[1]; } /** @@ -418,7 +441,7 @@ class SQL { 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)=(.+?)(?=;|$)/i',$dsn,$parts)) + if (preg_match('/^.+?(?:dbname|database)=(.+?)(?=;|$)/is',$dsn,$parts)) $this->dbname=$parts[1]; if (!$options) $options=array(); diff --git a/app/lib/db/sql/mapper.php b/app/lib/db/sql/mapper.php index a308967a..3a13c65b 100644 --- a/app/lib/db/sql/mapper.php +++ b/app/lib/db/sql/mapper.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -19,11 +25,6 @@ namespace DB\SQL; //! SQL data mapper class Mapper extends \DB\Cursor { - //@{ Error messages - const - E_Adhoc='Unable to process ad hoc field %s'; - //@} - protected //! PDO wrapper $db, @@ -48,6 +49,14 @@ class Mapper extends \DB\Cursor { return 'SQL'; } + /** + * Return mapped table + * @return string + **/ + function table() { + return $this->source; + } + /** * Return TRUE if field is defined * @return bool @@ -57,6 +66,20 @@ class Mapper extends \DB\Cursor { return array_key_exists($key,$this->fields+$this->adhoc); } + /** + * 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; + } + /** * Assign value to field * @return scalar @@ -72,8 +95,12 @@ class Mapper extends \DB\Cursor { $this->fields[$key]['changed']=TRUE; return $this->fields[$key]['value']=$val; } - // Parenthesize expression in case it's a subquery - $this->adhoc[$key]=array('expr'=>'('.$val.')','value'=>NULL); + // adjust result on existing expressions + if (isset($this->adhoc[$key])) + $this->adhoc[$key]['value']=$val; + else + // Parenthesize expression in case it's a subquery + $this->adhoc[$key]=array('expr'=>'('.$val.')','value'=>NULL); return $val; } @@ -89,7 +116,7 @@ class Mapper extends \DB\Cursor { return $this->fields[$key]['value']; elseif (array_key_exists($key,$this->adhoc)) return $this->adhoc[$key]['value']; - user_error(sprintf(self::E_Field,$key)); + user_error(sprintf(self::E_Field,$key),E_USER_ERROR); } /** @@ -163,7 +190,7 @@ class Mapper extends \DB\Cursor { /** * Build query string and execute - * @return array + * @return \DB\SQL\Mapper[] * @param $fields string * @param $filter string|array * @param $options array @@ -179,11 +206,7 @@ class Mapper extends \DB\Cursor { 'offset'=>0 ); $db=$this->db; - $remap=''; - foreach (explode(',',$fields) as $field) - $remap.=($remap?',':''). - (preg_match('/[()]/',$field)?'':($this->table.'.')).$field; - $sql='SELECT '.$remap.' FROM '.$this->table; + $sql='SELECT '.$fields.' FROM '.$this->table; $args=array(); if ($filter) { if (is_array($filter)) { @@ -195,31 +218,18 @@ class Mapper extends \DB\Cursor { } $sql.=' WHERE '.$filter; } - $pkeys=array(); - foreach ($this->fields as $key=>$field) - if ($field['pkey']) - $pkeys[]=$key; if ($options['group']) { - $group=array_unique(array_merge($pkeys, - $spec=explode(',',$options['group']))); - foreach ($group as &$field) { - $field=$db->quotekey($field); - unset($field); - } - $sql.=' JOIN (SELECT '.implode(',',$group).' FROM '. - $this->table.' GROUP BY '.implode(',',array_map( + $sql.=' GROUP BY '.implode(',',array_map( function($str) use($db) { - return preg_match('/^(\w+)(?:\h+HAVING|\h*(?:,|$))/i', - $str,$parts)? - ($db->quotekey($parts[1]). - (isset($parts[2])?(' '.$parts[2]):'')):$str; + return preg_replace_callback( + '/\b(\w+)\h*(HAVING.+|$)/i', + function($parts) use($db) { + return $db->quotekey($parts[1]); + }, + $str + ); }, - $spec)).') AS '.$db->quotekey('sub').' ON '; - $flag=''; - foreach ($pkeys as $pkey) - $sql.=($flag?' AND ':'').$this->table.'.'. - $db->quotekey($pkey).'='. - $db->quotekey('sub').'.'.$db->quotekey($pkey); + explode(',',$options['group']))); } if ($options['order']) { $sql.=' ORDER BY '.implode(',',array_map( @@ -233,6 +243,10 @@ class Mapper extends \DB\Cursor { } if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) && ($options['limit'] || $options['offset'])) { + $pkeys=array(); + foreach ($this->fields as $key=>$field) + if ($field['pkey']) + $pkeys[]=$key; $ofs=$options['offset']?(int)$options['offset']:0; $lmt=$options['limit']?(int)$options['limit']:0; if (strncmp($db->version(),'11',2)>=0) { @@ -280,7 +294,7 @@ class Mapper extends \DB\Cursor { /** * Return records that match criteria - * @return array + * @return \DB\SQL\Mapper[] * @param $filter string|array * @param $options array * @param $ttl int @@ -297,9 +311,11 @@ class Mapper extends \DB\Cursor { $adhoc=''; foreach ($this->adhoc as $key=>$field) $adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key); - return $this->select(implode(',', - array_map(array($this->db,'quotekey'),array_keys($this->fields))). - $adhoc,$filter,$options,$ttl); + return $this->select( + ($options['group'] && !preg_match('/mysql|sqlite/',$this->engine)? + $options['group']: + implode(',',array_map(array($this->db,'quotekey'), + array_keys($this->fields)))).$adhoc,$filter,$options,$ttl); } /** @@ -357,7 +373,8 @@ class Mapper extends \DB\Cursor { **/ function insert() { $args=array(); - $ctr=0; + $actr=0; + $nctr=0; $fields=''; $values=''; $filter=''; @@ -368,9 +385,10 @@ class Mapper extends \DB\Cursor { foreach ($this->fields as $key=>$field) if ($field['pkey']) $pkeys[$key]=$field['previous']; - if (isset($this->trigger['beforeinsert'])) + if (isset($this->trigger['beforeinsert']) && \Base::instance()->call($this->trigger['beforeinsert'], - array($this,$pkeys)); + array($this,$pkeys))===FALSE) + return $this; foreach ($this->fields as $key=>&$field) { if ($field['pkey']) { $field['previous']=$field['value']; @@ -378,13 +396,14 @@ class Mapper extends \DB\Cursor { empty($field['value']) && !$field['nullable']) $inc=$key; $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?'; - $nkeys[$ctr+1]=array($field['value'],$field['pdo_type']); + $nkeys[$nctr+1]=array($field['value'],$field['pdo_type']); + $nctr++; } if ($field['changed'] && $key!=$inc) { - $fields.=($ctr?',':'').$this->db->quotekey($key); - $values.=($ctr?',':'').'?'; - $args[$ctr+1]=array($field['value'],$field['pdo_type']); - $ctr++; + $fields.=($actr?',':'').$this->db->quotekey($key); + $values.=($actr?',':'').'?'; + $args[$actr+1]=array($field['value'],$field['pdo_type']); + $actr++; $ckeys[]=$key; } $field['changed']=FALSE; @@ -430,9 +449,10 @@ class Mapper extends \DB\Cursor { foreach ($this->fields as $key=>$field) if ($field['pkey']) $pkeys[$key]=$field['previous']; - if (isset($this->trigger['beforeupdate'])) + if (isset($this->trigger['beforeupdate']) && \Base::instance()->call($this->trigger['beforeupdate'], - array($this,$pkeys)); + array($this,$pkeys))===FALSE) + return $this; foreach ($this->fields as $key=>$field) if ($field['changed']) { $pairs.=($pairs?',':'').$this->db->quotekey($key).'=?'; @@ -441,14 +461,13 @@ class Mapper extends \DB\Cursor { } foreach ($this->fields as $key=>$field) if ($field['pkey']) { - $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?'; + $filter.=($filter?' AND ':' WHERE '). + $this->db->quotekey($key).'=?'; $args[$ctr+1]=array($field['previous'],$field['pdo_type']); $ctr++; } if ($pairs) { - $sql='UPDATE '.$this->table.' SET '.$pairs; - if ($filter) - $sql.=' WHERE '.$filter; + $sql='UPDATE '.$this->table.' SET '.$pairs.$filter; $this->db->exec($sql,$args); if (isset($this->trigger['afterupdate'])) \Base::instance()->call($this->trigger['afterupdate'], @@ -497,10 +516,10 @@ class Mapper extends \DB\Cursor { unset($field); } parent::erase(); - $this->skip(0); - if (isset($this->trigger['beforeerase'])) + if (isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], - array($this,$pkeys)); + array($this,$pkeys))===FALSE) + return 0; $out=$this->db-> exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args); if (isset($this->trigger['aftererase'])) @@ -531,11 +550,12 @@ class Mapper extends \DB\Cursor { /** * Hydrate mapper object using hive array variable * @return NULL - * @param $key string + * @param $var array|string * @param $func callback **/ - function copyfrom($key,$func=NULL) { - $var=\Base::instance()->get($key); + function copyfrom($var,$func=NULL) { + if (is_string($var)) + $var=\Base::instance()->get($var); if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) @@ -561,10 +581,13 @@ class Mapper extends \DB\Cursor { } /** - * Return schema + * Return schema and, if the first argument is provided, update it * @return array + * @param $fields NULL|array **/ - function schema() { + function schema($fields=null) { + if ($fields) + $this->fields = $fields; return $this->fields; } @@ -577,6 +600,16 @@ class Mapper extends \DB\Cursor { return array_keys($this->fields+($adhoc?$this->adhoc:array())); } + /** + * 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 diff --git a/app/lib/db/sql/session.php b/app/lib/db/sql/session.php index 6fdb9750..12c27f42 100644 --- a/app/lib/db/sql/session.php +++ b/app/lib/db/sql/session.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -137,8 +143,9 @@ class Session extends Mapper { * @param $db object * @param $table string * @param $force bool + * @param $onsuspect callback **/ - function __construct(\DB\SQL $db,$table='sessions',$force=TRUE) { + function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL) { if ($force) { $eol="\n"; $tab="\t"; @@ -178,8 +185,12 @@ class Session extends Mapper { ($agent=$this->agent()) && (!isset($headers['User-Agent']) || $agent!=$headers['User-Agent'])) { - session_destroy(); - $fw->error(403); + if (isset($onsuspect)) + $fw->call($onsuspect,array($this)); + else { + session_destroy(); + $fw->error(403); + } } $csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. $fw->hash(mt_rand()); diff --git a/app/lib/f3.php b/app/lib/f3.php index b4127bd6..96c7845e 100644 --- a/app/lib/f3.php +++ b/app/lib/f3.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/image.php b/app/lib/image.php index e5d96cc9..55a29ba9 100644 --- a/app/lib/image.php +++ b/app/lib/image.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -20,6 +26,7 @@ class Image { //@{ Messages const E_Color='Invalid color specified: %s', + E_File='File not found', E_Font='CAPTCHA font not found', E_Length='Invalid CAPTCHA length: %s'; //@} @@ -52,7 +59,7 @@ class Image { function rgb($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)); + 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)); @@ -221,11 +228,12 @@ class Image { function resize($width,$height,$crop=TRUE,$enlarge=TRUE) { // Adjust dimensions; retain aspect ratio $ratio=($origw=imagesx($this->data))/($origh=imagesy($this->data)); - if (!$crop) + if (!$crop) { if ($width/$ratio<=$height) $height=$width/$ratio; else $width=$height*$ratio; + } if (!$enlarge) { $width=min($origw,$width); $height=min($origh,$height); @@ -387,7 +395,7 @@ class Image { 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)); + user_error(sprintf(self::E_Length,$len),E_USER_ERROR); return FALSE; } $fw=Base::instance(); @@ -433,7 +441,7 @@ class Image { $fw->set($key,$seed); return $this->save(); } - user_error(self::E_Font); + user_error(self::E_Font,E_USER_ERROR); return FALSE; } @@ -553,15 +561,18 @@ class Image { * @param $flag bool * @param $path string **/ - function __construct($file=NULL,$flag=FALSE,$path='') { + function __construct($file=NULL,$flag=FALSE,$path=NULL) { $this->flag=$flag; if ($file) { $fw=Base::instance(); // Create image from file $this->file=$file; - foreach ($fw->split($path?:$fw->get('UI').';./') as $dir) + if (!isset($path)) + $path=$fw->get('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); } } diff --git a/app/lib/log.php b/app/lib/log.php index c995526d..6583cedb 100644 --- a/app/lib/log.php +++ b/app/lib/log.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/magic.php b/app/lib/magic.php index d38407c3..5669908a 100644 --- a/app/lib/magic.php +++ b/app/lib/magic.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/markdown.php b/app/lib/markdown.php index 31cbf940..9863d911 100644 --- a/app/lib/markdown.php +++ b/app/lib/markdown.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/matrix.php b/app/lib/matrix.php index 682474b4..1ebce6b9 100644 --- a/app/lib/matrix.php +++ b/app/lib/matrix.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/session.php b/app/lib/session.php index 0f5ef45a..8723c69f 100644 --- a/app/lib/session.php +++ b/app/lib/session.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -144,9 +150,9 @@ class Session { /** * Instantiate class - * @return object + * @param $onsuspect callback **/ - function __construct() { + function __construct($onsuspect=NULL) { session_set_save_handler( array($this,'open'), array($this,'close'), @@ -163,8 +169,12 @@ class Session { ($agent=$this->agent()) && (!isset($headers['User-Agent']) || $agent!=$headers['User-Agent'])) { - session_destroy(); - \Base::instance()->error(403); + if (isset($onsuspect)) + $fw->call($onsuspect,array($this)); + else { + session_destroy(); + $fw->error(403); + } } $csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. $fw->hash(mt_rand()); diff --git a/app/lib/smtp.php b/app/lib/smtp.php index 842ece66..f0351ae5 100644 --- a/app/lib/smtp.php +++ b/app/lib/smtp.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -134,15 +140,16 @@ class SMTP extends Magic { /** * Add e-mail attachment * @return NULL - * @param $file - * @param $alias + * @param $file string + * @param $alias string + * @param $cid string **/ - function attach($file,$alias=NULL) { + function attach($file,$alias=NULL,$cid=NULL) { if (!is_file($file)) - user_error(sprintf(self::E_Attach,$file)); + user_error(sprintf(self::E_Attach,$file),E_USER_ERROR); if (is_string($alias)) $file=array($alias=>$file); - $this->attachments[]=$file; + $this->attachments[]=array('filename'=>$file,'cid'=>$cid); } /** @@ -156,7 +163,7 @@ class SMTP extends Magic { return FALSE; // Message should not be blank if (!$message) - user_error(self::E_Blank); + user_error(self::E_Blank,E_USER_ERROR); $fw=Base::instance(); // Retrieve headers $headers=$this->headers; @@ -192,7 +199,7 @@ class SMTP extends Magic { $reqd=array('From','To','Subject'); foreach ($reqd as $id) if (empty($headers[$id])) - user_error(sprintf(self::E_Header,$id)); + user_error(sprintf(self::E_Header,$id),E_USER_ERROR); $eol="\r\n"; $str=''; // Stringify headers @@ -231,17 +238,18 @@ class SMTP extends Magic { $out.=$eol; $out.=$message.$eol; foreach ($this->attachments as $attachment) { - if (is_array($attachment)) { - list($alias, $file) = each($attachment); - $filename = $alias; - $attachment = $file; - } - else { - $filename = basename($attachment); + if (is_array($attachment['filename'])) { + list($alias,$file)=each($attachment); + $filename=$alias; + $attachment['filename']=$file; } + else + $filename=basename($attachment); $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="'.$filename.'"'.$eol; $out.=$eol; diff --git a/app/lib/template.php b/app/lib/template.php index 16938ca0..8c1c0e11 100644 --- a/app/lib/template.php +++ b/app/lib/template.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -67,7 +73,7 @@ class Template extends Preview { 'token($attrib['if']).') '):''). ('echo $this->render('. - (preg_match('/\{\{(.+?)\}\}/',$attrib['href'])? + (preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])? $this->token($attrib['href']): Base::instance()->stringify($attrib['href'])).','. '$this->mime,'.$hive.'); ?>'); @@ -253,7 +259,7 @@ class Template extends Preview { return call_user_func_array($this->custom[$func],$args); if (method_exists($this,$func)) return call_user_func_array(array($this,$func),$args); - user_error(sprintf(self::E_Method,$func)); + user_error(sprintf(self::E_Method,$func),E_USER_ERROR); } /** diff --git a/app/lib/test.php b/app/lib/test.php index 605b636d..05e6e6c5 100644 --- a/app/lib/test.php +++ b/app/lib/test.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -26,7 +32,9 @@ class Test { protected //! Test results - $data=array(); + $data=array(), + //! Success indicator + $passed=TRUE; /** * Return test results @@ -36,6 +44,14 @@ class Test { 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 @@ -54,6 +70,8 @@ class Test { } $this->data[]=$data; } + if (!$out && $this->passed) + $this->passed=FALSE; return $this; } diff --git a/app/lib/utf.php b/app/lib/utf.php index 9fe4c91a..fbfe0005 100644 --- a/app/lib/utf.php +++ b/app/lib/utf.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/web.php b/app/lib/web.php index 930a192c..ab3389ec 100644 --- a/app/lib/web.php +++ b/app/lib/web.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ @@ -127,7 +133,7 @@ class Web extends Prefab { header('Content-Type: '.($mime?:$this->mime($file))); if ($force) header('Content-Disposition: attachment; '. - 'filename='.basename($file)); + 'filename='.var_export(basename($file),TRUE)); header('Accept-Ranges: bytes'); header('Content-Length: '.$size); header('X-Powered-By: '.Base::instance()->get('PACKAGE')); @@ -198,19 +204,21 @@ class Web extends Prefab { (!$func || $fw->call($func,array($file))!==FALSE) && rename($tmp,$file['name']); } + $fetch=function($arr)use(&$fetch){ + if (!is_array($arr)) + return array($arr); + $data=array(); + foreach($arr as $k=>$sub) + $data=array_merge($data,$fetch($sub)); + return $data; + }; $out=array(); foreach ($_FILES as $name=>$item) { - if (is_array($item['name'])) { - // Transpose array - $tmp=array(); - foreach ($item as $keyx=>$cols) - foreach ($cols as $keyy=>$valy) - $tmp[$keyy][$keyx]=$valy; - $item=$tmp; - } - else - $item=array($item); - foreach ($item as $file) { + $files=array(); + 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']); @@ -258,8 +266,6 @@ class Web extends Prefab { curl_setopt($curl,CURLOPT_CUSTOMREQUEST,$options['method']); if (isset($options['header'])) curl_setopt($curl,CURLOPT_HTTPHEADER,$options['header']); - if (isset($options['user_agent'])) - curl_setopt($curl,CURLOPT_USERAGENT,$options['user_agent']); if (isset($options['content'])) curl_setopt($curl,CURLOPT_POSTFIELDS,$options['content']); curl_setopt($curl,CURLOPT_ENCODING,'gzip,deflate'); @@ -351,6 +357,7 @@ class Web extends Prefab { if (!$socket) return FALSE; stream_set_blocking($socket,TRUE); + stream_set_timeout($socket,$options['timeout']); fputs($socket,$options['method'].' '.$parts['path']. ($parts['query']?('?'.$parts['query']):'').' HTTP/1.0'.$eol ); @@ -361,7 +368,8 @@ class Web extends Prefab { $content=''; while (!feof($socket) && ($info=stream_get_meta_data($socket)) && - !$info['timed_out'] && $str=fgets($socket,4096)) + !$info['timed_out'] && !connection_aborted() && + $str=fgets($socket,4096)) $content.=$str; fclose($socket); $html=explode($eol.$eol,$content,2); @@ -412,7 +420,7 @@ class Web extends Prefab { foreach ($flags as $key=>$val) if ($val) return $this->wrapper=$key; - user_error(E_Request); + user_error(E_Request,E_USER_ERROR); } /** @@ -426,7 +434,7 @@ class Web extends Prefab { $new=array($new); foreach ($new as $hdr) { $old=preg_grep('/'.preg_quote(strstr($hdr,':',TRUE),'/').':.+/', - $old,PREG_GREP_INVERT); + $old,PREG_GREP_INVERT); array_push($old,$hdr); } } @@ -472,7 +480,9 @@ class Web extends Prefab { $this->subst($options['header'], array( 'Accept-Encoding: gzip,deflate', - 'User-Agent: Mozilla/5.0 (compatible; '.php_uname('s').')', + 'User-Agent: '.(isset($options['user_agent'])? + $options['user_agent']: + 'Mozilla/5.0 (compatible; '.php_uname('s').')'), 'Connection: close' ) ); @@ -531,7 +541,7 @@ class Web extends Prefab { * @param $header bool * @param $path string **/ - function minify($files,$mime=NULL,$header=TRUE,$path='') { + function minify($files,$mime=NULL,$header=TRUE,$path=NULL) { $fw=Base::instance(); if (is_string($files)) $files=$fw->split($files); @@ -540,7 +550,9 @@ class Web extends Prefab { preg_match('/\w+$/',$files[0],$ext); $cache=Cache::instance(); $dst=''; - foreach ($fw->split($path?:$fw->get('UI').';./') as $dir) + if (!isset($path)) + $path=$fw->get('UI').';./'; + foreach ($fw->split($path,FALSE) as $dir) foreach ($files as $file) if (is_file($save=$fw->fixslashes($dir.$file))) { if ($fw->get('CACHE') && @@ -635,7 +647,7 @@ class Web extends Prefab { if (ctype_space($src[$ptr])) { if ($ptr+1. */ @@ -87,15 +93,15 @@ class Geo extends \Prefab { $web=\Web::instance(); $query=array( 'lat'=>$latitude, - 'lng'=>$longitude, - 'username'=>$fw->hash($fw->get('IP')) + 'lon'=>$longitude ); + $req=$web->request( + 'http://api.openweathermap.org/data/2.5/weather?'. + http_build_query($query)); return ($req=$web->request( - 'http://ws.geonames.org/findNearByWeatherJSON?'. - http_build_query($query))) && - ($data=json_decode($req['body'],TRUE)) && - isset($data['weatherObservation'])? - $data['weatherObservation']: + 'http://api.openweathermap.org/data/2.5/weather?'. + http_build_query($query)))? + json_decode($req['body'],TRUE): FALSE; } diff --git a/app/lib/web/google/staticmap.php b/app/lib/web/google/staticmap.php index 8f6728f1..71acc550 100644 --- a/app/lib/web/google/staticmap.php +++ b/app/lib/web/google/staticmap.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/web/openid.php b/app/lib/web/openid.php index 1988387f..89173d0b 100644 --- a/app/lib/web/openid.php +++ b/app/lib/web/openid.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/lib/web/pingback.php b/app/lib/web/pingback.php index bbb32cf7..9dd750a3 100644 --- a/app/lib/web/pingback.php +++ b/app/lib/web/pingback.php @@ -10,7 +10,13 @@ terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or later. - Please see the LICENSE file for more information. + 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 . */ diff --git a/app/main/controller/Controller.php b/app/main/controller/Controller.php index 6e2f7287..2e1ae3b8 100644 --- a/app/main/controller/Controller.php +++ b/app/main/controller/Controller.php @@ -8,12 +8,21 @@ namespace Controller; use Model; +use DB; class Controller { protected $f3; private $template; + function __construct(){ + + $this->f3 = \Base::instance(); + + // initiate DB connection + DB\Database::instance('PF'); + } + /** * @param mixed $template */ @@ -28,15 +37,6 @@ class Controller { return $this->template; } - function __construct(){ - - $f3 = \Base::instance(); - $this->f3 = $f3; - - // init DB - $this->setDB('PF'); - } - /** * event handler */ @@ -55,34 +55,11 @@ class Controller { } /** - * set/change DB connection - * @param $type + * set change the DB connection + * @param string $database */ - protected function setDB($type = ''){ - - if($type === 'CCP'){ - // CCP DB - $db = new \DB\SQL( - $this->f3->get('DB_CCP_DNS') . $this->f3->get('DB_CCP_NAME'), - $this->f3->get('DB_CCP_USER'), - $this->f3->get('DB_CCP_PASS') - ); - }else{ - // Pathfinder DB - $db = new \DB\SQL( - $this->f3->get('DB_DNS') . $this->f3->get('DB_NAME'), - $this->f3->get('DB_USER'), - $this->f3->get('DB_PASS') - ); - } - $this->f3->set('DB', $db); - - // set DB timezone to UTC +00:00 (eve server time) - //$this->f3->get('DB')->exec('SET @@session.time_zone = "+00:00";'); - - // disable innoDB schema (relevant vor MySql 5.5) - // not necessary for MySql 5.6 - //$this->f3->get('DB')->exec('SET GLOBAL innodb_stats_on_metadata = OFF;'); + protected function setDB($database = 'PF'){ + DB\Database::instance()->setDB($database); } /** @@ -191,8 +168,9 @@ class Controller { return $validUser; } + /** - * logout function + * log the current user out */ public function logOut(){ diff --git a/app/main/controller/LogController.php b/app/main/controller/LogController.php index 0ea9fe38..59589033 100644 --- a/app/main/controller/LogController.php +++ b/app/main/controller/LogController.php @@ -11,30 +11,22 @@ namespace controller; class LogController extends Controller { - /** - * log types. The Value represents the log file name - * @var array - */ - protected static $logTypes = [ - 'debug' => 'debug' - ]; - /** * get an singleton instance for a logger instance - * @param $loggerType + * @param $logFileName * @return mixed */ - public static function getLogger($loggerType){ + public static function getLogger($logFileName){ $f3 = \Base::instance(); - $hiveKey = 'LOGGER' . $loggerType; + $hiveKey = 'LOGGER' . $logFileName; // check if log controller already exists if( !$f3->exists($hiveKey) ){ // create new logger instance - $logFile = self::$logTypes[$loggerType] . '.log'; + $logFile = $logFileName . '.log'; $f3->set($hiveKey, new \Log($logFile)); } diff --git a/app/main/controller/api/Map.php b/app/main/controller/api/Map.php index 34425293..3e349c0a 100644 --- a/app/main/controller/api/Map.php +++ b/app/main/controller/api/Map.php @@ -108,7 +108,8 @@ class Map extends \Controller\AccessController { foreach((array)$rows as $rowData){ $data = [ 'id' => $rowData->id, - 'label' => $rowData->label + 'label' => $rowData->label, + 'connectorDefinition' => $rowData->connectorDefinition ]; $connectionScopeData[$rowData->name] = $data; } @@ -319,16 +320,26 @@ class Map extends \Controller\AccessController { // cache time(s) per user should be equal or less than this function is called // prevent request flooding $responseTTL = $f3->get('PATHFINDER.TIMER.UPDATE_SERVER_MAP.DELAY') / 1000; + $mapData = (array)$f3->get('POST.mapData'); + $user = $this->_getUser(); $return = (object) []; $return->error = []; if($user){ - $cacheKey = 'user_map_data_' . $user->id; + // -> get active user object + $activeCharacter = $user->getActiveUserCharacter(); + + $cacheKey = 'user_map_data_' . $activeCharacter->id; + + // if there is any system/connection change data submitted -> clear cache + if(!empty($mapData)){ + $f3->clear($cacheKey); + } + if($f3->exists($cacheKey) === false ){ - $mapData = (array)$f3->get('POST.mapData'); // get current map data ======================================================== $maps = $user->getMaps(); @@ -356,8 +367,7 @@ class Map extends \Controller\AccessController { ){ // map changes expected ============================================= - // -> get active user object - $activeCharacter = $user->getActiveUserCharacter(); + // loop current user maps and check for changes foreach($maps as $map){ @@ -379,6 +389,7 @@ class Map extends \Controller\AccessController { // system belongs to the current map if(is_object($filteredMap->systems)){ // update + unset($systemData['updated']); $system = $filteredMap->systems->current(); $system->setData($systemData); $system->updatedCharacterId = $activeCharacter->characterId; @@ -407,6 +418,7 @@ class Map extends \Controller\AccessController { // connection belongs to the current map if(is_object($filteredMap->connections)){ // update + unset($connectionData['updated']); $connection = $filteredMap->connections->current(); $connection->setData($connectionData); $connection->save($user); diff --git a/app/main/controller/api/Route.php b/app/main/controller/api/Route.php index a02ce528..9ef26d7a 100644 --- a/app/main/controller/api/Route.php +++ b/app/main/controller/api/Route.php @@ -238,7 +238,7 @@ class Route extends \Controller\AccessController { if(count($rows) > 0){ // switch DB back to pathfinder DB - $this->setDB(); + $this->setDB('PF'); // clear cache table $query = "TRUNCATE system_neighbour"; diff --git a/app/main/controller/api/System.php b/app/main/controller/api/System.php index 69e7f77e..941b575c 100644 --- a/app/main/controller/api/System.php +++ b/app/main/controller/api/System.php @@ -149,11 +149,12 @@ class System extends \Controller\AccessController { public function search($f3, $params){ // switch DB + \DB\Database::instance(); $this->setDB('CCP'); $searchToken = ''; // check for search parameter - if( array_key_exists( 'arg1', $params) ){ + if( isset($params['arg1']) ){ $searchToken = $params['arg1']; } diff --git a/app/main/controller/api/User.php b/app/main/controller/api/User.php index 770108b0..d0bfbf19 100644 --- a/app/main/controller/api/User.php +++ b/app/main/controller/api/User.php @@ -117,6 +117,15 @@ class User extends Controller\Controller{ } } + /** + * log the current user out + clear character system log data + */ + public function logOut(){ + $this->deleteLog(); + + return parent::logOut(); + } + /** * save/update "map sharing" configurations for all map types * the user has access to diff --git a/app/main/controller/cron/Update.php b/app/main/cron/ccpSystemsUpdate.php similarity index 82% rename from app/main/controller/cron/Update.php rename to app/main/cron/ccpSystemsUpdate.php index 285eb956..5fa02c5d 100644 --- a/app/main/controller/cron/Update.php +++ b/app/main/cron/ccpSystemsUpdate.php @@ -6,9 +6,13 @@ * Time: 21:31 */ -namespace Controller\Cron; +namespace Cron; +use Controller; +use DB; -class Update extends \Controller\Controller { +class CcpSystemsUpdate { + + const LOG_TEXT = '%s prepare table (%.3F s), jump (%.3F s), kill (%.3F s), update all (%.3F s)'; protected $apiRequestOptions = [ 'timeout' => 5 @@ -25,36 +29,23 @@ class Update extends \Controller\Controller { 'factionKills' => 'system_kills_factions' ]; - /** - * event handler - */ - function beforeroute() { - - // controller access allowed for CLI-mode (command line) - if(php_sapi_name() != 'cli'){ - $this->f3->error(401, 'Cronjob: unauthorized access'); - } - - // set linebreak for status output - define('LNBR', PHP_EOL); - - parent::beforeroute(); - } - /** * check all system log tables for the correct number of system entries that will be locked * @return array */ - private function _prepareSystemLogTables(){ + private function prepareSystemLogTables(){ - $systemController = new \Controller\Api\System(); + $f3 = \Base::instance(); + + // get information for all systems from CCP DB + $systemController = new Controller\Api\System(); $systemsData = $systemController->getSystems(); // switch DB back to pathfinder - $this->setDB('PF'); + DB\Database::instance()->setDB('PF'); // insert systems into each log table if not exist - $this->f3->get('DB')->begin(); + $f3->get('DB')->begin(); foreach($this->logTables as $tableName){ // insert systems into jump log table @@ -64,14 +55,14 @@ class Update extends \Controller\Controller { foreach($systemsData as $systemData){ // skip WH systems -> no jump data available if($systemData['type']['name'] == 'k-space'){ - $this->f3->get('DB')->exec($sqlInsertSystem, array( + $f3->get('DB')->exec($sqlInsertSystem, array( ':systemId' => $systemData['systemId'] )); } } } - $this->f3->get('DB')->commit(); + $f3->get('DB')->commit(); return $systemsData; } @@ -79,21 +70,21 @@ class Update extends \Controller\Controller { /** * imports all relevant map stats from CCPs API - * >> php index.php "/cron/update/importmapdata" + * >> php index.php "/cron/importSystemData" * @param $f3 */ - public function importMapData($f3){ + function importSystemData($f3){ $time_start = microtime(true); // prepare system jump log table - $systemsData = $this->_prepareSystemLogTables(); + $systemsData = $this->prepareSystemLogTables(); $time_end = microtime(true); $execTimePrepareSystemLogTables = $time_end - $time_start; // get current jump Data ------------------------------------------------------- $time_start = microtime(true); - $apiPath = $this->f3->get('api_path.CCP_XML') . '/map/Jumps.xml.aspx'; + $apiPath = $f3->get('api_path.CCP_XML') . '/map/Jumps.xml.aspx'; $apiResponse = \Web::instance()->request($apiPath, $this->apiRequestOptions ); @@ -118,7 +109,7 @@ class Update extends \Controller\Controller { // get current kill Data ------------------------------------------------------- $time_start = microtime(true); - $apiPath = $this->f3->get('api_path.CCP_XML') . '/map/Kills.xml.aspx'; + $apiPath = $f3->get('api_path.CCP_XML') . '/map/Kills.xml.aspx'; $apiResponse = \Web::instance()->request($apiPath, $this->apiRequestOptions ); $killData = []; @@ -150,6 +141,7 @@ class Update extends \Controller\Controller { $time_start = microtime(true); // make sure last update is (at least) 1h ago $f3->get('DB')->begin(); + foreach($this->logTables as $key => $tableName){ $sql = "UPDATE " . $tableName . " @@ -221,10 +213,8 @@ class Update extends \Controller\Controller { $time_end = microtime(true); $execTimeUpdateTables = $time_end - $time_start; - // ------------------------ - echo 'prepare system log tables: ' . round($execTimePrepareSystemLogTables, 2) . 's' . LNBR; - echo 'get jump data: ' . round($execTimeGetJumpData, 2) . 's' . LNBR; - echo 'get kill data: ' . round($execTimeGetKillData, 2) . 's' . LNBR; - echo 'update log tables: ' . round($execTimeUpdateTables, 2) . 's' . LNBR; + // Log ------------------------ + $log = Controller\LogController::getLogger('cron_' . __FUNCTION__); + $log->write( sprintf(self::LOG_TEXT, __FUNCTION__, $execTimePrepareSystemLogTables, $execTimeGetJumpData, $execTimeGetKillData, $execTimeUpdateTables) ); } } \ No newline at end of file diff --git a/app/main/cron/characterUpdate.php b/app/main/cron/characterUpdate.php new file mode 100644 index 00000000..243ac1ef --- /dev/null +++ b/app/main/cron/characterUpdate.php @@ -0,0 +1,28 @@ +> php index.php "/cron/deleteLogData" + * @param $f3 + */ + function deleteLogData($f3){ + + DB\Database::instance()->setDB('PF'); + + $sqlDeleteCharacterLogs = "TRUNCATE TABLE character_log"; + $f3->get('DB')->exec($sqlDeleteCharacterLogs); + } + +} \ No newline at end of file diff --git a/app/main/cron/mapUpdate.php b/app/main/cron/mapUpdate.php new file mode 100644 index 00000000..4e0f27c9 --- /dev/null +++ b/app/main/cron/mapUpdate.php @@ -0,0 +1,70 @@ +> php index.php "/cron/deactivateMapData" + * @param $f3 + */ + function deactivateMapData($f3){ + + DB\Database::instance()->setDB('PF'); + + $sqlDeactivateExpiredMaps = "UPDATE map SET + active = 0 + WHERE + map.active = 1 AND + map.typeId = 2 AND + TIMESTAMPDIFF(DAY, map.created, NOW() ) > :lifetime"; + + $privateMapLifetime = (int)$f3->get('PATHFINDER.MAP.PRIVATE.LIFETIME'); + + $f3->get('DB')->exec($sqlDeactivateExpiredMaps, ['lifetime' => $privateMapLifetime]); + $deactivatedMapsCount = $f3->get('DB')->count(); + + // Log ------------------------ + $log = Controller\LogController::getLogger('cron_' . __FUNCTION__); + $log->write( sprintf(self::LOG_TEXT_MAPS, __FUNCTION__, $deactivatedMapsCount) ); + } + + /** + * delete all deactivated maps + * >> php index.php "/cron/deleteMapData" + * @param $f3 + */ + function deleteMapData($f3){ + + DB\Database::instance()->setDB('PF'); + + $sqlDeleteDisabledMaps = "DELETE FROM + map + WHERE + map.active = 0 AND + TIMESTAMPDIFF(DAY, map.updated, NOW() ) > :deletion_time"; + + $f3->get('DB')->exec($sqlDeleteDisabledMaps, ['deletion_time' => self::DAYS_UNTIL_MAP_DELETION]); + + $deletedMapsCount = $f3->get('DB')->count(); + + // Log ------------------------ + $log = Controller\LogController::getLogger('cron_' . __FUNCTION__); + $log->write( sprintf(self::LOG_TEXT_MAPS, __FUNCTION__, $deletedMapsCount) ); + } + +} \ No newline at end of file diff --git a/app/main/db/database.php b/app/main/db/database.php new file mode 100644 index 00000000..22616b85 --- /dev/null +++ b/app/main/db/database.php @@ -0,0 +1,64 @@ +setDB($database); + } + + /** + * set database + * @param string $database + */ + public function setDB($database = 'PF'){ + + $f3 = \Base::instance(); + + if($database === 'CCP'){ + // CCP DB + $db = $this->connect($f3->get('DB_CCP_DNS'), $f3->get('DB_CCP_NAME'), $f3->get('DB_CCP_USER'), $f3->get('DB_CCP_PASS')); + }else{ + // Pathfinder DB + $db = $this->connect($f3->get('DB_DNS'), $f3->get('DB_NAME'), $f3->get('DB_USER'), $f3->get('DB_PASS')); + } + + $f3->set('DB', $db); + + // set DB timezone to UTC +00:00 (eve server time) + $f3->get('DB')->exec('SET @@session.time_zone = "+00:00";'); + + // disable innoDB schema (relevant vor MySql 5.5) + // not necessary for MySql 5.6 + //$f3->get('DB')->exec('SET GLOBAL innodb_stats_on_metadata = OFF;'); + } + + /** + * connect to a database + * @param $dns + * @param $name + * @param $user + * @param $password + * @return SQL + */ + protected function connect($dns, $name, $user, $password){ + + $db = new SQL( + $dns . $name, + $user, + $password + ); + + return $db; + } + +} \ No newline at end of file diff --git a/app/main/model/BasicModel.php b/app/main/model/BasicModel.php index 5218dfea..a49e1d03 100644 --- a/app/main/model/BasicModel.php +++ b/app/main/model/BasicModel.php @@ -36,14 +36,6 @@ class BasicModel extends \DB\Cortex { */ protected $validate = []; - /** - * if the getData() function should be cached or not - * if set to "0" no data is cached - * cache will be updated on save/update - * @var int (seconds) - */ - protected $data_ttl = 0; - /** * getData() cache key prefix * -> do not change, otherwise cached data is lost @@ -93,6 +85,8 @@ class BasicModel extends \DB\Cortex { !is_object($currentVal) && $currentVal != $val ){ + //print_r($val); + //print_r($this->cast()); $this->touch('updated'); } } @@ -120,11 +114,14 @@ class BasicModel extends \DB\Cortex { if(is_array($this->fieldConf)){ - $staticFieldConfig = array( - 'updated' => array( + $staticFieldConfig = [ + 'created' => [ 'type' => Schema::DT_TIMESTAMP - ) - ); + ], + 'updated' => [ + 'type' => Schema::DT_TIMESTAMP + ] + ]; $this->fieldConf = array_merge($this->fieldConf, $staticFieldConfig); } @@ -342,19 +339,20 @@ class BasicModel extends \DB\Cortex { * update/set the getData() cache for this object * @param $cacheData * @param string $dataCacheKeyPrefix + * @param int $data_ttl */ - public function updateCacheData($cacheData, $dataCacheKeyPrefix = ''){ + public function updateCacheData($cacheData, $dataCacheKeyPrefix = '', $data_ttl = 300){ - // check if model is allowed to be cached + // check if data should be cached // and cacheData is not empty if( - $this->data_ttl > 0 && + $data_ttl > 0 && !empty( (array)$cacheData ) ){ $cacheKey = $this->getCacheKey($dataCacheKeyPrefix); if( !is_null($cacheKey) ){ - self::getF3()->set($cacheKey, $cacheData, $this->data_ttl); + self::getF3()->set($cacheKey, $cacheData, $data_ttl); } } } diff --git a/app/main/model/MapModel.php b/app/main/model/MapModel.php index 6f1d9f8b..e56f9071 100644 --- a/app/main/model/MapModel.php +++ b/app/main/model/MapModel.php @@ -12,14 +12,6 @@ class MapModel extends BasicModel { protected $table = 'map'; - /** - * max caching time for a map - * the cached date has to be cleared manually on any change - * this includes system, connection,... changes (all dependencies) - * @var int - */ - protected $data_ttl = 300; - protected $fieldConf = array( 'scopeId' => array( 'belongs-to-one' => 'Model\MapScopeModel' @@ -154,7 +146,10 @@ class MapModel extends BasicModel { // map connection data ---------------------------------------- $mapDataAll->connections = $this->getConnectionData(); - $this->updateCacheData($mapDataAll); + // max caching time for a map + // the cached date has to be cleared manually on any change + // this includes system, connection,... changes (all dependencies) + $this->updateCacheData($mapDataAll, '', 300); } return $mapDataAll; @@ -183,7 +178,8 @@ class MapModel extends BasicModel { * @return array|mixed */ public function getSystems(){ - $this->filter('systems', array('active = ?', 1)); + // orderBy x-Coordinate for cleaner frontend animation (left to right) + $this->filter('systems', ['active = ?', 1], ['order' => 'posX']); $systems = []; if($this->systems){ @@ -432,7 +428,7 @@ class MapModel extends BasicModel { $charactersData[] = $character->getData(true); } - $this->updateCacheData($charactersData, 'CHARACTERS'); + $this->updateCacheData($charactersData, 'CHARACTERS', 10); } return $charactersData; diff --git a/app/routes.cfg b/app/routes.ini similarity index 84% rename from app/routes.cfg rename to app/routes.ini index fc89ad00..5775ce57 100644 --- a/app/routes.cfg +++ b/app/routes.ini @@ -8,6 +8,3 @@ GET|POST @map: /map= Controller\MapController->showMap, 0 GET|POST /api/@controller/@action [ajax] = Controller\Api\@controller->@action, 0, 512 GET|POST /api/@controller/@action/@arg1 [ajax] = Controller\Api\@controller->@action, 0, 512 GET|POST /api/@controller/@action/@arg1/@arg2 [ajax] = Controller\Api\@controller->@action, 0, 512 - -; cronjob APIs -GET /cron/@controller/@action = Controller\Cron\@controller->@action diff --git a/build_js/app.js b/build_js/app.js deleted file mode 100644 index 54b63e07..00000000 --- a/build_js/app.js +++ /dev/null @@ -1 +0,0 @@ -requirejs.config({baseUrl:"build_js",stubModules:["text"],paths:{layout:"layout",dialog:"app/ui/dialog",jquery:"lib/jquery-1.11.2.min",bootstrap:"lib/bootstrap.min",text:"lib/requirejs/text",mustache:"lib/mustache.min",velocity:"lib/velocity.min",velocityUI:"lib/velocity.ui.min",templates:"../public/templates",slidebars:"lib/slidebars",jsPlumb:"lib/dom.jsPlumb-1.7.2-min",customScrollbar:"lib/jquery.mCustomScrollbar.concat.min",datatables:"lib/datatables/jquery.dataTables.min",datatablesBootstrap:"lib/datatables/dataTables.bootstrap",datatablesTableTools:"lib/datatables/extensions/TableTools/js/dataTables.tableTools",xEditable:"lib/bootstrap-editable.min",morris:"lib/morris.min",raphael:"lib/raphael-min",bootbox:"lib/bootbox.min",easyPieChart:"lib/jquery.easypiechart.min",dragToSelect:"lib/jquery.dragToSelect",hoverIntent:"lib/jquery.hoverIntent.minified",fullScreen:"lib/jquery.fullscreen.min",select2:"lib/select2.min",validator:"lib/validator.min",lazylinepainter:"lib/jquery.lazylinepainter-1.5.1.min",blueImpGallery:"lib/blueimp-gallery",blueImpGalleryHelper:"lib/blueimp-helper",blueImpGalleryBootstrap:"lib/bootstrap-image-gallery",easePack:"lib/EasePack.min",tweenLite:"lib/TweenLite.min",pnotify:"lib/pnotify/pnotify.core","pnotify.nonblock":"lib/pnotify/pnotify.nonblock","pnotify.desktop":"lib/pnotify/pnotify.desktop","pnotify.callbacks":"lib/pnotify/pnotify.callbacks"},shim:{bootstrap:{deps:["jquery"]},velocity:{deps:["jquery"]},velocityUI:{deps:["velocity"]},slidebars:{deps:["jquery"]},customScrollbar:{deps:["jquery"]},datatables:{deps:["jquery"]},datatablesTableTools:{deps:["datatables"]},datatablesBootstrap:{deps:["datatables"]},xEditable:{deps:["bootstrap"]},bootbox:{deps:["jquery","bootstrap"],exports:"bootbox"},morris:{deps:["jquery","raphael"],exports:"Morris"},pnotify:{deps:["jquery"]},easyPieChart:{deps:["jquery"]},dragToSelect:{deps:["jquery"]},hoverIntent:{deps:["jquery"]},fullScreen:{deps:["jquery"]},select2:{deps:["jquery"],exports:"Select2"},validator:{deps:["jquery","bootstrap"]},lazylinepainter:{deps:["jquery","bootstrap"]},blueImpGallery:{deps:["jquery"]}}});var mainScriptPath=document.body.getAttribute("data-script");requirejs([mainScriptPath]); \ No newline at end of file diff --git a/build_js/app/client.js b/build_js/app/client.js deleted file mode 100644 index afe4897a..00000000 --- a/build_js/app/client.js +++ /dev/null @@ -1 +0,0 @@ -define(["jquery"],function(e){var t=function(){n()},n=function(){e.ajax({url:"http://localhost/exodus4d/pathfinder/",headers:{foo:"bar"},complete:function(){alert(this.headers.foo)}})};return{getClient:t}}); \ No newline at end of file diff --git a/build_js/app/landingpage.js b/build_js/app/landingpage.js deleted file mode 100644 index 9830cf0c..00000000 --- a/build_js/app/landingpage.js +++ /dev/null @@ -1,125 +0,0 @@ -/*! jQuery v1.11.2 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ - -/*! - * Bootstrap v3.3.0 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -/** - * bootbox.js v4.4.0 - * - * http://bootboxjs.com/license.txt - */ - -/*! VelocityJS.org (1.2.1). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */ - -/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */ - -/* VelocityJS.org UI Pack (5.0.3). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License. Portions copyright Daniel Eden, Christian Pucci. */ - -/*! - * Validator v0.7.2 for Bootstrap 3, by @1000hz - * Copyright 2015 Cina Saffary - * Licensed under http://opensource.org/licenses/MIT - * - * https://github.com/1000hz/bootstrap-validator - */ - -/*! X-editable - v1.5.1 -* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery -* http://github.com/vitalets/x-editable -* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ - -/**! - * easyPieChart - * Lightweight plugin to render simple, animated and retina optimized pie charts - * - * @license - * @author Robert Fleischmann (http://robert-fleischmann.de) - * @version 2.1.6 - **/ - -/* - * blueimp helper JS 1.2.0 - * https://github.com/blueimp/Gallery - * - * Copyright 2013, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/* - * blueimp Gallery JS 2.14.1 - * https://github.com/blueimp/Gallery - * - * Copyright 2013, Sebastian Tschan - * https://blueimp.net - * - * Swipe implementation based on - * https://github.com/bradbirdsall/Swipe - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/* - * Bootstrap Image Gallery 3.0.1 - * https://github.com/blueimp/Bootstrap-Image-Gallery - * - * Copyright 2013, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*! - * VERSION: beta 1.9.4 - * DATE: 2014-07-17 - * UPDATES AND DOCS AT: http://www.greensock.com - * - * @license Copyright (c) 2008-2014, GreenSock. All rights reserved. - * This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for - * Club GreenSock members, the software agreement that was issued with your membership. - * - * @author: Jack Doyle, jack@greensock.com - **/ - -/*! - * VERSION: 1.13.1 - * DATE: 2014-07-22 - * UPDATES AND DOCS AT: http://www.greensock.com - * - * @license Copyright (c) 2008-2014, GreenSock. All rights reserved. - * This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for - * Club GreenSock members, the software agreement that was issued with your membership. - * - * @author: Jack Doyle, jack@greensock.com - */ - -/* - * Lazy Line Painter - * SVG Stroke animation. - * - * https://github.com/camoconnell/lazy-line-painter - * http://www.camoconnell.com - * - * Licensed under the MIT license. - * - */ - -!function(t,e){"object"==typeof module&&"object"==typeof module.exports?module.exports=t.document?e(t,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return e(t)}:e(t)}("undefined"!=typeof window?window:this,function(t,e){function i(t){var e=t.length,i=ae.type(t);return"function"===i||ae.isWindow(t)?!1:1===t.nodeType&&e?!0:"array"===i||0===e||"number"==typeof e&&e>0&&e-1 in t}function n(t,e,i){if(ae.isFunction(e))return ae.grep(t,function(t,n){return!!e.call(t,n,t)!==i});if(e.nodeType)return ae.grep(t,function(t){return t===e!==i});if("string"==typeof e){if(he.test(e))return ae.filter(e,t,i);e=ae.filter(e,t)}return ae.grep(t,function(t){return ae.inArray(t,e)>=0!==i})}function a(t,e){do t=t[e];while(t&&1!==t.nodeType);return t}function s(t){var e=we[t]={};return ae.each(t.match(be)||[],function(t,i){e[i]=!0}),e}function o(){fe.addEventListener?(fe.removeEventListener("DOMContentLoaded",r,!1),t.removeEventListener("load",r,!1)):(fe.detachEvent("onreadystatechange",r),t.detachEvent("onload",r))}function r(){(fe.addEventListener||"load"===event.type||"complete"===fe.readyState)&&(o(),ae.ready())}function l(t,e,i){if(void 0===i&&1===t.nodeType){var n="data-"+e.replace(Se,"-$1").toLowerCase();if(i=t.getAttribute(n),"string"==typeof i){try{i="true"===i?!0:"false"===i?!1:"null"===i?null:+i+""===i?+i:ke.test(i)?ae.parseJSON(i):i}catch(a){}ae.data(t,e,i)}else i=void 0}return i}function u(t){var e;for(e in t)if(("data"!==e||!ae.isEmptyObject(t[e]))&&"toJSON"!==e)return!1;return!0}function c(t,e,i,n){if(ae.acceptData(t)){var a,s,o=ae.expando,r=t.nodeType,l=r?ae.cache:t,u=r?t[o]:t[o]&&o;if(u&&l[u]&&(n||l[u].data)||void 0!==i||"string"!=typeof e)return u||(u=r?t[o]=X.pop()||ae.guid++:o),l[u]||(l[u]=r?{}:{toJSON:ae.noop}),("object"==typeof e||"function"==typeof e)&&(n?l[u]=ae.extend(l[u],e):l[u].data=ae.extend(l[u].data,e)),s=l[u],n||(s.data||(s.data={}),s=s.data),void 0!==i&&(s[ae.camelCase(e)]=i),"string"==typeof e?(a=s[e],null==a&&(a=s[ae.camelCase(e)])):a=s,a}}function p(t,e,i){if(ae.acceptData(t)){var n,a,s=t.nodeType,o=s?ae.cache:t,r=s?t[ae.expando]:ae.expando;if(o[r]){if(e&&(n=i?o[r]:o[r].data)){ae.isArray(e)?e=e.concat(ae.map(e,ae.camelCase)):e in n?e=[e]:(e=ae.camelCase(e),e=e in n?[e]:e.split(" ")),a=e.length;for(;a--;)delete n[e[a]];if(i?!u(n):!ae.isEmptyObject(n))return}(i||(delete o[r].data,u(o[r])))&&(s?ae.cleanData([t],!0):ie.deleteExpando||o!=o.window?delete o[r]:o[r]=null)}}}function h(){return!0}function d(){return!1}function f(){try{return fe.activeElement}catch(t){}}function m(t){var e=Fe.split("|"),i=t.createDocumentFragment();if(i.createElement)for(;e.length;)i.createElement(e.pop());return i}function g(t,e){var i,n,a=0,s=typeof t.getElementsByTagName!==Te?t.getElementsByTagName(e||"*"):typeof t.querySelectorAll!==Te?t.querySelectorAll(e||"*"):void 0;if(!s)for(s=[],i=t.childNodes||t;null!=(n=i[a]);a++)!e||ae.nodeName(n,e)?s.push(n):ae.merge(s,g(n,e));return void 0===e||e&&ae.nodeName(t,e)?ae.merge([t],s):s}function v(t){Pe.test(t.type)&&(t.defaultChecked=t.checked)}function y(t,e){return ae.nodeName(t,"table")&&ae.nodeName(11!==e.nodeType?e:e.firstChild,"tr")?t.getElementsByTagName("tbody")[0]||t.appendChild(t.ownerDocument.createElement("tbody")):t}function b(t){return t.type=(null!==ae.find.attr(t,"type"))+"/"+t.type,t}function w(t){var e=Ye.exec(t.type);return e?t.type=e[1]:t.removeAttribute("type"),t}function C(t,e){for(var i,n=0;null!=(i=t[n]);n++)ae._data(i,"globalEval",!e||ae._data(e[n],"globalEval"))}function x(t,e){if(1===e.nodeType&&ae.hasData(t)){var i,n,a,s=ae._data(t),o=ae._data(e,s),r=s.events;if(r){delete o.handle,o.events={};for(i in r)for(n=0,a=r[i].length;a>n;n++)ae.event.add(e,i,r[i][n])}o.data&&(o.data=ae.extend({},o.data))}}function T(t,e){var i,n,a;if(1===e.nodeType){if(i=e.nodeName.toLowerCase(),!ie.noCloneEvent&&e[ae.expando]){a=ae._data(e);for(n in a.events)ae.removeEvent(e,n,a.handle);e.removeAttribute(ae.expando)}"script"===i&&e.text!==t.text?(b(e).text=t.text,w(e)):"object"===i?(e.parentNode&&(e.outerHTML=t.outerHTML),ie.html5Clone&&t.innerHTML&&!ae.trim(e.innerHTML)&&(e.innerHTML=t.innerHTML)):"input"===i&&Pe.test(t.type)?(e.defaultChecked=e.checked=t.checked,e.value!==t.value&&(e.value=t.value)):"option"===i?e.defaultSelected=e.selected=t.defaultSelected:("input"===i||"textarea"===i)&&(e.defaultValue=t.defaultValue)}}function k(e,i){var n,a=ae(i.createElement(e)).appendTo(i.body),s=t.getDefaultComputedStyle&&(n=t.getDefaultComputedStyle(a[0]))?n.display:ae.css(a[0],"display");return a.detach(),s}function S(t){var e=fe,i=Je[t];return i||(i=k(t,e),"none"!==i&&i||(Ze=(Ze||ae("