1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-24 15:23:11 +02:00

946 Commits

Author SHA1 Message Date
Ryan Cramer
b429de71e3 Bump version to 3.0.164 2020-07-31 13:51:05 -04:00
Ryan Cramer
abc9cd8adc Fix issue processwire/processwire-issues#941 2020-07-31 09:32:04 -04:00
Ryan Cramer
4cc8e56e9d Add system update 18 for processwire/processwire-issues#1171 2020-07-31 08:06:14 -04:00
Ryan Cramer
45bf8e2bca Fix issue processwire/processwire-issues#1221 2020-07-31 08:00:31 -04:00
Ryan Cramer
b855b1f6f5 Fix issue processwire/processwire-issues#1136 plus some other minor unrelated adjustments 2020-07-30 15:45:44 -04:00
Ryan Cramer
7503ecd781 Fix issue processwire/processwire-issues#973 2020-07-30 12:04:37 -04:00
Ryan Cramer
6d8d504d44 Fix issue processwire/processwire-issues#887 2020-07-29 14:57:44 -04:00
Ryan Cramer
c339bd5fd2 Fix issue processwire/processwire-issues#1115 2020-07-28 16:18:13 -04:00
Ryan Cramer
95203ac8f2 Fix issue processwire/processwire-issues#1128 2020-07-28 15:29:56 -04:00
Ryan Cramer
a852f74641 Fix issue processwire/processwire-issues#1219 2020-07-28 14:57:37 -04:00
Ryan Cramer
ff1d63c0d4 Fix issue processwire/processwire-issues#1175 2020-07-27 16:38:53 -04:00
Ryan Cramer
7391f48c28 Attempt fix for issue processwire/processwire-issues#1179 2020-07-27 15:01:13 -04:00
Ryan Cramer
662734e405 Fix issue processwire/processwire-issues#1217 2020-07-27 14:29:43 -04:00
Ryan Cramer
475ae801d1 Fix issue processwire/processwire-issues#1082 as well as improve error handling when one places a fieldset closer before its opener 2020-07-27 13:07:40 -04:00
Ryan Cramer
b466e4fb99 Fix the jammed table salt shaker 2020-07-25 09:33:08 -04:00
Ryan Cramer
65d9e280ca Bump version to 3.0.163 2020-07-24 15:15:14 -04:00
Ryan Cramer
dd15aa9adb Update ProcessModule so that module installation options are now configurable from new $config->moduleInstall array. Plus update in-module instructions to describe how to install a module manually. 2020-07-24 14:57:30 -04:00
Ryan Cramer
380583a92c Various minor updates 2020-07-24 14:44:39 -04:00
Ryan Cramer
ecb58f988a Fix issue processwire/processwire-issues#1206 2020-07-24 14:35:34 -04:00
Ryan Cramer
88437e42cc Fix issue processwire/processwire-issues#1216 2020-07-24 14:21:58 -04:00
Ryan Cramer
4b1eefa9d0 Update JqueryWireTabs module to use jquery.cookie.js rather than settings it's own, hopefully to fix issue processwire/processwire-issues#1188 2020-07-24 14:15:59 -04:00
Ryan Cramer
4c8b474c29 Update jquery.cookie.js to support SameSite attribute along with 'Lax' as default value to suppress warnings from Firefox. Plus update it to ask ProcessWire for default of 'secure' value on cookies. 2020-07-24 14:11:20 -04:00
Ryan Cramer
efe60a17ba Add note in .htaccess for processwire/processwire-issues#1193 2020-07-24 12:32:25 -04:00
Ryan Cramer
b40a9e0de6 Fix issue processwire/processwire-issues#1197 2020-07-24 11:26:19 -04:00
Ryan Cramer
81686d9864 Some minor refactoring in ProcessPageList. Should also fix issue processwire/processwire-issues#1200 2020-07-24 11:14:32 -04:00
Ryan Cramer
ada963a2f1 Fix issue processwire/processwire-issues#1203 2020-07-24 08:51:13 -04:00
Ryan Cramer
8c5ed3e0b1 Add find() and findOne() methods to WireInputData class, enabling $input->post->find(), $input->get->find(), etc., supporting both wildcards and regex patterns 2020-07-23 16:54:07 -04:00
Ryan Cramer
c6c639c0db Move Tfa config inputfields in ProcessLogin to separate methods so that the inputfields can also potentially be used by other modules. 2020-07-23 16:11:24 -04:00
Ryan Cramer
77d6c0061e Various minor updates 2020-07-23 16:09:54 -04:00
Ryan Cramer
9f368febfc Add support for feature requested in processwire/processwire-issues#1208 2020-07-23 15:58:55 -04:00
Ryan Cramer
fe32580027 Fix issue processwire/processwire-issues#1212 2020-07-23 15:32:26 -04:00
Ryan Cramer
bea09627b2 Fix issue processwire/processwire-issues#1214 2020-07-23 12:56:21 -04:00
Ryan Cramer
d6b9ee8db8 Fix issue processwire/processwire-issues#1215 2020-07-23 12:51:53 -04:00
Ryan Cramer
7e3f10b2ff Attempt to fix issue processwire/processwire-issues#1118 2020-07-23 11:03:41 -04:00
Ryan Cramer
6a1b6a21ae Add new hooks: Pages::trashReady(), Pages::deleteBranchReady(), Pages::deletedBranch(). The deleteBranch hooks cover the case of recursive deletions where an entire branch of pages is deleted. For those cases, these hooks can be more useful than the regular deleteReady() and deleted() hooks because those two are called for every single page, rather than just for the initiating page. 2020-07-17 14:43:33 -04:00
Ryan Cramer
63a59e2317 Bump version to 3.0.162 2020-07-10 14:32:40 -04:00
Ryan Cramer
74ac0ceb7a Addtion of some Page-specific convenience methods added to PageArray class 2020-07-10 12:56:08 -04:00
Ryan Cramer
01a607f7dc Updates in ProcessPageSearch and InputfieldPageAutocompete for support of new find operators, though additional code may still be necessary but this is a start 2020-07-10 12:52:12 -04:00
Ryan Cramer
6f1fddfedf Fix issue where a user with remembered-TFA was still getting a notice that they had to enable TFA 2020-07-10 12:49:22 -04:00
Ryan Cramer
cd44d8b7f2 Minor update in InputfieldImage JS to make jQuery migrator happy 2020-07-10 12:47:41 -04:00
Ryan Cramer
0cc2bf4b79 DB query updates in WireSaveableItems class 2020-07-10 12:46:55 -04:00
Ryan Cramer
6f4c21d5b9 Update WireDatabasePDOStatement class so that you can optionally use it even when not in debug mode 2020-07-10 12:45:54 -04:00
Ryan Cramer
8e0f2ed721 Add User::hasTfa() method to quickly identify if user has two-factor authentication enabled. Also added supporting code in Tfa class. 2020-07-10 12:44:16 -04:00
Ryan Cramer
e806cdb044 Add several new Selector compare types useful for runtime identification 2020-07-10 12:42:57 -04:00
Ryan Cramer
f0f37853ca Minor optimization for Page::isLoaded($fieldName) method 2020-07-10 12:41:26 -04:00
Ryan Cramer
f52a0685c2 Update Inputfield phpdoc to include the theme* properties recognized by AdminThemeUikit 2020-07-10 12:40:46 -04:00
Ryan Cramer
a474ffa8f9 Add new convenience method Field::getContext(); 2020-07-10 12:39:57 -04:00
Ryan Cramer
4f98dc974a Minor AdminThemeUikit updates plus move access certain shared admin theme labels into AdminTheme::getLabels() method 2020-07-10 12:34:15 -04:00
Ryan Cramer
9c2c5e986e Various minor theme updates 2020-07-10 12:32:08 -04:00
Ryan Cramer
f7879126da Attempt fix for issue mentioned by @Rayden in processwire/processwire-issues#1207 2020-07-09 11:06:19 -04:00
Ryan Cramer
f095f4aa77 Fix issue processwire/processwire-issues#1209 2020-07-09 10:46:55 -04:00
Ryan Cramer
5b285ebc8c Various fixes and improvements related to Selectors and the find operators 2020-07-06 14:39:49 -04:00
Ryan Cramer
6acb8028e3 Update JqueryCore so that setting $config->debug = 'dev'; makes it use a newer jQuery version and with jquery-migrate enabled. 2020-07-03 16:11:57 -04:00
Ryan Cramer
9389d4abdc Update lots of core .js files to update and replace deprecated jQuery calls 2020-07-03 16:02:07 -04:00
Ryan Cramer
598c702e1e Update InputfieldSelector to support new text search operators 2020-07-03 15:52:32 -04:00
Ryan Cramer
70f1a7cc8b Add new SelectorContainsAnyWordsExpand operator (~|+=) and update the getDescription() methods for most Selector classes to bring greater consistency and less translation. 2020-07-03 15:48:30 -04:00
Ryan Cramer
803e5c26d3 Add $sanitizer->word() method and upgrade $sanitizer->entitiesMarkdown() method to support escaped characters, making it possible to avoid collisions with basic markdown 2020-07-03 15:46:34 -04:00
Ryan Cramer
09c67d5478 Various minor updates 2020-07-03 15:45:34 -04:00
Ryan Cramer
e4d534747a Continued improvements to DatabaseQuerySelectFulltext class, especially to improve quality of results on query expansion operators 2020-07-03 15:42:25 -04:00
Ryan Cramer
10541997d4 Update WireTextTools with 2 new methods: getWordAlternates() and findReplaceEscapeChars() 2020-07-03 15:40:36 -04:00
Ryan Cramer
d53e2ef323 Bump version to 3.0.161 2020-06-26 15:49:42 -04:00
Ryan Cramer
4276a5d917 Update PageFinder to support altOperators in selectors. This enables you to use more than one operator in a selector. For example, if you wanted to first try to find pages matching a phrase (*=), and if it doesn't find any, fallback to find pages matching words (~=), you can just append the two opers together. i.e. "field*=~=value". This works for any operator combinations and you can stack as many as you need. 2020-06-26 09:43:43 -04:00
Ryan Cramer
ade079dea7 Continued refactoring and improvements to DatabaseQuerySelectFulltext class 2020-06-26 09:43:18 -04:00
Ryan Cramer
d69724123d Update to $sanitizer->wordsArray() method, plus minor updates to $sanitizer->selectorValue() method 2020-06-26 09:41:57 -04:00
Ryan Cramer
ac18dad59c Update Selectors class to support altOperators (alternate fallback operators for when first does not match) 2020-06-26 09:41:17 -04:00
Ryan Cramer
26710f262e Minor updates to Tfa class 2020-06-26 09:39:41 -04:00
Ryan Cramer
735c118910 Update ProcessProfile so that Tfa class can add hooks when needed 2020-06-26 09:38:49 -04:00
Ryan Cramer
f4642e5fd3 Improvements to SelectorContainsAdvanced class, add support for altOperators property to Selector class, add new copyTo() method to Selector class. 2020-06-26 09:37:54 -04:00
Ryan Cramer
133ea5b222 Update for processwire/processwire-issues#1198 2020-06-25 07:14:49 -04:00
Ryan Cramer
a2884184e1 Attempt fix for issue processwire/processwire-issues#1198 2020-06-25 07:09:51 -04:00
Ryan Cramer
0d1031582a Bump version to 3.0.160 2020-06-19 16:56:28 -04:00
Ryan Cramer
7efb9e8a4b Minor updates Selector, Selectors, Sanitizer 2020-06-19 12:59:39 -04:00
Ryan Cramer
c9c06f833a Add several new text-matching Selector classes and also some refactoring in main Selectors class 2020-06-19 12:48:18 -04:00
Ryan Cramer
1f293cc4f4 Add new $sanitizer->flatArray() and $sanitizer->wordsArray() methods, plus some improvements to existing array() method 2020-06-19 12:42:14 -04:00
Ryan Cramer
d79d9286b1 Various minor updates. Plus add a $database->isStopword() method and improve error logging in WireDatabasePDOStatement 2020-06-19 12:35:21 -04:00
Ryan Cramer
32e031ab7d Fix issue processwire/processwire-issues#1196 2020-06-17 09:08:42 -04:00
Ryan Cramer
857d5556ae Fix issue processwire/processwire-issues#1199 2020-06-17 08:39:13 -04:00
Ryan Cramer
8d7f2d133c Minor adjustment in Tfa class 2020-06-12 15:15:05 -04:00
Ryan Cramer
602ac0e1a5 Minor improvements to WireDatabasePDO. Make requests for ft_min_word_len use InnoDB equivalent when InnoDB engine is active. 2020-06-12 12:50:32 -04:00
Ryan Cramer
8239e0b11e Some refactoring in InputfieldCheckbox.module 2020-06-12 12:49:38 -04:00
Ryan Cramer
d9ed3058db Update ProcessLogin to support configuration of TFA auto-enable (forced two-factor auth). Also requires that TfaEmail module is installed. Can be configured in ProcessLogin module settings. 2020-06-12 12:48:27 -04:00
Ryan Cramer
b0736cf6cb Additional upgrades to Tfa class. Add support for auto-enable (supported by TfaEmail), which enables you as the admin to force users to use 2FA by having it automatically enabled for their account when they attempt login. Add support for Tfa modules to specify translatable name, title and summary (rather than relying upon module info settings). Make all text labels configuration settings. Add additional error checking. Use Inputfields JS API to automatically highlight 2FA config settings when user is going through the setup process. Expand Tfa class to allow for Tfa modules to optionally specify configuration settings for after Tfa is enabled. Add detection for if user’s pass changes between time Tfa code sent and time Tfa code entered. Make minor improvements to user Tfa selection and setup. 2020-06-12 12:41:39 -04:00
Ryan Cramer
c41987d1db Bump version to 3.0.159 2020-06-05 14:52:23 -04:00
Ryan Cramer
d99abac75c Upgrade the two-factor authentication system (Tfa) so that it now supports the ability to fingerprint and remember a user’s browser and other aspects, so that the user doesn't have to re-enter their TFA code on every login (optional). 2020-06-05 13:44:05 -04:00
Ryan Cramer
4c83f401ba Documentation improvements and minor optimizations to base Wire class 2020-06-05 08:28:45 -04:00
Ryan Cramer
855cfd7370 Various minor adjustments, typo and text fixes 2020-06-05 08:15:14 -04:00
Ryan Cramer
fd2fdd91f0 Update the $config->sessionFingerprint option to also support fingerprinting of browser http "accept" header, plus update documentation for this to use bitmask examples rather than predefined numbers. 2020-06-05 08:08:50 -04:00
Ryan Cramer
2de85fab4b Refactoring and documentation improvements to the WireInputDataCookie class which manages the $input->cookie API var. Contained in these updates is also a fix for processwire/processwire-issues#1053 2020-06-05 08:06:27 -04:00
Ryan Cramer
cd4bd7073c Fix formatting on ProcessLogin::getLoginLinks 2020-05-31 17:45:11 -04:00
Ryan Cramer
9b43b4fb96 Fix in DatabaseQuery::getDebugQuery() 2020-05-29 15:41:52 -04:00
Ryan Cramer
f307d375f1 Bump version to 3.0.158 2020-05-29 15:32:26 -04:00
Ryan Cramer
8fcde02f6c Fix issue processwire/processwire-issues#911 2020-05-29 14:53:38 -04:00
Ryan Cramer
9add4e5f82 Add support for $page->parents(true); which returns parents in reverse order (closest to furthest) rather than breadcrumb order. 2020-05-29 14:14:30 -04:00
Ryan Cramer
1af391f4db Refactor of Fieldtype::___savePageField() method that now exclusively uses bind values on inserts, consistent with last week's updates to its parent Fieldtype class savePageField method. 2020-05-29 14:12:07 -04:00
Ryan Cramer
1664bce8c7 Improvements to the Fieldtype::trimDatabaseSchema() method 2020-05-29 14:10:27 -04:00
Ryan Cramer
d2e381f987 Update numerous core classes to support the new wired() method, plus correct any found instances of the ProcessWire instance not being passed along to a Wire derived instance. 2020-05-29 14:09:17 -04:00
Ryan Cramer
365af73635 Add support for custom PDO statement class WireDatabasePDOStatement. This is used rather than PDOStatement when in debug mode, so that it can translate bind values to actual values in queries that are used in the debug mode query log. 2020-05-29 13:54:45 -04:00
Ryan Cramer
b4751481b8 Update DatabaseQuery class to support a getDebugQuery() method that returns the query (string) with all bind variables populated into the SQL 2020-05-29 13:48:26 -04:00
Ryan Cramer
44405eed8a Several improvements the Debug::timer, including several new methods to give more control over timing. 2020-05-29 13:44:58 -04:00
Ryan Cramer
c770f573be Update base Wire class to support a new wired() method that is called when the ProcessWire instance is assigned to the object. This usually happens immediately after __construct(), but depends on how the object was instantiated. This solves the problem of needing to access API variables in a constructor and potentially having the wrong API variable instance in a multi-instance environment. 2020-05-29 13:41:46 -04:00
Ryan Cramer
810cae271f Fix issue processwire/processwire-issues#1142 2020-05-24 07:32:58 -04:00
Ryan Cramer
0b0d372274 Fix issue with name matching regex not getting bundled in bind value processwire/processwire-issues#1173 2020-05-22 14:54:36 -04:00
Ryan Cramer
c0a867f9df Fix exception that was getting thrown during OR-group fulltext matching 2020-05-22 14:49:13 -04:00
Ryan Cramer
4d4dde64eb Bump version to 3.0.157 2020-05-22 14:14:54 -04:00
Ryan Cramer
5094a8ce8e Upgrade default htaccess.txt file with a new section for various firewall blocking rules as examples and link to 7G Firewall, plus some minor improvements to a few existing rules. 2020-05-22 13:43:28 -04:00
Ryan Cramer
7223ff2356 Update FieldtypeComments, FieldtypeOptions and FieldtypePage to take advantage of new PageFinder and Database class improvements 2020-05-22 13:35:58 -04:00
Ryan Cramer
9d7d8f66af Add new $sanitizer methods: line(), line(), trunc(), and improvements to intArray() 2020-05-22 13:32:58 -04:00
Ryan Cramer
db542b9ba2 Improvements to MarkupRSS core module, mostly with code tweaks and new phpdoc 2020-05-22 13:31:07 -04:00
Ryan Cramer
05899763e2 Update Pages and PagesLoader classes to take advantage of new Database class features 2020-05-22 13:30:06 -04:00
Ryan Cramer
bca53c5cf7 Some refactoring in PageFinder to utilize features with the new Database classes 2020-05-22 13:28:00 -04:00
Ryan Cramer
06acbe57a3 Major refactor/rewrite of ProcessWire's Database classes (DatabaseQuery, DatabaseQuerySelect, DatabaseQuerySelectFulltext) 2020-05-22 13:25:28 -04:00
Ryan Cramer
99f778f109 Update Fieldtype and FieldtypeMulti to use bind params in a couple spots where they weren't and could support it 2020-05-22 13:23:07 -04:00
Ryan Cramer
88e04129c7 Bump version to 3.0.156 2020-05-15 14:47:19 -04:00
Ryan Cramer
f11abdc99d Fix issue processwire/processwire-issues#1147 2020-05-15 12:24:47 -04:00
Ryan Cramer
0543548fdd Rewrite the $sanitizer->selectorValue() method to be more thorough and support more options. The previous selectorValue() method can also still be used by specifying [ 'version' => 1 ] in the $options array argument. This update also fixes processwire/processwire-issues#1152 2020-05-15 11:06:36 -04:00
Ryan Cramer
1da1068d60 Fix issue processwire/processwire-issues#1166 plus some unrelated minor tweaks 2020-05-15 07:06:10 -04:00
Ryan Cramer
3ba72d4508 Fix issue processwire/processwire-issues#1157 2020-05-14 16:08:21 -04:00
Ryan Cramer
4620c2ebff Fix issue processwire/processwire-issues#1164 2020-05-14 15:27:40 -04:00
Ryan Cramer
ebe352d5cc Attempt fix for processwire/processwire-issues#1165 2020-05-14 15:11:11 -04:00
Ryan Cramer
c191a84f7a Fix issue processwire/processwire-issues#1169 2020-05-14 14:25:19 -04:00
Ryan Cramer
6f6a301295 Update FieldtypeDatetime to support empty/not-empty operators in InputfieldSelector 2020-05-14 12:12:13 -04:00
Ryan Cramer
4633146e20 Minor updates to Modules class 2020-05-14 12:09:46 -04:00
Ryan Cramer
3aacccedb1 Update $pages API to add getID() and getByIDs() methods, and add $pages->parents() method for direct access to methods in new PagesParents class 2020-05-14 12:09:06 -04:00
Ryan Cramer
5b31d9939b Update PagesLoader with minor improvements for getById() method and add new getNativeColumnValue() method 2020-05-14 12:06:42 -04:00
Ryan Cramer
fd2a06d4b2 Continued updates to new PagesParents class and update PagesEditor to use it 2020-05-14 12:05:30 -04:00
Ryan Cramer
851969c9e0 Add new PagesParents helper class for $pages API, accessible at $pages->parents()->methods. This class takes over management of the pages_parents table and related functionality. 2020-05-13 15:40:01 -04:00
Ryan Cramer
c790d31b8f Add Selectors::getReservedChars() method to keep track of reserved characters and their usages in selectors 2020-05-01 16:40:41 -04:00
Ryan Cramer
99f5d59ce4 Some updates to base DatabaseQuery class to improve bound value support among other things 2020-05-01 16:39:34 -04:00
Ryan Cramer
b5c4980b3b Various minor adjustments 2020-05-01 16:34:03 -04:00
Ryan Cramer
d07ba2661d Update WireHttp to allow for custom "setopt" options when using CURL 2020-05-01 15:44:29 -04:00
Ryan Cramer
3ab31805c6 Fix issue processwire/processwire-issues#1148 2020-04-29 17:49:25 -04:00
Ryan Cramer
6ae08c901b Fix issue processwire/processwire-issues#1162 and maybe (?) processwire/processwire-issues#1086 2020-04-27 15:59:00 -04:00
Ryan Cramer
ca8e779402 Fix issue processwire/processwire-issues#1159 2020-04-27 15:35:57 -04:00
Ryan Cramer
1cd1976a38 Fix issue processwire/processwire-issues#1155 2020-04-27 15:17:52 -04:00
Ryan Cramer
a157e690fa Bump version to 3.0.155 2020-04-24 15:36:40 -04:00
Ryan Cramer
f4883a46eb phpdoc updates in FieldtypeSelector.module 2020-04-24 13:09:59 -04:00
Ryan Cramer
57f228b600 Add new $input->canonicalUrl() method and add lots more options for $input->urlSegment() method. Also add urlSegment1(), urlSegment2(), etc. methods for same arguments/support as urlSegment() method but focused on specific segment number. 2020-04-24 13:05:37 -04:00
Ryan Cramer
e6551c9b4f Minor improvements to $page->url() method options for URL segments and pagination numbers. 2020-04-24 13:00:08 -04:00
Ryan Cramer
e36667624f Fix typo in phpdoc of Template.php 2020-04-24 12:37:10 -04:00
Ryan Cramer
670916e730 Bump version to 3.0.154 2020-04-17 15:29:10 -04:00
Ryan Cramer
dde4c92b78 Update core Debug class to use PHP 7.3 hrtime() function for timers (rather than microtime), when available. Also add support for configuring timer precision via Debug::timerSetting('precision', $n) where $n is number of decimals. Default is 4. 2020-04-17 08:21:23 -04:00
Ryan Cramer
89dbf0f96c Several updates to core/LanguageFunctions.php with biggest being the option to programmatically replace translation text (whether multi-language support is installed or not), useful for very easily updating core/module output to say exactly what you want, and without needing to install multi-language modules. Also moved the __() configuration options to dedicated methods which makes it more clear to use, as well as much better for documentation purposes: wireLangEntityEncode(), wireLangTranslations(), wireLangReplacements(). 2020-04-17 08:06:02 -04:00
Ryan Cramer
f9337d77a5 Minor adjustments in Pageimage and Pagefile classes 2020-04-15 16:40:12 -04:00
Ryan Cramer
b44669397a Fix issue processwire/processwire-issues#1119 2020-04-15 16:37:09 -04:00
Ryan Cramer
6bd7901011 Fix issue processwire/processwire-issues#1122 2020-04-15 16:33:57 -04:00
Ryan Cramer
ed0f573432 Fix issue processwire/processwire-issues#1144 2020-04-15 16:28:48 -04:00
Ryan Cramer
29fbf0b2f7 Fix issue processwire/processwire-issues#1073 2020-04-15 16:19:23 -04:00
Ryan Cramer
20cdbea7a8 Fix issue processwire/processwire-issues#1143 2020-04-15 15:58:29 -04:00
Ryan Cramer
fcb3f6abdc Fix issue processwire/processwire-issues#1117 2020-04-15 15:44:49 -04:00
Ryan Cramer
64bb5b09ce Fix issue processwire/processwire-issues#1139 2020-04-15 15:37:37 -04:00
Ryan Cramer
b79c5c0e45 Adjustment to prevent irrelevant error messages from appearing when creating new images field 2020-04-15 07:06:37 -04:00
Ryan Cramer
b957a81846 Update CKEditor version to 4.14.0 2020-04-15 06:44:03 -04:00
Ryan Cramer
ff6ec3b1b5 Update InputfieldWrapper so that it supports the "name=field_name" => [ settings ] calls for setMarkup(), like in LoginRegisterPro 2020-04-13 16:18:19 -04:00
Ryan Cramer
00de278dc7 Update FieldtypeImage and Pageimage to support new searchable width, height and ratio properties in the DB schema. These will become more useful over time as files are uploaded/saved and new info populates. 2020-04-10 12:48:28 -04:00
Ryan Cramer
359baa14dc Update FieldtypeFile along with Pagefile and Pagefiles classes to support additional DB schema for filesize, created_users_id and modified_users_id. Also adds new related API methods. 2020-04-10 12:45:00 -04:00
Ryan Cramer
9cd3fa677b Add a Selectors::getOperators() method that provides a lot of options for retrieving info about available operators 2020-04-10 12:42:16 -04:00
Ryan Cramer
9040bdc8d4 Update base Selector class so that descending classes can self identify the type of comparison they are performing 2020-04-10 12:41:05 -04:00
Ryan Cramer
48bb7c1734 Add a new $database->columnExists() method, plus a couple other unrelated minor adjustments 2020-04-10 12:40:15 -04:00
Ryan Cramer
ac7c9da4d1 Update FieldtypeMulti to isolate some existing functionality into dedicated methods for better reusability 2020-04-10 12:38:21 -04:00
Ryan Cramer
9a1f842437 Additional upgrades to FieldtypeComments and its related classes 2020-04-03 09:18:39 -04:00
Ryan Cramer
d3bbf6577e Fix issue where WireShutdown in fatal error state didn't use http 500 header when debug mode was on 2020-04-03 09:13:14 -04:00
Ryan Cramer
1c68448267 Refactoring of and improvements to the core TemplateFile class 2020-04-03 08:34:30 -04:00
Ryan Cramer
03effce1bd Update Page class so that createdUser/modifiedUser properties can be accessed internally (i.e. $this->modifiedUser) from classes that extend Page class. Also minor adjustment to PagePathHistory DB schema (not necessary to change existing installs) 2020-03-27 16:12:17 -04:00
Ryan Cramer
3c767e3fda Remove these newly added methods for now to focus and evaluate a little later (WireFileTools) 2020-03-27 16:08:24 -04:00
Ryan Cramer
671041a37f Refactoring of ProcessLogin to allow for more customization of text labels and markup, plus add requested option to allow users to login by either email or name. 2020-03-27 15:37:52 -04:00
Ryan Cramer
891afa38d4 Update FieldtypeComments::find() method to support inclusion of page.parent_id or page.templates_id in comment search 2020-03-27 15:24:04 -04:00
Ryan Cramer
be81265ee7 Add feature request for $files->pathToUrl() and $files->urlToPath() methods 2020-03-25 09:29:49 -04:00
Ryan Cramer
78b7d198a7 Update HookEvent::removeHook($key) so that it also accepts itself (HookEvent instance) as the $key 2020-03-25 09:28:45 -04:00
Ryan Cramer
f54342a20c Fix issue in Paths.php where getting URL for 'http[ModuleName]' incorrectly returned error 2020-03-25 09:27:37 -04:00
Ryan Cramer
278d323acd Bump version to 3.0.153 2020-03-20 15:36:10 -04:00
Ryan Cramer
833736a1fc Refactoring and optimizations to PagesLoader class, plus addition of a new $pages->has('selector') API method that enables you to quickly check if a page exists matching a selector. This is an alternative to using the $pages->count() method which is what you would have previously used for this. But the $pages->has() method can do it more quickly than $pages->count(), if all you need to know is if a page exists. As a bonus, it returns the first found page ID, so this method essentially does the same thing as a $pages->getID() method (if such a method existed). 2020-03-20 15:29:51 -04:00
Ryan Cramer
1020fb84e7 Update PageFinder to support option for returning all columns in pages table for matching pages 2020-03-20 14:15:29 -04:00
Ryan Cramer
542e138e42 Refactoring of core Template class to improve, optimize and simplify it, plus add new dedicated methods for childTemplates(), parentTemplates(), and allowNewPages(). 2020-03-20 14:13:33 -04:00
Ryan Cramer
00b89f71b3 Prevent an exception from getting thrown in one part of Pageimage where it's preferable for the error not to be fatal 2020-03-13 13:05:34 -04:00
Ryan Cramer
ffc7e6fb86 Upgrade $pages->uncache() method so that it can also accept page IDs in addition to Page objects 2020-03-13 12:43:44 -04:00
Ryan Cramer
550fc26300 Major refactoring of ProcessWire's FieldtypeComments classes and addition of new CommentListCustom and CommentFormCustom classes for more markup control over comment lists and forms. Plus several new API methods and other upgrades for comments. 2020-03-13 12:41:36 -04:00
Ryan Cramer
63a588bf00 Fix caching issue when multiple date fields in InputfieldDatetime 2020-03-07 08:53:37 -05:00
Ryan Cramer
d793249f86 Bump version to 3.0.152 2020-03-06 16:49:48 -05:00
Ryan Cramer
273183ddfb Add support for custom Page classes 2020-03-06 14:13:22 -05:00
Ryan Cramer
15793931f4 Major improvements to InputfieldDateTime including a significant refactoring, adding support for HTML5 date/time input types, and a new date selection input using separate selects for month, day and year 2020-03-06 14:04:17 -05:00
Ryan Cramer
de5b7d9207 Update PageFinder and add dedicated abilty to include template IDs alongside page IDs as a finder option 2020-03-06 13:48:38 -05:00
Ryan Cramer
6ae314cb14 Documentation improvements to InputfieldToggle module 2020-03-06 13:47:35 -05:00
Ryan Cramer
19d8d944bf Refactoring of WireClassLoader class ($classLoader API var) plus improvements and ability to support autoloading via class Prefix or Suffix 2020-03-06 13:46:22 -05:00
Ryan Cramer
765f42a4dc Various minor adjustments, code plus improvements to ProcessSessionDB module 2020-03-06 13:45:36 -05:00
Ryan Cramer
f584ec5317 Move the requiredAttr Inputfield config option from InputfieldText into Inputfield so that it can be used on other Inputfield types 2020-02-28 16:38:55 -05:00
Ryan Cramer
60d62ea3a1 Bump version to 3.0.151 2020-02-21 15:35:37 -05:00
Ryan Cramer
3670031682 Fix issue processwire/processwire-issues#1091 2020-02-21 12:10:24 -05:00
Ryan Cramer
32f068c5ea Minor improvements to WireShutdown fatal error reporting, plus prevent some fatal errors from rendering twice and start converting some Exception only handlers to also accept Throwable (for PHP 7.x \Error or \Exception). 2020-02-21 11:28:45 -05:00
Ryan Cramer
695dd79db6 Add support for $config->imageSizes setting which lets you name and predefine image sizes/options that you can later refer to by name on any $image->size() call (via the width argument). 2020-02-21 11:23:02 -05:00
Ryan Cramer
ed05a4a356 Correct missing namespace from index.php (only matters if trying to multi-instance boot off of multiple PW index.php files) 2020-02-21 11:15:01 -05:00
Ryan Cramer
34cc4e9b0f Add support for retaining abandoned translations in __('text') calls. This enables you to change the value in __('text') _x('text', 'context'), $this->_('text'), etc. calls without abandoning the existing translation. To use, specify bracket PHP array syntax with 2 or more phrases you'll accept translations for, rather than automatically abandoning them. This is useful in cases where you need to change the text, but do not want to automatically lose any existing translations. For example, the call: __(['new text', 'old text']); will use a translation for 'old text' if 'new text' has not yet been translated. In the admin translation tools, it identifies these as "fallback" translations. See phpdoc notes in __() function for more details. 2020-02-14 15:36:40 -05:00
Ryan Cramer
af6a68e06d Update ProcessLogin to support login by email address for the admin. To use, you must enable the "unique" flag on your "email" field (Setup > Fields > email > Advanced), and then you can enable login-by-email in the ProcessLogin module settings. 2020-02-14 15:15:28 -05:00
Ryan Cramer
eedad3a742 Remove some redundant code from ProcessField and fix an issue with a duplicate id attribute in markup. 2020-02-14 14:50:43 -05:00
Ryan Cramer
d154a8de28 Minor fixes in WireArray, PaginatedArray and CommentStars classes 2020-02-14 14:49:59 -05:00
Ryan Cramer
93f422a2cf Update inputfields.js so that you can specify #find-fieldName as a URL fragment on pages containing an InputfieldForm and it will locate and highlight the field identified by fieldName. Also update $page->editUrl() function to accept a field name to find in the page editor, which uses the inputfields.js #find-fieldName added in this commit, i.e. $page->editUrl('body'); 2020-02-14 14:45:50 -05:00
Ryan Cramer
0e045ad45d Add new $field->editUrl() method 2020-02-14 14:43:55 -05:00
Ryan Cramer
032df04fe1 Bump version to 3.0.150 2020-02-07 13:42:02 -05:00
Ryan Cramer
df33867a44 Fix issue processwire/processwire-issues#1083 2020-02-07 13:12:38 -05:00
Ryan Cramer
d528f2f133 Update FieldtypeEmail and InputfieldEmail to support MySQL UNIQUE index option (available as a toggle on Advanced tab when editing field in admin) 2020-01-31 11:28:53 -05:00
Ryan Cramer
687ea08633 Add new FieldsTableTools class as a helper for managing field tables and their indexes. Intended for non-public API (internal core) use, but methods can be accessed $fields->tableTools() 2020-01-31 11:25:08 -05:00
Ryan Cramer
e983442197 Update ProcessPageLister for improved column labels when subfields are in use 2020-01-31 11:23:22 -05:00
Ryan Cramer
3d2648ef01 Update ProcessPageEdit so that it detects when the same error message might get shown twice and prevents it from happening 2020-01-31 11:21:08 -05:00
Ryan Cramer
92ef3bbbc3 Update RepeaterPage class for secondary detection of 'forField' when page exists in unexpected location 2020-01-31 11:19:55 -05:00
Ryan Cramer
1c3ba1024f Update WireException class to support setting message and code 2020-01-31 11:15:47 -05:00
Ryan Cramer
57a3a99ebd phpdoc-specific adjustments to FieldtypeCache.module 2020-01-29 06:01:46 -05:00
Ryan Cramer
b7dfe9da1e Add Fieldtype::isDeleteValue() method to isolate that particular logic outside of savePageField() method so it can more easily be overridden 2020-01-29 06:00:00 -05:00
Ryan Cramer
2adf4b0707 Updates to $config->fileContentTypes 2020-01-29 05:58:59 -05:00
Ryan Cramer
20be0c7fd5 Improvements to PageComparison is() and if() methods, minor optimization in WireData, and minor phpdoc fix in WireCache 2020-01-24 10:47:07 -05:00
Ryan Cramer
c5417d98ad Fix issue processwire/processwire-issues#1071 plus some other improvements to FieldtypeModule 2020-01-24 10:03:16 -05:00
Ryan Cramer
045f69d96d Add wireClassNamespace() function to accopmany existing wireClassName() function and others. Also improve wireEmpty() function to recognize objects that can be cast to empty strings. 2020-01-24 10:01:32 -05:00
Ryan Cramer
57540cfab2 Fix issue processwire/processwire-issues#1064 2020-01-20 08:37:12 -05:00
Ryan Cramer
6bc706742e Fix issue processwire/processwire-issues#1065 2020-01-20 07:19:10 -05:00
Ryan Cramer
85c9e4f329 Fix issue processwire/processwire-issues#1067 2020-01-20 07:01:01 -05:00
Ryan Cramer
406bd7a2ea Fix issue processwire/processwire-issues#1066 2020-01-18 06:36:35 -05:00
Ryan Cramer
e2ace10dbc Bump version to 3.0.149 dev 2020-01-17 15:37:48 -05:00
Ryan Cramer
d68b8267b1 Various improvements to ProcessWire's $notices API var (Notices and Notice classes) 2020-01-17 11:35:01 -05:00
Ryan Cramer
51c1096c7b Several upgrades to ProcessCommentsManager. Lots of improvements here, but biggest functional additions are the ability to change the Page that any comment lives on, as well as the ability to change what comment another is replying to. 2020-01-17 11:30:12 -05:00
Ryan Cramer
bd7dd56f06 Various upgrades to the API of comments fields 2020-01-17 11:29:31 -05:00
Ryan Cramer
6b04c4e15a Update AdminThemeUikit search box to add a suggestion about typing "help" if you focus the input for a brief period without typing anything 2020-01-17 11:27:28 -05:00
Ryan Cramer
f5f83e8148 Fix issue processwire/processwire-issues#1062 2020-01-13 14:49:28 -05:00
Ryan Cramer
a4f036cb45 Fix issue processwire/processwire-issues#1060 2020-01-13 14:41:15 -05:00
Ryan Cramer
11ed423bca Fix issue processwire/processwire-issues#1061 2020-01-13 14:24:44 -05:00
Ryan Cramer
abc32780e3 Update ProcessForgotPassword to have a dedicated hookable renderForm() method for simpler hook modifications 2020-01-13 08:50:45 -05:00
Ryan Cramer
b8dcdb7758 Fix issue in User::editUrl() where it wasn't passing $options array to parent call 2020-01-13 08:49:30 -05:00
Ryan Cramer
0a1ec95a33 Update wireEmpty() function so that it also returns false when giving a NullPage or NullField object. Add WireNull interface for future Null objects. 2020-01-13 08:48:06 -05:00
Ryan Cramer
321ea0eed3 Update InputfieldPassword to support configurable placeholder attributes for old/new/confirm inputs via oldPassLabel, newPassLabel, confirmLabel properties that can be set from hooks. 2020-01-05 07:31:11 -05:00
Ryan Cramer
51629cdd5f Bump version to 3.0.148 2020-01-02 12:06:16 -05:00
Ryan Cramer
0f0d20c76b README file updates for new master version 2020-01-02 11:50:54 -05:00
Ryan Cramer
49df4445e2 Suppress missing root .htaccess file error in SystemUpdateChecks since the checks wouldn't even be able to run if an .htaccess or suitable replacement file wasn't already present. Fixes processwire/processwire-issues#1059 2020-01-02 06:02:49 -05:00
Ryan Cramer
6b177d0d0d Add InputfieldImage CSS fixes per processwire/processwire-issues#1048 2019-12-31 14:31:02 -05:00
Ryan Cramer
22808c316f Add a few to-do code snippets, comments and notes for 3.0.150+ (next dev branch after master merge) 2019-12-31 14:22:33 -05:00
Ryan Cramer
a0ddedc005 Update SessionHandlerDB to support utf8mb4 charset for its table when $config->dbCharset specifies it. 2019-12-31 14:05:15 -05:00
Ryan Cramer
e7c9a1b79f Fix issue where wrong directory separator slash could end up in language translation files 2019-12-27 15:57:12 -05:00
Ryan Cramer
7d4ef4b786 Add WireLog::disable() and WireLog::enable() functions to temporary disable (and later enable) a log during runtime 2019-12-27 15:55:08 -05:00
Ryan Cramer
deb2065465 Add support for a 'type' option in wireBytesStr() function, enabling you to force return value in bytes, mb, kb, gb rather than auto-detected. 2019-12-27 15:52:06 -05:00
Ryan Cramer
12dbe3f250 Fix issue processwire/processwire-issues#1056 2019-12-27 13:39:31 -05:00
Ryan Cramer
4bcb824e9b Fix issue processwire/processwire-issues#1057 2019-12-27 13:35:00 -05:00
Ryan Cramer
9a9bdb464a Minor tweaks in WireHooks, ProcessForgotPassword and SessionLoginThrottle 2019-12-20 15:06:57 -05:00
Ryan Cramer
a11403b913 Fix issue processwire/processwire-issues#1047 2019-12-20 12:31:06 -05:00
Ryan Cramer
6bae4780f0 Add suggestion from processwire/processwire-issues#1050 2019-12-20 11:59:59 -05:00
Ryan Cramer
8762b7a77a Fix issue processwire/processwire-issues#1051 2019-12-20 11:56:07 -05:00
Ryan Cramer
3bf29d050b Add support for configuring the useAutocomplete threshold in InputfieldSelector via a maxSelectOptions setting 2019-12-08 07:17:19 -05:00
Ryan Cramer
9b624b6602 Bump version to 3.0.147 2019-12-06 14:14:45 -05:00
Ryan Cramer
103adbc0e1 Fix issue processwire/processwire-issues#1046 2019-12-06 14:05:45 -05:00
Ryan Cramer
f95fa3e3a3 Fix issue processwire/processwire-issues#1045 2019-12-06 14:02:04 -05:00
Ryan Cramer
8e91a05fd1 Fix issue processwire/processwire-issues#1043 2019-12-06 13:51:49 -05:00
Ryan Cramer
987d23154d Fix issue processwire/processwire-issues#774 2019-12-03 11:55:49 -05:00
Ryan Cramer
a6e3626992 phpdoc typo fixes per processwire/processwire-issues#797 2019-12-03 11:11:19 -05:00
Ryan Cramer
b5aca0e964 Fix issue processwire/processwire-issues#1042 2019-12-03 10:51:42 -05:00
Ryan Cramer
3717224a5c Fix issue processwire/processwire-issues#1041 2019-12-03 10:44:23 -05:00
Ryan Cramer
71e9c6114e Update HTMLPurifier version to 4.12.0 2019-12-03 10:42:17 -05:00
Ryan Cramer
c81566db7e Fix issue processwire/processwire-issues#1040 2019-12-03 09:57:36 -05:00
Ryan Cramer
b1b968c6b5 Fix issue processwire/processwire-issues#1038 2019-12-03 09:44:40 -05:00
Ryan Cramer
5daa38729a A couple of small improvements to ProcessPageLister, including improved editable() check of mock Page per template that also now includes parent in the check, and improved debug output of selector that now includes modifications to selector made by PageFinder 2019-12-03 08:41:37 -05:00
Ryan Cramer
8a5a4b4461 Add a getNonEmptyValue() method to LanguagesPageFieldValue class and make use of it in ProcessPageEdit to avoid situations where headline was otherwise blank 2019-12-03 08:40:51 -05:00
Ryan Cramer
346218a524 Various minor updates 2019-12-03 08:40:08 -05:00
Ryan Cramer
fd89b7cebd Bump version to 3.0.146 2019-11-22 14:12:51 -05:00
Ryan Cramer
72713948fa Some minor improvements to the Tfa class 2019-11-22 14:06:25 -05:00
Ryan Cramer
8455e497b7 Various unrelated minor updates to numerous classes/files (phpdoc, typo fixes, and related) 2019-11-21 11:25:58 -05:00
Ryan Cramer
efb8f1f2e9 Update documentation for PageRender::renderPage() method per processwire/processwire-issues#987 2019-11-21 09:34:34 -05:00
Ryan Cramer
4656672c81 Fix issue processwire/processwire-issues#1030 2019-11-21 08:59:30 -05:00
Ryan Cramer
14803e22a8 Fix issue processwire/processwire-issues#1031 2019-11-21 08:39:01 -05:00
Ryan Cramer
fd7917c7d1 Fix issue processwire/processwire-issues#1012 2019-11-20 10:54:51 -05:00
Ryan Cramer
4f054c3c19 Fix issue processwire/processwire-issues#1035 2019-11-20 10:08:28 -05:00
Ryan Cramer
719c2ef3a9 Fix issue processwire/processwire-issues#1029 2019-11-20 09:56:47 -05:00
Ryan Cramer
1aaef35474 Fix issue processwire/processwire#1016 and some related minor optimizations 2019-11-20 09:35:54 -05:00
Ryan Cramer
66258de9ee Update MarkupAdminDataTable to correct issue where some properties indicated as readable were not. Also update phpdoc to indicate they are readable but not writable. 2019-11-20 08:32:18 -05:00
Ryan Cramer
cf03946633 Fix issue processwire/processwire-issues#1033 2019-11-20 06:10:21 -05:00
Ryan Cramer
e296542640 Remove phpdoc sort property for Pagefile/Pageimage per processwire/processwire-issues#1034 2019-11-20 05:38:43 -05:00
Ryan Cramer
8c5d3aff72 Accompanies previous commit (forgot to include this file) processwire/processwire-issues#1032 2019-11-15 15:15:12 -05:00
Ryan Cramer
8d82308c4c Add support for feature request issue processwire/processwire-issues#1032 2019-11-15 15:14:15 -05:00
Ryan Cramer
7a41ffb089 Update $pages->touch() method to support specifying date type to update 'modified', 'created' or 'published'. Add $pages->editor()->addStatus(), removeStatus() and saveStatus() methods as simpler alternatives to the existing savePageStatus() 2019-11-15 14:40:57 -05:00
Ryan Cramer
00635bf01d Minor updates, phpdoc tweaks and such 2019-11-15 14:35:15 -05:00
Ryan Cramer
595429d425 Add a minimumAge config setting for PagePathHistory module per processwire/processwire-issues#1014 and add an option for throwing a 404 while still allowing PagePathHistory to perform redirects (if it has any). Also add a function: wire404(); that you can call for this, in addition to: throw new Wire404Exception(true); 2019-11-12 12:06:08 -05:00
Ryan Cramer
4cc587bd1a Update comments fields to use custom 'CommentField' class rather than 'Field' class 2019-11-12 11:15:00 -05:00
Ryan Cramer
4eeca2eeeb Add ability for PageFinder and PagesLoader to retain additional info about selectors and PageFinder instance for debugging purposes 2019-11-12 11:07:39 -05:00
Ryan Cramer
0478c65938 Improvements to $modules->findByPrefix() method 2019-11-12 11:06:46 -05:00
Ryan Cramer
bbddcf1ca0 Add support for Fieldtypes to specify that a Field should use a custom class that extends the Field class, rather than always using the "Field" class. 2019-11-12 11:06:02 -05:00
Ryan Cramer
4e4b3afdcb Bump version to 3.0.145 2019-11-08 13:57:50 -05:00
Ryan Cramer
b972aab11b Various minor pending updates and optimiations to several classes 2019-11-08 10:29:58 -05:00
Ryan Cramer
dd87518987 Minor adjustments to various classes 2019-11-06 15:18:41 -05:00
Ryan Cramer
098e5d03d7 Some code improvements to PW installer 2019-11-06 15:13:36 -05:00
Ryan Cramer
7aa83e3e5f Update $sanitizer->testAll() to make hookable and add a couple more methods to it processwire/processwire-issues#85 2019-11-06 15:09:54 -05:00
Ryan Cramer
5887997698 Fix issue processwire/processwire-issues#1025 2019-11-05 11:56:28 -05:00
Ryan Cramer
a22e6287be Fix issue processwire/processwire-issues#1023 via @matjazpotocnik suggested fix 2019-11-05 11:08:40 -05:00
Ryan Cramer
c9713f0bc4 Fix issue processwire/processwire-issues#1010 add support for dependencies to InputfieldPageListSelectMultiple and InputfieldPageAutocomplete 2019-11-05 10:46:56 -05:00
Ryan Cramer
ceca16506a Fix issue processwire/processwire-issues#1015 2019-11-05 08:55:36 -05:00
Ryan Cramer
76943ac192 Optimizations in PageLoader class plus fix issue processwire/processwire-issues#1021 2019-11-05 07:35:22 -05:00
Ryan Cramer
b3337d0110 Bump version to 3.0.144 2019-11-01 09:58:44 -04:00
Ryan Cramer
d30a937b99 Fix issue processwire/processwire-issues#980 2019-11-01 06:19:10 -04:00
Ryan Cramer
51616e7393 Set default for mergeDups option in FileLog::save() method to 0 (disabled) per processwire/processwire-issues#1019 2019-11-01 05:59:12 -04:00
Ryan Cramer
69e2c0e729 Lots of upgrades to ProcessField overrides tab 2019-10-31 15:39:18 -04:00
Ryan Cramer
da36905422 Add new new full JS API for Inputfields, represented by 'Inputfields' JS var. See top of inputfields.js file for details. 2019-10-31 15:29:32 -04:00
Ryan Cramer
7d5cef5717 Add option for InputfieldCKEditor JS to support option for configuration via data attribute, useful for some dynamic insertion cases like in repeaters 2019-10-31 15:26:32 -04:00
Ryan Cramer
6208e42cac Add option to InputfieldCheckbox to enable it to render without labels (useful for when rendered as in first/last table column or similar situations) 2019-10-31 15:25:08 -04:00
Ryan Cramer
50e916b72f Add simple string diff markup generator to WireTextTools class, via diffMarkup() method 2019-10-29 06:11:10 -04:00
Ryan Cramer
80eb2ff3f1 Fix issue with bitwise operators not properly return true on $database->isOperator() call 2019-10-27 07:24:17 -04:00
Ryan Cramer
350b61d6ee Bump version to 3.0.143 2019-10-25 13:00:25 -04:00
Ryan Cramer
6fcc0502b6 Update $database->isOperator() method to allow for isolation or exclusion of bitwise operators 2019-10-25 11:07:42 -04:00
Ryan Cramer
4b8b06af0f Add new wireEmpty() function as more helpful PW alternative to PHP's empty() function, plus improvements to wireClassName() function 2019-10-25 10:59:46 -04:00
Ryan Cramer
7544e0b56f Fix issue processwire/processwire-issues#933 2019-10-25 10:53:50 -04:00
Ryan Cramer
063203af26 Fix issue processwire/processwire-issues#1011 2019-10-25 10:20:28 -04:00
Ryan Cramer
1d0dc756b6 Add support for custom type-specific Inputfield classes and attributes to be added via markup array in InputfieldWrapper 2019-10-25 10:06:18 -04:00
Ryan Cramer
a3251d8571 Fix issue processwire/processwire-issues#1008 2019-10-25 09:33:07 -04:00
Ryan Cramer
4012949146 Fix issue processwire/processwire-issues#1009 2019-10-25 09:25:48 -04:00
Ryan Cramer
7d4ca45673 Fix issue processwire/processwire-issues#1000 2019-10-21 09:04:34 -04:00
Ryan Cramer
8d12cb2340 Fix issue processwire/processwire-issues#1005 2019-10-21 06:15:30 -04:00
Ryan Cramer
a7a91b668e Fix issue processwire/processwire-issues#1003 2019-10-18 10:59:11 -04:00
Ryan Cramer
34bc5094b0 Fix issue processwire/processwire-issues#1001 2019-10-18 10:37:59 -04:00
Ryan Cramer
0154f9defb Fix a couple of multilanguage issues with the new file/image custom fields options 2019-10-18 09:52:58 -04:00
Ryan Cramer
2f9976b6f3 Updates/improvements to phpdoc in FieldtypeText 2019-10-18 06:19:24 -04:00
Ryan Cramer
7be5fb4a68 Refactor FieldtypeDatetime module so that it no longer extends FieldtypeText 2019-10-18 06:18:45 -04:00
Ryan Cramer
22fe5bf9ef Fix issue processwire/processwire-issues#992 2019-10-16 09:02:59 -04:00
Ryan Cramer
adbc5bf95a Partial update to modal.js per processwire/processwire-issues#993 2019-10-16 08:10:48 -04:00
Ryan Cramer
c57e29e0c4 Fix issue processwire/processwire-issues#996 2019-10-16 08:01:25 -04:00
Ryan Cramer
3e4e690040 Fix issue processwire/processwire-issues#998 2019-10-16 07:54:10 -04:00
Ryan Cramer
6ffa041c3f Fix issue processwire/processwire-issues#999 2019-10-16 06:12:14 -04:00
Ryan Cramer
d7596426f8 Fix issue processwire/processwire-issues#995 2019-10-16 05:41:45 -04:00
Ryan Cramer
71a726f13d Fix issue processwire/processwire-issues#991 2019-10-15 12:23:18 -04:00
Ryan Cramer
d4dca0ed73 Attempt fix for issue processwire/processwire-issues#989 2019-10-15 11:47:51 -04:00
Ryan Cramer
3be2c31d41 Attempt to fix issue processwire/processwire-issues#973 plus add support for partial string matching operators %= and ^= in dates 2019-10-15 09:48:46 -04:00
Ryan Cramer
42e87fef74 Add some extra descriptive text in ProcessRole for page-edit and page-view permissions. 2019-10-14 10:47:08 -04:00
Ryan Cramer
59ec2ee8cc Update FileLog to support some new save options, including the ability to collapse duplicate log ones to 1. Also update ProcessLogger to improve output in some instances. 2019-10-14 10:46:11 -04:00
Ryan Cramer
3702ef8408 Bump version to 3.0.142 2019-10-11 14:29:03 -04:00
Ryan Cramer
573048abb4 Add support for custom fields in file/image fields. Details and instructions coming on Friday, but if you want to preview, create a template with name "field-images" where the "images" part is the name of your file/image field. Add the fields to it that you want to represent your custom fields, and it's ready to use. Supports most core Fieldtypes (including multi-language) with the following exceptions that are not supported as custom fields in an File/Image field: CKEditor, Repeaters, PageTable, Files (nested), Images (nested), Comments, Cache, Selector. 2019-10-09 12:01:19 -04:00
Ryan Cramer
eae15ce88a Update ProcessPageEdit so that submit dropdown actions are hookable 2019-10-09 11:54:37 -04:00
Ryan Cramer
e0e7f6eae6 Update InputfieldPageAutocomplete so that its icon is better positioned when the field initially starts out hidden 2019-10-09 11:53:36 -04:00
Ryan Cramer
6b20f429a9 Various minor updates and optimizations to several core classes 2019-10-09 11:52:36 -04:00
Ryan Cramer
0a01b472a1 Update InputfieldAsmSelect to reduce number of options needed in ProcessWire.config, isolating options common to all InputfieldAsmSelect instances from those unique to individual instances 2019-10-04 10:45:24 -04:00
Ryan Cramer
655c4cdd24 Update markup regions to support nested pw-optional regions as well as include removed pw-optional regions in debug info per processwire/processwire-issues#883 and processwire/processwire-issues#984 2019-09-30 09:51:25 -04:00
Ryan Cramer
3c11bbf1d8 Fix issue processwire/processwire-issues#982 2019-09-27 11:44:20 -04:00
Ryan Cramer
869c6f4a60 Fix issue processwire/processwire-issues#979 2019-09-27 11:41:50 -04:00
Ryan Cramer
7bbf1a53b2 Add support for more automatic file inclusions during processwire status/state changes, and make it possible to customize the filenames for them, including the existing ones like ready.php, init.php, etc. Also some other related system level improvements. 2019-09-27 11:36:23 -04:00
Ryan Cramer
27235ca6b6 Fix issue processwire/processwire-issues#978 2019-09-26 15:20:30 -04:00
Ryan Cramer
5586dd074b Fix issue processwire/processwire-issues#970 2019-09-26 14:38:34 -04:00
Ryan Cramer
648d2731d5 Update to docs in MarkupPagerNav.module per processwire/processwire-issues#969 2019-09-26 12:16:46 -04:00
Ryan Cramer
1fe7172cd8 Fix issue processwire/processwire-issues#965 2019-09-26 11:31:27 -04:00
Ryan Cramer
aa50edcbb0 Update a non-wired WireArray to automatically inherit an added item's ProcessWire instance 2019-09-26 06:49:13 -04:00
Ryan Cramer
e634d7a4a5 Update PageArray phpdoc per processwire/processwire-issues#960 2019-09-26 06:43:42 -04:00
Ryan Cramer
6d8b1ff2e4 Fix issue processwire/processwire-issues#946 2019-09-25 07:46:41 -04:00
Ryan Cramer
f495baa911 Fix issue processwire/processwire-issues#942 2019-09-25 06:48:26 -04:00
Ryan Cramer
b80df87c82 Fix issue processwire/processwire-issues#939 2019-09-24 16:37:35 -04:00
Ryan Cramer
9f4807cf56 Fix issue processwire/processwire-issues#931 2019-09-24 12:30:11 -04:00
Ryan Cramer
6758fb525e Fix issue processwire/processwire-issues#930 2019-09-24 12:04:14 -04:00
Ryan Cramer
6c755a8a9c Optimization to Page::setQuietly() method to support more direct setting of integer base properties in Page::$settings 2019-09-24 11:43:23 -04:00
Ryan Cramer
aae7302283 Update PageArray setSelectors() and getSelectors() methods to support setting/getting as string as an alternate to previously only allowing a Selectors-instance 2019-09-24 11:42:08 -04:00
Ryan Cramer
a2fb255de0 Fix issue processwire/processwire-issues#927 use current created user for cloned pages rather than original created user 2019-09-24 09:34:08 -04:00
Ryan Cramer
8fbf63ce85 Update error message in Modules class to make it less ambiguous per processwire/processwire-issues#926 2019-09-24 08:47:51 -04:00
Ryan Cramer
6f71ff3f93 Bumping version to 3.0.141, some other updates in progress but will commit those next week 2019-09-20 15:40:23 -04:00
Ryan Cramer
b868320b70 Add $config->setLocation(), $config->setPath() and $config->setUrl() methods to allow runtime modification of system paths/URLs. 2019-09-20 10:48:12 -04:00
Ryan Cramer
710c222b5a Add a new Templates::fileModified() hookable method that is called whenever a change is detected to a template file. Plus update the Template and Templates class to make it possible for runtime modification of the templates path. 2019-09-20 10:46:47 -04:00
Ryan Cramer
7a76f4b857 Update InputfieldCheckbox to support label element attributes in output ($labelAttrs array), for API usage 2019-09-18 11:30:01 -04:00
Ryan Cramer
83b8b8c50f Update DatabaseQuerySelectFulltext class to suppor OR values when used outside of PageFinder (like when used directly from FieldtypeMulti) 2019-09-18 11:28:31 -04:00
Ryan Cramer
215e2c56e1 Update $input->cookie API variable so that it can now also set cookies (in addition to just getting them). Default cookie settings are controlled from new $config->cookieOptions array. 2019-09-18 11:26:42 -04:00
Ryan Cramer
e94f8bc089 Bump version to 3.0.140 2019-09-06 15:43:03 -04:00
Ryan Cramer
b01a7d77bd Additional updates to InputfieldToggle 2019-09-06 15:42:10 -04:00
Ryan Cramer
d37d38d8a2 Add a new FieldtypeToggle module to accompany the InputfieldToggle module so that you can create ProcessWire fields that use this type as an alternative to checkboxes (you can also convert existing checkbox fields to use this type) 2019-09-03 11:51:26 -04:00
Ryan Cramer
56acc68b83 Numerous updates/improvements to the new InputfieldToggle module 2019-09-03 11:51:05 -04:00
Ryan Cramer
86695f8499 Update FieldtypeMulti::savePageField to use InnoDB transactions when possible, plus add a new shortcut method: WireDatabasePDO::allowTransaction() 2019-09-03 11:49:58 -04:00
Ryan Cramer
38d66af6f1 Various minor updates 2019-09-03 11:48:48 -04:00
Ryan Cramer
51f554176d Add support for an Inputfield 'detail' property, similar to existing 'notes' property, but appears below it and in muted text. 2019-09-03 11:44:26 -04:00
Ryan Cramer
e2529912da Bump version to 3.0.139 2019-08-30 14:26:56 -04:00
Ryan Cramer
98b42a9958 Add support for inline column width adjustment when editing a template’s fields in ProcessTemplate 2019-08-30 14:25:56 -04:00
Ryan Cramer
68940a6a9c Minor adjustment to previous commit (removing development output) 2019-08-30 11:36:25 -04:00
Ryan Cramer
e94e10c631 Add new "Toggle" Inputfield module providing an often more useful alternative to the InputfieldCheckbox module. There is also an accompanying FieldtypeToggle, but it is still in development so probably won't be till next week's commits. 2019-08-30 11:34:07 -04:00
Ryan Cramer
f5d955ef5f Bump version to 3.0.138 2019-08-16 14:11:13 -04:00
Ryan Cramer
742ed9c479 Improvements to Comment and CommentList class to support hookable modification of individual comments during rendering 2019-08-16 14:10:25 -04:00
Ryan Cramer
0392e9babf Update htmlpurifier version to 4.11.0 2019-08-15 06:20:38 -04:00
Ryan Cramer
d5795c4bc3 Add methods/properties to Page class for querying file paths without referencing filesManager: hasFilesPath(), hasFiles(), filesPath(), filesUrl() 2019-08-15 05:44:24 -04:00
Ryan Cramer
8a148c80f4 Minor update to CommentList class when using {url} or {page.url} placeholders to make it remove a useless href attribute on any links when the page is unpublished or no longer exists. 2019-08-15 05:40:08 -04:00
Ryan Cramer
429b98023b Upgrade ImageSizer to provide a new getEngineInfo() method that enables you to get verbose information about all installed engines (such as supported source/target formats, and more) without having to supply a file to inspect. 2019-08-15 05:37:38 -04:00
Ryan Cramer
4a9a7fadb8 Fix issue where InputfieldSelector didn't properly recognize the 'pgesFindSelect' option from FieldtypePage/InputfieldPage (custom find) option 2019-08-10 10:25:54 -04:00
Ryan Cramer
8982c976c5 Various minor unrelated updates 2019-08-08 14:14:36 -04:00
Ryan Cramer
f22302aaaf Fix issue processwire/processwire-issues#952 2019-08-07 06:11:31 -04:00
Ryan Cramer
81c745c5ad Fix issue processwire/processwire-issues#953 2019-08-07 06:00:11 -04:00
Ryan Cramer
481eb7c545 Remove field name indicator from MarkupFieldtype.php output that wasn't intended to stay 2019-08-05 07:53:07 -04:00
Ryan Cramer
c2ee22b334 Fix issue processwire/processwire-issues#951 2019-08-05 05:48:59 -04:00
Ryan Cramer
e160920984 Fix php notice in new PageimageVariations.php class 2019-08-04 11:18:06 -04:00
Ryan Cramer
918b894d82 Bump version to 3.0.137 2019-08-02 14:25:28 -04:00
Ryan Cramer
d6c7273c63 Update the PagefileExtra class to support some additional properties related to file size 2019-08-02 10:32:42 -04:00
Ryan Cramer
0e9cc868b7 Minor refactoring in ProcessPageEditImageSelect, plus update the executeVariations() method to also show webp extras when applicable. 2019-08-02 10:30:57 -04:00
Ryan Cramer
2f6497d1e5 Add support for hooking multiple methods to the same event handler via addHook*() calls by separating the methods to hook with commas, or by providing an array rather than a string. Also updated the corresponding removeHook() method to support removing multiple in the same call. 2019-08-01 11:44:33 -04:00
Ryan Cramer
b179aa9afc Various improvements to asmSelect jQuery plugin, including better parent/child option support with technical improvements (now works with Safari too), and some code and documentation improvements as well. 2019-07-31 09:36:19 -04:00
Ryan Cramer
6479e78288 Minor refactor of Page::getFieldSubfieldValue() method 2019-07-31 09:34:11 -04:00
Ryan Cramer
3cb1f33a97 Minor improvements in core MarkupFieldtype class 2019-07-31 09:33:43 -04:00
Ryan Cramer
83369f1173 Some updates and refactoring in ProcessPageLister module 2019-07-31 09:32:51 -04:00
Ryan Cramer
d4318fbd2c Bump version to 3.0.136 2019-07-26 11:55:37 -04:00
Ryan Cramer
ddb4aebf60 Add new static Debug::backtrace() method to the Debug class. This returns a backtrace array that is simpler and more PW-specific than PHP's version. By default it excludes likely irrelevant (for most) hook-related method calls that usually fill up the backtrace. 2019-07-26 11:41:05 -04:00
Ryan Cramer
072536dc72 Update CKEditor version to 4.12.1 2019-07-26 05:59:01 -04:00
Ryan Cramer
ddc72b3aff Fix ProcessPageLister issue where it was resetting filters sometimes when it shouldn't 2019-07-21 07:11:42 -04:00
Ryan Cramer
960998cb46 Work-in-progress minor update to pwlink cke plugin 2019-07-06 18:02:33 -04:00
Ryan Cramer
851803396d Bump version to 3.0.135 2019-07-05 14:45:36 -04:00
Ryan Cramer
7b4fe18f85 Minor adjustment to SystemUpdaterChecks class 2019-07-05 11:54:23 -04:00
Ryan Cramer
5f705459a6 Improvements to Session and Notices classes 2019-07-05 11:53:49 -04:00
Ryan Cramer
161d6fb737 Upgrade ProcessWire installer (and related site profile files) to support specification of debug mode as one of the interactive installation options. Also updated some wording in various parts of the installer. 2019-07-05 11:35:25 -04:00
Ryan Cramer
562565ff42 Add system update #17 which adds a secondary layer of file protection with dedicated .htaccess files in various site directories that take over if the root .htaccess file ever goes missing 2019-07-04 10:52:37 -04:00
Ryan Cramer
b5b1a796ec Small improvements to ProcessWire installer 2019-07-04 10:47:58 -04:00
Ryan Cramer
5663803e05 Move post-login superuser system checks from ProcessLogin into their own class SystemUpdaterChecks as part of the SystemUpdater module 2019-07-04 10:43:24 -04:00
Ryan Cramer
05bbfe0ba1 Some additional minor .htaccess updates 2019-07-03 13:18:15 -04:00
Ryan Cramer
a336acfaa4 Improvements to SystemUpdater module so that it can support manual API calls 2019-07-03 12:20:27 -04:00
Ryan Cramer
0e00f24004 htaccess version update, including suggestions from Teppo, Netcarver, and several other updates while I was there. See the htaccess.txt file for upgrade instructions. 2019-07-02 14:31:08 -04:00
Ryan Cramer
c02420796d Improvements to LanguageSupportPageNames::languageAdded hook to to avoid potential of unnecessary error message appearing when adding language 2019-07-01 06:42:02 -04:00
Ryan Cramer
aaab673117 Minor optimization to static storage of Page runtime helper classes 2019-07-01 06:40:54 -04:00
Ryan Cramer
2b0d8f333a Some updates preparing for additonal webp options (new options not ready to use quite yet). 2019-06-30 11:01:23 -04:00
Ryan Cramer
f1913df4d4 Bump version to 3.0.134 2019-06-28 12:09:37 -04:00
Ryan Cramer
d1bfd2f0e8 Minor adjustments in ProcessPageLister.module 2019-06-28 12:08:58 -04:00
Ryan Cramer
04cfe51306 Fix issue processwire/processwire-issues#875 2019-06-28 11:05:35 -04:00
Ryan Cramer
277f16dba7 Fix issue processwire/processwire-issues#866 2019-06-27 13:53:01 -04:00
Ryan Cramer
46f62fa34f Fix issue processwire/processwire-issues#867 2019-06-27 12:31:23 -04:00
Ryan Cramer
52405a6a77 Fix issue processwire/processwire-issues#870 2019-06-27 12:26:59 -04:00
Ryan Cramer
e1a664826f Fix issue processwire/processwire-issues#871 2019-06-27 10:27:42 -04:00
Ryan Cramer
4d2e8faece Add feature request for InputfieldSubmit to support independent button text from button value per processwire/processwire-issues#877 2019-06-27 10:04:57 -04:00
Ryan Cramer
2b29ffaf75 Fix issue processwire/processwire-issues#878 2019-06-27 08:27:43 -04:00
Ryan Cramer
08551319c9 ProcessTemplate field wording update per processwire/processwire-issues#876 2019-06-26 12:11:40 -04:00
Ryan Cramer
7e0f2080e6 Fix issue processwire/processwire-issues#880 2019-06-26 11:30:32 -04:00
Ryan Cramer
fdd2e7216d Fix issue processwire/processwire-issues#884 2019-06-26 10:51:30 -04:00
Ryan Cramer
f6702407ff Fix issue processwire/processwire-issues#897 2019-06-26 10:43:03 -04:00
Ryan Cramer
ab94e7b682 Attempt fix for processwire/processwire-issues#907 2019-06-26 10:15:54 -04:00
Ryan Cramer
3024314680 Fix issue processwire/processwire-issues#905 2019-06-25 11:16:43 -04:00
Ryan Cramer
d193bb4fb1 Fix issue processwire/processwire-issues#910 2019-06-25 10:59:14 -04:00
Ryan Cramer
57e2137fc4 Fix issue processwire/processwire-issues#913 2019-06-25 07:54:27 -04:00
Ryan Cramer
63ed8bff65 Test commit 2019-06-23 07:16:16 -04:00
Ryan Cramer
35e3e4266a Fix issue processwire/processwire-issues#904 2019-06-21 11:10:59 -04:00
Ryan Cramer
49d0348b4a Fix issue processwire/processwire-issues#902 2019-06-21 11:09:53 -04:00
Ryan Cramer
903e6b3527 Improvements to InputfieldSelector so that it is no longer necessary to have separate "Field" and "Field..." options, as now there is just "Field..." with default behavior being the same as the previous "Field" but with the ability to select subfields. This makes selection quite a bit simpler and less verbose and and saves a step. 2019-06-20 10:47:01 -04:00
Ryan Cramer
734c56dbbe Improve operator extraction in Selectors class so that it doesn't potentially match non-existent operators and throw an unnecessary "unknown operator - selector value property escaped?" error message 2019-06-20 10:41:21 -04:00
Ryan Cramer
fdc1c61833 Improvements in PageFinder to handle some additional types of native field queries like partial match of parent.path, matching parent.status, and some others. 2019-06-20 10:27:52 -04:00
Ryan Cramer
3577b81591 Add PageFinder support for partial match operator on parent.path selectors like: parent.path%=something 2019-06-19 10:34:26 -04:00
Ryan Cramer
62b81164a0 Add some new option manipulation methods to InputfieldSelect.module, which are also inherited by all of PW's single/multi-selection Inputfields 2019-06-19 09:32:10 -04:00
Ryan Cramer
5f3e9af378 A few minor adjustments 2019-06-19 09:30:53 -04:00
Ryan Cramer
bccdc2ace4 Bump version to 3.0.133 2019-06-14 13:07:49 -04:00
Ryan Cramer
108f8c5511 Minor adjustments to ProcessPageLister and InputfieldSelector 2019-06-14 11:53:58 -04:00
Ryan Cramer
51c6becf9a Update asmSelect to support 1-level of parent/child relationships for selectable options 2019-06-14 11:51:20 -04:00
Ryan Cramer
eb299ee598 Add support for Lister user-specific bookmarks. This enables users to create their own private or shared bookmarks in Lister. 2019-06-12 15:37:26 -04:00
Ryan Cramer
d977cabb82 Add optional support for an executeUnknown() method to Process modules. This method is called when an unknown method is requested. It is not enabled by default, unless you add an executeUnknown() method to the Process module. 2019-06-12 15:34:18 -04:00
Ryan Cramer
2ba96d3f2b A couple of adjustments to ProcessPageLister module to correct issue of neverending spinner when ajax result was missing an expected row, plus improve the ajax spinner output, especially for AdminThemUikit which previously was not showing a spinner. 2019-06-10 09:03:18 -04:00
Ryan Cramer
f95b026a95 Improvements to boolean html attribute handling for Inputfield class setAttribute() method 2019-06-07 12:20:11 -04:00
Ryan Cramer
204335e2d3 Add support for $page->meta() method for maintaining persistent meta data for pages independent of fields and the normal load/save process. 2019-06-07 12:17:27 -04:00
Ryan Cramer
50a6f3585e Add InputfieldCheckbox checked() method as an optional shortcut for getting/setting checkbox state 2019-06-07 12:13:02 -04:00
Ryan Cramer
08bea26a17 Add WireDatabasePDO tableExists() method 2019-06-07 12:12:24 -04:00
Ryan Cramer
5235ab2a89 Add $session->getVal() methods which work the same as get() but let you speicfy the fallback value 2019-06-07 12:11:52 -04:00
Ryan Cramer
26435563d3 Add new $sanitizer->attrName() method and some other optimizations to Sanitizer class 2019-06-07 12:11:03 -04:00
Ryan Cramer
f6c210f686 Various minor updates 2019-06-07 12:08:55 -04:00
Ryan Cramer
a88c6a49c7 Fix issue where using crop/resize tools on an image within a NESTED repeater item would not save the changes after clicking the Save button in the page editor. 2019-06-04 09:30:35 -04:00
Ryan Cramer
ad2f60b544 Settings label typo fix in InputfieldRepeater.module 2019-05-26 08:39:37 -04:00
Ryan Cramer
4b733af0bf Fix issue with repeater item settings toggle and visibility (as used by RM) not working when ajax repeater item starts collapsed 2019-05-26 08:34:35 -04:00
Ryan Cramer
680d885abb Bump version to 3.0.132 2019-05-24 16:29:44 -04:00
Ryan Cramer
d8bbde74d7 A couple other ImageSizerEngine adjustments for webp 2019-05-24 14:22:07 -04:00
Ryan Cramer
50f8024ba3 Additional updates to PR #141 expanding WEBP support 2019-05-24 12:20:13 -04:00
Ryan Cramer
bbd3aba191 Merge branch 'horst-n-webP-support' into dev 2019-05-23 15:50:21 -04:00
Ryan Cramer
9964473776 Updates to @horst-n PR #141 for webp image support 2019-05-23 15:49:06 -04:00
Ryan Cramer
7f218c6a6b Merge branch 'webP-support' of https://github.com/horst-n/processwire into horst-n-webP-support 2019-05-22 14:49:45 -04:00
Ryan Cramer
0fd6a2aaed Update RepeaterPage class with methods to identify root forField and forPage in nested repeaters 2019-05-22 14:03:34 -04:00
Ryan Cramer
73f0c915de Various minor primarily phpdoc updates 2019-05-22 14:03:03 -04:00
Ryan Cramer
2b7f80d575 Several major improvements to WireTextTools::markupToText() method, plus related updates in Sanitizer and WireMail classes 2019-05-22 13:50:12 -04:00
Ryan Cramer
f1d5f12835 A couple of minor adjustments 2019-05-17 13:26:38 -04:00
Ryan Cramer
bac9736e49 Update WireHttp::sendFile() to support a 'data' option that lets you provide a string of content to send as a download, rather than pulling from existing file. 2019-05-17 13:21:12 -04:00
horst-n
6e580f71e1 changed the return of webpUrl()
to return the calculated URL, even if there is no webp copy available!
2019-05-04 18:42:11 +02:00
horst-n
88d4b11db4 added object as output format to the getDebugInfo
I figured out that this is useful to have for (automated) testing and debugging too. It is much easier to get a value for conditional coding via an object:
```
$dbgInfo = $image->getDebugInfo($options, 'object');
if($dbgInfo->engines->selectedEngine == ....
```
2019-05-04 11:21:58 +02:00
Ryan Cramer
f83b4ae259 Bump version to 3.0.131 2019-05-03 15:11:41 -04:00
Ryan Cramer
2880ff6d57 Various minor updates, mostly phpdoc related 2019-05-03 11:59:18 -04:00
Ryan Cramer
3a094f6447 Adjustment to WireTextTools::markupToText() method to prevent it from adding redundant newlines 2019-05-03 11:54:54 -04:00
Ryan Cramer
12f6253af6 Update WireHttp::sendFile() method to support partial downloads or ranges via HTTP_RANGE headers. Browsers use this particularly with media files (audio/video), especially Safari and iOS devices. But this also enables partial and pause/resume of any kind of downloads. 2019-05-03 11:40:29 -04:00
horst-n
d361c6c11e added a webp support check for engines
see: https://processwire.com/talk/topic/14236-webp-support/page/3/?tab=comments#comment-185020
2019-05-03 13:54:04 +02:00
horst-n
fa917b30e0 fixed a bug with internal checking
if a webp copy exists or not. Using $this->hasWebp() within the resize method doesn't work. Changed to test for file_exists of the temporary webp copy.
2019-05-02 10:37:08 +02:00
horst-n
b46bdfea94 updated font-family with more monospace fonts 2019-04-29 19:29:53 +02:00
horst-n
e8e2594d3e force new creation for condition
where the regular variation exists and should not be recreated, but where a webp copy, that do not exist, is requested too.
2019-04-29 17:30:06 +02:00
horst-n
540ae4f2d9 rearanged and grouped the output of getDebugInfo 2019-04-29 16:51:57 +02:00
Ryan Cramer
f2dbdb118e Fix issue processwire/processwire-issues#865 and processwire/processwire-issues#864 2019-04-29 10:07:49 -04:00
horst-n
53c414aa60 reworked the getDebugInfo method,
trying to make the code more readable.
But the main goal for me is, that the output is best readable! :)
2019-04-27 23:15:44 +02:00
horst-n
d062bc4345 added to phpdocs 2019-04-27 23:13:26 +02:00
horst-n
e6f4fbcb40 minor changes and cleaning phpdoc 2019-04-27 23:12:37 +02:00
horst-n
ba6803f425 refactored getDebugInfo() method 2019-04-27 13:21:07 +02:00
horst-n
32afdbdf52 cleaning up a bit 2019-04-27 11:55:50 +02:00
horst-n
1b96368aa2 cleaning and beautyfying 2019-04-27 10:46:39 +02:00
horst-n
a4431829d9 added method and property webpFilename 2019-04-27 10:37:10 +02:00
horst-n
ec2c2cf4e0 cleaning minor things and whitespaces
and added "Options Hierarchy" to the getDebugInfo method
2019-04-27 10:27:07 +02:00
horst-n
8da54040d4 added a verbose debug method
that can be called manually on every imagevariation and with individual options array.
2019-04-27 00:36:42 +02:00
horst-n
ebe7a5d80f added a verbose debug info method 2019-04-27 00:30:58 +02:00
horst-n
a6aa39d75d added webp params to imagesizerOptions 2019-04-26 19:01:05 +02:00
horst-n
9f01172a4b added 2 aliases
beginning with webp, as we also use two new options beginning with webp. Maybe this is better to avoid confusion.
webpAdd, webpQuality, webpUrl, webpSrc
2019-04-26 18:32:23 +02:00
horst-n
04135462c8 refactored webp creation and compression 2019-04-26 18:11:04 +02:00
horst-n
023a672e26 Update Pageimage.php 2019-04-25 11:01:57 +02:00
horst-n
232f66dedf minor changes 2019-04-25 11:01:31 +02:00
horst-n
35b9a403e1 rearranged code for webP saving 2019-04-25 11:01:13 +02:00
horst-n
4e534e0315 add property and method for webpAdd 2019-04-25 08:59:58 +02:00
horst-n
8a63886da1 remove debug code 2019-04-25 00:41:47 +02:00
horst-n
b531275a6e make more robust filename creation
and remove debug code
2019-04-25 00:41:14 +02:00
horst-n
f3f4e427b0 changed to more robust filename creation 2019-04-25 00:21:09 +02:00
horst-n
a5a101a456 documentation for webp properties 2019-04-24 23:40:16 +02:00
horst-n
7f132638d0 added properties for webp support
with the property $image->hasWebp we can detect, for example in a template file, if an image variation has a dependant webp file, so we can render conditional markup with srcset or picture elements.
And urlWebp and srcWebp for returning the URL.
2019-04-24 23:23:05 +02:00
horst-n
63018ab1cd add support for webp
added option "webpAdd" and "webpQuality" that allows to create a webp file as sidecar file when creating a JPEG, GIF or PNG file.
2019-04-24 21:32:05 +02:00
horst-n
bbe13583c5 add webp support to GD 2019-04-24 21:29:09 +02:00
horst-n
c1814c8e6b add option webpQuality 2019-04-24 21:28:33 +02:00
Ryan Cramer
6d38df65b4 Fix issue processwire/processwire-issues#857 2019-04-23 06:29:02 -04:00
Ryan Cramer
889f1e753d Fix issue processwire/processwire-issues#850 2019-04-23 06:07:37 -04:00
Ryan Cramer
6ed9385052 Add support for Comment::statusFeatured to InputfieldCommentsAdmin 2019-04-19 09:36:30 -04:00
Ryan Cramer
5cbe7d562d Upgrade ProcessCommentsManager to take better advantage of Uikit admin output and add support for featured comment status 2019-04-19 09:26:26 -04:00
Ryan Cramer
ebed9f6279 Add FieldtypeComments::count() method which does the same thing as the find() but instead returns a count. Plus add support for new Comment::statusFeatured status for support of featured comments 2019-04-19 09:23:40 -04:00
Ryan Cramer
f41c61e490 Upgrades and improvements to CommentFilterAkismet class 2019-04-19 09:22:46 -04:00
Ryan Cramer
2a63a44485 Minor adjustments 2019-04-19 09:19:03 -04:00
Ryan Cramer
761c7640c7 Add new $config->sessionForceIP property that lets you override the return value of $session->getIP() 2019-04-19 09:16:58 -04:00
Ryan Cramer
0b98667bc9 Fix issue processwire/processwire-issues#770 2019-04-17 11:11:02 -04:00
Ryan Cramer
f30e084e3d Fix issue processwire/processwire-issues#772 2019-04-17 05:59:58 -04:00
Ryan Cramer
e5582f5555 Fix issue processwire/processwire-issues#851 2019-04-16 10:17:05 -04:00
Ryan Cramer
72324fdbb1 Fix issue processwire/processwire-issues#854 2019-04-16 10:03:42 -04:00
Ryan Cramer
21164eaef5 Fix issue processwire/processwire-issues#852 2019-04-16 06:17:23 -04:00
Ryan Cramer
f0c79f38cf Additional update for processwire/processwire-issues#849 2019-04-16 06:08:52 -04:00
Ryan Cramer
3b8d92a975 Fix issue processwire/processwire-issues#849 2019-04-16 05:59:55 -04:00
Ryan Cramer
144ce409b1 Bump version to 3.0.130 2019-04-12 21:44:44 -04:00
Ryan Cramer
932ad94dc4 Add WireDatabaseBackup::dropAllTables() method for feature request processwire/processwire-requests#296 2019-04-12 10:46:51 -04:00
Ryan Cramer
099acacc61 Improvements to the __debugInfo() return values for some classes as well as those requested in processwire/processwire-issues#575 2019-04-12 10:28:23 -04:00
Ryan Cramer
ca94bd3958 Additional fix for processwire/processwire-issues#587 2019-04-12 06:19:54 -04:00
Ryan Cramer
da309f335b Fix issue processwire/processwire-issues#744 2019-04-11 12:23:01 -04:00
Ryan Cramer
12aede03fe Add new $cache->renderFile() method that works like $files->render() but caches the output according to WireCache rules. Also updated $files->render() to support a 'cache' option, which provides the same result. 2019-04-11 11:48:34 -04:00
Ryan Cramer
b0f11c15c5 Fix issue processwire/processwire-issues#847 2019-04-10 15:06:17 -04:00
Ryan Cramer
dcf226995c Improvements and minor refactoring in WireCache.php ($cache API var) 2019-04-10 11:55:17 -04:00
Ryan Cramer
d03ed24c75 Various phpdoc improvements 2019-04-10 05:58:23 -04:00
Ryan Cramer
612d886dd8 Typo fixes per processwire/processwire-issues#797 2019-04-09 06:16:13 -04:00
Ryan Cramer
3bed484c80 Fix issue processwire/processwire-issues#845 2019-04-09 06:08:10 -04:00
Ryan Cramer
a6cb458c06 Fix issue processwire/processwire-issues#826 2019-04-08 13:51:38 -04:00
Ryan Cramer
5af01e44d0 Fix issue processwire/processwire-issues#844 2019-04-08 06:22:13 -04:00
Ryan Cramer
da73398da1 Fix issue processwire/processwire-issues#843 2019-04-08 06:12:24 -04:00
Ryan Cramer
357ce2d642 Bump version to 3.0.129 2019-04-05 15:08:36 -04:00
Ryan Cramer
e2176d9446 Add $datetime->elapsedTimeStr() method to accompany existing relativeTimeStr() method. This one enables you to show difference in two dates/times rather than just relative to current date/time. 2019-04-05 15:05:05 -04:00
Ryan Cramer
d4ca0d6e45 Update SessionCSRF to use more purpose-specific WireRandom class rather than Password class for random string generation 2019-04-05 15:01:49 -04:00
Ryan Cramer
c58d00863a Add $sanitizer->httpUrl() method to accompany existing url() method 2019-04-05 15:01:14 -04:00
Ryan Cramer
b122456eb2 Fix issue processwire/processwire-issues#441 2019-04-05 13:12:18 -04:00
Ryan Cramer
e0b1c5c1bd Add update to accommodate different "not" logic in a new notAll() method for WireArray/PageArray per processwire/processwire-issues#383 2019-04-05 12:06:08 -04:00
Ryan Cramer
711b6b8f3d Add additional check to make sure one isn't deleting the page being viewed per processwire/processwire-issues#356 2019-04-05 11:03:31 -04:00
Ryan Cramer
6d7198f2a6 Add $config->installedAfter($date) and $config->installedBefore($date) methods 2019-04-05 11:02:28 -04:00
Ryan Cramer
45d6158be1 Fix issue processwire/processwire-issues#332 FieldsetTab was sometimes not auto-loading AJAX-loaded tab after form submit 2019-04-05 08:48:10 -04:00
Ryan Cramer
40c8eea7c6 update Fieldtypes::sort() method for new argument per processwire/processwire-issues#841 2019-04-02 14:04:42 -04:00
Ryan Cramer
8cdb9886e7 Fix phpdoc typos per processwire/processwire-issues#797 2019-04-02 11:29:18 -04:00
Ryan Cramer
dd183c0571 Fix issue processwire/processwire-issues#840 2019-04-02 11:24:29 -04:00
Ryan Cramer
e9620a3a47 Fix issue processwire/processwire-issues#726 2019-04-02 10:44:06 -04:00
Ryan Cramer
7793b79e03 Add support for PHP SORT_* constants per processwire/processwire-issues#841 2019-04-02 09:43:40 -04:00
Ryan Cramer
2e7db9c40f Fix issue processwire/processwire-issues#778 2019-04-01 11:49:10 -04:00
Ryan Cramer
7bd2e3fc7f Add versions of many PHP string functions to WireTextTools to abstract away the mb_string test and update Sanitizer to use them where appropriate 2019-04-01 11:32:04 -04:00
Ryan Cramer
dac7be6af4 Add support for email blacklists via $config->wireMail('blacklist') property 2019-04-01 11:31:07 -04:00
Ryan Cramer
b87404ade8 Bump version to 3.0.128 2019-03-29 15:34:22 -04:00
Ryan Cramer
d9ab9c56d5 Add $sanitizer->checkbox() method 2019-03-29 15:30:32 -04:00
Ryan Cramer
2a108b4cd0 Fix issue processwire/processwire-issues#239 (display Password description and notes in correct locations). Plus add support for disabling and/or relaxing some of the strict password requirements per a previous request (which I can't seem to find at the moment). 2019-03-29 11:49:01 -04:00
Ryan Cramer
478d299322 Add support for combined hooks that can be executed as either method or property per issue processwire/processwire-issues#232 2019-03-29 11:07:40 -04:00
Ryan Cramer
c4086084d8 Fix issue processwire/processwire-issues#214 and refactoring of dependent selects code in InputfieldPage.js 2019-03-29 10:11:35 -04:00
Ryan Cramer
bc917c6553 Enhancement/fix for processwire/processwire-issues#838 2019-03-28 10:31:32 -04:00
Ryan Cramer
ce8c369510 Fix issue processwire/processwire-issues#834 2019-03-28 05:52:44 -04:00
Ryan Cramer
f7b99d7676 Fix issue processwire/processwire-issues#837 2019-03-26 12:21:18 -04:00
Ryan Cramer
e0a96d40a2 Attempt fix for issue processwire/processwire-issues#835 2019-03-26 11:09:18 -04:00
Ryan Cramer
cf2be06dfc Add AdminThemeDefault/AdminThemeReno column widths fix as suggested by @Toutouwai in processwire/processwire-issues#306 plus add typo fix per processwire/processwire-issues#797 2019-03-26 11:00:11 -04:00
Ryan Cramer
f121425f0d Some updates to PagesType class and update all PagesType hooks from deprecated to fully functional per processwire/processwire-issues#360 2019-03-26 10:08:26 -04:00
Ryan Cramer
768b27f4c8 Fix issue in ProcessPageLister where images were not displaying if the gridMode was set to "list (verbose)", plus a couple of other minor related adjustments 2019-03-23 08:02:45 -04:00
Ryan Cramer
2cad3ed11b Various minor adjustments, but nothing interesting 2019-03-22 17:02:44 -04:00
Ryan Cramer
a2e7c35f95 Typo fix in AdminThemeUikit.module per processwire/processwire-issues#797 2019-03-21 13:05:32 -04:00
Ryan Cramer
505429e517 Fix issue processwire/processwire-issues#832 2019-03-21 11:12:01 -04:00
Ryan Cramer
40f35241e4 Fix issue processwire/processwire-issues#831 2019-03-21 10:14:03 -04:00
Ryan Cramer
1995067b96 Additional fix for issue processwire/processwire-issues#724 2019-03-21 10:06:48 -04:00
Ryan Cramer
02f05c6f67 Update to allow spaces in URL segments when allowed by config.pageNameWhitelist and config.pageNameCharset=UTF8 per processwire/processwire-issues#720 2019-03-21 09:19:01 -04:00
Ryan Cramer
7905fb5180 Add support for page.sort in PageFinder (DB selectors) per processwire/processwire-issues#496 2019-03-21 06:26:06 -04:00
Ryan Cramer
99cc9f8be7 Add support for noTrim option to InputfieldText/InputfieldTextarea per processwire/processwire-issues#459 2019-03-19 10:52:47 -04:00
Ryan Cramer
4ef5ebc8fb Fix issue processwire/processwire-issues#364 2019-03-19 10:29:36 -04:00
Ryan Cramer
cfb533869d Fix issue processwire/processwire-issues#334 2019-03-19 09:54:37 -04:00
Ryan Cramer
019057f846 Fix phpdoc typo in Module.php per processwire/processwire-issues#797 2019-03-19 09:35:45 -04:00
Ryan Cramer
2aa2342e18 Fix issue processwire/processwire-issues#768 2019-03-19 09:09:44 -04:00
Ryan Cramer
d72062a805 Fix issue processwire/processwire-issues#668 2019-03-19 08:48:05 -04:00
Ryan Cramer
40c05893ea Fix issue processwire/processwire-issues#828 2019-03-19 06:25:34 -04:00
Ryan Cramer
762439bcc3 Fix issue processwire/processwire-issues#766 2019-03-19 05:58:58 -04:00
Ryan Cramer
23208d1ade Update InputfieldSubmit to support disabling of populating selected dropdown value to submit button value 2019-03-17 07:43:20 -04:00
Ryan Cramer
6013f16c96 Small adjustment to liveSearch and missing 'n' property notice in last commit per @matjazpotocnik 2019-03-13 05:45:53 -04:00
Ryan Cramer
cbfe97de20 Upgrade ProcessPageSearchLive "view all" search results to use tabs rather than headings for improved usability 2019-03-11 12:48:42 -04:00
Ryan Cramer
68150887f6 Update JqueryWireTabs to add a render() method that provides an all-in-one tab rendering solution for the PW admin without any other setup/initialization needed. 2019-03-11 12:45:00 -04:00
Ryan Cramer
e427a03ff5 Fix issue processwire/processwire-issues#679 2019-03-08 11:54:08 -05:00
Ryan Cramer
a307d441d1 Add $sanitizer->minLength() method and another minor phpdoc typo fix per processwire/processwire-issues#797 2019-03-08 10:27:18 -05:00
Ryan Cramer
207b1ed26c Fix typo in phpdoc on WireDatabaseBackup per processwire/processwire-issues#797 2019-03-08 10:23:17 -05:00
Ryan Cramer
fc88d64a03 Additional fix for processwire/processwire-issues#817 2019-03-05 11:42:10 -05:00
Ryan Cramer
a7c3b001ce Attempt fix for issue processwire/processwire-issues#205 2019-03-05 09:15:10 -05:00
Ryan Cramer
1d9279f421 Fix issue processwire/processwire-issues#824 2019-03-04 11:44:30 -05:00
Ryan Cramer
621c34a417 Fix issue processwire/processwire-issues#124 2019-03-04 11:01:33 -05:00
Ryan Cramer
c41b670330 Fix issue processwire/processwire-issues#305 2019-03-04 10:54:07 -05:00
Ryan Cramer
b158b71c42 Update per processwire/processwire-issues#648 2019-03-04 10:10:58 -05:00
Ryan Cramer
cdb6dbaee1 Additional update for processwire/processwire-issues#183 also make repeaters editable to non-superusers with front-end editing options A and B. 2019-03-04 09:29:10 -05:00
Ryan Cramer
c38c166c67 Fix issue processwire/processwire-issues#817 2019-03-04 06:24:19 -05:00
Ryan Cramer
906640ecb7 Bump version to 3.0.127 plus some other minor updates 2019-03-01 15:38:48 -05:00
Ryan Cramer
ba8202d869 Improvements to PagesNames::pageNameFromFormat() method 2019-03-01 13:25:25 -05:00
Ryan Cramer
a43ea0ffa1 Fix issue processwire/processwire-issues#820 2019-03-01 11:58:30 -05:00
Ryan Cramer
8952afbddb Fix issue processwire/processwire-issues#183 allow for front-end editing of fields in repeater items, individually per-field by non-superusers with edit access to the page the owns the repeater field. 2019-03-01 11:36:52 -05:00
Ryan Cramer
731cc3e92e Fix issue processwire/processwire-issues#432 2019-03-01 09:39:00 -05:00
Ryan Cramer
be84331dbd Fix issue processwire/processwire-issues#818 where listable but not editable or viewable page in dropdown for Pages > Tree > [items] was navigating to some useless JSON output. Also disabled the fallback to view URL when page is viewable but not editable. 2019-03-01 09:21:18 -05:00
Ryan Cramer
b76deded68 Fix issue processwire/processwire-issues#821 front-edit modal "link" dialog in CKEditor was not respecting current language for link helper tools (autocomplete and PageList selection) 2019-03-01 06:28:10 -05:00
Ryan Cramer
7dbdcec2a0 Fix issue processwire/processwire-issues#819 2019-02-28 12:59:32 -05:00
Ryan Cramer
e73c2343d1 Add "New" dropdown menu option to ProcessModule per comment in processwire/processwire-issues#641 2019-02-28 12:26:25 -05:00
Ryan Cramer
fc2103b024 doc typo fixes per processwire/processwire-issues#797 2019-02-28 12:20:24 -05:00
Ryan Cramer
bb571b66c0 Fix issue processwire/processwire-issues#615 2019-02-28 12:10:18 -05:00
Ryan Cramer
a2ec801a1a Fix issue processwire/processwire-issues#795 2019-02-28 09:03:15 -05:00
Ryan Cramer
2f908e44e7 Add support for previously unimplemented status Page::statusUnique, which adds support for globally unique page names. This commit also changes the the previous commit Page::statusIncomplete to value 128 because it turns out 256 was used in a 3rd party module and it seemed safer to use 128, which was occuped by Page::statusVersions, which has never been used. I've also changed the name of Page::statusIncomplete to Page::statusFlagged since the status indicates an error occurred during last interactive save rather than specifically incomplete. 2019-02-27 11:55:08 -05:00
Ryan Cramer
8b82f48c4d Fix typo from earlier today for processwire/processwire-issues#252 2019-02-26 13:12:07 -05:00
Ryan Cramer
9af4df040e Update WireHooks to allow for (and ignore) an empty argument string "()" after hook definition (first argument) in addHook() methods. 2019-02-26 10:22:47 -05:00
Ryan Cramer
1b53ed15a9 Apply solution for processwire/processwire-issues#252 - publish via page tree was showing "pub" button even when page was missing requirements. Note that this solution takes effect only upon page save in the page editor, so will not affect pages in an existing installation that may not be meeting requirements. 2019-02-26 09:43:08 -05:00
Ryan Cramer
9a75980f8d Add ProcessPageListRender::getNumChildren() hook per issue/request processwire/processwire-issues#649 2019-02-26 08:39:29 -05:00
Ryan Cramer
706dd519af Fix issue in InputfieldPageAutocomplete where it was displaying the wrong icon for item-sort, and it was also wasn't positioned quite right if the autocomplete appeared in a non-default tab. 2019-02-26 08:07:45 -05:00
Ryan Cramer
c80ae0acd8 A few minor phpdoc/code comments 2019-02-22 17:45:20 -05:00
Ryan Cramer
c6410c6478 Update MarkupFieldtype to auto-link page references when page is viewable 2019-02-22 16:34:37 -05:00
Ryan Cramer
41e2bbd22b Some updates for processwire/processwire-issues#126 plus some improvements to ProcessLanguage and add a refresh link in the language translation phrase live-search 2019-02-22 07:43:47 -05:00
Ryan Cramer
f968137514 Apply additional fix for processwire/processwire-issues#539 2019-02-21 15:32:36 -05:00
Ryan Cramer
0326b1c67d Fix issue processwire/processwire-issues#773 2019-02-21 11:21:55 -05:00
Ryan Cramer
1aa2c55b9f Add support for children.custom_field=value selectors per processwire/processwire-requests#174 and processwire/processwire-issues#809 2019-02-21 11:10:57 -05:00
Ryan Cramer
5483804d49 Improve __debugInfo() in Selector and Selectors classes 2019-02-21 11:08:25 -05:00
Ryan Cramer
774c2152ee Update $sanitizer->selectorValue() method to also accept array for its value argument. When array ['a','b','c'] is present, it converts to a sanitized a|b|c string. 2019-02-21 09:58:31 -05:00
Ryan Cramer
65e283055a Update PaginatedArray::getPaginationString() to allow specifying an alternate label (zeroLabel) when there are zero items available. 2019-02-21 09:57:42 -05:00
Ryan Cramer
0c150eec42 Update Pageimage::render() method to accept manually defined alt or class attributes 2019-02-21 09:57:10 -05:00
Ryan Cramer
d49df28641 Fix issue processwire/processwire-issues#513 2019-02-21 05:31:00 -05:00
Ryan Cramer
16b69f4a10 Some improvements and fixes to the multi-language support in Lister (also affects ListerPro) 2019-02-16 10:16:33 -05:00
Ryan Cramer
2c3e949d6e Bump version to 3.0.126 plus some other minor adjustments 2019-02-15 14:31:26 -05:00
Ryan Cramer
33bac98b83 Fix issue processwire/processwire-issues#599 2019-02-15 10:16:02 -05:00
Ryan Cramer
126c1392d4 Fix additional phpdoc typos per processwire/processwire-issues#797 2019-02-14 08:01:39 -05:00
Ryan Cramer
a993a14de9 Additional adjustment for processwire/processwire-issues#811 2019-02-14 06:02:09 -05:00
Ryan Cramer
3f5773e674 Add new $page->if(condition, yes, no) convenience method 2019-02-13 14:53:59 -05:00
Ryan Cramer
b499aad60e Fix issue processwire/processwire-issues#811 2019-02-12 10:53:54 -05:00
Ryan Cramer
82d513ec54 Update for processwire/processwire-issues#358 to prevent port 443 from being appended to undefined $config->httpHost when scheme is HTTPS. 2019-02-12 09:14:44 -05:00
Ryan Cramer
feb071b006 Make WireDateTime::relativeTimeStr() hookable per processwire/processwire-issues#793 2019-02-12 08:27:40 -05:00
Ryan Cramer
a47c6102f7 Some updates to the Pageimage::render() method plus add a Pageimages::render() method to accompany it. 2019-02-12 06:13:00 -05:00
Ryan Cramer
6c8d0e5eff Move implementation for wirePopulateStringTags() function into WireTextTools class. Add new WireData() shortcut function for consistency with existing WireArray() and PageArray() functions. 2019-02-12 05:25:37 -05:00
Ryan Cramer
839d9325cc Fix issue processwire/processwire-issues#331 2019-02-08 15:21:25 -05:00
Ryan Cramer
09a7d8e7ed Add $sanitizer->fieldSubfield() method for sanitizing "field.subfield" strings. 2019-02-08 14:46:48 -05:00
Ryan Cramer
65454985b1 Various minor adjustments 2019-02-08 14:46:20 -05:00
Ryan Cramer
5b45d17991 Attempt fix for issue processwire/processwire-issues#304 2019-02-07 11:34:35 -05:00
Ryan Cramer
ea62d73215 Fix issue processwire/processwire-issues#807 2019-02-07 09:35:07 -05:00
Ryan Cramer
ac0f4f0017 Fix issue processwire/processwire-issues#805 2019-02-05 09:27:56 -05:00
Ryan Cramer
09c936dba9 Attempt to fix processwire/processwire-issues#801 2019-02-05 08:46:10 -05:00
Ryan Cramer
1215e706e1 Fix issue processwire/processwire-issues#787 2019-02-05 08:39:21 -05:00
Ryan Cramer
69bd63eb4c Fix issue processwire/processwire-issues#779 where $pages('/') wasn't working 2019-02-05 08:32:39 -05:00
Ryan Cramer
733780f8f6 Fix phpdoc typos per issue processwire/processwire-issues#797 2019-02-04 15:19:08 -05:00
Ryan Cramer
7d79864dc9 Fix issue processwire/processwire-issues#790 plus add Pageimage::render() method 2019-02-04 14:53:49 -05:00
Ryan Cramer
c6cb728b60 Fix issue processwire/processwire-issues#788 2019-02-04 12:07:16 -05:00
Ryan Cramer
0ac9046b28 Add error message to resolve issue processwire/processwire-issues#786 2019-02-04 11:58:51 -05:00
Ryan Cramer
4348ca746a Fix issue processwire/processwire-issues#785 2019-02-04 11:20:47 -05:00
Ryan Cramer
0be324b4d5 Fix issue processwire/processwire-issues#781 2019-02-04 10:51:31 -05:00
Ryan Cramer
debb1cd511 Add $sanitizer->chars() method and update phpdoc at top of Sanitizer class for documentation purposes on API reference site. 2019-02-04 10:32:36 -05:00
Ryan Cramer
6ad5239666 Bump version to 3.0.125 2019-01-25 15:00:32 -05:00
Ryan Cramer
1aad2430d5 Update WireInput/WireInputData to optimize for sanitizer updates 2019-01-25 11:58:26 -05:00
Ryan Cramer
e879baf189 Add new $sanitizer methods: range(), min(), max(), bit(), maxLength(), maxBytes(), methodExists(), validate() and valid(). Also added support for executing multiple sanitizers in one call with the new sanitize() method. 2019-01-25 11:42:38 -05:00
Ryan Cramer
4a7919c9a9 Update the __('text') translation function to support setting of options with a __(true, 'optionName', 'optionValue') call. Added support for an entityEncode option and translations option for setting predefined fallback translations. Also made major updates to the phpdoc for the translation functions. 2019-01-25 11:33:17 -05:00
Ryan Cramer
02f9220529 Some minor 1-line tweaks to various files 2019-01-25 11:32:47 -05:00
Ryan Cramer
411cce0418 Update the $input->get(), $input->post(), and $input->cookie() methods to support new arguments for support of auto-sanitization, validation and fallback values. 2019-01-18 11:51:59 -05:00
Ryan Cramer
7bbb97e6e4 phpdoc improvements to WireFileTools, FunctionsWireAPI, FunctionsAPI and Functions files. 2019-01-18 11:50:22 -05:00
Ryan Cramer
97adac20ed Bump version to 3.0.124 2019-01-11 15:53:43 -05:00
Ryan Cramer
52e20c489d Add new $sanitizer->trim() method that can trim of all known UTF-8 whitespace types (or given chars) from beginning and ending of string. This is something I discovered PHP's trim() fucntion does not do. 2019-01-11 09:30:51 -05:00
Ryan Cramer
f9ded64ba3 Improve the WireHttp class by adding a CURL-based get(), post(), head() or send() fallback that is used (if CURL available) when the fopen() method fails. It can also be f orced from the new $options argument added to all the sending methods. 2019-01-11 09:29:32 -05:00
Ryan Cramer
2735ee397e Update PW's error handler to recognize failed functions API calls when functions API isn't enabled, and provide instruction on how to enable it 2019-01-11 09:13:40 -05:00
Ryan Cramer
1a94578b1e Make the functions API automatically enabled by default in core site profiles 2019-01-11 09:10:43 -05:00
Ryan Cramer
3517295e27 Minor improvements to default CKEditor contents.css, bumping up the font size slightly and increasing the left/right margin a bit 2019-01-11 09:08:58 -05:00
Ryan Cramer
770b75bb8a Inconsequential SCSS to CSS recompiles from phpstorm 2019-01-11 09:01:07 -05:00
Ryan Cramer
703fa29c85 Improvements to searchability of FieldtypeOptions fields, now enabling it to match either value or title. 2019-01-11 08:52:12 -05:00
Ryan Cramer
36c0f401e7 Phpdoc improvements to various core classes 2019-01-11 08:48:20 -05:00
Ryan Cramer
1601cdec73 Update MarkupQA class to support custom settings in $config->markupQA with the most useful one being 'ignorePaths' array containing starting paths that should be ignored when it comes to link abstraction. 2019-01-11 08:45:13 -05:00
Ryan Cramer
649d2569ab Bump version to 3.0.123 2018-12-21 14:30:49 -05:00
Ryan Cramer
df4ab1dbd3 Update for processwire/processwire-issues#408 InputfieldSelector count() to wireCount() 2018-12-20 11:52:01 -05:00
Ryan Cramer
05367367c0 Add $options argument support to WireMailTools::new(), i.e. $mail->new(array $options) 2018-12-20 11:17:02 -05:00
Ryan Cramer
67898cb36a Add additional mbstring check to Sanitizer and add two new whitespace reduction methods (for internal use) 2018-12-20 11:15:34 -05:00
Ryan Cramer
e20b6917d3 Fix issue processwire/processwire-issues#724 UTF-8 pagenames in non-default language 2018-12-20 10:58:26 -05:00
Ryan Cramer
a0570bb2a0 Update for processwire/processwire-issues#767 2018-12-19 12:24:22 -05:00
Ryan Cramer
1b059b0ced Bump version to 3.0.122 plus some other minor adjustments 2018-12-14 14:24:50 -05:00
Ryan Cramer
a89543944d Update WireArray::import() to support duplicate items when duplicate checking is turned off per processwire/processwire-issues#767 2018-12-14 13:12:52 -05:00
Ryan Cramer
b773c81ae9 Fix issue processwire/processwire-issues#766 auto-remove UTF-8 value of &#8232 line-seperator entity in $sanitizer->text() function 2018-12-14 13:07:26 -05:00
Ryan Cramer
e8d2eed1ba Add @horst-n fix for processwire/processwire-issues#715 2018-12-14 11:05:07 -05:00
Ryan Cramer
05fd707b9c Fix issue processwire/processwire-issues#763 for ProcessRole, plus this commit also contains some minor tweaks to ProcessPageLister 2018-12-14 09:03:23 -05:00
Ryan Cramer
c1b9349a7c Fix issue with Safari column widths in AdminThemeUikit that use showIf conditions per processwire/processwire-issues#480 2018-12-14 07:58:26 -05:00
Ryan Cramer
6801e2df94 Fix issue processwire/processwire-issues#756 make link modal files selection not require being open before it can be populated with files from page selection 2018-12-14 06:01:02 -05:00
Ryan Cramer
6d1b558d85 Fix issue processwire/processwire-issues#723 2018-12-12 14:28:24 -05:00
Ryan Cramer
365d49bcaa Fix issue processwire/processwire-issues#749 2018-12-12 14:19:57 -05:00
Ryan Cramer
a40f24d722 A couple of updates to account for new PHP 7.2/7.3 notices per processwire/processwire-issues#408 2018-12-12 11:59:02 -05:00
Ryan Cramer
f39d4387d5 Various minor tweaks/updates and bump version to 3.0.121 2018-12-07 14:58:03 -05:00
Ryan Cramer
3b6b5eea7d Update locale warning message in ProcessLogin per processwire/processwire-issues#732 2018-12-07 12:09:57 -05:00
Ryan Cramer
3e690b8ea4 Update phpdoc @return statement for Page::index() 2018-12-07 10:15:32 -05:00
Ryan Cramer
af0afe9f95 Additional updates for processwire/processwire-issues#751 plus some enhancements to PageFinder 2018-12-07 09:57:07 -05:00
Ryan Cramer
0dc8766491 Fix issue processwire/processwire-issues#751 with Page::index() not working for hidden/unpublished pages 2018-12-06 14:05:17 -05:00
Ryan Cramer
d7b7acb8e5 Various minor adjustments and bump version to 3.0.120 2018-11-30 13:52:24 -05:00
Ryan Cramer
0442572885 Fix issue processwire/processwire-issues#758 unable to select "17:10:02" time format in FieldtypeDatetime 2018-11-30 13:42:15 -05:00
Ryan Cramer
65b3edb761 Fix issue processwire/processwire-issues#750 where WireShutdown email was missing a send() call 2018-11-30 13:21:33 -05:00
Ryan Cramer
9aa0de6351 Fix issue processwire/processwire-issues#743 2018-11-30 13:15:27 -05:00
Ryan Cramer
45e8a00395 Fix issue processwire/processwire-issues#741 2018-11-30 12:29:20 -05:00
Ryan Cramer
a8d4a6cd46 Add phpdoc for Template::icon per processwire/processwire-issues#739 2018-11-30 12:03:49 -05:00
Ryan Cramer
91fdca02ea Fix issue processwire/processwire-issues#728 2018-11-29 14:12:33 -05:00
Ryan Cramer
ffdc1031de Fix issue processwire/processwire-issues#727 2018-11-29 14:02:46 -05:00
Ryan Cramer
c7447bf838 Fix issue processwire/processwire-issues#726 where Pages > Tree > dropdown was displaying liternal icon names (in addition to actual icon) in some cases when it shouldn't 2018-11-29 13:59:36 -05:00
Ryan Cramer
009dec332c Fix issue processwire/processwire-issues#725 where fallback email for comment notifications not working if hostname had port number 2018-11-29 13:31:05 -05:00
Ryan Cramer
e6213c23b3 Attempt fix for issue processwire/processwire-issues#724 where creating page from page reference field using UTF8 charset was failing. 2018-11-29 13:19:40 -05:00
Ryan Cramer
0cb693c450 Fix issue processwire/processwire-issues#719 where 'parent' property didn't work for $page->setAndSave() 2018-11-29 12:01:57 -05:00
Ryan Cramer
fcc353bc3c Updates per processwire/processwire-issues#704 with WireTempDir log entries 2018-11-29 11:12:11 -05:00
Ryan Cramer
cc212b3254 Add hasNext() and hasPrev() methods to PaginatedArray class 2018-11-21 10:49:53 -05:00
Ryan Cramer
b083c96717 Test commit, please ignore 2018-11-20 17:01:10 -05:00
Ryan Cramer
e61bb4982a Bump version to 3.0.119 2018-11-16 16:05:14 -05:00
Ryan Cramer
fad1e11575 Add a setting() function to the Functions API for more convenient management of site-specific runtime settings 2018-11-16 11:39:12 -05:00
Ryan Cramer
3eecb6cf25 Update Pageimage::getVariations() and Pageimage::removeVariations() methods with several new options, primarily to support a new PageAction module for clearing out old image variations 2018-11-16 09:51:06 -05:00
Ryan Cramer
96f62b313f Add WireArray::slices($qty) method that returns the WireArray sliced into $qty equal parts (new WireArray objects) 2018-11-09 14:26:32 -05:00
Ryan Cramer
32f594de2a Update Pages Export/Import class to support export/import of page created and modified dates 2018-11-09 14:25:34 -05:00
Ryan Cramer
93a9747657 Add @BitPoet processwire/processwire-requests#233 to make ajax file upload use chunked method for potential reduced memory usage and support of larger file uploads 2018-11-08 09:28:03 -05:00
Ryan Cramer
c146c71de3 Add @Toutouwai processwire/processwire-requests#242 to support automatic open of collapsed Inputfield when file dragged into file/image or CKEditor field that supports it 2018-11-08 09:04:04 -05:00
Ryan Cramer
7629e518e3 Remove unintended dev constant from AdminThemeUikit.module 2018-11-05 14:58:56 -05:00
Ryan Cramer
a46336de00 Bump version to 3.0.118 2018-11-02 08:07:24 -04:00
Ryan Cramer
e65c287099 Update AdminThemeUikit Uikit version to rc17 2018-11-02 06:55:19 -04:00
Ryan Cramer
a355bd74e6 Add renderedExtras() hook to ProcessPageLister, plus make the finalSelector property modifiable if needed by hooks, per request from @renobird 2018-11-02 06:44:49 -04:00
Ryan Cramer
a0192327e0 Some refactoring in ProcessPageEdit, plus fix issue where user having page-edit-lang-[lang] permission while having non-default language selected in their profile could receive incorrect "missing required field" error for Page Name, when making edits. 2018-11-02 06:41:35 -04:00
Ryan Cramer
09ee56b41d Add new “What URLs redirect to this page?” fieldset in the page editor Settings tab (near the bottom). This section enables you to see all URLs that redirect to the page, and also lets you add new redirect URLs or remove existing ones. Currently available to superusers only. 2018-10-31 10:29:06 -04:00
Ryan Cramer
6fb6837406 Add $page->addUrl($url, [$language]); and $page->removeUrl($url); methods that allow you to add or remove redirects to a page programatically. This is provided by updates to the PagePathHistory module, which also received several unrelated updates, like support for virtual path history, which is historical URLs for a page determined by changes to parent pages. 2018-10-31 10:22:47 -04:00
Ryan Cramer
54537e77fa Add new $files->unlink() and $files->rename() methods to files API var, plus enhance many file methods with additional security for path verification, and update PW's various usages of unlink/rename to use the new versions provided by the files API var. 2018-10-31 09:30:15 -04:00
Ryan Cramer
0f9eb0aaf5 Add hasValue(), hasTitle() and hasID() methods to SelectableOptionArray class 2018-10-26 12:21:10 -04:00
Ryan Cramer
7331bac132 Addition of a $page->numParents() method/property which reflects the number of parents the page has, aka depth in the tree. This commit also has several small adjustments and fixes, including a fix for the issue introduced last week that caused issues with WireArray in versions of PHP prior to 7.x 2018-10-26 12:18:46 -04:00
Ryan Cramer
b964fd1a15 Bump version to 3.0.117 2018-10-19 14:48:19 -04:00
Ryan Cramer
d12a78d07b Update WireArray class to support usage as a general purpose array (that can hold strings, numbers, etc.), rather than being limited to support of Wire derived objects. Also add WireArray::new($items) static method that lets you create a new WireArray and add $items to it in one shot. The same new() method works on any WireArray derived type such as PageArray and any others. 2018-10-19 11:14:29 -04:00
Ryan Cramer
f5cda0acdb Update the FieldtypeComments classes to support for more output configuration options for CommentForm and CommentList 2018-10-19 11:12:58 -04:00
Ryan Cramer
faaf8adec9 Add support for a "pw-optional" or "data-pw-optional" attribute to Markup Regions. When a defined region of markup has this boolean attribute present, the wrapping region tags will be removed from the document if they will be blank (i.e. nothing was populated to them). 2018-10-19 11:06:52 -04:00
Ryan Cramer
cf322b6dca Improvements to FieldtypeTextarea::importValue() to improve accuracy of linked asset URLs on pages imported from one site to another where the root URL has changed (subdirectory vs. non subdirectory), as well as better handling asset URLs where the link included a hostname 2018-10-19 11:04:30 -04:00
Ryan Cramer
0b8382ab3e A few small improvements to the PagesExportImport class 2018-10-19 11:02:54 -04:00
Ryan Cramer
6367fb7668 Forgot to bump version to 3.0.116 on Friday, so this fixes that 2018-10-08 13:45:46 -04:00
Ryan Cramer
e2b6453a96 Fix issue processwire/processwire-issues#718 FieldtypeFloat float typecast in getPrecision() method 2018-10-05 10:00:12 -04:00
Ryan Cramer
8e22bee1b5 Fix issue processwire/processwire-issues#716 where $page traversal methods like next() and prev() were not working on RepeaterPage items 2018-10-05 09:48:20 -04:00
Ryan Cramer
5fddd95b43 Add new Page::numDescendants() method and property, plus descendants() and descendant() alias methods. Add Page::findOne() method. Update ProcessPageList with the ability to customize what is shown in the numChildren/count shown for each Page, along with the ability to display the the newly added Page descendants numbers instead of or in addition to the Page numChildren. 2018-10-05 08:11:16 -04:00
Ryan Cramer
faa0d4f4df Update ProcessPageTrash module to be more clear, showing confirmation before page list, and adding option to specify time limit. Also now reports more verbose info about the trash operation including a "pages trashed per second" rate for comparison purposes on different DB engines and settings. Related to processwire/processwire-issues#692 2018-10-03 11:50:30 -04:00
Ryan Cramer
e9c7178a22 Optimizations and updates to PagesTrash class emptyTrash() process, plus add transaction support to empty trash process per processwire/processwire-issues#692 though currently I'm not seeing any effect from it 2018-10-03 11:46:22 -04:00
Ryan Cramer
0ec70c875f Update PagesEditor::savePageStatus() method to also accept array of page IDs (rather than just 1 page ID). Also accepts Page or PageArray objects now too. 2018-10-03 11:45:07 -04:00
Ryan Cramer
17c872b079 Set a default user-agent header in WireHttp to resolve issues where accessed hostname seems to require it (noticed this on a couple hosts running Varnish, which seem to throw a 400/403 if no user-agent header present) 2018-10-03 06:10:26 -04:00
Ryan Cramer
546340652f Attempt fix for issue processwire/processwire-issues#661 where cached PageList data could re-appear when closing then re-opening an item in the PageList. Was an issue if an item was moved or trashed, because it could appear to still be in the old location in the PageList even when it wasn't. This commit also refactors several parts in ProcessPageList.js. 2018-10-01 11:21:43 -04:00
Ryan Cramer
11cb8deaa2 Bump version to 3.0.115 2018-09-28 14:23:16 -04:00
Ryan Cramer
3ab9b358e5 Update ProcessLogin to support a configuration option to specify roles that should be prompted to enable two-factor authentication 2018-09-28 11:14:19 -04:00
Ryan Cramer
2f2bb118b0 Update ProcessProfile to use a dialog window prompt (via vex) when saving changes to a field requires current password. 2018-09-28 10:31:50 -04:00
Ryan Cramer
c55a547458 Add allowOriginal option to Pageimage::size() and Pageimage::maxSize() to avoid creating variations when original image is already at target dimension 2018-09-27 08:48:02 -04:00
Ryan Cramer
45f0c14831 Adjustment for issue processwire/processwire-issues#709 2018-09-27 07:54:57 -04:00
Ryan Cramer
e6bcd3e44c Fix issue processwire/processwire-issues#712 to correct issue with $config->sessionHistory=1; setting not working correctly 2018-09-27 07:39:53 -04:00
Ryan Cramer
a1bac571f3 Fix issue processwire/processwire-issues#708 Modules refresh showing unnecessary error message 2018-09-26 11:11:54 -04:00
Ryan Cramer
42511ebb77 Additional tweak for processwire/processwire-issues#709 2018-09-26 11:04:53 -04:00
Ryan Cramer
42ec0e3cf4 Some refactoring in WireTempDir class and attempt fix of issue processwire/processwire-issues#704 2018-09-26 09:40:40 -04:00
Ryan Cramer
bf8baf36dd Add new methods to $files API var: fileInPath($file, $path); unixDirName($dir); unixFileName($file); filePutContents($file, $contents); 2018-09-26 09:40:16 -04:00
Ryan Cramer
e5900e7567 Attempt fix for processwire/processwire-issues#704 2018-09-25 10:50:54 -04:00
Ryan Cramer
bddea16255 Clarify wording in phpdoc of Wire::trackChanges() method per processwire/processwire-issues#707 2018-09-25 10:26:02 -04:00
Ryan Cramer
f2edf04602 Fix issue processwire/processwire-issues#709 where Pageimage::maxSize() could crop when it shouldn't when source image smaller than maxSize requested dimensions 2018-09-25 10:13:51 -04:00
Ryan Cramer
3b4cdeb991 Make it possible to add Pagefile from another Page to Pagefiles WireArray per request processwire/processwire-issues#710 2018-09-25 09:32:39 -04:00
Ryan Cramer
96bd37eec9 Bump version to 3.0.114 2018-09-21 14:03:32 -04:00
Ryan Cramer
b4aec46a67 Add $database->supportsTransaction($table = '') method to return true or false as to whether the database (or a specific table) supports transactions 2018-09-21 13:30:57 -04:00
Ryan Cramer
9a7e5d5bd3 Add PagefilesManager->importFiles() and PagefilesManager->replaceFiles() methods 2018-09-21 13:29:47 -04:00
Ryan Cramer
f0cc6f1134 Update to support module autoload order per request from @adrianbj 2018-09-21 12:29:15 -04:00
Ryan Cramer
8add252813 Fix issue processwire/processwire-issues#700 where ProcessPermission "add new" was incorrectly showing Save+Publish buttons 2018-09-20 11:58:20 -04:00
Ryan Cramer
8973a9de49 Bump version to 3.0.113 2018-09-14 15:29:00 -04:00
Ryan Cramer
d7a84f223c Update CKEditor version from 4.8.0 to 4.10.1 2018-09-14 15:26:48 -04:00
Ryan Cramer
42b46152eb Refactoring of SessionLoginThrottle. Prevents it from being too aggressive when TFA is in use, improves clarity of message to user, and adds the ability to log failures. 2018-09-14 12:03:16 -04:00
Ryan Cramer
64680df68f Refactoring of ProcessController and add support for access controlled methods that works with the existing moduleInfo['nav'][]['permission'] setting. Previously that permission was only used to determine whether to show the item in the nav, and you had to do your own separate permission check in the actual method implementation. This commit also moves all the WireException definitions to the main core/Exceptions file. 2018-09-14 11:02:30 -04:00
Ryan Cramer
590a69502c Update $mail (WireMailTools) API var with some fluent interface shortcuts, enabling you to start with $mail->to(mail), $mail->from(email), or $mail->subject(email), all of which return a WireMail instance, rather than having to start with a $mail->new() to get the WireMail instance. While minor, this makes for a little bit shorter API calls when using WireMail fluent interface. 2018-09-14 10:36:14 -04:00
Ryan Cramer
c1c705ab59 Refactoring of WireShutdown class and related minor updates to some other classes 2018-09-14 10:34:56 -04:00
Ryan Cramer
89caff1bdc Fix issue processwire/processwire-issues#697 pages with $template->noMove setting could not be restored/moved out of the trash 2018-09-13 12:11:43 -04:00
Ryan Cramer
0284bd28a1 Fix issue processwire/processwire-issues#695 change order of listable() call in ProcessPageList::renderNavJSON so that one could make an viewable/editable page non-listable in the tree nav dropdown 2018-09-13 11:17:21 -04:00
Ryan Cramer
84e5fd6b51 Fix issue processwire/processwire-issues#690 where isMoveable() error messages were not yet multi-language translatable 2018-09-13 10:33:40 -04:00
Ryan Cramer
2c9b25fdfd Fix issue processwire/processwire-issues#686 where FieldtypeCheckbox::markupValue() result ended up blank after a PHP strip_tags() 2018-09-13 10:07:29 -04:00
Ryan Cramer
272077b1cf Fix issue processwire/processwire-issues#685 where templates with noParents=-1 setting (only one allowed) were not shown as selectable in Template field in page editor 2018-09-13 09:08:44 -04:00
Ryan Cramer
818d9f50a6 Fix issue processwire/processwire-issues#683 where $pages->findOne() didn't have exclusion/filter enabled if finding unpublished or hidden page that is editable & viewable to current user when the selector includes only reference to the page's id. 2018-09-13 08:50:49 -04:00
Ryan Cramer
efb7a8ace2 Fix issue processwire/processwire-issues#682 fix typo about what tab to change the unpublished setting 2018-09-13 08:34:27 -04:00
Ryan Cramer
844946b706 Fix issue processwire/processwire-issues#681 where Fieldtype exceptions getting caught when saving page, which could interfere with InnoDB transactions when Fieldtype throws an Exception 2018-09-13 08:29:49 -04:00
Ryan Cramer
dfc0c4da52 Fix issue processwire/processwire-issues#680 where ProcessLanguageTranslator does not skip over /site/assets/ on Windows servers 2018-09-13 06:21:31 -04:00
Ryan Cramer
adca762ee1 Fix issue processwire/processwire-issues#679 capture Exception in ProcessLanguageTranslator when it throws due to too many directories 2018-09-13 06:12:13 -04:00
Ryan Cramer
468ecb6b29 Make ProcessPageSearchLive::execute() hookable per processwire/processwire-issues#675 2018-09-12 13:20:26 -04:00
Ryan Cramer
7a89f4f188 Fix issue processwire/processwire-issues#671 where LiveSearch pages list could also show users if custom configured field name present on user template 2018-09-12 12:57:11 -04:00
Ryan Cramer
313fee873c Fix issue processwire/processwire-issues#669 admin liveSearch noSearchTypes option not working for 'pages', 'trash' or 'modules'. 2018-09-12 12:35:49 -04:00
Ryan Cramer
8315776960 Module download/install process label updates per processwire/processwire-issues#641 2018-09-12 11:58:52 -04:00
Ryan Cramer
6c9b475559 Add @horst-n PR #118 which fixes processwire/processwire-issues#628 in ImageSizerEngine 2018-09-12 11:16:33 -04:00
Ryan Cramer
b07d7341eb Improvements and refactorings to inputfields.js, plus add processwire/processwire-requests#224, and addition of new public API methods: InputfieldFocus($inputfield) which finds, opens and focuses an Inputfield (making it visible) regardless of where it is in the interface; InputfieldOpen($inputfield) which opens a collapsed Inputfield; and InputfieldClose($inputfield) which does the opposite. Thanks to @Toutouwai for the feature request and example code that got this started. 2018-09-11 11:31:34 -04:00
Ryan Cramer
e2478aa401 Add new string case conversion methods to Sanitizer: hyphenCase(), snakeCase(), camelCase(), and pascalCase() 2018-09-10 11:15:59 -04:00
Ryan Cramer
d260b269c5 Bump version to 3.0.112 2018-09-07 14:45:21 -04:00
Ryan Cramer
77cf1b9c71 Typo fix in ProcessField 2018-09-07 14:43:15 -04:00
Ryan Cramer
af423e5406 Update WireHttp to support more HTTP response codes (the success ones, in addition to the existing error codes) 2018-09-07 14:42:04 -04:00
Ryan Cramer
7e5f79e9b3 Fix issue where drag/drop image into non-default language CKEditor field was showing error message 2018-09-04 12:01:46 -04:00
Ryan Cramer
4c48e1d941 Refactoring in ProcessPageClone.module and related hooks and multi-language support, plus some improvements to new PagesNames class. 2018-08-30 06:22:02 -04:00
Ryan Cramer
aa34e8a0f7 Bump version to 3.0.111 2018-08-24 16:41:05 -04:00
Ryan Cramer
cc53b835b6 Refactoring of the PagesTrash class and related, plus some minor additions to PagesNames class 2018-08-24 10:41:47 -04:00
Ryan Cramer
dfa8cc7b74 Refactoring of the PagesEditor class, primarily addition of a new PagesNames class for handling page names, duplicate names, untitled pages, incrementing page names, etc. 2018-08-23 10:30:12 -04:00
Ryan Cramer
088938454b Move random generation functions from Password to new WireRandom class, and add several new methods for generating random strings, numbers, arrays, etc. 2018-08-21 11:23:20 -04:00
Ryan Cramer
ff123065eb Update the clone/copy repeater item function to also account for InputfieldTable rows that may be present in the repeater 2018-08-19 07:01:54 -04:00
Ryan Cramer
5554e87b47 Fix issue processwire/processwire-issues#667 where cloning a FieldtypeOptions field was only cloning the field, and not the options. 2018-08-13 05:56:57 -04:00
Ryan Cramer
563f4d237b Bump version to 3.0.110 2018-08-10 15:32:08 -04:00
Ryan Cramer
3c923dfb93 Some updates and improvements to the Password::randomAlnum() function 2018-08-10 11:36:51 -04:00
Ryan Cramer
acd42bbfc9 Update ProcessPageView to support optional single subdir for page files when $config->pagefileSecure mode is active. This enables it to work when pagefileSecure mode is active and a module (like ProDrafts) uses a subdirectory of of a page's file/asset directory 2018-08-09 11:15:06 -04:00
Ryan Cramer
2e672428a4 Fix issue processwire/processwire-issues#659 where PageFinder "_custom" option didn't yet have support for "field.owner.property=value" selectors 2018-08-08 15:05:21 -04:00
Ryan Cramer
82641a5521 Add request processwire/processwire-issues#614 to make InputfieldPassword use version of id attribute for confirm input rather than name attribute, for cases where someone might want to have multiple password fields in the same document. 2018-08-08 14:48:10 -04:00
Ryan Cramer
9fc4ff9e21 Add request processwire/processwire-issues#657 to clarify what characters are allowed when creating templates, plus some other minor improvements in ProcessTemplate 2018-08-08 09:17:46 -04:00
Ryan Cramer
fc8f6ad51c Fix issue processwire/processwire-issues#658 where non-specified, non-default-language pageNumUrlPrefix settings weren't properly falling back to $config default setting 2018-08-08 08:05:37 -04:00
Ryan Cramer
cee8504db9 Fix issue processwire/processwire-issues#656 where recently added Inputfield::setParent() update was causing issues (ListerPro config tab as one example) 2018-08-08 06:21:01 -04:00
Ryan Cramer
6d1dc83a45 Fix issue processwire/processwire-issues#639 where template configured to unpublish when missing required field was not applying to missing required fields within repeater items. 2018-08-08 06:16:04 -04:00
Ryan Cramer
69424df158 Fix issue processwire/processwire-issues#642 where new ProcessPageSearch feature was not using correct edit URL when searching for users 2018-08-07 06:17:32 -04:00
Ryan Cramer
6cc5a01cf5 Fix issue processwire/processwire-issues#646 where repeater trash icon click not triggering InputfieldStateChanged on InputfieldRepeater on case one forgets to save their changes 2018-08-07 06:02:58 -04:00
Ryan Cramer
683e24e010 Add an update to accommodate request in processwire/processwire-issues#648 for wording of warning message when a 3rd party hook has changed a page's name behind the scenes after it was saved 2018-08-06 11:31:35 -04:00
Ryan Cramer
a1676b0adb Fix issue processwire/processwire-issues#650 cropped images in image fields with overwrite option enabled not showing 2018-08-06 10:33:17 -04:00
Ryan Cramer
6882abe40a Fix issue processwire/processwire-issues#653 Inputfield parent issue causing out of memory errors when two buttons lacking name attributes 2018-08-06 08:21:21 -04:00
Ryan Cramer
c9a210ec35 Bump version to 3.0.109 2018-08-03 12:45:52 -04:00
Ryan Cramer
f61bca5d9f A couple of other minor updates to Session and FieldtypeModule 2018-08-03 12:21:00 -04:00
Ryan Cramer
95adb8039c Updates to Tfa base class, plus updates to ProcessLogin for TFA support 2018-08-03 12:17:12 -04:00
Ryan Cramer
5af6e63358 Add two-factor authentication module base class. Two modules that implmement it coming shortly. 2018-08-02 12:33:06 -04:00
Ryan Cramer
730b61a3c3 Various minor module updates 2018-08-02 12:31:41 -04:00
Ryan Cramer
fb9d1458a8 Update FieldtypeModule to support a couple of new config options for blank value and showing a "none" option when used with radios. 2018-08-02 12:23:18 -04:00
Ryan Cramer
fa2c8e30b9 Fix PSR-4 stacked namespace loading issue in WireClassLoader 2018-08-02 12:21:58 -04:00
Ryan Cramer
75889d3ac9 Update Password class with new methods for random string generation 2018-08-02 12:19:38 -04:00
Ryan Cramer
5e389ff65a Upgrade Inputfield and InputfieldWrapper base classes to support new getting, setting, and traversal methods. Plus a new $inputfields->new() method that lets you create a new Inputfield and add it to the wrapper in one step. 2018-08-02 12:17:59 -04:00
Ryan Cramer
c01289edb7 Attempt fix for issue processwire/processwire-issues#635 with FileCompiler and PHP touch() when PW run under different user accounts 2018-07-19 06:58:21 -04:00
Ryan Cramer
22fe5794d0 Fix issue processwire/processwire-issues#634 where single-use tokens in SessionCSRF needed an automatic reset to be consistent with behavior described the phpdoc 2018-07-18 07:45:38 -04:00
Ryan Cramer
b439a4488f Fix issue processwire/processwire-issues#631 where page-edit-lang-[name] permissions could prevent intended "addable" permission on templates. 2018-07-18 06:48:59 -04:00
Ryan Cramer
b6ec6cd679 Fix issue processwire/processwire-issues#629 where $config->pagefileSecure combined with non-superuser editing user profile didn't show images in image fields 2018-07-17 11:41:33 -04:00
Ryan Cramer
411fedf785 Fix issue processwire/processwire-issues#627 InputfieldCKEditor when extraAllowedContent empty 2018-07-16 11:37:56 -04:00
Ryan Cramer
2fb5ebbc96 Fix issue processwire/processwire-issues#625 where nested repeater with image field when combined with $config->pagefileSecure and access control without guest page-view permission was not appearing for logged-in non-superuser 2018-07-16 11:31:45 -04:00
Ryan Cramer
1404d1ef0f A couple of minor adjustments for 3.0.108 2018-07-13 13:25:18 -04:00
Ryan Cramer
1f4ca1b8f4 Some additional adjustments to the live search, including the addition of a "help" option, which you can access just by typing "help" into the search box. Bumped version to 3.0.108 2018-07-13 11:19:26 -04:00
Ryan Cramer
9741fbb818 Add 'language' option to $page->editUrl() method 2018-07-12 14:54:34 -04:00
Ryan Cramer
e038cb3d89 Update 3 admin themes for live search code, plus some other minor adjustments 2018-07-12 14:53:46 -04:00
Ryan Cramer
484d183054 Add a $modules->findByInfo() method that enables finding modules by factors in their module info, without instantiating the modules. Also some optimizations to the $modules->getModuleInfo() method. 2018-07-12 10:48:22 -04:00
Ryan Cramer
f8c41366c5 Update wireInstanceOf($instance, $className)) function to support interfaces for $className argument, regardless of whether the $instance argument is an object or a string (class name). Previous versions did not fully support interfaces for the $className argument. 2018-07-12 06:22:35 -04:00
Ryan Cramer
cd7a684b85 Add "searchable" module support to ProcessPageType and ProcessUser 2018-07-11 16:13:42 -04:00
Ryan Cramer
6b2f2243da Add "searchable" module support to ProcessTemplate and ProcessField 2018-07-11 16:13:00 -04:00
Ryan Cramer
14fce7e11a Add "searchable" module support to ProcessCommentsManager 2018-07-11 16:11:34 -04:00
Ryan Cramer
be9d07c57b Add new "addable" and "deletable" options to AsmSelect, enabling you to have AsmSelects where the ability to add and/or delete items is suppressed. With both options disabled, it's useful for sorting fixed groups of items. 2018-07-11 16:09:38 -04:00
Ryan Cramer
2e095c9cf4 Add url(), httpUrl() and editUrl() methods to Comment class. 2018-07-11 16:08:08 -04:00
Ryan Cramer
0dc2f17b5a Add a couple of new static methods to Selectors class for examining selector operators 2018-07-11 16:06:52 -04:00
Ryan Cramer
04187ed19f Update PaginatedArray/PageArray::getPaginationString() method to support new options (documented in phpdoc) 2018-07-11 16:05:48 -04:00
Ryan Cramer
756c9298a5 Add SearchableModule interface to Module definition, add getProcessPage() to Process module base class (which returns the Page object the Process page lives on), plus a couple of minor phpdoc improvements in core classes 2018-07-11 16:02:40 -04:00
Ryan Cramer
c3e0bbec86 Bump version to 3.0.107 2018-06-29 15:11:41 -04:00
Ryan Cramer
30bc99cfd7 Update ProcessPageList to have module configuration setting as to whether or not Trash and Restore is available to non-superusers. Plus add new Restore tab to ProcessPageEdit that appears when editing a page in the trash. Previously you could only restore by using the PageList "restore" action. 2018-06-29 12:27:01 -04:00
Ryan Cramer
bf62fbb897 Upgrade ProcessPageList to support showing and use of trash to non-superusers, for pages user is allowed to edit 2018-06-28 12:59:59 -04:00
Ryan Cramer
8e084a1ba0 Add new collapsed “What pages point to this one?” field on page editor Settings tab. This also demonstrates use of the $page->references() and $page->links(); methods. 2018-06-28 12:56:29 -04:00
Ryan Cramer
2883d2adb5 Add new $page->restorable() method to accompany the existing $page->trashable() method. This method returns true/false as to whether or not the page can be restored from the trash to its original location by the current user. Also add a PagePermissions::trashListable() method for determining whether the trash is viewable by the current user. 2018-06-28 12:53:56 -04:00
Ryan Cramer
54fad20ffd Add new Page methods: $page->links() which returns PageArray of other pages linking to it; $page->references() which returns PageArray of other pages referencing it in Page fields; $page->urls() which returns array of all URLs that can refer to the page, in all languages, and including both previous and current URLs. Plus add these properties: $page->references; $page->links; $page->numLinks; $page->numReferences; $page->hasLinks; $page->hasReferences; $page->referencing; $page->urls; see phpdoc for details on these properties. 2018-06-28 12:49:27 -04:00
Ryan Cramer
94524a8776 Add new find methods to FieldtypePage, FieldtypeTextarea and MarkupQA for finding page references and links specific to a given page. The public interfaces to these will be in the Page class (coming in next commit). 2018-06-28 12:47:05 -04:00
Ryan Cramer
5a9976f7a7 Improvements to User::hasPermission() method to support new permission detection options in context argument. 2018-06-28 12:43:09 -04:00
Ryan Cramer
8ef358eac7 Support option of forcing use of core WireMail even when other WireMail modules installed (primarily for testing/debugging purposes) 2018-06-28 12:42:03 -04:00
Ryan Cramer
bd325ba123 Fix issue where InputfieldImage could potentially detect ajax request mode when not applicable 2018-06-28 12:39:43 -04:00
Ryan Cramer
67bbd9641d Improvements to PagePathHistory module to support new options for getPathHistory() method 2018-06-28 12:38:24 -04:00
Ryan Cramer
fb65040836 Improvements to ProcessPageType and some phpdoc improvements to ProcessRole and ProcessPageLister 2018-06-28 12:35:18 -04:00
Ryan Cramer
cccc2d1161 Add lazy-loading option to WireInput, specified by $config->wireInputLazy=true; 2018-06-28 12:29:50 -04:00
Ryan Cramer
b54b3bace1 Add $modules->getModuleInfoProperty($module, 'name'); to retrieve just one property of module info, when useful. 2018-06-27 11:07:59 -04:00
Ryan Cramer
e5e13c1904 Fix issue processwire/processwire-issues#624 where WireHttp was producing error on the occasion when HTTP code present without text (which seems to be rare as far as I can tell). 2018-06-27 10:39:19 -04:00
Ryan Cramer
be1a203247 Fix issue processwire/processwire-issues#622 in $sanitizer->url() to workaround that PHP’s FILTER_VALIDATE_URL does not accept underscores in hostnames, despite their use being fairly common (even if not technically valid). 2018-06-27 10:24:59 -04:00
Ryan Cramer
1e912c4a4d Fix issue processwire/processwire-issues#623 where WireHttp::download() method was not working with URLs having encoded spaces when downloading with the "fopen" option (the "curl" option was not affected). Added a couple new $options to $sanitizer->url() method for dictating how encoded characters should be handled. Added WireHttp::setValidateURLOptions() method for cases where you want to manually specify different options for validating the URL in WireHttp. 2018-06-27 08:30:31 -04:00
Ryan Cramer
637f81579e Remove debug message() calls in ProcessPageEdit.module 2018-06-20 11:10:45 -04:00
Ryan Cramer
643c9d3a87 Bump version to 3.0.106 2018-06-15 13:19:15 -04:00
Ryan Cramer
afb4c4dbfd Add support for users with user-admin-all permission to be able to assign roles with user-admin permission per processwire/processwire-issues#607 2018-06-15 11:17:25 -04:00
Ryan Cramer
c82dba8835 Some upgrades and code consolidation in MarkupPagerNav, plus make it populate a $config->pagerHeadTags that one can output in a document head after rendering pagination, containing the <link rel='next|prev'.../> tags when desired. 2018-06-15 11:06:05 -04:00
Ryan Cramer
e60b79bd78 Upgrade $input->urlSegmentStr() method with new arguments, add an $input->pageNumStr() method, and add an $input->httpHostUrl() method that includes current scheme and hostname without path. 2018-06-15 11:03:38 -04:00
Ryan Cramer
28d275fe4d Fix issue processwire/processwire-issues#616 where PHP 7.2 is throwing deprecation notices for default arguments of idn_to_utf8() and idn_to_ascii(). The deprecation change and new defaults for next major PHP version does not appear to affect our usage after several tests, so opted to suppress these notices. 2018-06-15 07:58:50 -04:00
Ryan Cramer
828a80f1f6 Fix issue processwire/processwire-issues#621 where PageFrontEdit module config modal in AdminThemeDefault was showing extra/unnecessary buttons on modal dialog 2018-06-15 06:38:04 -04:00
Ryan Cramer
325ee3da15 Add a couple of new traversal methods to Inputfield: $inputfield->getRootParent(); and $inputfield->getForm();. While it was possible to get this info before, this makes it more obvious and straightforward. 2018-06-13 15:34:41 -04:00
Ryan Cramer
bd35c02e81 Improve support for Field tags by adding a new "Manage Tags" button at the bottom of the fields list screen, enabling you to add or remove fields to/from tags. In addition tags can now be used in $pages->find() searches, i.e. $pages->find("my_tag%=something"); would search all fields in the "my_tag" collection. 2018-06-13 15:31:55 -04:00
Ryan Cramer
a465fab672 Several fixes and impovements to PageFrontEdit per processwire/processwire-issues#609. Plus, this commit also improves PageFrontEdit usage with AdminThemeUikit, and improves behavior of multi-language fields. 2018-06-12 07:43:55 -04:00
Ryan Cramer
738c2db3c5 Correct issue in JqueryUI/modal.js where it used $() rather than jQuery() in one spot, and also fix issue where modal would stay open in some cases when we didn't want it to 2018-06-12 07:12:04 -04:00
Ryan Cramer
9ac3bfb5ad Various small code improvements and optimizations to ProcessPageEdit.module 2018-06-12 07:09:35 -04:00
Ryan Cramer
fff1250211 Various minor updates: add a $config->version($minVersion) function, add @since tags in Sanitizer, update count() => wireCount() in FieldtypePageTable and add PHPdoc hints where phpstorm is asking for them, update InputfieldImage to track change for the field when an action manipulates the image file, plus some other minor odds and ends. 2018-06-12 07:06:40 -04:00
Ryan Cramer
9ef1ac1486 Fix issue where drag-to-upload to CKE field within a repeater wasn't working quite right. Also updated it to default to drag-upload to image field in repeater item, if one is present (and supports more than one item), and use the owning page as a fallback for upload. 2018-06-10 08:08:53 -04:00
Ryan Cramer
d57b3ffcfc Bump version to 3.0.105 2018-06-01 15:39:56 -04:00
Ryan Cramer
6ef23f5c75 A couple of minor updates to AdminThemeUikit 2018-06-01 14:08:19 -04:00
Ryan Cramer
f6d8d3510f Add the “Regular” site profile to the core as another installation profile option 2018-06-01 13:26:07 -04:00
Ryan Cramer
ecf812224f Add $mail->sendHTML() and $mail->mailHTML() methods which are the same as their send() and mail() counterparts, but assume the given message body is HTML, and text-body is auto-generated. 2018-06-01 09:17:37 -04:00
Ryan Cramer
c0437702ce Fix issue processwire/processwire-issues#610 where .sort-date spans were showing in MarkupAdminDataTable columns at mobile width 2018-06-01 06:55:50 -04:00
Ryan Cramer
2abc454ce1 Add @rolandtoth PR #110 and PR #111 that fix issues with LanguageTabs in AdminThemeDefault/AdminThemeReno themes. 2018-05-31 14:55:33 -04:00
Ryan Cramer
07ab8ef9fc Add support for multi-language options support to InputfieldSelect, and modules that descend from it (radios, checkboxes, asmSelect, selectMultiple, etc). This enables you to use selectable options in multiple languages when used in FormBuilder or any other tool that uses InputfieldSelect without a Fieldtype. 2018-05-31 14:27:39 -04:00
Ryan Cramer
d2da3adcf7 Add new $mail->mail() method, which is the same as the existing $mail->send() method except that its arguments duplicate those of PHP mail(), making it possible to use as a drop-in replacement for PHP mail(). 2018-05-31 13:17:22 -04:00
Ryan Cramer
7c159f9c1a Some reworking of and improvements to the WireMail class, though no change to external interface (and should be no changes required for classes extending it). 2018-05-31 13:15:32 -04:00
Ryan Cramer
863abee8bb mprovements and new options for $sanitizer methods: text(), textarea(), unentities(). Plus, add new $sanitizer->removeWhitespace() method that can remove 99 different types of whitespace that can appear in a string (which includes the obvious whitespace, but also various UTF-8 and html-entity based whitespace). 2018-05-31 11:07:36 -04:00
Ryan Cramer
46e774ba38 Fix issue in FieldtypeComments::find() where "sort=something" selector wasn't always working if DatabaseQuerySelectFulltext modified the query 2018-05-30 07:29:00 -04:00
Ryan Cramer
bdaf8810cb Various minor AdminThemeUikit updates, mostly styles adjustments. Bump version to 3.0.104 and a couple other minor tweaks. 2018-05-25 11:10:41 -04:00
Ryan Cramer
5b38c26806 Update InputfieldCKEditor figure/figcaption styles to bind width of caption to width of image 2018-05-25 10:40:15 -04:00
Ryan Cramer
81ceb184b2 Update FieldtypeComments CommentList class to support a footer option 2018-05-25 10:38:40 -04:00
Ryan Cramer
ad9973ca33 Additional fix for processwire/processwire-issues#602 add z-index to InputfieldRepeater to prevent lost outline at bottom of field when repeater depth enabled 2018-05-24 05:47:56 -04:00
Ryan Cramer
da73572b95 Fix issue in Firefox where repeaters with depth in AdminThemeUikit had strange outline on containing Inputfield element per suggestion in processwire/processwire-issues#602 2018-05-24 05:45:21 -04:00
Ryan Cramer
c1dc6a9b9f Add @Notanotherdotcom PR #96 to allow use of field names with brackets (array) in field dependencies 2018-05-23 08:34:33 -04:00
Ryan Cramer
d06fd64171 Fix issue processwire/processwire-issues#603 where toggling repeater item open/closed makes outline disappear the second time you do it 2018-05-23 08:28:54 -04:00
Ryan Cramer
c512fd68e3 Fix issue processwire/processwire-issues#598 in FieldtypeOptions where manually assigned option IDs were not working 2018-05-23 07:45:33 -04:00
Ryan Cramer
91930953b6 Additional update for processwire/processwire-issues#596 2018-05-23 05:43:08 -04:00
Ryan Cramer
af3db8b5c1 Fix issue processwire/processwire-issues#600 where label saved to log was showing wrong path 2018-05-21 09:45:24 -04:00
Ryan Cramer
771c275432 Fix issue processwire/processwire-issues#597 allow radio button labels to wrap when needed 2018-05-21 09:40:35 -04:00
Ryan Cramer
79fa81d8a8 Add enhancement processwire/processwire-issues#596 show field labels rather than field names in Inputfield error messages 2018-05-21 09:23:47 -04:00
Ryan Cramer
2ba8d29ce2 Fix issue processwire/processwire-issues#595 where horizontal separator in dropdown nav wasn't quite right for the Page Add > Bookmarks option 2018-05-21 08:47:04 -04:00
Ryan Cramer
76a15a2539 Some other tweaks to Fieldtype and FieldtypeComments and bump version to 3.0.103 2018-05-18 15:07:24 -04:00
Ryan Cramer
9c38d29402 Improve support for fields that can be placed in custom header of CommentList class 2018-05-18 10:15:04 -04:00
Ryan Cramer
0fbd5a5882 Significant improvements to FieldtypeComments::find() method, adding support for selectors containing multiple sort values, OR values (for most properties), fulltext index searching of comment text, and matching comments based on field values present on the page that contains the comment field. 2018-05-18 10:12:04 -04:00
Ryan Cramer
aa34db127e Add WireLog option for specifying which user should be recorded with the log entry (if different from current user). 2018-05-18 09:21:13 -04:00
Ryan Cramer
7b2692f0cd Add new removeAllFor($namespace) method to Session, and improve the multi-IP option in the getIP() method 2018-05-18 09:19:01 -04:00
Ryan Cramer
ac480032c6 Major improvements and configuration options added to ProcessForgotPassword module. Also improves it for non-admin uses such as with the LoginRegister module. Additional added hooks also support more customization. 2018-05-18 09:17:38 -04:00
Ryan Cramer
8dda4368ea Bump version to 3.0.102 2018-05-11 12:00:35 -04:00
Ryan Cramer
8b285cfb17 Add @kixe PR #94 which adds a "disallow parallel sessions" option to SessionHandlerDB. When enabled, only one login can be maintained at a time per user. 2018-05-11 09:52:46 -04:00
Ryan Cramer
63cba339e4 Add "Cancel" JS callback to ProcessWire.confirm(message, funcOk, funcCancel) per PR #108 2018-05-11 08:20:26 -04:00
Ryan Cramer
717c1d2961 Add PR #107 so that panel.js triggers pw-panel-closed and pw-panel-opened events in JS 2018-05-11 06:33:33 -04:00
Ryan Cramer
8ef088f625 Some minor updates to WireTextTools::truncate() method to improve sentence type matches 2018-05-11 06:14:15 -04:00
Ryan Cramer
d7e0cccecb Apply the LanguageSupportPageNames duplicate checking feature without debug/advanced modes per processwire/processwire-issues#322 2018-05-10 06:25:30 -04:00
Ryan Cramer
7458d0504c Make InputfieldSelect show blank option even when selection is required per processwire/processwire-issues#585 2018-05-10 06:02:57 -04:00
Ryan Cramer
04123e305a Fix issue processwire/processwire-issues#588 where InputfieldFieldsetOpen lacked class InputfieldFieldset, which prevented it from having background color in AdminThemeUikit 2018-05-10 05:52:40 -04:00
Ryan Cramer
2d8dc82010 Fix issue processwire/processwire-issues#590 dropdown hover ajax menus using fix suggested in issue report 2018-05-10 05:37:29 -04:00
Ryan Cramer
e520c09f7a Update InputfieldPageAutocomplete.js to initialize the autocomplete on focus event, rather than on document ready. This resolves a render time issue in AdminThemeUikit when there are a lot of autocomplete inputs present, per @Toutouwai 2018-05-08 09:25:07 -04:00
Ryan Cramer
2f20fe402c Some code cleanup in ProcessPageSearch, plus add a hook for processwire/processwire-issues#584 2018-05-08 08:48:52 -04:00
Ryan Cramer
5127be3b35 Fix issue processwire/processwire-issues#587 where "Pages > Recent > Created" dropdown was showing pages created by user rather than all users. 2018-05-08 08:09:13 -04:00
Ryan Cramer
211cf78403 Fix issue processwire/processwire-issues#586 unnecessary "clone_field" property in field settings after a field is cloned. Unrelated: this commit also converts InputfieldPageAutocomplete to use an scss file. 2018-05-08 08:03:41 -04:00
Ryan Cramer
a3d2a447a9 Add fix for issue processwire/processwire-issues#584 allow quotes in InputfieldPageAutocomplete selectors 2018-05-08 05:43:24 -04:00
Ryan Cramer
ebe163452d Bump version to 3.0.101 2018-05-04 15:35:01 -04:00
Ryan Cramer
8d38ad7c44 Add $sanitizer->truncate() function per processwire/processwire-requests#163 2018-05-04 15:32:54 -04:00
Ryan Cramer
589d745caa Some small CSS fixes in ProcessModule 2018-05-02 06:24:49 -04:00
Ryan Cramer
c2bc83bc40 Attempt fix for processwire/processwire-issues#582 requiredIf and ProcessPageEdit behaviors 2018-05-02 06:14:47 -04:00
Ryan Cramer
027275ec40 Fix issue processwire/processwire-issues#583 $pages->clone() was failing when page had files and output formatting state was true. 2018-05-02 05:53:26 -04:00
Ryan Cramer
3296b699e2 Fix issue in InputfieldImage where negative degree rotate image actions were getting converted to positive degree rotates due to sanitization. 2018-05-01 06:09:21 -04:00
Ryan Cramer
8c18e3bf0d Fix issue processwire/processwire-issues#580 2018-04-30 05:56:36 -04:00
Ryan Cramer
ecdd3a3895 Bump version to 3.0.100 2018-04-27 16:34:17 -04:00
Ryan Cramer
cfed957df2 Update $pages->newPage() method to support option of accepting array of fields/properties to set when creating the page object 2018-04-27 11:21:13 -04:00
Ryan Cramer
b0abd3b9de Additional update for processwire/processwire-issues#575 2018-04-27 10:28:13 -04:00
Ryan Cramer
43c3bb52c3 Additional adjustment for processwire/processwire-requests#183 to correct enter-key detection that was using wrong element id. 2018-04-27 10:13:50 -04:00
Ryan Cramer
904db74f1a Add custom __debugInfo() methods to Pagefile, Pageimage, and Pagefiles per processwire/processwire-issues#575 plus some other minor updates to Pagefile/Pageimage 2018-04-27 09:55:31 -04:00
Ryan Cramer
c2324f0a8a Fix issue processwire/processwire-issues#576 adjustment in InputfieldPassword.js for EmailNewUser module 2018-04-27 06:09:07 -04:00
Ryan Cramer
2a49fe1087 Attempt to fix issue processwire/processwire-issues#560 add support for viewable permission with User pages outside admin structure when developer intends it 2018-04-26 10:04:49 -04:00
Ryan Cramer
105ba2b8eb Add __debugInfo() method to WireInput class per processwire/processwire-issues#575 2018-04-26 08:48:09 -04:00
Ryan Cramer
e53e4b4bd0 Add processwire/processwire-requests#183 focus URL input field when ProcessPageEditLink opens 2018-04-26 08:32:42 -04:00
Ryan Cramer
2cc69a1f0b Fix issue processwire/processwire-issues#571 where configuring a FieldtypeFieldsetClose in Setup > Fields would result in error in AdminThemeUikit 2018-04-25 11:16:05 -04:00
Ryan Cramer
c7dfb37a7e Fix issue processwire/processwire-issues#570 where InputfieldImage in a Repeater item, crop/focus/variations buttons didn't show to non-superusers 2018-04-25 11:09:59 -04:00
Ryan Cramer
d691b743b0 Fix issue processwire/processwire-issues#569 when Inputfield::textFormatMarkdown constant used it wasn't replacing default wrapping markup 2018-04-25 09:07:18 -04:00
Ryan Cramer
ba400767ab Fix issue processwire/processwire-issues#568 for ProcessModule table.AdminDataList tables, plus convert CSS to SCSS 2018-04-25 08:21:47 -04:00
Ryan Cramer
ace36fb415 Fix issue processwire/processwire-issues#567 w/AdminThemeUikit search box ctrl/cmd-click did not open new tab 2018-04-25 06:12:50 -04:00
Ryan Cramer
a9051a252f Fix issue processwire/processwire-issues#565 with InputfieldSelector preview count and _custom selector option was not working before 2018-04-25 05:55:22 -04:00
Ryan Cramer
f2912bcfd4 A couple of minor adjustments in AdminThemeUikit 2018-04-23 05:42:12 -04:00
Ryan Cramer
184059b5d6 Additional improvements to $config->noHTTPS option for support of hostnames 2018-04-20 10:33:43 -04:00
Ryan Cramer
bc037f840e Minor adjustment to InputfieldRepeater and publish status to simplify a ProDrafts hook 2018-04-19 10:52:08 -04:00
Ryan Cramer
16c20439f1 Add support for $config->noHTTPS. When boolean true, it cancels any HTTPS requirements set per-template, simplifying cases where you copy a production site to dev site that may not have the same HTTPS capabilities. So for that case, the dev site would want to have $config->noHTTPS=true; in /site/config.php 2018-04-19 10:49:58 -04:00
Ryan Cramer
a6bd85f6b2 Some additional updates for AdminThemeUikit to improve on irregular Inputfield column widths and global uk-form-large or uk-form-small settings 2018-04-19 10:47:13 -04:00
Ryan Cramer
19f557b487 Several updates to AdminThemeUikit including additional config settings, new input appearance adjustments, option to use percentage-based widths rather than uk-width classes, and more. processwire/processwire-issues#564 processwire/processwire-issues#480 2018-04-18 09:05:29 -04:00
Ryan Cramer
c550c6678b Some minor adjustments in preparation for a couple AdminThemeUikit additions 2018-04-18 08:50:53 -04:00
Ryan Cramer
d2ebb4cd56 Update base AdminTheme class for more flexible class management functions 2018-04-18 08:47:20 -04:00
Ryan Cramer
566d60b152 Small optimization for for __('text') translation function when used in PHP 5.4.0 or newer and no textdomain specified. 2018-04-13 09:17:09 -04:00
Ryan Cramer
5884f05b9f Fix issue in MarkupFieldtype when rendering file/image fields within repeater items 2018-04-13 08:50:02 -04:00
Ryan Cramer
6b3b0b7782 Further updates to AdminThemeUikit Inputfield width adjustments per processwire/processwire-issues#480 2018-04-13 08:31:17 -04:00
Ryan Cramer
ae5b955ec8 Fix issue in FieldtypeFieldsetOpen.module where hooks might not get called depending on module load order 2018-04-08 08:17:09 -04:00
Ryan Cramer
341342dc5b Bump version to 3.0.98 2018-04-06 14:25:49 -04:00
Ryan Cramer
9d11d87e9b Update InputfieldSelect::addOptionsString() to allow for user-defined disabled option if line in option string is prefixed with "disabled:" 2018-04-06 07:14:48 -04:00
Ryan Cramer
c5147a5279 Make Inputfield requiredLabel customizable per field from API via $inputfield->requiredLabel property, to provide option to override default "missing requird value" label. 2018-04-06 06:56:54 -04:00
Ryan Cramer
f27feb2bbf Followup fix for issue processwire/processwire-issues#322 check for duplicate non-default language page name when creating new page 2018-04-06 06:46:42 -04:00
Ryan Cramer
eaa2cb4d89 Fix issue processwire/processwire-issues#558 remove unused filemtime() call in ProcessLanguage.module 2018-04-06 05:46:30 -04:00
Ryan Cramer
215348b797 Fix issue processwire/processwire-issues#556 fix issue when cloning Pagefile that has name ending with "-0". 2018-04-06 05:43:25 -04:00
Ryan Cramer
6dc7a122a6 Fix issue processwire/proceswire-issues#555 in ProcessProfile where 50% width Inputfield followed by submit button resulted in submit button in wrong position 2018-04-06 05:37:22 -04:00
Ryan Cramer
c5c7ae4232 Bump to 3.0.97 and update to add a Pagefile::HTTPURL to accompany the existing Pagefile::URL property (uppercase implies cache busting URLs). 2018-03-30 10:48:40 -04:00
Ryan Cramer
2722345aac Attempt to fix issue processwire/processwire-issues#554 isue with $files->include() and Windows paths 2018-03-30 10:45:35 -04:00
Ryan Cramer
b1214fa0c4 Fix issue processwire/processwire-issues#553 where a note in AdminThemeUikit config screen needed to be updated for new module location 2018-03-30 09:53:46 -04:00
Ryan Cramer
8cbe05d1e7 Fix issue processwire/processwire-issues#277 where Inputfield::collapsedNever constant didn't work quite right in some cases 2018-03-30 09:41:32 -04:00
Ryan Cramer
deb448851b Fix issue processwire/processwire-issues#544 where FieldsetTab in User editor was not viewable to non-superuser with user-admin permission 2018-03-28 09:43:44 -04:00
Ryan Cramer
b7e90f896c Fix issue processwire/processwire-issues#551 Pages::trashed and Pages::restored hooks were getting called twice 2018-03-28 06:31:36 -04:00
Ryan Cramer
164d132be8 Fix issue processwire/processwire-issues#548 WireLog::getLines method wasn't passing options array to FileLog::find 2018-03-28 05:59:40 -04:00
Ryan Cramer
bd85c025e2 Fix issue in ProcessCommentsManager where the tabs were not showing active state properly in AdminThemeUikit. Plus some minor CSS updates in InputfieldPage and ProcessPageLister. 2018-03-22 07:59:16 -04:00
Ryan Cramer
a5e5ae7b47 Update FieldtypePage to support a runtime option to create Page references that don't exist when value is set by title. This is to accommodate a feature in ImportPagesCSV module. 2018-03-22 07:48:26 -04:00
Ryan Cramer
3e6840748b Update FieldtypeInteger so that it will still convert to integer even if number prefixed with USD or EUR symbol. 2018-03-22 07:46:02 -04:00
Ryan Cramer
89ed62a6d8 Prevent stars input from showing in InputfieldCommentsAdmin.module when stars feature is not enabled 2018-03-22 07:43:47 -04:00
1185 changed files with 94005 additions and 27312 deletions

View File

@@ -410,7 +410,7 @@ https://processwire.com/about/license/mit/
The MIT License (MIT)
Copyright (c) 2018 Ryan Cramer <or other year/person if indicated in file>
Copyright (c) 2020 Ryan Cramer <or other year/person if indicated in file>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

157
README.md
View File

@@ -9,54 +9,139 @@ can be read at: https://github.com/processwire/processwire/blob/master/README.md
1. [About](#about-processwire)
2. [Installation](#installing-processwire)
3. [Upgrading](#upgrading-processwire)
4. [Troubleshooting](https://processwire.com/docs/install/troubleshooting/)
4. [Troubleshooting](https://processwire.com/docs/start/install/troubleshooting/)
5. [Support](#support-and-links)
## About ProcessWire
ProcessWire is an open source content management system (CMS) and web
application framework aimed at the needs of designers, developers and their
clients. ProcessWire gives you more control over your fields, templates and
markup than other platforms, and provides a powerful template system that
works the way you do. Not to mention, ProcessWire's API makes working with
your content easy and enjoyable. Managing and developing a site in
ProcessWire is shockingly simple compared to what you may be used to.
ProcessWire is a friendly and powerful open source CMS with an API that is a
joy to use at any scale. It is both a content management system (CMS) and
framework (CMF) built to save you time and work the way you do. With all custom
fields, a secure foundation, proven scalability and performance, ProcessWire
connects all of your content seamlessly, making your job fast, easy and fun.
* [ProcessWire Home](https://processwire.com)
* [API Reference](https://processwire.com/api/ref/)
* [Download](https://processwire.com/download/)
* [Support](https://processwire.com/talk/)
* [Modules/Plugins](http://modules.processwire.com)
ProcessWire gives you more control over your fields, templates and markup than
other platforms, while ProcessWires API makes working with your content easy and
enjoyable. Managing and developing a site in ProcessWire is shockingly simple
compared to what you may be used to.
ProcessWire is widely trusted by web professionals for its exceptional consistency,
stability and security; revered by web developers for its API that saves time and
makes work fun; valued by web designers for its adaptability and flexibility with
modern website/application content management needs; and loved by clients for its
no-nonsense interface and ease-of-use in adding, updating and maintaining content.
New versions of ProcessWire are released just about every week on the
development branch.
### Background
ProcessWire is a timeless tool for web professionals that has always been
committed to the long term. It started in 2003, gained the name ProcessWire
in 2006, and has been in active development as an open source project since 2010.
Now more than a decade later (2020), we're just getting started, as ProcessWire
continues to grow and develop into the next 10 years and beyond.
While ProcessWire has been around for a long time, dont feel bad if you havent
heard of it till today. We are fundamentally different from other projects in
that we dont make a lot of noise, were not into promotion, we value quality
over quantity, sustainability over growth, and a friendly community over
popularity. ProcessWire is designed to be a silent partner, not easily
identified from the front-end of any website. We dont aim to be big, we are
instead focused on being best-in-class.
Web developers find ProcessWire when the time is right, after theyve tried
some other platforms. And once they start using ProcessWire, they tend to
stay—ProcessWire is addictive, easy to maintain for the long term, and doesnt
have the security and upgrade woes of other platforms. But dont take our word
for it; unless your livelihood depends on some other platform, find out for
yourself.
### Community
ProcessWire is more than just software, it is also a friendly community
of web professionals dedicated to building great sites and applications, and
helping others do so too. Please visit and join our
[friendly community](https://processwire.com/talk/)
in the ProcessWire forums, subscribe to our
[weekly newsletter](https://processwire.com/community/newsletter/subscribe/)
for the latest ProcessWire news, check out our
[website showcase](https://processwire.com/sites/)
to see what others are building with ProcessWire, and read our
[blog](https://processwire.com/blog/)
to stay up-to-date with the latest ProcessWire versions.
### Learn more
* [ProcessWire website](https://processwire.com)
* [About ProcessWire](https://processwire.com/about/)
* [Support forums](https://processwire.com/talk/)
* [Documentation](https://processwire.com/docs/)
* [API reference](https://processwire.com/api/ref/)
* [Downloads](https://processwire.com/download/)
* [Modules/plugins](https://modules.processwire.com)
* [Showcase](https://processwire.com/sites/)
-----------------------------------------------------------------
## Installing ProcessWire
Simply extract the ProcessWire files to an http accessible location and
load the URL in your web browser. This will start the installer. See our
[Installation Guide](https://processwire.com/docs/install/new/) for more
[Installation Guide](https://processwire.com/docs/start/install/new/) for more
details and instructions. If you run into any trouble, please see our
[Troubleshooting Guide](https://processwire.com/docs/install/troubleshooting/).
[Troubleshooting Guide](https://processwire.com/docs/start/install/troubleshooting/).
## Upgrading ProcessWire
Before proceeding with any version upgrade, please read the
[Upgrading ProcessWire](https://processwire.com/docs/install/upgrade/)
Before proceeding with any version upgrade, please see the
[Upgrading ProcessWire](https://processwire.com/docs/start/install/upgrade/)
guide and keep it open during your upgrade in case you need to refer back to it.
If upgrading from one 3.x version to another, please use the
[General Upgrade Process](https://processwire.com/docs/install/upgrade/#general-upgrade-process).
Chances are that you can upgrade simply by replacing the /wire/ directory.
### Upgrading from ProcessWire 3.x (earlier version)
When upgrading from one 3.x version to another, please use the
[General Upgrade Process](https://processwire.com/docs/start/install/upgrade/#general-upgrade-process).
This consists primarily of making sure you've got everything backed up and then
just replacing your `/wire/` directory with the one from the newest version.
In addition, if you are currently running any 3.x version prior to 3.0.135,
you will also want to upgrade your root `.htaccess` file to the newest version:
#### Upgrading your .htaccess file
* If you haven't made any custom modifications to your .htaccess file then you
can simply replace the old one with the new one. The new one is in a file
named `htaccess.txt` so you'll rename it to `.htaccess` after removing
your old one (all in the same directory as this README file).
* If your .htaccess file does have custom modifications, you know what they
are, and are comfortable applying them to the new one — go ahead and
follow the step above and then make those same modifications to the new
.htaccess file.
* If you aren't sure what custom modifications your .htaccess file might
have, or how to apply them to the new one, please see this post which will
quickly guide you through it:
[How to upgrade an existing .htaccess file](https://processwire.com/blog/posts/pw-3.0.135/#how-to-update-an-existing-htaccess-file)
*If you are curious what's new in this latest .htaccess file version,
please see [this post](https://processwire.com/blog/posts/pw-3.0.135/)
for all the details.*
### Upgrading from ProcessWire 2.x
If upgrading from ProcessWire 2.5 or older, we recommend that you upgrade
to ProcessWire [2.8](https://github.com/processwire/processwire-legacy) or
[2.7](https://github.com/ryancramerdesign/processwire) first. Both of those
versions include details in the README file on how to upgrade from these
older versions of ProcessWire. To upgrade from ProcessWire 2.6 (or newer)
to ProcessWire [2.7](https://github.com/ryancramerdesign/processwire) first.
This version includes details in the README file on how to upgrade from that
older version of ProcessWire. To upgrade from ProcessWire 2.6 (or newer)
to ProcessWire 3.x, please follow the instructions below.
1. Login to the admin of your site.
@@ -85,19 +170,19 @@ to ProcessWire 3.x, please follow the instructions below.
`$config->debug` setting back to `false` in your /site/config.php file.
**Troubleshooting a 3.x upgrade**
If you run into any trouble upgrading, please see our troubleshooting guide
located at <https://processwire.com/download/troubleshooting/#upgrades>.
If you run into any trouble upgrading, please see our
[troubleshooting upgrades guide](https://processwire.com/docs/start/install/troubleshooting/#troubleshooting-upgrades).
### Pro module upgrade notes
- If using [FormBuilder](https://processwire.com/api/modules/form-builder/),
we recommend using only v0.3.0 or newer.
- If using [ProCache](https://processwire.com/api/modules/procache/),
- If using [FormBuilder](https://processwire.com/store/form-builder/),
we recommend using only v0.3.0 or newer, but v0.4.0 or newer if possible.
- If using [ProCache](https://processwire.com/store/pro-cache/),
we recommend using only v3.1.4 or newer.
- If using [ListerPro](https://processwire.com/api/modules/lister-pro/),
- If using [ListerPro](https://processwire.com/store/lister-pro/),
we recommend using only v1.0.9 or newer.
- If using [ProFields](https://processwire.com/api/modules/profields/),
- If using [ProFields](https://processwire.com/store/pro-fields/),
we recommend grabbing the latest versions in the ProFields support board.
- If using ProCache and you upgraded your .htaccess file, you should
go to your ProCache settings after the upgrade to have it update
@@ -131,14 +216,16 @@ resolved any issues.
## Support and Links
* [ProcessWire Support](https://processwire.com/talk/)
* [ProcessWire Weekly](https://weekly.pw/)
* [ProcessWire Support Forums](https://processwire.com/talk/)
* [ProcessWire Weekly News](https://weekly.pw/)
* [ProcessWire Blog](https://processwire.com/blog/)
* [Sites running ProcessWire](https://processwire.com/sites/)
* [Subscribe to ProcessWire Weekly email](https://processwire.com/community/newsletter/subscribe/)
* [Submit your site to our directory](https://processwire.com/sites/submit/)
* [Follow @processwire on Twitter](http://twitter.com/processwire/)
* [Contact ProcessWire](https://processwire.com/contact/)
* [Sites running ProcessWire](https://processwire.com/about/sites/)
------
Copyright 2018 by Ryan Cramer / Ryan Cramer Design, LLC
Copyright 2020 by Ryan Cramer / Ryan Cramer Design, LLC

View File

@@ -1,26 +1,78 @@
#################################################################################################
# START PROCESSWIRE HTACCESS DIRECTIVES
# @version 3.0
# @indexVersion 300
# @htaccessVersion 301
#################################################################################################
#
# Upgrading htaccess (or index) version 300 to 301
# -----------------------------------------------------------------------------------------------
# If you never modified your previous .htaccess file, then you can simply replace it with this
# one. If you have modified your .htaccess file, then you will want to copy/paste some updates
# to the old one instead:
# If your htaccess/index version is 300, upgrade to this version by replacing all of sections #5
# and #15 (Access Restrictions). Also take a look at section #9, which you might also consider
# replacing if using HTTPS, though it is not required. (For instance, HSTS might be worthwhile)
#
# Following that, optionally review the rest of the file to see if there are any other changes
# you also want to apply. Sections tagged "(v301)" are new or have significant changes.
#
# When finished, add a line at the top identical to the "htaccessVersion 301" that you see at
# the top of this file. This tells ProcessWire your .htaccess file is up-to-date.
#
# Resolving 500 errors
# -----------------------------------------------------------------------------------------------
# Depending on your server, some htaccess rules may not be compatible and result in a 500 error.
# If you experience this, find all instances of the term "(500)" in this file for suggestions on
# things you can change to resolve 500 errors.
#
# Optional features
# -----------------------------------------------------------------------------------------------
# Many of the rules in this .htaccess file are optional and commented out by default. While the
# defaults are okay for many, you may want to review each section in this .htaccess file for
# optional rules that you can enable to increase security, speed or best practices. To quickly
# locate all optional rules, search this file for all instances of "(O)".
#
# If using a load balancer
# -----------------------------------------------------------------------------------------------
# If using a load balancer (like those available from AWS) some htaccess rules will need to
# change. Search this file for instances of "(L)" for details.
#
# -----------------------------------------------------------------------------------------------
# 1. Don't show directory indexes, but do follow symbolic links
# 500 NOTE: Some cloud hosting companies don't allow +FollowSymLinks.
# Uncomment +SymLinksifOwnerMatch and comment +FollowSymLinks if you have 500 errors.
# If that doesn't resolve the error, then set it back to +FollowSymLinks.
# 1. Apache Options
#
# Note: If you experience a (500) error, it may indicate your host does not allow setting one or
# more of these options. First try replacing the +FollowSymLinks with +SymLinksifOwnerMatch.
# If that does not work, try commenting them all out, then uncommenting one at a time to
# determine which one is the source of the 500 error.
# -----------------------------------------------------------------------------------------------
# Do not show directory indexes (strongly recommended)
Options -Indexes
# Do not use multiviews (v301)
Options -MultiViews
# Do follow symbolic links
Options +FollowSymLinks
# Options +SymLinksifOwnerMatch
# Character encoding: Serve text/html or text/plain as UTF-8
AddDefaultCharset UTF-8
# -----------------------------------------------------------------------------------------------
# 2. Let ProcessWire handle 404s
# 2. ErrorDocument settings: Have ProcessWire handle 404s
#
# For options and optimizations (O) see:
# https://processwire.com/blog/posts/optimizing-404s-in-processwire/
# -----------------------------------------------------------------------------------------------
ErrorDocument 404 /index.php
# -----------------------------------------------------------------------------------------------
# 3. Handle request for missing favicon.ico/robots.txt files (no ending quote for Apache 1.3)
# -----------------------------------------------------------------------------------------------
@@ -33,6 +85,7 @@ ErrorDocument 404 /index.php
ErrorDocument 404 "The requested file robots.txt was not found.
</Files>
# -----------------------------------------------------------------------------------------------
# 4. Protect from XSS with Apache headers
# -----------------------------------------------------------------------------------------------
@@ -42,18 +95,25 @@ ErrorDocument 404 /index.php
# you will need to remove this one if you want to allow external iframes
Header always append X-Frame-Options SAMEORIGIN
# to prevent cross site scripting (IE8+ proprietary)
# To prevent cross site scripting (IE8+ proprietary)
Header set X-XSS-Protection "1; mode=block"
# prevent mime-based attacks via content sniffing (IE+Chrome)
# Optionally (O) prevent mime-based attacks via content sniffing (IE+Chrome)
# Header set X-Content-Type-Options "nosniff"
</IfModule>
# -----------------------------------------------------------------------------------------------
# 5. Protect ProcessWire system files
# 5. Prevent access to various types of files (v301)
#
# Note that some of these rules are duplicated by RewriteRules or other .htaccess files, as we
# try to maintain two layers of protection when/where possible.
# -----------------------------------------------------------------------------------------------
<FilesMatch "\.(inc|info|info\.json|module|sh|sql)$|^\..*$|composer\.(json|lock)$">
# 5A. Block access to inc, info, info.json/php, module/php, sh, sql and composer files
# -----------------------------------------------------------------------------------------------
<FilesMatch "\.(inc|info|info\.(json|php)|module|module\.php|sh|sql)$|^\..*$|composer\.(json|lock)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
@@ -62,9 +122,23 @@ ErrorDocument 404 /index.php
</IfModule>
</FilesMatch>
# 5B. Block bak, conf, dist, ini, log, orig, sh, sql, swo, swp, ~, and more
# -----------------------------------------------------------------------------------------------
<FilesMatch "(^#.*#|\.(bak|conf|dist|in[ci]|log|orig|sh|sql|sw[op])|~)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
</IfModule>
</FilesMatch>
# -----------------------------------------------------------------------------------------------
# 6. Override a few PHP settings that can't be changed at runtime (not required)
# 500 NOTE: Try commenting out this entire section below if getting Apache 500 errors.
# Note: try commenting out this entire section below if getting Apache (500) errors.
# -----------------------------------------------------------------------------------------------
<IfModule mod_php5.c>
@@ -73,67 +147,192 @@ ErrorDocument 404 /index.php
php_flag register_globals off
</IfModule>
# -----------------------------------------------------------------------------------------------
# 7. Set default directory index files
# -----------------------------------------------------------------------------------------------
DirectoryIndex index.php index.html index.htm
# -----------------------------------------------------------------------------------------------
# 8. ProcessWire requires mod_rewrite
# 8. Enable Apache mod_rewrite (required)
# -----------------------------------------------------------------------------------------------
<IfModule mod_rewrite.c>
RewriteEngine On
AddDefaultCharset UTF-8
# -----------------------------------------------------------------------------------------------
# 9. If you only want to allow HTTPS, uncomment the RewriteCond and RewriteRule lines below.
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTPS} off
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# If using an AWS load balancer, use these two lines below instead of those above:
# RewriteCond %{HTTP:X-Forwarded-Proto} =http
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# 8A. Optionally (O) set a rewrite base if rewrites are not working properly on your server.
# -----------------------------------------------------------------------------------------------
# In addition, if your site directory starts with a "~" you will most likely have to use this.
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
# Examples of RewriteBase (root and subdirectories):
# RewriteBase /
# RewriteBase /pw/
# RewriteBase /~user/
# 8B. Set an environment variable so the installer can detect that mod_rewrite is active.
# -----------------------------------------------------------------------------------------------
# 10. Set an environment variable so the installer can detect that mod_rewrite is active.
# Note that some web hosts don't support this. If you get a 500 error, you might try
# commenting out this SetEnv line below.
# -----------------------------------------------------------------------------------------------
# Note that some web hosts don't support this. If you get a (500) error, try commenting out this
# SetEnv line below.
<IfModule mod_env.c>
SetEnv HTTP_MOD_REWRITE On
</IfModule>
# -----------------------------------------------------------------------------------------------
# 11. OPTIONAL: Set a rewrite base if rewrites aren't working properly on your server.
# And if your site directory starts with a "~" you will most likely have to use this.
# -----------------------------------------------------------------------------------------------
# RewriteBase /
# RewriteBase /pw/
# RewriteBase /~user/
# -----------------------------------------------------------------------------------------------
# 12. Access Restrictions: Keep web users out of dirs that begin with a period,
# 9. Optionally Force HTTPS (O)
# -----------------------------------------------------------------------------------------------
# Note that on some web hosts you may need to replace %{HTTPS} with %{ENV:HTTPS} in order
# for it to work (in sections 9A and 9D below). If on a load balancer or proxy setup, you will
# likely need to use 9B rather than 9A, and 9E rather than 9D.
# -----------------------------------------------------------------------------------------------
# 9A. To redirect HTTP requests to HTTPS, uncomment the lines below (also see note above):
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTPS} !=on
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# 9B. If using load balancer/AWS or behind proxy, use the following rather than 9A above: (L)
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTP:X-Forwarded-Proto} =http
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# 9C. If using cPanel AutoSSL or Let's Encrypt webroot you may need to MOVE one of the below
# lines after the first RewriteCond in 9A or 9B to allow certificate validation:
# -----------------------------------------------------------------------------------------------
# RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/
# RewriteCond %{REQUEST_URI} !^/\.well-known/cpanel-dcv/[\w-]+$
# RewriteCond %{REQUEST_URI} !^/\.well-known/pki-validation/[A-F0-9]{32}\.txt(?:\ Comodo\ DCV)?$
# 9D. Store current scheme in a 'proto' environment variable for later use
# -----------------------------------------------------------------------------------------------
RewriteCond %{HTTPS} =on
RewriteRule ^ - [env=proto:https]
RewriteCond %{HTTPS} !=on
RewriteRule ^ - [env=proto:http]
# 9E. If using load balancer/AWS or behind proxy, use lines below rather than 9D: (L)
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTP:X-Forwarded-Proto} =https
# RewriteRule ^ - [env=proto:https]
# RewriteCond %{HTTP:X-Forwarded-Proto} =http
# RewriteRule ^ - [env=proto:http]
# 9F. Tell web browsers to only allow access via HSTS: Strict-Transport-Security (O) (v301)
# -----------------------------------------------------------------------------------------------
# This forces client-side SSL redirection. Before enabling be absolutely certain you can
# always serve via HTTPS because it becomes non-revokable for the duration of your max-age.
# See link below for details and options (note 'max-age=31536000' is 1-year):
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
<IfModule mod_headers.c>
# Uncomment one (1) line below & adjust as needed to enable Strict-Transport-Security (HSTS):
# Header always set Strict-Transport-Security "max-age=31536000;"
# Header always set Strict-Transport-Security "max-age=31536000; includeSubdomains"
# Header always set Strict-Transport-Security "max-age=31536000; preload"
# Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>
# Section 10 intentionally omitted for future use
# -----------------------------------------------------------------------------------------------
# 11. Nuisance blocking/firewall
# -----------------------------------------------------------------------------------------------
# None of these are enabled by default, but are here for convenience when the need arises.
# Review and uncomment as needed. For more complete firewall (and more overhead), the 7G firewall
# (or latest version) is worth considering, see: https://perishablepress.com/7g-firewall/
# -----------------------------------------------------------------------------------------------
# 11A. Block via IP addresses
# -----------------------------------------------------------------------------------------------
# Note that IP addresses here are examples only and should be replaced with actual IPs.
# Block single IP address
# Deny from 111.222.333.444
# Block multiple IP addresses
# Deny from 111.222.333.444 44.33.22.11
# Block IP address ranges (999.88.*, 99.88.77.*, 1.2.3.*)
# Deny from 999.888 99.88.77 1.2.3
# 11B. Block via request URI (matches strings anywhere in request URL)
# -----------------------------------------------------------------------------------------------
# RewriteCond %{REQUEST_URI} (bad-word|wp-admin|wp-content) [NC]
# RewriteRule .* - [F,L]
# 11B. Block via user agent strings (matches strings anywhere in user-agent)
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTP_USER_AGENT} (bad-bot|mean-bot) [NC]
# RewriteRule .* - [F,L]
# 11C. Block via remote hosts
# -----------------------------------------------------------------------------------------------
# RewriteCond %{REMOTE_HOST} (bad-host|annoying-host) [NC]
# RewriteRule .* - [F,L]
# 11D. Block via HTTP referrer (matches anywhere in referrer URL)
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTP_REFERER} (bad-referrer|gross-referrer) [NC]
# RewriteRule .* - [F,L]
# 11E. Block unneeded request methods (only if you do not need them)
# -----------------------------------------------------------------------------------------------
# RewriteCond %{REQUEST_METHOD} ^(connect|debug|delete|move|put|trace|track) [NC]
# RewriteRule .* - [F,L]
# 11F. Limit file upload size from Apache (i.e. 10240000=10 MB, adjust as needed)
# -----------------------------------------------------------------------------------------------
# LimitRequestBody 10240000
# -----------------------------------------------------------------------------------------------
# 12. Access Restrictions: Keep web users out of dirs or files that begin with a period,
# but let services like Lets Encrypt use the webroot authentication method.
# -----------------------------------------------------------------------------------------------
RewriteRule "(^|/)\.(?!well-known)" - [F]
# -----------------------------------------------------------------------------------------------
# 13. OPTIONAL: Redirect users to the 'www.' version of the site (uncomment to enable).
# For example: http://processwire.com/ would be redirected to http://www.processwire.com/
# 13. Optional domain redirects (O)
#
# Redirect domain.com to www.domain.com redirect (or www to domain.com redirect).
# If using then uncomment either 13A or 13B, do NOT uncomment both of them or nothing will work.
# -----------------------------------------------------------------------------------------------
# 13A. Redirect domain.com and *.domain.com to www.domain.com (do not combine with 13B):
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTP_HOST} !^www\. [NC]
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# RewriteCond %{SERVER_ADDR} !=127.0.0.1
# RewriteCond %{SERVER_ADDR} !=::1
# RewriteRule ^ %{ENV:PROTO}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# 13B. Redirect www.domain.com to domain.com (do not combine with 13A):
# -----------------------------------------------------------------------------------------------
# RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
# RewriteRule ^ %{ENV:PROTO}://%1%{REQUEST_URI} [R=301,L]
# -----------------------------------------------------------------------------------------------
# 14. OPTIONAL: Send URLs with non-ASCII name-format characters to 404 page (optimization)
# 14. Optionally send URLs with non-ASCII name-format characters to 404 page (optimization).
#
# This ensures that ProcessWire does not spend time processing URLs that we know ahead of time
# are going to result in 404s. Uncomment lines below to enable. (O)
# -----------------------------------------------------------------------------------------------
# RewriteCond %{REQUEST_URI} "[^-_.a-zA-Z0-9/~]"
@@ -141,49 +340,92 @@ DirectoryIndex index.php index.html index.htm
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule ^(.*)$ index.php?it=/http404/ [L,QSA]
# -----------------------------------------------------------------------------------------------
# 15. Access Restrictions: Protect ProcessWire system files
# 15. Access Restrictions (v301)
# -----------------------------------------------------------------------------------------------
# 15A. Keep http requests out of specific files and directories
# -----------------------------------------------------------------------------------------------
# Allow screenshot files (for install.php only: this 1 line below may be removed after install)
RewriteCond %{REQUEST_URI} !(^|/)site-[^/]+/install/[^/]+\.(jpg|jpeg|png|gif)$
# Prevent all the following rules from blocking images in site install directories
RewriteCond %{REQUEST_URI} !(^|/)site-[^/]+/install/[^/]+\.(jpg|jpeg|png|gif|webp|svg)$
# Block access to any htaccess files
RewriteCond %{REQUEST_URI} (^|/)\.htaccess$ [NC,OR]
# Block access to protected assets directories
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/assets/(cache|logs|backups|sessions|config|install|tmp)($|/.*$) [OR]
# Block acceess to the /site/install/ directory
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/install($|/.*$) [OR]
# Block dirs in /site/assets/ dirs that start with a hyphen
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/assets.*/-.+/.* [OR]
# Block access to /wire/config.php, /site/config.php, /site/config-dev.php, and /wire/index.config.php
RewriteCond %{REQUEST_URI} (^|/)(wire|site|site-[^/]+)/(config|index\.config|config-dev)\.php$ [OR]
# Block access to any PHP-based files in /templates-admin/
RewriteCond %{REQUEST_URI} (^|/)(wire|site|site-[^/]+)/templates-admin($|/|/.*\.(php|html?|tpl|inc))$ [OR]
# Block access to any PHP or markup files in /site/templates/
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/templates($|/|/.*\.(php|html?|tpl|inc))$ [OR]
# Block access to any PHP files in /site/assets/
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/assets($|/|/.*\.php)$ [OR]
# Block access to any PHP files in core or core module directories
RewriteCond %{REQUEST_URI} (^|/)wire/(core|modules)/.*\.(php|inc|tpl|module|info\.json)$ [OR]
# Block access to any PHP files in /site/modules/
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/modules/.*\.(php|inc|tpl|module|info\.json)$ [OR]
# Block access to any software identifying txt files
RewriteCond %{REQUEST_URI} (^|/)(COPYRIGHT|INSTALL|README|htaccess)\.(txt|md|textile)$ [OR]
RewriteCond %{REQUEST_URI} (^|/)(\.htaccess|htaccess\..*)$ [NC,OR]
# Block access to various assets directories
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/assets/(cache|logs|backups|sessions|config|install|tmp)($|/.*$) [NC,OR]
# Block access to the /site/install/ directories
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/install($|/.*$) [NC,OR]
# Block dirs in /site/assets/dirs that start with a hyphen (see config.pagefileSecure)
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/assets.*/-.+/.* [NC,OR]
# Block access to /wire/config.php, /site/config.php, /site/config-dev.php, /wire/index.config.php, etc.
RewriteCond %{REQUEST_URI} (^|/)(wire|site|site-[^/]+)/(config|index\.config|config-dev)\.php($|/) [NC,OR]
# Block access to any PHP-based files in /site/templates-admin/ or /wire/templates-admin/
RewriteCond %{REQUEST_URI} (^|/)(wire|site|site-[^/]+)/templates-admin($|/|/.*\.(php|html?|tpl|inc))($|/) [NC,OR]
# Block access to any PHP or markup files in /site/templates/ or /site-*/templates/
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/templates($|/|/.*\.(php|html?|tpl|inc))($|/) [NC,OR]
# Block access to any files in /site/classes/ or /site-*/classes/
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/classes($|/.*) [NC,OR]
# Block access to any PHP files within /site/assets/ and further
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/assets($|/|/.*\.ph(p|ps|tml|p[0-9]))($|/) [NC,OR]
# Block access to any PHP, module, inc or info files in core or core modules directories
RewriteCond %{REQUEST_URI} (^|/)wire/(core|modules)/.*\.(php|inc|tpl|module|info\.json)($|/) [NC,OR]
# Block access to any PHP, tpl or info.json files in /site/modules/ or /site-*/modules/
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/modules/.*\.(php|inc|tpl|module|info\.json)$ [NC,OR]
# Block access to any software identifying txt, markdown or textile files
RewriteCond %{REQUEST_URI} (^|/)(COPYRIGHT|INSTALL|README|htaccess)\.(txt|md|textile)$ [NC,OR]
# Block potential arbitrary backup files within site directories for things like config
RewriteCond %{REQUEST_URI} (^|/)(site|site-[^/]+)/(config[^/]*/?|[^/]+\.php.*)$ [NC,OR]
# Block access throughout to temporary files ending with tilde created by certain editors
RewriteCond %{REQUEST_URI} \.(html?|inc|json|lock|module|php|py|rb|sh|sql|tpl|tmpl|twig)~$ [NC,OR]
# Block access to names of potential backup file extensions within wire or site directories
RewriteCond %{REQUEST_URI} (^|/)(wire/|site[-/]).+\.(bak|old|sql|sw[op]|(bak|php|sql)[./]+.*)[\d.]*$ [NC,OR]
# Block all http access to the default/uninstalled site-default directory
RewriteCond %{REQUEST_URI} (^|/)site-default/
# If any conditions above match, issue a 403 forbidden
RewriteRule ^.*$ - [F,L]
# 15B. Block archive file types commonly used for backup purposes (O)
# -----------------------------------------------------------------------------------------------
# This blocks requests for zip, rar, tar, gz, and tgz files that are sometimes left on servers
# as backup files, and thus can be problematic for security. This rule blocks those files
# unless they are located within the /site/assets/files/ directory. This is not enabled by
# default since there are many legitimate use cases for these files, so uncomment the lines
# below if you want to enable this.
# RewriteCond %{REQUEST_URI} \.(zip|rar|tar|gz|tgz)$ [NC]
# RewriteCond %{REQUEST_URI} !(^|/)(site|site-[^/]+)/assets/files/\d+/ [NC]
# RewriteRule ^.*$ - [F,L]
# PW-PAGENAME
# -----------------------------------------------------------------------------------------------
# 16a. Ensure that the URL follows the name-format specification required by PW
# 16A. Ensure that the URL follows the name-format specification required by PW
# See also directive 16b below, you should choose and use either 16a or 16b.
# -----------------------------------------------------------------------------------------------
RewriteCond %{REQUEST_URI} "^/~?[-_.a-zA-Z0-9/]*$"
# -----------------------------------------------------------------------------------------------
# 16b. Alternative name-format specification for UTF8 page name support.
# 16B. Alternative name-format specification for UTF8 page name support. (O)
# If used, comment out section 16a above and uncomment the directive below. If you have updated
# your $config->pageNameWhitelist make the characters below consistent with that.
# -----------------------------------------------------------------------------------------------
@@ -201,7 +443,7 @@ DirectoryIndex index.php index.html index.htm
RewriteCond %{REQUEST_FILENAME} !(favicon\.ico|robots\.txt)
# -----------------------------------------------------------------------------------------------
# 18. OPTIONAL: Prevent ProcessWire from attempting to serve images or anything in /site/assets/.
# 18. Optionally (O) prevent PW from attempting to serve images or anything in /site/assets/.
# Both of these lines are optional, but can help to reduce server load. However, they
# are not compatible with the $config->pagefileSecure option (if enabled) and they
# may produce an Apache 404 rather than your regular 404. You may uncomment the two lines
@@ -211,18 +453,20 @@ DirectoryIndex index.php index.html index.htm
# section #2 above that makes ProcessWire the 404 handler.
# -----------------------------------------------------------------------------------------------
# RewriteCond %{REQUEST_FILENAME} !\.(jpg|jpeg|gif|png|ico)$ [NC]
# RewriteCond %{REQUEST_FILENAME} !\.(jpg|jpeg|gif|png|ico|webp|svg)$ [NC]
# RewriteCond %{REQUEST_FILENAME} !(^|/)site/assets/
# -----------------------------------------------------------------------------------------------
# 19. Pass control to ProcessWire if all the above directives allow us to this point.
# For regular VirtualHosts (most installs)
# -----------------------------------------------------------------------------------------------
RewriteRule ^(.*)$ index.php?it=$1 [L,QSA]
# -----------------------------------------------------------------------------------------------
# 20. If using VirtualDocumentRoot (500 NOTE): comment out the one above and use this one instead
# 20. If using VirtualDocumentRoot (500): comment out the one above and use this one instead
# -----------------------------------------------------------------------------------------------
# RewriteRule ^(.*)$ /index.php?it=$1 [L,QSA]
</IfModule>

View File

@@ -10,7 +10,7 @@
* do so after you have installed the site, as the installer is not informed
* of any changes made in this file.
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* @version 3.0
@@ -29,7 +29,7 @@ $rootPath = __DIR__;
if(DIRECTORY_SEPARATOR != '/') $rootPath = str_replace(DIRECTORY_SEPARATOR, '/', $rootPath);
$composerAutoloader = $rootPath . '/vendor/autoload.php'; // composer autoloader
if(file_exists($composerAutoloader)) require_once($composerAutoloader);
if(!class_exists("ProcessWire", false)) require_once("$rootPath/wire/core/ProcessWire.php");
if(!class_exists("ProcessWire\\ProcessWire", false)) require_once("$rootPath/wire/core/ProcessWire.php");
$config = ProcessWire::buildConfig($rootPath);
if(!$config->dbName) {
@@ -50,7 +50,7 @@ $wire = null;
try {
// Bootstrap ProcessWire's core and make the API available with $wire
$wire = new ProcessWire($config);
$process = $wire->modules->get('ProcessPageView');
$process = $wire->modules->get('ProcessPageView'); /** @var ProcessPageView $process */
$wire->wire('process', $process);
echo $process->execute($config->internal);
$config->internal ? $process->finished() : extract($wire->wire('all')->getArray());

View File

@@ -11,7 +11,7 @@
* If that file exists, the installer will not run. So if you need to re-run this installer for any
* reason, then you'll want to delete that file. This was implemented just in case someone doesn't delete the installer.
*
* ProcessWire 3.x, Copyright 2017 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
* @todo have installer set session name
@@ -105,26 +105,26 @@ class Installer {
require("./wire/modules/AdminTheme/AdminThemeUikit/install-head.inc");
if(isset($_POST['step'])) switch($_POST['step']) {
case 0: $this->initProfile(); break;
case 1: $this->compatibilityCheck(); break;
case 2: $this->dbConfig(); break;
case 4: $this->dbSaveConfig(); break;
case 5: require("./index.php");
/** @var ProcessWire $wire */
$wire->modules->refresh();
$this->adminAccountSave($wire);
break;
default:
$this->welcome();
} else $this->welcome();
$step = $this->post('step');
if($step === null) {
$this->welcome();
} else {
$step = (int) $step;
switch($step) {
case 0: $this->initProfile(); break;
case 1: $this->compatibilityCheck(); break;
case 2: $this->dbConfig(); break;
case 4: $this->dbSaveConfig(); break;
case 5: require("./index.php");
/** @var ProcessWire $wire */
$wire->modules->refresh();
$this->adminAccountSave($wire);
break;
default:
$this->welcome();
}
}
require("./wire/modules/AdminTheme/AdminThemeUikit/install-foot.inc");
}
@@ -228,8 +228,8 @@ class Installer {
echo "
<p>A site installation profile is a ready-to-use and modify site for ProcessWire.
If you are just getting started with ProcessWire, we recommend choosing
the <em>Default</em> site profile. If you already know what you are doing,
If you are just getting started with ProcessWire, we recommend choosing the <em>Regular</em>
or <em>Default</em> site profile. If you already know what you are doing,
you might prefer the <em>Blank</em> site profile.
<p style='width: 240px;'>
<select class='uk-select' name='profile' id='select-profile'>
@@ -266,10 +266,10 @@ class Installer {
} else if(is_dir("./site/")) {
$this->alertOk("Found /site/ — already installed? ");
} else if(isset($_POST['profile'])) {
} else if($this->post('profile')) {
$profiles = $this->findProfiles();
$profile = preg_replace('/[^-a-zA-Z0-9_]/', '', $_POST['profile']);
$profile = $this->post('profile', 'name');
if(empty($profile) || !isset($profiles[$profile]) || !is_dir(dirname(__FILE__) . "/$profile")) {
$this->alertErr("Profile not found");
$this->selectProfile();
@@ -322,7 +322,7 @@ class Installer {
}
$this->checkFunction("filter_var", "Filter functions (filter_var)");
$this->checkFunction("mysqli_connect", "MySQLi (not required by core, but may be required by some 3rd party modules)");
$this->checkFunction("mysqli_connect", "MySQLi (not used by core, but may still be used by some older 3rd party modules)");
$this->checkFunction("imagecreatetruecolor", "GD 2.0 or newer");
$this->checkFunction("json_encode", "JSON support");
$this->checkFunction("preg_match", "PCRE support");
@@ -334,7 +334,7 @@ class Installer {
if(function_exists('apache_get_modules')) {
if(in_array('mod_rewrite', apache_get_modules())) $this->ok("Found Apache module: mod_rewrite");
else $this->err("Apache mod_rewrite does not appear to be installed and is required by ProcessWire.");
else $this->err("Apache 'mod_rewrite' module does not appear to be installed and is required by ProcessWire.");
} else {
// apache_get_modules doesn't work on a cgi installation.
// check for environment var set in htaccess file, as submitted by jmarjie.
@@ -342,7 +342,11 @@ class Installer {
if($mod_rewrite) {
$this->ok("Found Apache module (cgi): mod_rewrite");
} else {
$this->err("Unable to determine if Apache mod_rewrite (required by ProcessWire) is installed. On some servers, we may not be able to detect it until your .htaccess file is place. Please click the 'check again' button at the bottom of this screen, if you haven't already.");
$this->err(
"Unable to determine if Apache mod_rewrite (required by ProcessWire) is installed. " .
"On some servers, we may not be able to detect it until your .htaccess file is place. " .
"Please click the 'check again' button at the bottom of this screen, if you haven't already."
);
}
}
@@ -538,11 +542,35 @@ class Installer {
$this->sectionStop();
$this->sectionStart('fa-server HTTP Host Names');
$this->p("What host names will this installation run on now and in the future? Please enter one host per line. You may also choose to leave this blank to auto-detect on each request, but we recommend using this whitelist for the best security in production environments.");
$this->p("This field is recommended but not required. You can set this later by editing the file <u>/site/config.php</u> (setting \$config->httpHosts).", "detail");
$this->p(
"What host names will this installation run on now and in the future? Please enter one host per line. " .
"You can also modify this setting later by editing the <code>\$config->httpHosts</code> setting in the <u>/site/config.php</u> file."
);
$rows = substr_count($values['httpHosts'], "\n") + 2;
$this->textarea('httpHosts', '', $values['httpHosts'], $rows);
$this->sectionStop();
$this->sectionStart('fa-bug Debug mode?');
$this->p(
"When debug mode is enabled, errors and exceptions are visible in ProcessWires output. This is helpful when developing a website or testing ProcessWire. " .
"When debug mode is NOT enabled, fatal errors/exceptions halt the request with an ambiguous http 500 error, and non-fatal errors are not shown. " .
"Regardless of debug mode, fatal errors are always logged and always visible to superusers. " .
"Debug mode should not be enabled for live or production sites, but at this stage (installation) it is worthwhile to have it enabled. "
);
$noChecked = empty($values['debugMode']) ? "checked='checked'" : "";
$yesChecked = empty($noChecked) ? "checked='checked'" : "";
$this->p(
"<label><input type='radio' name='debugMode' $yesChecked value='1'> <strong>Enabled</strong> " .
"<span class='uk-text-small uk-text-muted'>(recommended while sites are in development or while testing ProcessWire)</span></label><br />" .
"<label><input type='radio' name='debugMode' $noChecked value='0'> <strong>Disabled</strong> " .
"<span class='uk-text-small uk-text-muted'>(recommended once a site goes live or becomes publicly accessible)</span></label> "
);
$this->p(
"You can also enable or disable debug mode at any time by editing the <u>/site/config.php</u> file and setting " .
"<code>\$config->debug = true;</code> or <code>\$config->debug = false;</code>"
);
$this->sectionStop();
$this->btn("Continue", 4);
$this->p("Note: After you click the button above, be patient &hellip; it may take a minute.", "detail");
}
@@ -559,13 +587,17 @@ class Installer {
// file permissions
$fields = array('chmodDir', 'chmodFile');
foreach($fields as $field) {
$value = (int) $_POST[$field];
if(strlen("$value") !== 3) $this->alertErr("Value for '$field' is invalid");
else $this->$field = "0$value";
$value = $this->post($field, 'int');
if(strlen("$value") !== 3) {
$this->alertErr("Value for '$field' is invalid");
} else {
$this->$field = "0$value";
}
$values[$field] = $value;
}
$timezone = (int) $_POST['timezone'];
// timezone
$timezone = $this->post('timezone', 'int');
$timezones = $this->timezones();
if(isset($timezones[$timezone])) {
$value = $timezones[$timezone];
@@ -578,8 +610,9 @@ class Installer {
$values['timezone'] = 'America/New_York';
}
// http hosts
$values['httpHosts'] = array();
$httpHosts = trim($_POST['httpHosts']);
$httpHosts = $this->post('httpHosts', 'textarea');
if(strlen($httpHosts)) {
$httpHosts = str_replace(array("'", '"'), '', $httpHosts);
$httpHosts = explode("\n", $httpHosts);
@@ -589,12 +622,15 @@ class Installer {
}
$values['httpHosts'] = $httpHosts;
}
// debug mode
$values['debugMode'] = $this->post('debugMode', 'int');
// db configuration
$fields = array('dbUser', 'dbName', 'dbPass', 'dbHost', 'dbPort', 'dbEngine', 'dbCharset');
foreach($fields as $field) {
$value = get_magic_quotes_gpc() ? stripslashes($_POST[$field]) : $_POST[$field];
$value = $this->post($field, 'string');
$value = substr($value, 0, 255);
if(strpos($value, "'") !== false) $value = str_replace("'", "\\" . "'", $value); // allow for single quotes (i.e. dbPass)
$values[$field] = trim($value);
@@ -661,7 +697,7 @@ class Installer {
$query = $database->query("SHOW TABLES");
$tables = $query->fetchAll(\PDO::FETCH_COLUMN);
$numTables = count($tables);
$dbTablesAction = isset($_POST['dbTablesAction']) ? $_POST['dbTablesAction'] : '';
$dbTablesAction = $this->post('dbTablesAction', 'string');
if($numTables && $dbTablesAction) {
if($dbTablesAction === 'remove') {
@@ -750,11 +786,22 @@ class Installer {
*/
protected function dbSaveConfigFile(array $values) {
if(self::TEST_MODE) return true;
if(self::TEST_MODE) return true;
$file = __FILE__;
$time = time();
$host = empty($values['httpHosts']) ? '' : implode(',', $values['httpHosts']);
$salt = md5(mt_rand() . microtime(true));
$cfg = "\n/**" .
if(function_exists('random_bytes')) {
$authSalt = sha1(random_bytes(random_int(40, 128)));
$tableSalt = sha1(random_int(0, 65535) . "$host$file$time");
} else {
$authSalt = md5(mt_rand() . microtime(true));
$tableSalt = md5(mt_rand() . "$host$file$time");
}
$cfg =
"\n/**" .
"\n * Installer: Database Configuration" .
"\n * " .
"\n */" .
@@ -771,11 +818,24 @@ class Installer {
"\n" .
"\n/**" .
"\n * Installer: User Authentication Salt " .
"\n * " .
"\n * Must be retained if you migrate your site from one server to another" .
"\n * " .
"\n * This value was randomly generated for your system on " . date('Y/m/d') . "." .
"\n * This should be kept as private as a password and never stored in the database." .
"\n * Must be retained if you migrate your site from one server to another." .
"\n * Do not change this value, or user passwords will no longer work." .
"\n * " .
"\n */" .
"\n\$config->userAuthSalt = '$salt'; " .
"\n\$config->userAuthSalt = '$authSalt'; " .
"\n" .
"\n/**" .
"\n * Installer: Table Salt (General Purpose) " .
"\n * " .
"\n * Use this rather than userAuthSalt when a hashing salt is needed for non user " .
"\n * authentication purposes. Like with userAuthSalt, you should never change " .
"\n * this value or it may break internal system comparisons that use it. " .
"\n * " .
"\n */" .
"\n\$config->tableSalt = '$tableSalt'; " .
"\n" .
"\n/**" .
"\n * Installer: File Permission Configuration" .
@@ -817,6 +877,18 @@ class Installer {
$cfg = rtrim($cfg, ", ") . ");\n\n";
}
$cfg .=
"\n/**" .
"\n * Installer: Debug mode?" .
"\n * " .
"\n * When debug mode is true, errors and exceptions are visible. " .
"\n * When false, they are not visible except to superuser and in logs. " .
"\n * Should be true for development sites and false for live/production sites. " .
"\n * " .
"\n */" .
"\n\$config->debug = " . ($values['debugMode'] ? 'true;' : 'false;') .
"\n\n";
if(($fp = fopen("./site/config.php", "a")) && fwrite($fp, $cfg)) {
fclose($fp);
$this->alertOk("Saved configuration to ./site/config.php");
@@ -863,9 +935,10 @@ class Installer {
if(is_dir($profile . "files")) $this->profileImportFiles($profile);
else $this->mkdir("./site/assets/files/");
$this->mkdir("./site/assets/cache/");
$this->mkdir("./site/assets/logs/");
$this->mkdir("./site/assets/sessions/");
$this->mkdir("./site/assets/cache/", true, true);
$this->mkdir("./site/assets/logs/", true, true);
$this->mkdir("./site/assets/backups/", true, true);
$this->mkdir("./site/assets/sessions/", true, true);
} else {
$this->ok("A profile is already imported, skipping...");
@@ -890,6 +963,16 @@ class Installer {
} else {
// they are installing site-default already
}
// install the site/.htaccess (not really required but potentially useful fallback)
$dir = "./site/";
$defaultDir = "./site-default/";
if(is_file($dir . 'htaccess.txt')) {
$this->renameFile($dir . 'htaccess.txt', $dir . '.htaccess');
} else if(is_file($defaultDir . 'htaccess.txt')) {
$this->copyFile($defaultDir . 'htaccess.txt', $dir . '.htaccess');
}
$this->sectionStop();
$this->adminAccount();
}
@@ -1036,18 +1119,25 @@ class Installer {
$this->sectionStart("fa-bath Cleanup");
$this->p("Directories and files listed below are no longer needed and should be removed. If you choose to leave any of them in place, you should delete them before migrating to a production environment.", "detail");
$this->p($this->getRemoveableItems($wire, true));
$this->p($this->getRemoveableItems(true));
$this->sectionStop();
$this->btn("Continue", 5);
}
protected function getRemoveableItems($wire, $getMarkup = false, $removeNow = false) {
/**
* Get post-install optionally removable items
*
* @param bool $getMarkup Get markup of options/form inputs rather than array of items?
* @param bool $removeNow Allow processing of submitted form (via getMarkup) to remove items now?
* @return array|string
*
*/
protected function getRemoveableItems($getMarkup = false, $removeNow = false) {
$root = dirname(__FILE__) . '/';
$isPost = $wire->input->post->remove_items !== null;
$postItems = $isPost ? $wire->input->post->remove_items : array();
if(!is_array($postItems)) $postItems = array();
$isPost = $this->post('remove_items') !== null;
$postItems = $this->post('remove_items', 'array');
$out = '';
$items = array(
@@ -1099,7 +1189,7 @@ class Installer {
$success = true;
}
if($success) {
$this->ok("Completed: " . $item['label']);
// $this->ok("Completed: " . $item['label']);
} else {
$this->err("Unable to remove $item[file] - please remove manually, as it is no longer needed");
}
@@ -1111,7 +1201,9 @@ class Installer {
}
}
if(empty($out)) $out = "None found";
if($getMarkup) return $out;
return $items;
}
@@ -1201,22 +1293,36 @@ class Installer {
$this->sectionStop();
$this->sectionStart("fa-life-buoy Complete &amp; Secure Your Installation");
$this->getRemoveableItems($wire, false, true);
$this->getRemoveableItems(false, true);
$this->ok("Note that future runtime errors are logged to <b>/site/assets/logs/errors.txt</b> (not web accessible).");
$this->ok("For more configuration options see <b>/wire/config.php</b> and place any edits in /site/config.php.");
$this->ok("For more configuration options see <b>/wire/config.php</b> and place any edits in <u>/site/config.php</u>.");
$this->ok("Consider making your <b>/site/config.php</b> file non-writable, and readable only to you and Apache.");
$this->ok("View and edit your <b>.htaccess</b> file to force HTTPS, setup redirects, and more.");
$this->p(
"Please make your <b>/site/config.php</b> file non-writable, and readable only to you and Apache.<br />" .
"<a target='_blank' href='https://processwire.com/docs/security/file-permissions/#securing-your-site-config.php-file'>" .
"How to secure your /site/config.php file <i class='fa fa-angle-right'></i></a>"
"<a target='_blank' href='https://processwire.com/docs/security/'>" .
"Lean more about securing your ProcessWire installation <i class='fa fa-angle-right'></i></a>"
);
$this->sectionStop();
if(is_writable("./site/modules/")) wireChmod("./site/modules/", true);
$this->sectionStart("fa-coffee Use The Site!");
$this->ok("Your admin URL is <a href='./$adminName/'>/$adminName/</a>");
$this->p("If you'd like, you may change this later by editing the admin page and changing the name.", "detail");
$this->sectionStart("fa-coffee Get Started!");
$this->ok(
"Your admin URL is <a target='_blank' href='./$adminName/'>/$adminName/</a>"
);
$this->ok(
"Learn more about ProcessWire in the <a target='_blank' href='https://processwire.com/docs/'>documentation</a> " .
"and <a target='_blank' href='https://processwire.com/api/ref/'>API reference</a>. "
);
$this->ok(
"Visit our <a target='_blank' href='https://processwire.com/talk/'>support forums</a> for friendly help and discussion."
);
$this->ok(
"<a target='_blank' href='https://processwire.com/community/newsletter/subscribe/'>Subscribe to keep up-to-date</a> " .
"with new versions and important updates."
);
$this->sectionStop();
$this->btn("Login to Admin", 1, 'sign-in', false, true, "./$adminName/");
@@ -1236,13 +1342,14 @@ class Installer {
/**
* @param string $str
* @param string $icon
*
*/
protected function alertOk($str) {
protected function alertOk($str, $icon = 'check') {
if($this->inSection) {
$this->ok($str);
} else {
echo "\n<div class='uk-alert uk-alert-primary'><i class='fa fa-fw fa-check'></i> $str</div>";
echo "\n<div class='uk-alert uk-alert-primary'><i class='fa fa-fw fa-$icon'></i> $str</div>";
}
}
@@ -1312,15 +1419,15 @@ class Installer {
* Report success
*
* @param string $str
* @param string $icon
* @return bool
*
*/
protected function ok($str) {
protected function ok($str, $icon = 'check') {
if(!$this->inSection) {
$this->alertOk($str);
} else {
//echo "\n<li class='ui-state-highlight'><i class='fa fa-check-square-o'></i> $str</li>";
echo "\n<div class=''><i class='fa fa-fw fa-check'></i> $str</div>";
echo "\n<div class=''><i class='fa fa-fw fa-$icon'></i> $str</div>";
}
return true;
}
@@ -1341,7 +1448,7 @@ class Installer {
if($float) $class .= " uk-float-left";
$type = 'submit';
if($href) $type = 'button';
if($href) echo "<a href='$href'>";
if($href) echo "<a href='$href' target='_blank'>";
echo "\n<p><button name='step' type='$type' class='ui-button ui-widget ui-state-default $class ui-corner-all' value='$value'>";
echo "<span class='ui-button-text'><i class='fa fa-$icon'></i> $label</span>";
echo "</button></p>";
@@ -1498,7 +1605,74 @@ class Installer {
protected function clear() {
echo "\n<div style='clear: both;'></div>";
}
protected function post($key, $sanitizer = '') {
$value = isset($_POST[$key]) ? $_POST[$key] : null;
if($value === null && empty($sanitizer)) return null;
if(version_compare(PHP_VERSION, "5.4.0", "<") && function_exists('get_magic_quotes_gpc')) {
if(get_magic_quotes_gpc()) $value = stripslashes($value);
}
switch($sanitizer) {
case 'intSigned':
$value = (int) $value;
break;
case 'int':
$value = (int) $value;
if($value < 0) $value = 0;
break;
case 'text':
$value = (string) $value;
if(strlen($value)) {
$value = str_replace(array("\r", "\n", "\t"), ' ', "$value");
$value = trim(strip_tags($value));
if(strlen($value) > 255) $value = substr($value, 0, 255);
}
break;
case 'textarea':
$value = (string) $value;
if(strlen($value)) {
$value = strip_tags($value);
$value = str_replace(array("\r\n", "\r"), "\n", $value);
if(strlen($value) > 4096) $value = substr($value, 0, 4096);
$value = trim($value);
}
break;
case 'string':
$value = trim((string) $value);
break;
case 'pageName':
$value = strtolower($value);
// no-break: passthrough to 'name' intentional...
case 'name':
$value = trim((string) $value);
if(strlen($value)) {
$value = preg_replace('/[^-._a-z0-9]/', '-', $value);
while(strpos($value, '--') !== false) $value = str_replace('--', '-', $value);
$value = trim($value, '-');
}
break;
case 'fieldName':
$value = trim((string) $value);
if(strlen($value)) {
$value = preg_replace('/[^_a-zA-Z0-9]/', '_', $value);
while(strpos($value, '__') !== false) $value = str_replace('__', '_', $value);
$value = trim($value, '_');
}
break;
case 'bool':
$value = $value ? true : false;
break;
case 'array':
$value = is_array($value) ? $value : array();
break;
}
return $value;
}
/******************************************************************************************************************
* FILE FUNCTIONS
@@ -1508,21 +1682,60 @@ class Installer {
/**
* Create a directory and assign permission
*
* @param string $path
* @param bool $showNote
* @param string $path Path to create
* @param bool $showNote Show notification about what was done?
* @param bool $block Add an htaccess file that blocks http access? (default=false)
* @return bool
*
*/
protected function mkdir($path, $showNote = true) {
protected function mkdir($path, $showNote = true, $block = false) {
if(self::TEST_MODE) return true;
if(is_dir($path) || mkdir($path)) {
$path = rtrim($path, '/') . '/';
$isDir = is_dir($path);
if($isDir || mkdir($path)) {
chmod($path, octdec($this->chmodDir));
if($showNote) $this->alertOk("Created directory: $path");
return true;
if($showNote && !$isDir) $this->alertOk("Created directory: $path");
$result = true;
} else {
if($showNote) $this->alertErr("Error creating directory: $path");
return false;
$result = false;
}
$file = $path . '.htaccess';
if($result && $block && !file_exists($file)) {
$data = array(
'# Start ProcessWire:pwball (install)',
'# Block all access (fallback if root .htaccess missing)',
'<IfModule mod_authz_core.c>',
' Require all denied',
'</IfModule>',
'<IfModule !mod_authz_core.c>',
' Order allow,deny',
' Deny from all',
'</IfModule>',
'# End ProcessWire:pwball',
);
file_put_contents($file, implode("\n", $data));
chmod($file, octdec($this->chmodFile));
}
return $result;
}
protected function copyFile($src, $dst) {
if(!@copy($src, $dst)) {
$this->alertErr("Unable to copy $src => $dst (please copy manually if possible)");
return false;
}
chmod($dst, octdec($this->chmodFile));
return true;
}
protected function renameFile($src, $dst) {
if(!@rename($src, $dst)) {
$this->alertErr("Unable to rename $src => $dst (please rename manually if possible)");
return false;
}
chmod($dst, octdec($this->chmodFile));
return true;
}
/**
@@ -1560,10 +1773,17 @@ class Installer {
closedir($dir);
return true;
}
}
/**
* Get all timezone selections
*
* @return array
*
*/
protected function timezones() {
$timezones = timezone_identifiers_list();
if(!is_array($timezones)) return array('UTC');
$extras = array(
'US Eastern|America/New_York',
'US Central|America/Chicago',

View File

@@ -17,7 +17,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
*/
@@ -28,18 +28,6 @@ if(!defined("PROCESSWIRE")) die();
/** @var Config $config */
/**
* Enable debug mode?
*
* Debug mode causes additional info to appear for use during dev and debugging.
* This is almost always recommended for sites in development. However, you should
* always have this disabled for live/production sites.
*
* @var bool
*
*/
$config->debug = false;
/**
* Prepend template file
*
@@ -51,5 +39,19 @@ $config->debug = false;
*/
$config->prependTemplateFile = '_init.php';
/**
* Allow core API variables to also be accessed as functions?
*
* Recommended. This enables API varibles like $pages to also be accessed as pages(),
* as an example. And so on for most other core variables.
*
* Benefits are better type hinting, always in scope, and potentially shorter API calls.
* See the file /wire/core/FunctionsAPI.php for details on these functions.
*
* @var bool
*
*/
$config->useFunctionsAPI = true;
/*** INSTALLER CONFIG ********************************************************************/

View File

@@ -17,7 +17,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
*/
@@ -29,16 +29,18 @@ if(!defined("PROCESSWIRE")) die();
/** @var Config $config */
/**
* Enable debug mode?
* Allow core API variables to also be accessed as functions?
*
* Debug mode causes additional info to appear for use during dev and debugging.
* This is almost always recommended for sites in development. However, you should
* always have this disabled for live/production sites.
* Recommended. This enables API varibles like $pages to also be accessed as pages(),
* as an example. And so on for most other core variables.
*
* Benefits are better type hinting, always in scope, and potentially shorter API calls.
* See the file /wire/core/FunctionsAPI.php for details on these functions.
*
* @var bool
*
*/
$config->debug = false;
$config->useFunctionsAPI = true;
/*** INSTALLER CONFIG ********************************************************************/

View File

@@ -17,7 +17,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
*/
@@ -29,16 +29,18 @@ if(!defined("PROCESSWIRE")) die();
/** @var Config $config */
/**
* Enable debug mode?
* Allow core API variables to also be accessed as functions?
*
* Debug mode causes additional info to appear for use during dev and debugging.
* This is almost always recommended for sites in development. However, you should
* always have this disabled for live/production sites.
* Recommended. This enables API varibles like $pages to also be accessed as pages(),
* as an example. And so on for most other core variables.
*
* Benefits are better type hinting, always in scope, and potentially shorter API calls.
* See the file /wire/core/FunctionsAPI.php for details on these functions.
*
* @var bool
*
*/
$config->debug = false;
$config->useFunctionsAPI = true;
/*** INSTALLER CONFIG ********************************************************************/

View File

@@ -17,7 +17,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
*/
@@ -28,18 +28,6 @@ if(!defined("PROCESSWIRE")) die();
/** @var Config $config */
/**
* Enable debug mode?
*
* Debug mode causes additional info to appear for use during dev and debugging.
* This is almost always recommended for sites in development. However, you should
* always have this disabled for live/production sites.
*
* @var bool
*
*/
$config->debug = false;
/**
* Prepend template file
*
@@ -62,7 +50,19 @@ $config->prependTemplateFile = '_init.php';
*/
$config->appendTemplateFile = '_main.php';
/**
* Allow core API variables to also be accessed as functions?
*
* Recommended. This enables API varibles like $pages to also be accessed as pages(),
* as an example. And so on for most other core variables.
*
* Benefits are better type hinting, always in scope, and potentially shorter API calls.
* See the file /wire/core/FunctionsAPI.php for details on these functions.
*
* @var bool
*
*/
$config->useFunctionsAPI = true;
/*** INSTALLER CONFIG ********************************************************************/

View File

@@ -1,5 +1,7 @@
<?php namespace ProcessWire;
if(!defined("PROCESSWIRE")) die();
/**
* ProcessWire Request Finished
* ============================

12
site-default/htaccess.txt Normal file
View File

@@ -0,0 +1,12 @@
# Start ProcessWire:pwbphp (install)
# Block PHP files from direct access (optional fallback if root .htaccess missing)
<FilesMatch "\.(php|module|inc)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# End ProcessWire:pwbphp

View File

@@ -1,5 +1,7 @@
<?php namespace ProcessWire;
if(!defined("PROCESSWIRE")) die();
/**
* ProcessWire Bootstrap Initialization
* ====================================

View File

@@ -1,5 +1,7 @@
<?php namespace ProcessWire;
if(!defined("PROCESSWIRE")) die();
/**
* ProcessWire Bootstrap API Ready
* ===============================

View File

@@ -17,7 +17,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
*/
@@ -28,18 +28,6 @@ if(!defined("PROCESSWIRE")) die();
/** @var Config $config */
/**
* Enable debug mode?
*
* Debug mode causes additional info to appear for use during dev and debugging.
* This is almost always recommended for sites in development. However, you should
* always have this disabled for live/production sites.
*
* @var bool
*
*/
$config->debug = false;
/**
* Prepend template file
*
@@ -62,6 +50,20 @@ $config->prependTemplateFile = '_init.php';
*/
$config->appendTemplateFile = '_main.php';
/**
* Allow core API variables to also be accessed as functions?
*
* Recommended. This enables API varibles like $pages to also be accessed as pages(),
* as an example. And so on for most other core variables.
*
* Benefits are better type hinting, always in scope, and potentially shorter API calls.
* See the file /wire/core/FunctionsAPI.php for details on these functions.
*
* @var bool
*
*/
$config->useFunctionsAPI = true;
/*** INSTALLER CONFIG ********************************************************************/

View File

@@ -106,7 +106,7 @@
<!-- search engine -->
<form class='search' action='<?php echo $pages->get('template=search')->url; ?>' method='get'>
<label for='search' class='visually-hidden'><?php echo _x('Search:', 'label'); ?></label>
<input type='text' name='q' id='search' placeh older='<?php echo _x('Search', 'placeholder'); ?>' />
<input type='text' name='q' id='search' placeholder='<?php echo _x('Search', 'placeholder'); ?>' />
<button type='submit' name='submit' class='visually-hidden'><?php echo _x('Search', 'button'); ?></button>
</form>

View File

@@ -0,0 +1,4 @@
<?php
// Intentionally left blank to test that htaccess rewrite rules are working.
// Accessing this file from http should produce a '403 forbidden' error,
// since all PHP files are blocked under /assets/.

32
site-regular/config.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
/**
* ProcessWire Configuration File
*
* Site-specific configuration for ProcessWire.
* https://processwire.com/api/ref/config/
*
* Please see the file /wire/config.php which contains all configuration options you may
* specify here. Simply copy any of the configuration options from that file and paste
* them into this file in order to modify them.
*
* ProcessWire 3.x
* Copyright (C) 2019 by Ryan Cramer
*
* https://processwire.com
*
*/
if(!defined("PROCESSWIRE")) die();
/*** SITE CONFIG *************************************************************************/
$config->prependTemplateFile = '_init.php';
$config->appendTemplateFile = '_main.php';
$config->useMarkupRegions = true;
$config->useFunctionsAPI = true;
/*** INSTALLER CONFIG ********************************************************************/

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,6 @@
<?php if(!defined("PROCESSWIRE_INSTALL")) die();
$info = array(
'title' => "Regular Uikit 3.x site/blog profile",
'summary' => "This is a simple/regular blog site profile that uses Uikit 3 on the front-end and demonstrates several features new to ProcessWire 3.x.",
'screenshot' => "screen_shot_2017-01-27_at_1_30_19_pm.png"
);

View File

@@ -0,0 +1,718 @@
# --- WireDatabaseBackup {"time":"2017-01-27 13:32:39","user":"","dbName":"pw_xyz","description":"","tables":[],"excludeTables":["pages_drafts","pages_roles","permissions","roles","roles_permissions","users","users_roles","user","role","permission"],"excludeCreateTables":[],"excludeExportTables":["field_roles","field_permissions","field_email","field_pass","caches","session_login_throttle","page_path_history"]}
DROP TABLE IF EXISTS `caches`;
CREATE TABLE `caches` (
`name` varchar(191) NOT NULL,
`data` mediumtext NOT NULL,
`expires` datetime NOT NULL,
PRIMARY KEY (`name`),
KEY `expires` (`expires`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `field_body`;
CREATE TABLE `field_body` (
`pages_id` int(10) unsigned NOT NULL,
`data` mediumtext NOT NULL,
PRIMARY KEY (`pages_id`),
FULLTEXT KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1', '<p>This is a simple <a href=\"https://processwire.com\">ProcessWire</a> site profile that is somewhat like our default site profile, but also includes a blog. It demonstrates development of various features including some new to ProcessWire 3.x. The front-end of this profile uses the <a href=\"http://www.getuikit.com\" target=\"_blank\" rel=\"noreferrer\">Uikit 3</a> library and includes a library of time-saving functions for working with Uikit 3. Below are a few highlights you\'ll find in this site profile:</p>\n\n<ul><li>Use of markup regions and the new ProcessWire functions API.</li>\n <li>Use of Uikit 3 in template files and includes a handy PHP library of Uikit-specific functions.</li>\n <li>Demonstrates front-end editing features on <a data-pwid=1024 href=\"/about/front-end-editor-demo/\">this page</a>.</li>\n <li>Uses pagination (after 10+ blog posts) and demonstrates use of comments as well.</li>\n <li>Demonstrates caching of markup (see mobile off-canvas navigation).</li>\n <li>Demonstrates use of a Page reference field, as used by categories in the blog.</li>\n <li>The template files are easy-to-read and modify, and serve as a good platform to build from.</li>\n <li>Demonstrates implementation of a custom hook function (see in the /site/ready.php file).</li>\n</ul>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('27', '<h3>The page you were looking for is not found.</h3>\n\n<p>Please use the navigation above to find the page, or use the search engine in the footer. </p>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1001', '<p>Dolore ad nunc, mos accumsan paratus duis suscipit luptatum facilisis macto uxor iaceo quadrum. Demoveo, appellatio elit neque ad commodo ea. Wisi, iaceo, tincidunt at commoveo rusticus et, ludus. Feugait at blandit bene blandit suscipere abdo duis ideo bis commoveo pagus ex, velit. Consequat commodo roto accumsan, duis transverbero.</p>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1002', '<p>Iusto incassum appellatio cui macto genitus vel. Lobortis aliquam luctus, roto enim, imputo wisi tamen. Ratis odio, genitus acsi, neo illum consequat consectetuer ut.</p>\n\n<p>Patria iriure vel vel autem proprius indoles ille sit. Tation blandit refoveo, accumsan ut ulciscor lucidus inhibeo capto aptent opes, foras.</p>\n\n<h3>Dolore ea valde refero feugait utinam luctus</h3>\n\n<p><img alt=\"Copyright by Austin Cramer for DesignIntelligence. This is a placeholder while he makes new ones for us.\" class=\"align_right hidpi\" src=\"/site/assets/files/1002/psych_cartoon_4-20.300x0-is-hidpi.jpg\" width=\"300\" />Usitas, nostrud transverbero, in, amet, nostrud ad. Ex feugiat opto diam os aliquam regula lobortis dolore ut ut quadrum. Esse eu quis nunc jugis iriure volutpat wisi, fere blandit inhibeo melior, hendrerit, saluto velit. Eu bene ideo dignissim delenit accumsan nunc. Usitas ille autem camur consequat typicus feugait elit ex accumsan nutus accumsan nimis pagus, occuro. Immitto populus, qui feugiat opto pneum letalis paratus. Mara conventio torqueo nibh caecus abigo sit eum brevitas. Populus, duis ex quae exerci hendrerit, si antehabeo nobis, consequat ea praemitto zelus.</p>\n\n<p>Immitto os ratis euismod conventio erat jus caecus sudo. code test Appellatio consequat, et ibidem ludus nulla dolor augue abdo tego euismod plaga lenis. Sit at nimis venio venio tego os et pecus enim pneum magna nobis ad pneum. Saepius turpis probo refero molior nonummy aliquam neque appellatio jus luctus acsi. Ulciscor refero pagus imputo eu refoveo valetudo duis dolore usitas. Consequat suscipere quod torqueo ratis ullamcorper, dolore lenis, letalis quia quadrum plaga minim.</p>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1004', '<p>Magna in gemino, gilvus iusto capto jugis abdo mos aptent acsi qui. Utrum inhibeo humo humo duis quae. Lucidus paulatim facilisi scisco quibus hendrerit conventio adsum.</p>\n\n<h2>Si lobortis singularis genitus ibidem saluto</h2>\n\n<ul><li>Feugiat eligo foras ex elit sed indoles hos elit ex antehabeo defui et nostrud.</li>\n <li>Letatio valetudo multo consequat inhibeo ille dignissim pagus et in quadrum eum eu.</li>\n <li>Aliquam si consequat, ut nulla amet et turpis exerci, adsum luctus ne decet, delenit.</li>\n <li>Commoveo nunc diam valetudo cui, aptent commoveo at obruo uxor nulla aliquip augue.</li>\n</ul><p>Iriure, ex velit, praesent vulpes delenit capio vero gilvus inhibeo letatio aliquip metuo qui eros. Transverbero demoveo euismod letatio torqueo melior. Ut odio in suscipit paulatim amet huic letalis suscipere eros causa, letalis magna.</p>\n\n<ol><li>Feugiat eligo foras ex elit sed indoles hos elit ex antehabeo defui et nostrud.</li>\n <li>Letatio valetudo multo consequat inhibeo ille dignissim pagus et in quadrum eum eu.</li>\n <li>Aliquam si consequat, ut nulla amet et turpis exerci, adsum luctus ne decet, delenit.</li>\n <li>Commoveo nunc diam valetudo cui, aptent commoveo at obruo uxor nulla aliquip augue.</li>\n</ol>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1015', '<p>Fixed effect pulse current remote integer potentiometer anomoly. Gigabyte recognition deviation active sequential bypass echo distributed. Embedded encapsulated mainframe reducer logarithmic potentiometer duplex. Software metafile reducer deviation boolean overflow bridgeware.</p>\n\n<p>Patch internet nano. Converter a inversion recursive adaptive encapsulated transport floating-point transistorized plasma microscopic node. PC duplex partitioned. Network scalar dithering encapsulated generator normalizing. Remote interval fixed plasma normalizing microscopic procedural scalar dynamic read-only high boolean.</p>\n\n<h3>Reducer hybrid force key</h3>\n\n<p>Cascading wave network logarithmic digital powered scan. Frequency coordinated particle transmission supporting. Log distributed bus scan force particle computer inversion servicing reverberated device. In coordinated services backbone silicon hyperlinked. Scalar error fiber transponder digital.</p>\n\n<p>Vector developer connectivity connectivity modular supporting broadband solution. For modular vector timer indeterminate debugged optical kilohertz procedural procedural. Infrared fuzzy procedural capacitance fiber. Algorithm direct procedural echo. Digital bridgeware by timer fragmentation ethernet inducer phase network.</p>\n\n<p>Transaction active by. Effect partitioned by timer system services computer. Spawned coordinated developer fuzzy. Technician fuzzy supporting protocol coordinated ethernet. Bridgeware video remote prototype development.</p>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1021', '<p>Grown plus industry open for when when sharpest ordinary offer by. Better huggable opportunity too. Rosy sleek while exclusive gentle not on. Offer colossal silky this sweet magically announcing durable sold soaking our try. Sold one zesty velvety awesome flavored ever with effervescent gentle. Screamin\' improved permanent treat now tasty we space 100%.</p>\n\n<p>Think affordable artificial blast while choice. Appetizing available really thank-you out proven desire fresh rich. Natural and flash power effective grand premium. Secret lifetime grand quenches by ocean as comfort golden youthful fast. Disposable zesty dazzling open sure spacious multi-purpose the super market rare.</p>\n\n<p>Spring special bigger wherever only this comfort tummy extravaganza save. Very messy keen leading incredible.</p>\n\n<p>Hearty brand chocolatey comfort admire ultra. Want kids touch discount love appetizing talking inside buttery. For keeps admire youthful. Wherever super thirsty lasting limited discover picky can\'t.</p>\n\n<p>Good appreciate flexible product best. Full-bodied don\'t customer gigantic also.</p>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1022', '<p>Genuine symphony solid educated de-jour regal gifted guests. Using gilded member silk dignified gilded panoramic art politically. Diamond upper brokerage pleasure society reserved. First-class topiary treasure travel is the best wishlist vacation solid penthouse world.</p>\n\n<p>Board marquis estate career blissfull treasure saphire. Delegate cultered regal marquis cigar sterling penthouse.</p>\n\n<p>Sterling butler solid penthouse gilded gilded pedigree wine using investments cigar. Cultered doctoral symphony extra accredited. Private benefactor monogram high-rise a.</p>\n\n<p>Career gilded extra aristocratic cruise brilliant impresario. European ambassador acumen ambassador. Rare suite cruise club crafted butler grande.</p>\n\n<p>Distinctly rich auction penthouse travel.</p>');
INSERT INTO `field_body` (`pages_id`, `data`) VALUES('1024', '<p>If you are logged in with edit access to this page, you can double-click this body copy to edit it. You can also do the same to edit the headline above, or the sidebar text to the right.</p>\n\n<p>Illum aliquip loquor. Hendrerit interdico dolor zelus diam metuo causa lobortis scisco. Euismod damnum quibus ideo patria opto. Haero odio jus virtus haero pagus erat cogo diam minim vulputate autem.</p>\n\n<h3>Ullamcorper venio bene</h3>\n\n<p>Amet ea oppeto nullus esse meus immitto sudo dignissim. Letalis velit utrum luptatum ullamcorper illum ad fere molior populus ut. Et augue eligo jumentum populus nonummy virtus. Valetudo odio ex opes mos delenit immitto ex. Illum tincidunt commoveo nostrud et ratis ne vulputate vereor tego.</p>\n\n<ul><li>Capto elit vel eu esse quia</li>\n <li>Te gemino natu et augue ad</li>\n <li>Amet aliquip valde blandit olim facilisi</li>\n</ul><p>Nulla iusto pertineo camur similis enim abigo luptatum ymo nullus. Inhibeo nutus pagus capto dolus capio pecus. Pala vereor esse melior nisl bis. Veniam eros consequat.</p>');
DROP TABLE IF EXISTS `field_categories`;
CREATE TABLE `field_categories` (
`pages_id` int(10) unsigned NOT NULL,
`data` int(11) NOT NULL,
`sort` int(10) unsigned NOT NULL,
PRIMARY KEY (`pages_id`,`sort`),
KEY `data` (`data`,`pages_id`,`sort`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_categories` (`pages_id`, `data`, `sort`) VALUES('1015', '1017', '0');
INSERT INTO `field_categories` (`pages_id`, `data`, `sort`) VALUES('1015', '1018', '1');
INSERT INTO `field_categories` (`pages_id`, `data`, `sort`) VALUES('1021', '1018', '1');
INSERT INTO `field_categories` (`pages_id`, `data`, `sort`) VALUES('1021', '1019', '0');
INSERT INTO `field_categories` (`pages_id`, `data`, `sort`) VALUES('1022', '1019', '0');
INSERT INTO `field_categories` (`pages_id`, `data`, `sort`) VALUES('1022', '1020', '1');
INSERT INTO `field_categories` (`pages_id`, `data`, `sort`) VALUES('1021', '1027', '2');
DROP TABLE IF EXISTS `field_comments`;
CREATE TABLE `field_comments` (
`pages_id` int(10) unsigned NOT NULL,
`data` text NOT NULL,
`sort` int(10) unsigned NOT NULL,
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`status` tinyint(3) NOT NULL DEFAULT '0',
`cite` varchar(128) NOT NULL DEFAULT '',
`email` varchar(250) NOT NULL DEFAULT '',
`created` int(10) unsigned NOT NULL,
`created_users_id` int(10) unsigned NOT NULL,
`ip` varchar(15) NOT NULL DEFAULT '',
`user_agent` varchar(250) NOT NULL DEFAULT '',
`website` varchar(250) NOT NULL DEFAULT '',
`parent_id` int(10) unsigned NOT NULL DEFAULT '0',
`flags` int(10) unsigned NOT NULL DEFAULT '0',
`code` varchar(128) DEFAULT NULL,
`subcode` varchar(40) DEFAULT NULL,
`upvotes` int(10) unsigned NOT NULL DEFAULT '0',
`downvotes` int(10) unsigned NOT NULL DEFAULT '0',
`stars` tinyint(3) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `pages_id_sort` (`pages_id`,`sort`),
KEY `status` (`status`,`email`(191)),
KEY `pages_id` (`pages_id`,`status`,`created`),
KEY `created` (`created`,`status`),
KEY `code` (`code`),
KEY `subcode` (`subcode`),
FULLTEXT KEY `data` (`data`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `field_comments` (`pages_id`, `data`, `sort`, `id`, `status`, `cite`, `email`, `created`, `created_users_id`, `ip`, `user_agent`, `website`, `parent_id`, `flags`, `code`, `subcode`, `upvotes`, `downvotes`, `stars`) VALUES('1021', 'They good night the piper good night good queen white as snow they magical beans winding path up the hill dragon beautiful dress. So loud magic wand took fought angry lion ding-dong. Winding path fought ran away whale swallowed crystal ball poison apple took the piper sang twinkled.', '2', '1', '1', 'Jim', 'jim@processwire.com', '1485450830', '41', '0.0.0.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', '', '0', '0', 'aeHvmkn88ncb4214OXegP8uUrQy6D5UZcaD9pPxT9gPFDdEOf1EqCM6UD6JUnY7Jtv9MPNjcPrJWUxKhyh89r1H6nywk1Se_GdwAoj2guU_9YYa9MEgiuJUekuk93YvE', 'JzPW6751GqTqk1Oh__k0IbNfOi_Nc6nYvPPa2wl6', '0', '0', NULL);
INSERT INTO `field_comments` (`pages_id`, `data`, `sort`, `id`, `status`, `cite`, `email`, `created`, `created_users_id`, `ip`, `user_agent`, `website`, `parent_id`, `flags`, `code`, `subcode`, `upvotes`, `downvotes`, `stars`) VALUES('1021', 'LED harmonic nominal femtosecond data solid alphanumeric alphanumeric. By sampling bus recursive null. Modular timer recognition passive interval. Theory capacitance application fragmentation with supporting indeterminate. Microscopic record indeterminate scalar concept deviation system.', '3', '2', '1', 'ryan', 'ryan@processwire.com', '1485453231', '41', '0.0.0.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', 'https://processwire.com', '0', '0', 'eWwAZPZHyC4JcShlKDPrr5Y_rC8sntJOildm2ecqUegXPwgfwmRhyOn5ssyQhABWwaweM74e_TApOLMQu4MGt9lSf7VcxH994ciwggF0f3lpEdJ3OMtjYe4MvW4gDzNF', 'CcaWnsxrcWYxfCgnnvYc2DEN7qU3bd_7HVm3NK_0', '0', '0', NULL);
INSERT INTO `field_comments` (`pages_id`, `data`, `sort`, `id`, `status`, `cite`, `email`, `created`, `created_users_id`, `ip`, `user_agent`, `website`, `parent_id`, `flags`, `code`, `subcode`, `upvotes`, `downvotes`, `stars`) VALUES('1022', 'Run Spot play help I am hungry I can help too oh no. He wants to play oh no for a ride I can help for a ride too on our bikes chase the cat for a ride. Bring it here walk we have two bark I am hungry jump high now he is funny it is Sally for a ride oh please. We can oh please down the toy I can help no Jane is looking she is happy share with them.\n\nLook too eating cake he wants to play he wants to play down don\'t worry on our bikes on our bikes. I am hungry down jump high they are silly and oh no help but. Thank you over there I can help Dick said over there on our bikes see Puff over there do it Jane is looking I can see he is fast. Quick in the wagon no jump high.', '2', '3', '1', 'Ryan', 'ryan@processwire.com', '1485528109', '41', '0.0.0.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', 'https://processwire.com', '0', '0', 'CVaOB64FqwwVlmAXUbhP5KbZQDFM7OarKON1ZIb_PYP9cDkpcb0NCYp56iAPHTZoIjJEZNQ4mnuEyLkYw97XkfOgmtRDd33rfZB0Zt1yfDKOjY4tdkWP08BKUbI_MImr', 'ySOMziIfOxoi_BfPVBNxNaGVrKHGzHGtFbnbkodo', '0', '0', NULL);
DROP TABLE IF EXISTS `field_comments_votes`;
CREATE TABLE `field_comments_votes` (
`comment_id` int(10) unsigned NOT NULL,
`vote` tinyint(4) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`ip` varchar(15) NOT NULL DEFAULT '',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`comment_id`,`ip`,`vote`),
KEY `created` (`created`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `field_date`;
CREATE TABLE `field_date` (
`pages_id` int(10) unsigned NOT NULL,
`data` datetime NOT NULL,
PRIMARY KEY (`pages_id`),
KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_date` (`pages_id`, `data`) VALUES('1015', '2017-01-25 00:00:00');
INSERT INTO `field_date` (`pages_id`, `data`) VALUES('1022', '2017-01-26 00:00:00');
INSERT INTO `field_date` (`pages_id`, `data`) VALUES('1021', '2017-01-27 00:00:00');
DROP TABLE IF EXISTS `field_email`;
CREATE TABLE `field_email` (
`pages_id` int(10) unsigned NOT NULL,
`data` varchar(191) NOT NULL DEFAULT '',
PRIMARY KEY (`pages_id`),
KEY `data_exact` (`data`),
FULLTEXT KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `field_headline`;
CREATE TABLE `field_headline` (
`pages_id` int(10) unsigned NOT NULL,
`data` text NOT NULL,
PRIMARY KEY (`pages_id`),
FULLTEXT KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_headline` (`pages_id`, `data`) VALUES('1', 'Uikit 3 site/blog profile');
INSERT INTO `field_headline` (`pages_id`, `data`) VALUES('27', '404 Page Not Found');
INSERT INTO `field_headline` (`pages_id`, `data`) VALUES('1001', 'About Us');
INSERT INTO `field_headline` (`pages_id`, `data`) VALUES('1024', 'Demonstration of front-end-editing');
DROP TABLE IF EXISTS `field_images`;
CREATE TABLE `field_images` (
`pages_id` int(10) unsigned NOT NULL,
`data` varchar(191) NOT NULL,
`sort` int(10) unsigned NOT NULL,
`description` text NOT NULL,
`modified` datetime DEFAULT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`pages_id`,`sort`),
KEY `data` (`data`),
KEY `modified` (`modified`),
KEY `created` (`created`),
FULLTEXT KEY `description` (`description`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_images` (`pages_id`, `data`, `sort`, `description`, `modified`, `created`) VALUES('1002', 'psych_cartoon_4-20.jpg', '0', 'Copyright by Austin Cramer for DesignIntelligence. This is a placeholder while he makes new ones for us.', '2017-01-24 06:11:43', '2017-01-24 06:11:43');
INSERT INTO `field_images` (`pages_id`, `data`, `sort`, `description`, `modified`, `created`) VALUES('1021', 'screen_shot_2017-01-27_at_10_46_35_am.png', '0', '', '2017-01-27 10:56:13', '2017-01-27 10:56:13');
DROP TABLE IF EXISTS `field_pass`;
CREATE TABLE `field_pass` (
`pages_id` int(10) unsigned NOT NULL,
`data` char(40) NOT NULL,
`salt` char(32) NOT NULL,
PRIMARY KEY (`pages_id`),
KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii;
DROP TABLE IF EXISTS `field_permissions`;
CREATE TABLE `field_permissions` (
`pages_id` int(10) unsigned NOT NULL,
`data` int(11) NOT NULL,
`sort` int(10) unsigned NOT NULL,
PRIMARY KEY (`pages_id`,`sort`),
KEY `data` (`data`,`pages_id`,`sort`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `field_process`;
CREATE TABLE `field_process` (
`pages_id` int(11) NOT NULL DEFAULT '0',
`data` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`pages_id`),
KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('10', '7');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('23', '10');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('3', '12');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('8', '12');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('9', '14');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('6', '17');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('11', '47');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('16', '48');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('21', '50');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('29', '66');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('30', '68');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('22', '76');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('28', '76');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('2', '87');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('300', '104');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('301', '109');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('302', '121');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('303', '129');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('31', '136');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('304', '138');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('1007', '150');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('1009', '158');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('1011', '159');
INSERT INTO `field_process` (`pages_id`, `data`) VALUES('1025', '165');
DROP TABLE IF EXISTS `field_roles`;
CREATE TABLE `field_roles` (
`pages_id` int(10) unsigned NOT NULL,
`data` int(11) NOT NULL,
`sort` int(10) unsigned NOT NULL,
PRIMARY KEY (`pages_id`,`sort`),
KEY `data` (`data`,`pages_id`,`sort`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `field_sidebar`;
CREATE TABLE `field_sidebar` (
`pages_id` int(10) unsigned NOT NULL,
`data` mediumtext NOT NULL,
PRIMARY KEY (`pages_id`),
FULLTEXT KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_sidebar` (`pages_id`, `data`) VALUES('1', '<h3>Requirements</h3>\n\n<p>This site profile requires ProcessWire 3.0.105 or newer, Uikit 3, and the server must be running on PHP 5.4 or newer.</p>');
INSERT INTO `field_sidebar` (`pages_id`, `data`) VALUES('1002', '<h3>Sudo nullus</h3>\r\n\r\n<p>Et torqueo vulpes vereor luctus augue quod consectetuer antehabeo causa patria tation ex plaga ut. Abluo delenit wisi iriure eros feugiat probo nisl aliquip nisl, patria. Antehabeo esse camur nisl modo utinam. Sudo nullus ventosus ibidem facilisis saepius eum sino pneum, vicis odio voco opto.</p>');
INSERT INTO `field_sidebar` (`pages_id`, `data`) VALUES('1024', '<h3>Double click me</h3>\n\n<p>Esca demoveo exputo sagaciter ullamcorper inhibeo ut nimis refoveo praemitto defui ut. Hendrerit ratis dignissim ea eligo. Genitus utinam suscipere caecus ad neque verto at regula saluto esse turpis. Refero autem et nulla ibidem caecus fere acsi plaga in turpis. Nobis sit nunc esse capio suscipit vulpes facilisis brevitas. Pagus odio eros accumsan et interdico nunc abdo eligo epulae.</p>');
DROP TABLE IF EXISTS `field_summary`;
CREATE TABLE `field_summary` (
`pages_id` int(10) unsigned NOT NULL,
`data` mediumtext NOT NULL,
PRIMARY KEY (`pages_id`),
FULLTEXT KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_summary` (`pages_id`, `data`) VALUES('1', 'A simple blog site about nothing in particular.');
INSERT INTO `field_summary` (`pages_id`, `data`) VALUES('1001', 'This is a placeholder page with two child pages to serve as an example. ');
INSERT INTO `field_summary` (`pages_id`, `data`) VALUES('1002', 'Dolore ea valde refero feugait utinam luctus. Probo velit commoveo et, delenit praesent, suscipit zelus, hendrerit zelus illum facilisi, regula. ');
INSERT INTO `field_summary` (`pages_id`, `data`) VALUES('1004', 'Mos erat reprobo in praesent, mara premo, obruo iustum pecus velit lobortis te sagaciter populus.');
INSERT INTO `field_summary` (`pages_id`, `data`) VALUES('1005', 'View this template\'s source for a demonstration of how to create a basic site map. ');
INSERT INTO `field_summary` (`pages_id`, `data`) VALUES('1024', 'If you are logged in with edit access, pages using the basic-page-edit template (like this one) are editable on the front-end.');
DROP TABLE IF EXISTS `field_title`;
CREATE TABLE `field_title` (
`pages_id` int(10) unsigned NOT NULL,
`data` text NOT NULL,
PRIMARY KEY (`pages_id`),
KEY `data_exact` (`data`(191)),
FULLTEXT KEY `data` (`data`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1', 'Home');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('2', 'Admin');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('3', 'Pages');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('6', 'Add Page');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('7', 'Trash');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('8', 'Tree');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('9', 'Save Sort');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('10', 'Edit');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('11', 'Templates');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('16', 'Fields');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('21', 'Modules');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('22', 'Setup');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('23', 'Login');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('27', '404 Page');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('28', 'Access');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('29', 'Users');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('30', 'Roles');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('31', 'Permissions');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('32', 'Edit pages');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('34', 'Delete pages');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('35', 'Move pages (change parent)');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('36', 'View pages');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('50', 'Sort child pages');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('51', 'Change templates on pages');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('52', 'Administer users');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('53', 'User can update profile/password');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('54', 'Lock or unlock a page');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('300', 'Search');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('301', 'Empty Trash');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('302', 'Insert Link');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('303', 'Insert Image');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('304', 'Profile');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1000', 'Search');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1001', 'About');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1002', 'Child page example 1');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1004', 'Child page example 2');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1005', 'Site Map');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1006', 'Use Page Lister');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1007', 'Find');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1009', 'Recent');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1010', 'Can see recently edited pages');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1011', 'Logs');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1012', 'Can view system logs');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1013', 'Can manage system logs');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1014', 'Blog');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1015', 'Phase data extended transaction');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1016', 'Categories');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1017', 'Coffee');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1018', 'Beer');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1019', 'Plants');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1020', 'Cats');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1021', 'Think affordable artificial blast');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1022', 'Genuine symphony');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1023', 'Use the front-end page editor');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1024', 'Front-end editing demo');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1025', 'Comments');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1026', 'Use the comments manager');
INSERT INTO `field_title` (`pages_id`, `data`) VALUES('1027', 'Recipes');
DROP TABLE IF EXISTS `fieldgroups`;
CREATE TABLE `fieldgroups` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) CHARACTER SET ascii NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=102 DEFAULT CHARSET=utf8;
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('2', 'admin');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('83', 'basic-page');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('101', 'basic-page-edit');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('98', 'blog');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('97', 'blog-post');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('99', 'categories');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('100', 'category');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('1', 'home');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('5', 'permission');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('4', 'role');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('80', 'search');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('88', 'sitemap');
INSERT INTO `fieldgroups` (`id`, `name`) VALUES('3', 'user');
DROP TABLE IF EXISTS `fieldgroups_fields`;
CREATE TABLE `fieldgroups_fields` (
`fieldgroups_id` int(10) unsigned NOT NULL DEFAULT '0',
`fields_id` int(10) unsigned NOT NULL DEFAULT '0',
`sort` int(11) unsigned NOT NULL DEFAULT '0',
`data` text,
PRIMARY KEY (`fieldgroups_id`,`fields_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('1', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('1', '44', '5', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('1', '76', '3', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('1', '78', '1', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('1', '79', '2', '{\"label\":\"Site tagline\"}');
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('1', '82', '4', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('2', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('2', '2', '1', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('3', '3', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('3', '4', '2', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('3', '92', '1', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('4', '5', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('5', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('80', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('83', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('83', '44', '5', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('83', '76', '3', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('83', '78', '1', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('83', '79', '2', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('83', '82', '4', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('88', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('88', '79', '1', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('97', '1', '0', '{\"columnWidth\":75}');
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('97', '44', '3', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('97', '76', '2', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('97', '97', '1', '{\"columnWidth\":25}');
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('97', '98', '4', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('97', '99', '5', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('98', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('99', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('100', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('101', '1', '0', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('101', '44', '5', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('101', '76', '3', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('101', '78', '1', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('101', '79', '2', NULL);
INSERT INTO `fieldgroups_fields` (`fieldgroups_id`, `fields_id`, `sort`, `data`) VALUES('101', '82', '4', NULL);
DROP TABLE IF EXISTS `fields`;
CREATE TABLE `fields` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(128) CHARACTER SET ascii NOT NULL,
`name` varchar(191) CHARACTER SET ascii NOT NULL,
`flags` int(11) NOT NULL DEFAULT '0',
`label` varchar(191) NOT NULL DEFAULT '',
`data` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
KEY `type` (`type`)
) ENGINE=MyISAM AUTO_INCREMENT=100 DEFAULT CHARSET=utf8;
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('1', 'FieldtypePageTitle', 'title', '13', 'Title', '{\"required\":1,\"textformatters\":[\"TextformatterEntities\"],\"size\":0,\"maxlength\":255}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('2', 'FieldtypeModule', 'process', '25', 'Process', '{\"description\":\"The process that is executed on this page. Since this is mostly used by ProcessWire internally, it is recommended that you don\'t change the value of this unless adding your own pages in the admin.\",\"collapsed\":1,\"required\":1,\"moduleTypes\":[\"Process\"],\"permanent\":1}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('3', 'FieldtypePassword', 'pass', '24', 'Set Password', '{\"collapsed\":1,\"size\":50,\"maxlength\":128}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('4', 'FieldtypePage', 'roles', '24', 'Roles', '{\"derefAsPage\":0,\"parent_id\":30,\"labelFieldName\":\"name\",\"inputfield\":\"InputfieldCheckboxes\",\"description\":\"User will inherit the permissions assigned to each role. You may assign multiple roles to a user. When accessing a page, the user will only inherit permissions from the roles that are also assigned to the page\'s template.\"}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('5', 'FieldtypePage', 'permissions', '24', 'Permissions', '{\"derefAsPage\":0,\"parent_id\":31,\"labelFieldName\":\"title\",\"inputfield\":\"InputfieldCheckboxes\"}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('44', 'FieldtypeImage', 'images', '0', 'Images', '{\"extensions\":\"gif jpg jpeg png\",\"adminThumbs\":1,\"inputfieldClass\":\"InputfieldImage\",\"maxFiles\":0,\"descriptionRows\":1,\"fileSchema\":2,\"textformatters\":[\"TextformatterEntities\"],\"outputFormat\":1,\"defaultValuePage\":0,\"defaultGrid\":0,\"icon\":\"camera\"}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('76', 'FieldtypeTextarea', 'body', '0', 'Body', '{\"inputfieldClass\":\"InputfieldCKEditor\",\"rows\":10,\"contentType\":1,\"toolbar\":\"Format, Bold, Italic, -, RemoveFormat\\nNumberedList, BulletedList, -, Blockquote\\nPWLink, Unlink, Anchor\\nPWImage, Table, HorizontalRule, SpecialChar\\nPasteText, PasteFromWord\\nScayt, -, Sourcedialog\",\"inlineMode\":0,\"useACF\":1,\"usePurifier\":1,\"formatTags\":\"p;h2;h3;h4;h5;h6;pre;address\",\"extraPlugins\":[\"pwimage\",\"pwlink\",\"sourcedialog\"],\"removePlugins\":\"image,magicline\",\"toggles\":[2,4,8],\"htmlOptions\":[2],\"collapsed\":0,\"minlength\":0,\"maxlength\":0,\"showCount\":0}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('78', 'FieldtypeText', 'headline', '0', 'Headline', '{\"description\":\"Use this instead of the Title if a longer headline is needed than what you want to appear in navigation.\",\"textformatters\":[\"TextformatterEntities\"],\"collapsed\":2,\"size\":0,\"maxlength\":1024,\"minlength\":0,\"showCount\":0}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('79', 'FieldtypeTextarea', 'summary', '1', 'Summary', '{\"textformatters\":[\"TextformatterEntities\"],\"inputfieldClass\":\"InputfieldTextarea\",\"collapsed\":2,\"rows\":3,\"contentType\":0}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('82', 'FieldtypeTextarea', 'sidebar', '0', 'Sidebar', '{\"inputfieldClass\":\"InputfieldCKEditor\",\"rows\":5,\"contentType\":1,\"toolbar\":\"Format, Bold, Italic, -, RemoveFormat\\r\\nNumberedList, BulletedList, -, Blockquote\\r\\nPWLink, Unlink, Anchor\\r\\nPWImage, Table, HorizontalRule, SpecialChar\\r\\nPasteText, PasteFromWord\\r\\nScayt, -, Sourcedialog\",\"inlineMode\":0,\"useACF\":1,\"usePurifier\":1,\"formatTags\":\"p;h2;h3;h4;h5;h6;pre;address\",\"extraPlugins\":[\"pwimage\",\"pwlink\",\"sourcedialog\"],\"removePlugins\":\"image,magicline\",\"toggles\":[2,4,8],\"collapsed\":2}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('92', 'FieldtypeEmail', 'email', '9', 'E-Mail Address', '{\"size\":70,\"maxlength\":255}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('97', 'FieldtypeDatetime', 'date', '0', 'Date', '{\"dateOutputFormat\":\"j F Y\",\"collapsed\":0,\"size\":25,\"datepicker\":3,\"timeInputSelect\":0,\"dateInputFormat\":\"Y\\/m\\/d\",\"defaultToday\":1,\"placeholder\":\"yyyy\\/mm\\/dd\",\"icon\":\"calendar\"}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('98', 'FieldtypePage', 'categories', '0', 'Categories', '{\"derefAsPage\":0,\"inputfield\":\"InputfieldAsmSelect\",\"parent_id\":1016,\"template_id\":46,\"labelFieldName\":\"title\",\"addable\":1,\"collapsed\":0}');
INSERT INTO `fields` (`id`, `type`, `name`, `flags`, `label`, `data`) VALUES('99', 'FieldtypeComments', 'comments', '0', 'Comments', '{\"schemaVersion\":6,\"moderate\":1,\"redirectAfterPost\":1,\"quietSave\":1,\"useNotify\":0,\"deleteSpamDays\":3,\"depth\":0,\"useWebsite\":1,\"dateFormat\":\"relative\",\"useVotes\":0,\"useStars\":0,\"useGravatar\":\"g\",\"collapsed\":0}');
DROP TABLE IF EXISTS `modules`;
CREATE TABLE `modules` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`class` varchar(128) CHARACTER SET ascii NOT NULL,
`flags` int(11) NOT NULL DEFAULT '0',
`data` text NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `class` (`class`)
) ENGINE=MyISAM AUTO_INCREMENT=167 DEFAULT CHARSET=utf8;
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('1', 'FieldtypeTextarea', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('2', 'FieldtypeNumber', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('3', 'FieldtypeText', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('4', 'FieldtypePage', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('6', 'FieldtypeFile', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('7', 'ProcessPageEdit', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('10', 'ProcessLogin', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('12', 'ProcessPageList', '0', '{\"pageLabelField\":\"title\",\"paginationLimit\":25,\"limit\":50}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('14', 'ProcessPageSort', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('15', 'InputfieldPageListSelect', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('17', 'ProcessPageAdd', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('25', 'InputfieldAsmSelect', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('27', 'FieldtypeModule', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('28', 'FieldtypeDatetime', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('29', 'FieldtypeEmail', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('30', 'InputfieldForm', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('32', 'InputfieldSubmit', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('33', 'InputfieldWrapper', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('34', 'InputfieldText', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('35', 'InputfieldTextarea', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('36', 'InputfieldSelect', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('37', 'InputfieldCheckbox', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('38', 'InputfieldCheckboxes', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('39', 'InputfieldRadios', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('40', 'InputfieldHidden', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('41', 'InputfieldName', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('43', 'InputfieldSelectMultiple', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('45', 'JqueryWireTabs', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('46', 'ProcessPage', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('47', 'ProcessTemplate', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('48', 'ProcessField', '32', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('50', 'ProcessModule', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('55', 'InputfieldFile', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('56', 'InputfieldImage', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('57', 'FieldtypeImage', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('60', 'InputfieldPage', '0', '{\"inputfieldClasses\":[\"InputfieldSelect\",\"InputfieldSelectMultiple\",\"InputfieldCheckboxes\",\"InputfieldRadios\",\"InputfieldAsmSelect\",\"InputfieldPageListSelect\",\"InputfieldPageListSelectMultiple\",\"InputfieldPageAutocomplete\"]}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('61', 'TextformatterEntities', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('66', 'ProcessUser', '0', '{\"showFields\":[\"name\",\"email\",\"roles\"]}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('67', 'MarkupAdminDataTable', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('68', 'ProcessRole', '0', '{\"showFields\":[\"name\"]}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('76', 'ProcessList', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('78', 'InputfieldFieldset', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('79', 'InputfieldMarkup', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('80', 'InputfieldEmail', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('83', 'ProcessPageView', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('84', 'FieldtypeInteger', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('85', 'InputfieldInteger', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('86', 'InputfieldPageName', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('87', 'ProcessHome', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('89', 'FieldtypeFloat', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('90', 'InputfieldFloat', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('94', 'InputfieldDatetime', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('97', 'FieldtypeCheckbox', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('98', 'MarkupPagerNav', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('103', 'JqueryTableSorter', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('104', 'ProcessPageSearch', '1', '{\"searchFields\":\"title\",\"displayField\":\"title path\"}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('105', 'FieldtypeFieldsetOpen', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('106', 'FieldtypeFieldsetClose', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('107', 'FieldtypeFieldsetTabOpen', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('108', 'InputfieldURL', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('109', 'ProcessPageTrash', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('111', 'FieldtypePageTitle', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('112', 'InputfieldPageTitle', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('113', 'MarkupPageArray', '3', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('114', 'PagePermissions', '3', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('115', 'PageRender', '3', '{\"clearCache\":1}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('116', 'JqueryCore', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('117', 'JqueryUI', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('121', 'ProcessPageEditLink', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('122', 'InputfieldPassword', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('125', 'SessionLoginThrottle', '11', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('129', 'ProcessPageEditImageSelect', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('131', 'InputfieldButton', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('133', 'FieldtypePassword', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('134', 'ProcessPageType', '33', '{\"showFields\":[]}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('135', 'FieldtypeURL', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('136', 'ProcessPermission', '1', '{\"showFields\":[\"name\",\"title\"]}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('137', 'InputfieldPageListSelectMultiple', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('138', 'ProcessProfile', '1', '{\"profileFields\":[\"pass\",\"email\"]}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('139', 'SystemUpdater', '1', '{\"systemVersion\":15,\"coreVersion\":\"3.0.50\"}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('148', 'AdminThemeDefault', '10', '{\"colors\":\"classic\"}', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('149', 'InputfieldSelector', '42', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('150', 'ProcessPageLister', '32', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('151', 'JqueryMagnific', '1', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('152', 'PagePathHistory', '3', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('155', 'InputfieldCKEditor', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('156', 'MarkupHTMLPurifier', '0', '', '2017-01-24 06:11:43');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('158', 'ProcessRecentPages', '1', '', '2017-01-24 06:12:09');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('159', 'ProcessLogger', '1', '', '2017-01-24 06:12:17');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('160', 'InputfieldIcon', '0', '', '2017-01-24 06:12:17');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('161', 'FieldtypeComments', '1', '', '2017-01-26 11:32:48');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('162', 'InputfieldCommentsAdmin', '0', '', '2017-01-26 11:32:48');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('163', 'InputfieldPageAutocomplete', '0', '', '2017-01-27 11:18:20');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('164', 'PageFrontEdit', '2', '', '2017-01-27 11:32:31');
INSERT INTO `modules` (`id`, `class`, `flags`, `data`, `created`) VALUES('165', 'ProcessCommentsManager', '1', '', '2017-01-27 12:17:47');
DROP TABLE IF EXISTS `page_path_history`;
CREATE TABLE `page_path_history` (
`path` varchar(191) NOT NULL,
`pages_id` int(10) unsigned NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`path`),
KEY `pages_id` (`pages_id`),
KEY `created` (`created`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `pages`;
CREATE TABLE `pages` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`parent_id` int(11) unsigned NOT NULL DEFAULT '0',
`templates_id` int(11) unsigned NOT NULL DEFAULT '0',
`name` varchar(128) CHARACTER SET ascii NOT NULL,
`status` int(10) unsigned NOT NULL DEFAULT '1',
`modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified_users_id` int(10) unsigned NOT NULL DEFAULT '2',
`created` timestamp NOT NULL DEFAULT '2015-12-18 06:09:00',
`created_users_id` int(10) unsigned NOT NULL DEFAULT '2',
`published` datetime DEFAULT NULL,
`sort` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `name_parent_id` (`name`,`parent_id`),
KEY `parent_id` (`parent_id`),
KEY `templates_id` (`templates_id`),
KEY `modified` (`modified`),
KEY `created` (`created`),
KEY `status` (`status`),
KEY `published` (`published`)
) ENGINE=MyISAM AUTO_INCREMENT=1029 DEFAULT CHARSET=utf8;
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1', '0', '1', 'home', '9', '2017-01-27 13:29:31', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('2', '1', '2', 'processwire', '1035', '2017-01-24 06:12:10', '40', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '6');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('3', '2', '2', 'page', '21', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('6', '3', '2', 'add', '21', '2017-01-24 06:12:22', '40', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('7', '1', '2', 'trash', '1039', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '7');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('8', '3', '2', 'list', '1045', '2017-01-24 06:15:58', '40', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('9', '3', '2', 'sort', '1047', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('10', '3', '2', 'edit', '1045', '2017-01-24 06:15:58', '40', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '3');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('11', '22', '2', 'template', '21', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('16', '22', '2', 'field', '21', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('21', '2', '2', 'module', '21', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('22', '2', '2', 'setup', '21', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('23', '2', '2', 'login', '1035', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '4');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('27', '1', '29', 'http404', '1035', '2017-01-27 12:25:04', '41', '2017-01-24 06:11:43', '3', '2017-01-24 06:11:43', '5');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('28', '2', '2', 'access', '13', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '3');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('29', '28', '2', 'users', '29', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('30', '28', '2', 'roles', '29', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('31', '28', '2', 'permissions', '29', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('32', '31', '5', 'page-edit', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('34', '31', '5', 'page-delete', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '3');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('35', '31', '5', 'page-move', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '4');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('36', '31', '5', 'page-view', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('37', '30', '4', 'guest', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('38', '30', '4', 'superuser', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('40', '29', '3', 'guest', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('41', '29', '3', 'admin', '1', '2017-01-24 06:12:10', '40', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('50', '31', '5', 'page-sort', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '5');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('51', '31', '5', 'page-template', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '6');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('52', '31', '5', 'user-admin', '25', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '10');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('53', '31', '5', 'profile-edit', '1', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '13');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('54', '31', '5', 'page-lock', '1', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '8');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('300', '3', '2', 'search', '1045', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '5');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('301', '3', '2', 'trash', '1047', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '5');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('302', '3', '2', 'link', '1041', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '6');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('303', '3', '2', 'image', '1041', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '7');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('304', '2', '2', 'profile', '1025', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '41', '2017-01-24 06:11:43', '5');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1000', '1', '26', 'search', '1025', '2017-01-26 09:55:14', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '4');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1001', '1', '29', 'about', '1', '2017-01-25 08:36:43', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1002', '1001', '29', 'what', '1', '2017-01-27 12:08:45', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1004', '1001', '29', 'background', '1', '2017-01-27 09:45:20', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1005', '1', '34', 'site-map', '1', '2017-01-26 09:55:10', '41', '2017-01-24 06:11:43', '2', '2017-01-24 06:11:43', '3');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1006', '31', '5', 'page-lister', '1', '2017-01-24 06:11:43', '40', '2017-01-24 06:11:43', '40', '2017-01-24 06:11:43', '9');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1007', '3', '2', 'lister', '1', '2017-01-24 06:11:43', '40', '2017-01-24 06:11:43', '40', '2017-01-24 06:11:43', '8');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1009', '3', '2', 'recent-pages', '1', '2017-01-24 06:12:09', '40', '2017-01-24 06:12:09', '40', '2017-01-24 06:12:09', '9');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1010', '31', '5', 'page-edit-recent', '1', '2017-01-24 06:12:09', '40', '2017-01-24 06:12:09', '40', '2017-01-24 06:12:09', '10');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1011', '22', '2', 'logs', '1', '2017-01-24 06:12:17', '40', '2017-01-24 06:12:17', '40', '2017-01-24 06:12:17', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1012', '31', '5', 'logs-view', '1', '2017-01-24 06:12:17', '40', '2017-01-24 06:12:17', '40', '2017-01-24 06:12:17', '11');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1013', '31', '5', 'logs-edit', '1', '2017-01-24 06:12:17', '40', '2017-01-24 06:12:17', '40', '2017-01-24 06:12:17', '12');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1014', '1', '44', 'blog', '1', '2017-01-25 15:22:52', '41', '2017-01-25 15:22:52', '41', '2017-01-25 15:22:52', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1015', '1014', '43', 'phase-data-extended-transaction', '1', '2017-01-26 06:18:13', '41', '2017-01-25 15:23:04', '41', '2017-01-25 15:23:20', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1016', '1', '45', 'categories', '1', '2017-01-26 05:55:33', '41', '2017-01-26 05:54:06', '41', '2017-01-26 05:54:06', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1017', '1016', '46', 'coffee', '1', '2017-01-26 05:54:49', '41', '2017-01-26 05:54:46', '41', '2017-01-26 05:54:46', '0');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1018', '1016', '46', 'beer', '1', '2017-01-26 05:54:53', '41', '2017-01-26 05:54:53', '41', '2017-01-26 05:54:53', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1019', '1016', '46', 'plants', '1', '2017-01-26 05:56:01', '41', '2017-01-26 05:56:01', '41', '2017-01-26 05:56:01', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1020', '1016', '46', 'cats', '1', '2017-01-26 06:10:41', '41', '2017-01-26 06:10:41', '41', '2017-01-26 06:10:41', '3');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1021', '1014', '43', 'think-affordable-artificial-blast', '1', '2017-01-27 12:37:31', '41', '2017-01-26 06:38:37', '41', '2017-01-26 06:39:03', '1');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1022', '1014', '43', 'genuine-symphony', '1', '2017-01-27 12:18:48', '41', '2017-01-26 09:50:20', '41', '2017-01-26 09:50:54', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1023', '31', '5', 'page-edit-front', '1', '2017-01-27 11:32:31', '41', '2017-01-27 11:32:31', '41', '2017-01-27 11:32:31', '13');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1024', '1001', '47', 'front-end-editor-demo', '1', '2017-01-27 12:31:38', '41', '2017-01-27 12:01:56', '41', '2017-01-27 12:03:43', '2');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1025', '22', '2', 'comments', '1', '2017-01-27 12:17:47', '41', '2017-01-27 12:17:47', '41', '2017-01-27 12:17:47', '3');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1026', '31', '5', 'comments-manager', '1', '2017-01-27 12:17:47', '41', '2017-01-27 12:17:47', '41', '2017-01-27 12:17:47', '14');
INSERT INTO `pages` (`id`, `parent_id`, `templates_id`, `name`, `status`, `modified`, `modified_users_id`, `created`, `created_users_id`, `published`, `sort`) VALUES('1027', '1016', '46', 'recipes', '1', '2017-01-27 12:37:06', '41', '2017-01-27 12:37:06', '41', '2017-01-27 12:37:06', '4');
DROP TABLE IF EXISTS `pages_access`;
CREATE TABLE `pages_access` (
`pages_id` int(11) NOT NULL,
`templates_id` int(11) NOT NULL,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`pages_id`),
KEY `templates_id` (`templates_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('32', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('34', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('35', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('36', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('37', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('38', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('50', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('51', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('52', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('53', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('54', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1006', '2', '2017-01-24 06:11:43');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1010', '2', '2017-01-24 06:12:09');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1012', '2', '2017-01-24 06:12:17');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1013', '2', '2017-01-24 06:12:17');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1014', '1', '2017-01-25 15:22:52');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1015', '1', '2017-01-25 15:23:04');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1016', '1', '2017-01-26 05:54:06');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1017', '1', '2017-01-26 05:54:46');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1018', '1', '2017-01-26 05:54:53');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1019', '1', '2017-01-26 05:56:01');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1020', '1', '2017-01-26 06:10:41');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1021', '1', '2017-01-26 06:38:37');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1022', '1', '2017-01-26 09:50:20');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1023', '2', '2017-01-27 11:32:31');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1024', '1', '2017-01-27 12:01:56');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1026', '2', '2017-01-27 12:17:47');
INSERT INTO `pages_access` (`pages_id`, `templates_id`, `ts`) VALUES('1027', '1', '2017-01-27 12:37:06');
DROP TABLE IF EXISTS `pages_parents`;
CREATE TABLE `pages_parents` (
`pages_id` int(10) unsigned NOT NULL,
`parents_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`pages_id`,`parents_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('2', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('3', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('3', '2');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('7', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('22', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('22', '2');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('28', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('28', '2');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('29', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('29', '2');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('29', '28');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('30', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('30', '2');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('30', '28');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('31', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('31', '2');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('31', '28');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('1001', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('1002', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('1002', '1001');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('1004', '1');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('1004', '1001');
INSERT INTO `pages_parents` (`pages_id`, `parents_id`) VALUES('1005', '1');
DROP TABLE IF EXISTS `pages_sortfields`;
CREATE TABLE `pages_sortfields` (
`pages_id` int(10) unsigned NOT NULL DEFAULT '0',
`sortfield` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`pages_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `pages_sortfields` (`pages_id`, `sortfield`) VALUES('1016', 'name');
DROP TABLE IF EXISTS `session_login_throttle`;
CREATE TABLE `session_login_throttle` (
`name` varchar(128) NOT NULL,
`attempts` int(10) unsigned NOT NULL DEFAULT '0',
`last_attempt` int(10) unsigned NOT NULL,
PRIMARY KEY (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `templates`;
CREATE TABLE `templates` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) CHARACTER SET ascii NOT NULL,
`fieldgroups_id` int(10) unsigned NOT NULL DEFAULT '0',
`flags` int(11) NOT NULL DEFAULT '0',
`cache_time` mediumint(9) NOT NULL DEFAULT '0',
`data` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
KEY `fieldgroups_id` (`fieldgroups_id`)
) ENGINE=MyISAM AUTO_INCREMENT=48 DEFAULT CHARSET=utf8;
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('1', 'home', '1', '0', '0', '{\"useRoles\":1,\"noParents\":1,\"slashUrls\":1,\"compile\":3,\"label\":\"Home\",\"modified\":1485537359,\"ns\":\"ProcessWire\",\"roles\":[37]}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('2', 'admin', '2', '8', '0', '{\"useRoles\":1,\"parentTemplates\":[2],\"allowPageNum\":1,\"redirectLogin\":23,\"slashUrls\":1,\"noGlobal\":1,\"compile\":3,\"modified\":1453457709,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('3', 'user', '3', '8', '0', '{\"useRoles\":1,\"noChildren\":1,\"parentTemplates\":[2],\"slashUrls\":1,\"pageClass\":\"User\",\"noGlobal\":1,\"noMove\":1,\"noTrash\":1,\"noSettings\":1,\"noChangeTemplate\":1,\"nameContentTab\":1}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('4', 'role', '4', '8', '0', '{\"noChildren\":1,\"parentTemplates\":[2],\"slashUrls\":1,\"pageClass\":\"Role\",\"noGlobal\":1,\"noMove\":1,\"noTrash\":1,\"noSettings\":1,\"noChangeTemplate\":1,\"nameContentTab\":1}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('5', 'permission', '5', '8', '0', '{\"noChildren\":1,\"parentTemplates\":[2],\"slashUrls\":1,\"guestSearchable\":1,\"pageClass\":\"Permission\",\"noGlobal\":1,\"noMove\":1,\"noTrash\":1,\"noSettings\":1,\"noChangeTemplate\":1,\"nameContentTab\":1}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('26', 'search', '80', '0', '0', '{\"noChildren\":1,\"noParents\":1,\"allowPageNum\":1,\"slashUrls\":1,\"compile\":3,\"label\":\"Search\",\"modified\":1485526981,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('29', 'basic-page', '83', '0', '0', '{\"slashUrls\":1,\"compile\":3,\"label\":\"Basic page\",\"modified\":1485526981,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('34', 'sitemap', '88', '0', '0', '{\"noChildren\":1,\"noParents\":1,\"redirectLogin\":23,\"slashUrls\":1,\"compile\":3,\"label\":\"Sitemap\",\"modified\":1485427810,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('43', 'blog-post', '97', '0', '0', '{\"parentTemplates\":[44],\"slashUrls\":1,\"compile\":3,\"label\":\"Blog post\",\"modified\":1485532830,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('44', 'blog', '98', '0', '0', '{\"sortfield\":\"-97\",\"noParents\":-1,\"childTemplates\":[43],\"allowPageNum\":1,\"slashUrls\":1,\"compile\":3,\"label\":\"Blog\",\"modified\":1485530079,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('45', 'categories', '99', '0', '0', '{\"noParents\":-1,\"childTemplates\":[46],\"slashUrls\":1,\"compile\":3,\"label\":\"Categories\",\"modified\":1485541446,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('46', 'category', '100', '0', '0', '{\"noChildren\":1,\"parentTemplates\":[45],\"allowPageNum\":1,\"slashUrls\":1,\"compile\":3,\"label\":\"Category\",\"modified\":1485530079,\"ns\":\"ProcessWire\"}');
INSERT INTO `templates` (`id`, `name`, `fieldgroups_id`, `flags`, `cache_time`, `data`) VALUES('47', 'basic-page-edit', '101', '0', '0', '{\"slashUrls\":1,\"compile\":3,\"label\":\"Basic page (front-end editable)\",\"modified\":1485536717,\"ns\":\"ProcessWire\"}');
UPDATE pages SET created_users_id=41, modified_users_id=41, created=NOW(), modified=NOW();
# --- /WireDatabaseBackup {"numTables":26,"numCreateTables":33,"numInserts":416,"numSeconds":0}

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@@ -0,0 +1,88 @@
ABOUT /SITE/MODULES/
====================
This directory /site/modules/ is where you may install additional plugin modules.
These modules are specific to your site only. There is also a corresponding
/wire/modules/ directory, which contains ProcessWire's core modules (and best to
leave those alone).
If safe for your hosting environment, you may wish to make this directory
writable to PHP so that the installation of your modules can be managed from
ProcessWire's admin. However, this is not necessarily safe in all shared hosting
environments and is completely optional.
Where to get modules?
---------------------
Visit the modules directory at: http://modules.processwire.com
Installing modules from the ProcessWire admin
---------------------------------------------
If your /site/modules/ directory is writable, you can install modules from
ProcessWire's admin directly from the Modules Directory, from a ZIP file or from
a URL to a ZIP file. In your ProcessWire admin, see Modules > New for
installation options.
Installing modules from the file system
---------------------------------------
Each module (and any related files) should live in a directory of its own. The
directory should generally carry the same name as the module. For instance, if
you are installing a module named ProcessDatabaseBackups.module, then it should
live in the directory /site/modules/ProcessDatabaseBackups/.
Once you have placed a new module in this directory, you need to let ProcessWire
know about it. Login to the admin and click "Modules". Then click the "Check for
new modules" button. It will find your new module(s). Click the "Install" button
next to any new modules that you want to install.
Removing modules
----------------
The first step in removing a module is to uninstall it from ProcessWire (if it
isn't already). You do this by going to the "Modules" page, and "Site" tab in
your ProcessWire admin. Click the "Uninstall" button next to the module you
want to remove.
After the module is uninstalled, you may remove the module files. If your
modules file system is writable to ProcessWire, it will give you a "Delete"
button next to the module in your "Modules" admin page. You may click that to
remove the module files.
If your file system is not writable, you may remove the module files manually
from the file system (via SFTP or whatever tool you are using to manage your
files on the server).
Interested in learning how to make your own modules?
----------------------------------------------------
We've created two "Hello World" modules as examples for those interested in
learning module development:
- Helloworld.module demonstrates the basics of modules and hooks.
http://modules.processwire.com/modules/helloworld/
- ProcessHello.module demonstrates the basics of how to create a Process
module. Process modules are those that create applications in the admin.
http://modules.processwire.com/modules/process-hello/
There is a module development forum located at:
https://processwire.com/talk/forum/19-moduleplugin-development/
For a tutorial on how to create modules, see:
http://wiki.processwire.com/index.php/Module_Creation
Additional resources
--------------------
To find and download new modules, see the modules directory at:
http://modules.processwire.com/
For more information about modules, see the documentation at:
http://processwire.com/api/modules/
For discussion and support of modules, see:
http://processwire.com/talk/forum/4-modulesplugins/

47
site-regular/ready.php Normal file
View File

@@ -0,0 +1,47 @@
<?php namespace ProcessWire;
/**
* ProcessWire Bootstrap API Ready
* ===============================
* This ready.php file is called during ProcessWire bootstrap initialization process.
* This occurs after the current page has been determined and the API is fully ready
* to use, but before the current page has started rendering. This file receives a
* copy of all ProcessWire API variables. This file is an idea place for adding your
* own hook methods.
*
*/
if(!defined("PROCESSWIRE")) die();
/** @var ProcessWire $wire */
/**
* Example of a custom hook method
*
* This hook adds a “numPosts” method to pages using template “category”.
* The return value is the quantity of posts in category.
*
* Usage:
* ~~~~~
* $numPosts = $page->numPosts(); // returns integer
* numPosts = $page->numPosts(true); // returns string like "5 posts"
* ~~~~~
*
*/
$wire->addHook('Page(template=category)::numPosts', function($event) {
/** @var Page $page */
$page = $event->object;
// only category pages have numPosts
if($page->template != 'category') return;
// find number of posts
$numPosts = $event->pages->count("template=blog-post, categories=$page");
if($event->arguments(0) === true) {
// if true argument was specified, format it as a "5 posts" type string
$numPosts = sprintf(_n('%d post', '%d posts', $numPosts), $numPosts);
}
$event->return = $numPosts;
});

View File

@@ -0,0 +1,11 @@
<?php namespace ProcessWire;
/**
* This _init.php file is called automatically by ProcessWire before every page render
*
*/
/** @var ProcessWire $wire */
include_once('./_uikit.php');

View File

@@ -0,0 +1,137 @@
<?php namespace ProcessWire;
// _main.php template file, called after a pages template file
$home = pages()->get('/'); // homepage
$siteTitle = 'Regular';
$siteTagline = $home->summary;
// as a convenience, set location of our 3rd party resources (Uikit and jQuery)...
urls()->set('uikit', 'wire/modules/AdminTheme/AdminThemeUikit/uikit/dist/');
urls()->set('jquery', 'wire/modules/Jquery/JqueryCore/JqueryCore.js');
// ...or if you prefer to use CDN hosted resources, use these instead:
// urls()->set('uikit', 'https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.40/');
// urls()->set('jquery', 'https://code.jquery.com/jquery-2.2.4.min.js');
?><!DOCTYPE html>
<html lang='en'>
<head id='html-head'>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title id='html-title'><?=page()->title?></title>
<meta name="description" content="<?=page()->summary?>">
<link rel="stylesheet" href="<?=urls()->uikit?>css/uikit.min.css" />
<link rel="stylesheet" href="<?=urls()->templates?>styles/main.css">
<script src="<?=urls()->jquery?>"></script>
<script src="<?=urls()->uikit?>js/uikit.min.js"></script>
<script src="<?=urls()->uikit?>js/uikit-icons.min.js"></script>
</head>
<body id='html-body'>
<!-- MASTHEAD -->
<header class='uk-background-muted'>
<div id='masthead' class="uk-container">
<h2 id='masthead-logo' class='uk-text-center uk-margin-medium-top uk-margin-small-bottom'>
<a href='<?=urls()->root?>'>
<img src='<?=urls()->templates?>styles/images/coffee4.svg' alt='coffee'><br />
</a>
<?=$siteTitle?>
</h2>
<p id='masthead-tagline' class='uk-text-center uk-text-small uk-text-muted uk-margin-remove'>
<?=$siteTagline?>
</p>
<nav id='masthead-navbar' class="uk-navbar-container" uk-navbar>
<div class="uk-navbar-center uk-visible@m">
<?=ukNavbarNav($home->and($home->children), [
'dropdown' => [ 'basic-page', 'categories' ]
])?>
</div>
</nav>
</div>
</header>
<!-- MAIN CONTENT -->
<main id='main' class='uk-container uk-margin uk-margin-large-bottom'>
<?php if(page()->parent->id > $home->id) echo ukBreadcrumb(page(), [ 'class' => 'uk-visible@m' ]); ?>
<div class='uk-grid uk-grid-large' uk-grid>
<div id='content' class='uk-width-expand'>
<h1 id='content-head' class='uk-margin-small-top'>
<?=page()->get('headline|title')?>
</h1>
<div id='content-body'>
<?=page()->body?>
</div>
</div>
<aside id='sidebar' class='uk-width-1-3@m'>
<?=page()->sidebar?>
</aside>
</div>
</main>
<?php if(config()->debug && user()->isSuperuser()): // display region debugging info ?>
<section id='debug' class='uk-section uk-section-muted'>
<div class='uk-container'>
<!--PW-REGION-DEBUG-->
</div>
</section>
<?php endif; ?>
<!-- FOOTER -->
<footer class='uk-section uk-section-secondary'>
<div id='footer' class='uk-container'>
<div class='uk-grid' uk-grid>
<div class='uk-width-1-3@m uk-flex-last@m uk-text-center'>
<form class='uk-search uk-search-default' action='<?=pages()->get('template=search')->url?>' method='get'>
<button type='submit' class='uk-search-toggle uk-search-icon-flip' uk-search-icon></button>
<input type='search' id='search-query' name='q' class='uk-search-input' placeholder='Search&hellip;'>
</form>
</div>
<div class='uk-width-2-3@m uk-flex-first@m uk-text-center uk-text-left@m'>
<h3 class='uk-margin-remove'>
<?=$siteTitle?>
<small class='uk-text-small uk-text-muted'><?=$siteTagline?></small>
</h3>
<p class='uk-margin-remove'>
<small class='uk-text-small uk-text-muted'>&copy; <?=date('Y')?> &bull;</small>
<a href='https://processwire.com'>Powered by ProcessWire CMS</a>
</p>
</div>
</div>
</div>
</footer>
<!-- OFFCANVAS NAV TOGGLE -->
<a id='offcanvas-toggle' class='uk-hidden@m' href="#offcanvas-nav" uk-toggle>
<?=ukIcon('menu', 1.3)?>
</a>
<!-- OFFCANVAS NAVIGATION -->
<div id="offcanvas-nav" uk-offcanvas>
<div class="uk-offcanvas-bar">
<h3><a href='<?=urls()->root?>'><?=$siteTitle?></a></h3>
<?php
// offcanvas navigation
// example of caching generated markup (for 600 seconds/10 minutes)
echo cache()->get('offcanvas-nav', 10, function() {
return ukNav(pages()->get('/')->children(), [
'depth' => 1,
'accordion' => true,
'blockParents' => [ 'blog' ],
'repeatParent' => true,
'noNavQty' => 20
]);
});
?>
</div>
</div>
<?php if(page()->editable): ?>
<!-- PAGE EDIT LINK -->
<a id='edit-page' href='<?=page()->editUrl?>'>
<?=ukIcon('pencil')?> Edit
</a>
<?php endif; ?>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
<?php namespace ProcessWire;
/**
* Admin template just loads the admin application controller,
* and admin is just an application built on top of ProcessWire.
*
* This demonstrates how you can use ProcessWire as a front-end
* to another application.
*
* Feel free to hook admin-specific functionality from this file,
* but remember to leave the require() statement below at the end.
*
*/
require($config->paths->adminTemplates . 'controller.php');

View File

@@ -0,0 +1,21 @@
<?php namespace ProcessWire;
// this template is very much like the basic-page template except that it
// demonstrates making the headline, body and sidebar fields editable on the
// front-end, using the <edit> tags
?>
<h1 id='content-head'>
<edit field='headline'><?=page()->headline?></edit>
</h1>
<div id='content-body'>
<edit field='body'><?=page()->body?></edit>
<?=ukDescriptionListPages(page()->children)?>
</div>
<aside id='sidebar'>
<?=ukNav(page()->rootParent, "depth=3, class=uk-margin-medium-bottom")?>
<edit field='sidebar'><?=page()->sidebar?></edit>
</aside>

View File

@@ -0,0 +1,10 @@
<?php namespace ProcessWire; ?>
<nav pw-append='content-body'>
<?=ukDescriptionListPages(page()->children)?>
</nav>
<aside id='sidebar' pw-prepend>
<?=ukNav(page()->rootParent, "depth=3")?>
</aside>

View File

@@ -0,0 +1,49 @@
<?php namespace ProcessWire; ?>
<head id='html-head' pw-append>
<script src='<?=urls()->FieldtypeComments?>comments.min.js'></script>
<link rel="stylesheet" href="<?=urls()->FieldtypeComments?>comments.css">
</head>
<div id='content'>
<?php
// blog post content
echo ukBlogPost(page());
// comments
$comments = page()->comments;
// comment list
if(count($comments)) {
echo ukHeading3("Comments", "icon=comments");
echo ukComments($comments);
}
// comment form
echo ukHeading3("Post a comment", "icon=comment");
echo ukCommentForm($comments);
// link to the next blog post, if there is one
$nextPost = page()->next();
if($nextPost->id): ?>
<p class='next-blog-post'>
Next <?=ukIcon('chevron-right')?>
<a href='<?=$nextPost->url?>'><?=$nextPost->title?></a>
</p>
<?php endif; ?>
</div>
<aside id='sidebar' pw-prepend>
<?php
$img = page()->images->first();
if($img) {
$img = $img->width(600);
echo "<p class='uk-text-center'><img src='$img->url' alt='$img->description'></p>";
}
?>
<?=ukNav(page()->parent->children('limit=10'), [ 'heading' => 'Recent blog posts' ])?>
<p><a href='<?=page()->parent->url?>'>More posts<?=ukIcon('arrow-right')?></a></p>
</aside>

View File

@@ -0,0 +1,20 @@
<?php namespace ProcessWire;
// This is the template file for main /blog/ page that lists blog post summaries.
// If there are more than 10 posts, it also paginates them.
?>
<div id='content'>
<?php
echo ukHeading1(page()->title, 'divider');
$posts = page()->children('limit=10');
echo ukBlogPosts($posts);
?>
</div>
<aside id='sidebar'>
<?php
$categories = pages()->get('/categories/');
echo ukNav($categories->children, [ 'header' => $categories->title ]);
?>
</aside>

View File

@@ -0,0 +1,16 @@
<?php namespace ProcessWire; ?>
<div class='uk-child-width-1-2@s uk-child-width-1-3@m uk-grid uk-grid-match uk-margin-large-bottom' pw-append='content' uk-grid>
<?php foreach(page()->children as $category): ?>
<a class='uk-link-reset' href='<?=$category->url?>'>
<div class='uk-card uk-card-default uk-card-hover uk-card-body'>
<h3 class='uk-card-title uk-margin-remove'><?=$category->title?></h3>
<span class='uk-text-muted'><?=$category->numPosts(true)?></span>
</div>
</a>
<?php endforeach; ?>
</div>
<aside id='sidebar'>
<?=ukNav(pages()->get('/blog/')->children('limit=3'), [ 'header' => 'Recent posts' ])?>
</aside>

View File

@@ -0,0 +1,16 @@
<?php namespace ProcessWire; ?>
<div id='content'>
<?php
echo ukHeading1(page()->title, 'divider');
$posts = pages()->get('/blog/')->children("categories=$page, limit=10");
echo ukBlogPosts($posts);
?>
</div>
<aside id='sidebar'>
<?php
$categories = page()->parent->children();
echo ukNav($categories);
?>
</aside>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>500 Internal Server Error</title>
</head>
<body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or misconfiguration and was unable to complete your request.</p>
<p>{message}</p>
</body>
</html>

View File

@@ -0,0 +1,21 @@
When a fatal error occurs, ProcessWire displays the message:
"Unable to complete this request due to an error."
The message is intentionally vague for security purposes.
Details will be logged to /site/assets/logs/errors.txt.
When present in this directory, the file 500.html will be
displayed instead of the generic error message above. Feel
free to modify this file to show whatever you would like.
Please note the following:
* 500.html is plain HTML and has no PHP or API access.
* You may enter the tag {message} and ProcessWire will
replace this with additional details when applicable.
When not applicable, it will make it blank.
* If you are logged in as an admin, ProcessWire will
give you a detailed error message rather than 500.html.

View File

@@ -0,0 +1,32 @@
<?php namespace ProcessWire;
// get most recent blog post
$blog = pages()->get('/blog/');
$blogPost = $blog->child();
?>
<h1 id='content-head'>
<?=page()->headline?>
</h1>
<div class='uk-margin-top' id='content-body'>
<?=page()->body?>
<hr>
<p class='uk-margin-small'>
<a class='uk-button uk-button-link uk-link-muted' href='<?=$blog->url?>'>
In the blog
</a>
</p>
<?=ukBlogPost($blogPost)?>
<p class='uk-margin-small'>
<a href='<?=$blog->url?>'>More blog posts <?=ukIcon('arrow-right')?></a>
</p>
</div>
<aside id='sidebar'>
<?=ukNav(pages()->get('/categories/')->children)?>
<div class='uk-card uk-card-default uk-card-hover uk-card-body uk-margin-medium-top'>
<?=page()->sidebar?>
</div>
</aside>

View File

@@ -0,0 +1,4 @@
We typically use this directory for javascript files,
but this site profile does not have any at present.
This file is a placeholder so that this directory
exists in Git.

View File

@@ -0,0 +1,49 @@
<?php namespace ProcessWire;
// look for a GET variable named 'q' and sanitize it
$q = input()->get('q');
// sanitize to text, which removes markup, newlines, too long, etc.
$q = sanitizer()->text($q);
// did $q have anything in it after sanitizing to text?
if($q) {
// Make the search query appear in the top-right search box.
// Always entity encode any user input that also gets output
echo '<input id="search-query" value="' . sanitizer()->entities($q) . '">';
// Sanitize for placement within a selector string. This is important for any
// values that you plan to bundle in a selector string like we are doing here.
// It quotes them when necessary, and removes characters that might cause issues.
$q = sanitizer()->selectorValue($q);
// Search the title and body fields for our query text.
// Limit the results to 50 pages. The has_parent!=2 excludes irrelevant admin
// pages from the search, for when an admin user performs a search.
$selector = "title|body~=$q, limit=50, has_parent!=2";
// Find pages that match the selector
$matches = pages()->find($selector);
} else {
$matches = array();
}
// unset the variable that we no longer need, since it can contain user input
unset($q);
?>
<div id='content-body'>
<?php
// did we find any matches?
if(count($matches)) {
// yes we did, render them
echo ukAlert("Found $matches->count page(s)", "default", "search");
echo ukDescriptionListPages($matches);
} else {
// we didn't find any
echo ukAlert("Sorry, no results were found.", "danger", "warning");
}
?>
</div>

View File

@@ -0,0 +1,9 @@
<?php namespace ProcessWire; ?>
<div id='content-body'>
<?php
$home = pages()->get('/');
echo ukNav($home, [ 'depth' => 4 ])
?>
</div>

View File

@@ -0,0 +1,382 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:ns1="http://sozi.baierouge.fr"
id="svg2"
sodipodi:docname="coffeecup-3d.svg"
inkscape:export-filename="D:\mocholand\Development\vectorial-cofee-cup\coffeecup-3d.png"
viewBox="0 0 444.47 310.87"
inkscape:export-xdpi="90"
version="1.1"
inkscape:export-ydpi="90"
inkscape:version="0.47pre3 "
>
<defs
id="defs4"
>
<radialGradient
id="radialGradient4309"
gradientUnits="userSpaceOnUse"
cy="351.05"
cx="368.58"
gradientTransform="matrix(1 0 0 .54810 0 141.63)"
r="185.64"
inkscape:collect="always"
>
<stop
id="stop4197"
style="stop-color:#e2e2e2"
offset="0"
/>
<stop
id="stop4199"
style="stop-color:#989898"
offset="1"
/>
</radialGradient
>
<linearGradient
id="linearGradient4311"
y2="321.49"
gradientUnits="userSpaceOnUse"
x2="369.95"
y1="346.56"
x1="369.95"
inkscape:collect="always"
>
<stop
id="stop4220"
style="stop-color:#838383"
offset="0"
/>
<stop
id="stop4222"
style="stop-color:#c6c6c6;stop-opacity:0"
offset="1"
/>
</linearGradient
>
<linearGradient
id="linearGradient4313"
y2="378.81"
gradientUnits="userSpaceOnUse"
x2="388.13"
gradientTransform="translate(0 -60)"
y1="327.72"
x1="368.58"
inkscape:collect="always"
>
<stop
id="stop4265"
style="stop-color:#461f00"
offset="0"
/>
<stop
id="stop4267"
style="stop-color:#753300"
offset="1"
/>
</linearGradient
>
<linearGradient
id="linearGradient4315"
y2="327.72"
gradientUnits="userSpaceOnUse"
x2="368.58"
gradientTransform="translate(0 -60)"
y1="362.58"
x1="368.58"
inkscape:collect="always"
>
<stop
id="stop4275"
style="stop-color:#ffffff"
offset="0"
/>
<stop
id="stop4277"
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
/>
</linearGradient
>
<filter
id="filter4335"
height="2.8094"
width="1.3596"
color-interpolation-filters="sRGB"
y="-.90469"
x="-.17981"
inkscape:collect="always"
>
<feGaussianBlur
id="feGaussianBlur4337"
stdDeviation="12.0625"
inkscape:collect="always"
/>
</filter
>
<radialGradient
id="radialGradient4339"
gradientUnits="userSpaceOnUse"
cy="582.55"
cx="408.1"
gradientTransform="matrix(1 0 0 .18691 0 409.16)"
r="222.23"
inkscape:collect="always"
>
<stop
id="stop4109"
style="stop-color:#e9e9e9"
offset="0"
/>
<stop
id="stop4119"
style="stop-color:#b3b3b3"
offset="1"
/>
</radialGradient
>
</defs
>
<sodipodi:namedview
id="base"
bordercolor="#666666"
inkscape:pageshadow="2"
inkscape:window-y="-4"
pagecolor="#ffffff"
inkscape:window-height="816"
inkscape:window-maximized="1"
inkscape:zoom="1"
inkscape:window-x="-4"
showgrid="false"
borderopacity="1.0"
inkscape:current-layer="layer1"
inkscape:cx="238.91057"
inkscape:cy="148.53567"
inkscape:window-width="1152"
inkscape:pageopacity="0.0"
inkscape:document-units="px"
/>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(-145.59 -250.53)"
>
<path
id="path4182"
sodipodi:rx="222.23357"
sodipodi:ry="62.629459"
style="fill-rule:evenodd;fill:#595959"
sodipodi:type="arc"
d="m630.34 492.74c0 34.589-99.497 62.629-222.23 62.629-122.74 0-222.23-28.04-222.23-62.629s99.497-62.629 222.23-62.629c122.74 0 222.23 28.04 222.23 62.629z"
transform="matrix(.94938 0 0 1.0617 -20.871 -28.258)"
sodipodi:cy="492.73767"
sodipodi:cx="408.10162"
/>
<path
id="path4159"
sodipodi:rx="222.23357"
sodipodi:ry="62.629459"
style="fill-rule:evenodd;fill:#f2f2f2"
sodipodi:type="arc"
d="m630.34 492.74c0 34.589-99.497 62.629-222.23 62.629-122.74 0-222.23-28.04-222.23-62.629s99.497-62.629 222.23-62.629c122.74 0 222.23 28.04 222.23 62.629z"
transform="translate(-40.279 -1.7029)"
sodipodi:cy="492.73767"
sodipodi:cx="408.10162"
/>
<path
id="path3333"
sodipodi:rx="222.23357"
sodipodi:ry="62.629459"
style="fill-rule:evenodd;fill:url(#radialGradient4339)"
sodipodi:type="arc"
d="m630.34 492.74c0 34.589-99.497 62.629-222.23 62.629-122.74 0-222.23-28.04-222.23-62.629s99.497-62.629 222.23-62.629c122.74 0 222.23 28.04 222.23 62.629z"
transform="translate(-40.279 -5.7029)"
sodipodi:cy="492.73767"
sodipodi:cx="408.10162"
/>
<path
id="path4186"
sodipodi:rx="123.3854"
sodipodi:ry="29.52813"
style="fill-opacity:.40698;fill:#858585"
sodipodi:type="arc"
d="m487.21 550.38c0 16.308-55.242 29.528-123.39 29.528-68.144 0-123.39-13.22-123.39-29.528s55.242-29.528 123.39-29.528c68.144 0 123.39 13.22 123.39 29.528z"
transform="translate(6 -66)"
sodipodi:cy="550.38397"
sodipodi:cx="363.82874"
/>
<path
id="path4317"
sodipodi:rx="80.5"
sodipodi:ry="16"
style="opacity:.75;filter:url(#filter4335);fill:#000000"
sodipodi:type="arc"
d="m448 483.36c0 8.8366-36.041 16-80.5 16s-80.5-7.1634-80.5-16 36.041-16 80.5-16 80.5 7.1634 80.5 16z"
transform="matrix(.93409 0 0 .91146 23.008 44.818)"
sodipodi:cy="483.36218"
sodipodi:cx="367.5"
/>
<g
id="g4295"
transform="translate(0,4)"
>
<path
id="path4188"
style="fill:url(#radialGradient4309)"
d="m182.94 284.44c18.733 116.39 94.715 203.5 185.62 203.5 90.911 0 166.92-87.107 185.66-203.5h-371.28z"
/>
<path
id="path4193"
sodipodi:rx="181.46394"
sodipodi:ry="25.064081"
style="fill:#eaeaea"
sodipodi:type="arc"
d="m551.41 346.56c0 13.843-81.244 25.064-181.46 25.064s-181.46-11.222-181.46-25.064c0-13.843 81.244-25.064 181.46-25.064s181.46 11.222 181.46 25.064z"
transform="matrix(1.0234 0 0 1.6116 -10.017 -271.57)"
sodipodi:cy="346.55768"
sodipodi:cx="369.94583"
/>
<path
id="path4216"
sodipodi:rx="181.46394"
sodipodi:ry="25.064081"
style="fill:url(#linearGradient4311)"
sodipodi:type="arc"
d="m551.41 346.56c0 13.843-81.244 25.064-181.46 25.064s-181.46-11.222-181.46-25.064c0-13.843 81.244-25.064 181.46-25.064s181.46 11.222 181.46 25.064z"
transform="matrix(.98128 0 0 1.4117 5.5602 -205.8)"
sodipodi:cy="346.55768"
sodipodi:cx="369.94583"
/>
<path
id="path4255"
style="fill:url(#linearGradient4313)"
d="m368.59 267.72c-91.89 0-166.42 12.631-166.62 28.219 25.429 13.369 90.44 22.875 166.62 22.875 76.175 0 141.16-9.5089 166.59-22.875-0.2008-15.588-74.704-28.219-166.59-28.219z"
/>
<path
id="path4271"
sodipodi:nodetypes="cccc"
style="opacity:0.04;fill:url(#linearGradient4315)"
d="m368.59 267.72c-87.769 0-159.71 11.539-166.16 26.156 107.47-8.4524 217.07-11.433 332.28 0-6.4479-14.617-78.356-26.156-166.12-26.156z"
/>
<path
id="path4293"
sodipodi:nodetypes="cccc"
style="fill-opacity:.15116;fill:#ffffff"
d="m213.75 309.23c9.0001 2.9648 14.241 3.9652 20.516 5.5258 0 0 1.2916 65.75 23.592 103.41-24.432-33.615-44.951-65.463-44.108-108.94z"
/>
</g
>
</g
>
<metadata
>
<rdf:RDF
>
<cc:Work
>
<dc:format
>image/svg+xml</dc:format
>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
/>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/"
/>
<dc:publisher
>
<cc:Agent
rdf:about="http://openclipart.org/"
>
<dc:title
>Openclipart</dc:title
>
</cc:Agent
>
</dc:publisher
>
<dc:title
>Realistic Coffee cup - Front 3D view</dc:title
>
<dc:date
>2009-10-17T06:56:28</dc:date
>
<dc:description
>Realistic Coffee cup, with a shiny 3d look, front view.</dc:description
>
<dc:source
>https://openclipart.org/detail/27828/realistic-coffee-cup---front-3d-view-by-mokush</dc:source
>
<dc:creator
>
<cc:Agent
>
<dc:title
>mokush</dc:title
>
</cc:Agent
>
</dc:creator
>
<dc:subject
>
<rdf:Bag
>
<rdf:li
>cofe</rdf:li
>
<rdf:li
>cofee</rdf:li
>
<rdf:li
>coffe</rdf:li
>
<rdf:li
>coffee</rdf:li
>
<rdf:li
>coffeecup</rdf:li
>
<rdf:li
>cup</rdf:li
>
<rdf:li
>photorealistic</rdf:li
>
</rdf:Bag
>
</dc:subject
>
</cc:Work
>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/"
>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
/>
</cc:License
>
</rdf:RDF
>
</metadata
>
</svg
>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,111 @@
#html-body, h1, h2, h3, h4, h5 {
/* default font */
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
#masthead-logo img {
/* image for masthead logo */
width: 100px;
}
#edit-page {
/* the edit page link that appears when page is editable */
position: absolute;
top: 10px;
right: 10px
}
#offcanvas-toggle {
/* the hamburger icon that toggles the mobile navigation */
position: absolute;
top: 10px;
left: 15px;
}
#offcanvas-nav .uk-search-input {
/* the search box that appears in offcanvas nav */
width: 100%;
}
img.uk-comment-avatar {
/* avatar that appears in comments */
width: 60px;
height: 60px;
}
@media only screen and (max-width: 959px) {
/* custom adjustments for mobile layouts under 960px */
#masthead {
/* primary nav is not visible, so it needs some padding here */
padding-bottom: 20px;
}
}
/****************************************************************
* Bodycopy text and images
*
* These styles are good to have on any ProcessWire site
*
*/
.align_left {
/* for images placed in rich text editor */
float: left;
margin: 0 1em 0.5em 0;
position: relative;
top: 0.5em;
max-width: 50%;
}
.align_right {
/* for images placed in rich text editor */
float: right;
margin: 0 0 0.5em 1em;
max-width: 50%;
}
.align_center {
/* for images placed in rich text editor */
display: block;
margin: 1em auto;
position: relative;
top: 0.5em;
}
figure {
/* figure for image that has a caption */
display: table;
margin: 1em 0;
}
figure figcaption {
/* display caption text below image contained to image width */
display: table-caption;
caption-side: bottom;
font-size: 13px;
line-height: 1.4em;
margin-top: 5px;
color: #777;
}
@media only screen and (max-width: 767px) {
/* common PW mobile layout adjustments for widths under 768px */
.align_left, .align_right, .align_center {
/* display images in center rather than aligned */
display: block;
float: none;
margin: 1em auto;
max-width: 100%;
}
figure,
figure figcaption {
/* let figcaption display as wide as needed below image */
display: block;
text-align: center;
}
}

View File

@@ -142,6 +142,44 @@ $config->useFunctionsAPI = false;
*/
$config->useMarkupRegions = false;
/**
* Use custom page classes? Specify boolean true to enable.
*
* When enabled, if a class with name "[TemplateName]Page" (in ProcessWire namespace) exists
* in /site/classes/[TemplateName]Page.php, and it extends ProcessWires Page class, then the
* Page will be created with that class rather than the default Page class. For instance,
* template “home” would look for a class named “HomePage” and template "blog-post" (or
* "blog_post") would look for a class named “BlogPostPage” (file: BlogPostPage.php).
*
* If you create a file named /site/classes/DefaultPage.php with a DefaultPage class within
* it (that extends Page), then it will be used for all pages that would otherwise use the
* `Page` class.
*
* @var bool|string
* @since 3.0.152
*
*/
$config->usePageClasses = false;
/**
* Disable all HTTPS requirements?
*
* Use this option only for development or staging environments, on sites where you are
* otherwise requiring HTTPS. By setting this option to something other than false, you
* can disable any HTTPS requirements specified per-template, enabling you to use your
* site without SSL during development or staging, etc.
*
* The following options are available:
* - boolean true: Disable HTTPS requirements globally
* - string containing hostname: Disable HTTPS requirements only for specified hostname.
* - array containing hostnames: Disable HTTPS requirements for specified hostnames.
*
* @var bool|string|array
*
*/
$config->noHTTPS = false;
/*** 2. DATES & TIMES *************************************************************************/
@@ -228,8 +266,8 @@ $config->sessionExpireSeconds = 86400;
* // if there is a session cookie, a session is likely already in use so keep it going
* if($session->hasCookie()) return true;
*
* // if URL is an admin URL, allow session
* if(strpos($_SERVER['REQUEST_URI'], $session->config->urls->admin) === 0) return true;
* // if URL is an admin URL, allow session (replace /processwire/ with your admin URL)
* if(strpos($_SERVER['REQUEST_URI'], '/processwire/) === 0) return true;
*
* // otherwise disallow session
* return false;
@@ -255,33 +293,73 @@ $config->sessionChallenge = true;
/**
* Use session fingerprint?
*
* Should login sessions be tied to IP and user agent?
* IP fingerprinting may be problematic on dynamic IPs.
* Below are the possible values:
* Should login sessions also be tied to a fingerprint of the browser?
* Fingerprinting can be based upon browser-specific headers and/or
* IP addresses. But note that IP fingerprinting will be problematic on
* dynamic IPs.
*
* 0 or false: Fingerprint off
* 1 or true: Fingerprint on with default/recommended setting (currently 10).
* 2: Fingerprint only the remote IP
* 4: Fingerprint only the forwarded/client IP (can be spoofed)
* 8: Fingerprint only the useragent
* 10: Fingerprint the remote IP and useragent (default)
* 12: Fingerprint the forwarded/client IP and useragent
* 14: Fingerprint the remote IP, forwarded/client IP and useragent (all).
* Predefined settings:
*
* If using fingerprint in an environment where the users
* IP address may change during the session, you should
* fingerprint only the useragent, or disable fingerprinting.
* - 0 or false: Fingerprint off
* - 1 or true: Fingerprint on with default setting (remote IP & useragent)
*
* Custom settings:
*
* - 2: Remote IP
* - 4: Forwarded/client IP (can be spoofed)
* - 8: Useragent
* - 16: Accept header
*
* To use the custom settings above, select one or more of those you want
* to fingerprint, note the numbers, and use them like in the examples:
* ~~~~~~
* // to fingerprint just remote IP
* $config->sessionFingerprint = 2;
*
* // to fingerprint remote IP and useragent:
* $config->sessionFingerprint = 2 | 8;
*
* // to fingerprint remote IP, useragent and accept header:
* $config->sessionFingerprint = 2 | 8 | 16;
* ~~~~~~
*
* If using fingerprint in an environment where the users IP address may
* change during the session, you should fingerprint only the useragent
* and/or accept header, or disable fingerprinting.
*
* If using fingerprint with an AWS load balancer, you should
* use one of the options that uses the “client IP” rather than
* the “remote IP”, fingerprint only the useragent, or disable
* fingerprinting.
* If using fingerprint with an AWS load balancer, you should use one of
* the options that uses the “client IP” rather than the “remote IP”,
* fingerprint only useragent and/or accept header, or disable fingerprinting.
*
* @var int
*
*/
$config->sessionFingerprint = 1;
/**
* Force current session IP address (overriding auto-detect)
*
* This overrides the return value of `$session->getIP()` method.
* Use this property only for setting the IP address. To get the IP address
* always use the `$session->getIP()` method instead.
*
* This is useful if you are in an environment where the remote IP address
* comes from some property other than the REMOTE_ADDR in $_SERVER. For instance,
* if you are using a load balancer, whats usually detected as the IP address is
* actually the IP address between the load balancer and the server, rather than
* the client IP address. So in that case, youd want to set this property as
* follows:
* ~~~~~
* $config->sessionForceIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
* ~~~~~
* If you dont have a specific need to override the IP address of the user
* then you should leave this blank.
*
* @var string
*
*/
$config->sessionForceIP = '';
/**
* Use secure cookies when on HTTPS?
*
@@ -358,6 +436,7 @@ $config->loginDisabledRoles = array(
*
* Set to false do disable the option for compiled template files.
* When set to true, it will be used unless a given template's 'compile' option is set to 0.
* This setting also covers system status files like /site/ready.php, /site/init.php, etc. (3.0.142+)
*
* @var bool
*
@@ -552,19 +631,60 @@ $config->contentTypes = array(
*/
$config->fileContentTypes = array(
'?' => '+application/octet-stream',
'txt' => '+text/plain',
'csv' => '+text/csv',
'pdf' => '+application/pdf',
'doc' => '+application/msword',
'docx' => '+application/msword',
'xls' => '+application/excel',
'xlsx' => '+application/excel',
'docx' => '+application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls' => '+application/vnd.ms-excel',
'xlsx' => '+application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'ppt' => '+application/vnd.ms-powerpoint',
'pptx' => '+application/vnd.openxmlformats-officedocument.presentationml.presentation',
'rtf' => '+application/rtf',
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/x-png',
'svg' => 'image/svg+xml'
'png' => 'image/png',
'svg' => 'image/svg+xml',
'webp' => 'image/webp',
'zip' => '+application/zip',
'mp3' => 'audio/mpeg',
);
/**
* Named predefined image sizes and options
*
* Specify associative arrays of predefined image sizes indexed by name.
* Each item should have at least 'width' and 'height' indexes. But they can also have any
* other option accepted by the `Pageimage::size()` method `$options` argument.
*
* You can use your defined sizes by name in a Pageimage::size() call by specifying the
* size name rather than the `$width` argument, like this:
* ~~~~~~
* $image = $page->images->first();
* $landscape = $image->size('landscape');
* echo "<img src='$landscape->url' alt='$landscape->description' />";
* ~~~~~~
* Feel free to completely overwrite the default $config->imageSizes in your /site/config.php
* file * as this particular setting is not used by the core.
*
* @var array
* @since 3.0.151
*
*/
$config->imageSizes = array(
// Example 1: Landscape (try this one if you want to test the feature)
'landscape' => array('width' => 600, 'height' => 300),
// Example 2: Thumbnails in admin (260 height, proportional width)
// 'admin' => array('width' => 0, 'height' => 260),
// Example 3: Portrait, with additional options:
// 'portrait' => array('width' => 300, 'height' => 500, 'quality' => 80, 'suffix' => 'portrait'),
// Example 4: Square size cropping towards (preserving) top/center (north):
// 'squareNorth' => array('width' => 400, 'height' => 400, 'cropping' => 'north'),
);
/**
* Image sizer options
@@ -578,6 +698,7 @@ $config->fileContentTypes = array(
* #property string sharpening Sharpening mode, enter one of: none, soft, medium, strong
* #property int quality Image quality, enter a value between 1 and 100, where 100 is highest quality (and largest files)
* #property float defaultGamma Default gamma of 0.5 to 4.0 or -1 to disable gamma correction (default=2.0)
* #property bool webpAdd Create a WEBP copy with every new image variation? (default=false)
*
* @var array
*
@@ -591,6 +712,25 @@ $config->imageSizerOptions = array(
'quality' => 90, // quality: 1-100 where higher is better but bigger
'hidpiQuality' => 60, // Same as above quality setting, but specific to hidpi images
'defaultGamma' => 2.0, // defaultGamma: 0.5 to 4.0 or -1 to disable gamma correction (default=2.0)
'webpAdd' => false, // set this to true, if the imagesizer engines should create a Webp copy with every (new) image variation
);
/**
* Options for webp images
*
* #property int quality Quality setting where 1-100 where higher is better but bigger
* #property bool useSrcExt Use source file extension in webp filename? (file.jpg.webp rather than file.webp)
* #property bool useSrcUrlOnSize Fallback to source file URL when webp file is larger than source?
* #property bool useSrcUrlOnFail Fallback to source file URL when webp file fails for some reason?
*
* @var array
*
*/
$config->webpOptions = array(
'quality' => 90, // Quality setting of 1-100 where higher is better but bigger
'useSrcExt' => false, // Use source file extension in webp filename? (file.jpg.webp rather than file.webp)
'useSrcUrlOnSize' => true, // Fallback to source file URL when webp file is larger than source?
'useSrcUrlOnFail' => true, // Fallback to source file URL when webp file fails for some reason?
);
/**
@@ -787,6 +927,16 @@ $config->pageNameCharset = 'ascii';
*/
$config->pageNameWhitelist = '-_.abcdefghijklmnopqrstuvwxyz0123456789æåäßöüđжхцчшщюяàáâèéëêěìíïîõòóôøùúûůñçčćďĺľńňŕřšťýžабвгдеёзийклмнопрстуфыэęąśłżź';
/**
* Name to use for untitled pages
*
* When page has this name, the name will be changed automatically (to a field like title) when it is possible to do so.
*
* @var string
*
*/
$config->pageNameUntitled = "untitled";
/**
* Maximum paginations
*
@@ -812,11 +962,79 @@ $config->maxPageNum = 999;
*/
$config->wireInputOrder = 'get post';
/**
* Lazy-load get/post/cookie input into $input API var?
*
* This is an experimental option for reduced memory usage when a lot of input data is present.
*
* This prevents PW from keeping separate copies of get/post/cookie data, and it instead works
* directly from the PHP $_GET, $_POST and $_COOKIE vars.
*
* This option is also useful in that anything you SET to PWs $input->get/post/cookie also gets
* set to the equivalent PHP $_GET, $_POST and $_COOKIE.
*
* @var bool
*
*/
$config->wireInputLazy = false;
/**
* Options for setting cookies from $input->cookie()->set(...)
*
* Additional details about some of these options can also be found on PHPs [setcookie](https://www.php.net/manual/en/function.setcookie.php) doc page.
*
* #property int age Max age of cookies in seconds or 0 to expire with session (3600=1hr, 86400=1day, 604800=1week, 2592000=30days, etc.)
* #property string|null Cookie path or null for PW installations root URL (default=null).
* #property string|null|bool domain Cookie domain: null for current hostname, true for all subdomains of current domain, domain.com for domain and all subdomains, www.domain.com for www subdomain.
* #property bool|null secure Transmit cookies only over secure HTTPS connection? (true, false, or null to auto-detect, using true option for cookies set when HTTPS is active).
* #property bool httponly When true, cookie is http/server-side and not visible to JS code in most browsers.
*
* @var array
* @since 3.0.141
*
*/
$config->cookieOptions = array(
'age' => 604800, // Max age of cookies in seconds or 0 to expire with session (3600=1hr, 86400=1day, 604800=1week, 2592000=30days, etc.)
'path' => null, // Cookie path/URL or null for PW installations root URL (default=null).
'domain' => null, // Cookie domain: null for current hostname, true for all subdomains of current domain, domain.com for domain and all subdomains, www.domain.com for www subdomain.
'secure' => null, // Transmit cookies only over secure HTTPS connection? (true, false, or null to auto-detect, substituting true for cookies set when HTTPS is active).
'httponly' => false, // When true, cookie is http/server-side only and not visible to client-side JS code.
'fallback' => true, // If set cookie fails (perhaps due to output already sent), attempt to set at beginning of next request? (default=true)
);
/*** 7. DATABASE ********************************************************************************/
/**
* Database name
*
*/
$config->dbName = '';
/**
* Database username
*
*/
$config->dbUser = '';
/**
* Database password
*
*/
$config->dbPass = '';
/**
* Database host
*
*/
$config->dbHost = '';
/**
* Database port
*
*/
$config->dbPort = 3306;
/**
* Database character set
*
@@ -871,30 +1089,6 @@ $config->dbPath = '';
*/
$config->dbLowercaseTables = true;
/**
* Database username
*
*/
$config->dbUser = '';
/**
* Database password
*
*/
$config->dbPass = '';
/**
* Database host
*
*/
$config->dbHost = '';
/**
* Database port
*
*/
$config->dbPort = 3306;
/**
* Database init command (PDO::MYSQL_ATTR_INIT_COMMAND)
*
@@ -985,7 +1179,7 @@ $config->moduleCompile = true;
* @var string
*
*/
$config->moduleServiceURL = 'http://modules.processwire.com/export-json/';
$config->moduleServiceURL = 'https://modules.processwire.com/export-json/';
/**
* Modules service API key
@@ -995,7 +1189,30 @@ $config->moduleServiceURL = 'http://modules.processwire.com/export-json/';
* @var string
*
*/
$config->moduleServiceKey = (__NAMESPACE__ ? 'pw300' : 'pw280');
$config->moduleServiceKey = 'pw301';
/**
* Allowed module installation options (in admin)
*
* Module installation options you want to be available from the admin Modules > Install tab.
* For any of the options below, specify boolean `true` to allow, `false` to disallow, or
* specify string `'debug'` to allow only when ProcessWire is in debug mode.
*
* - `directory`: Allow installation or upgrades from ProcessWire modules directory?
* - `upload`: Allow installation by file upload?
* - `download`: Allow installation by file download from URL?
*
* @todo consider whether the 'directory' option should also be limited to 'debug' only.
*
* @var array
* @since 3.0.163
*
*/
$config->moduleInstall = array(
'directory' => true, // allow install from ProcessWire modules directory?
'upload' => 'debug', // allow install by module file upload?
'download' => 'debug', // allow install by download from URL?
);
/**
* Substitute modules
@@ -1017,11 +1234,39 @@ $config->substituteModules = array(
* Note you can add any other properties to the wireMail array that are supported by WireMail settings
* like weve done with from, fromName and headers here. Any values set here become defaults for the
* WireMail module.
*
* Blacklist property
* ==================
* The blacklist property lets you specify email addresses, domains, partial host names or regular
* expressions that prevent sending to certain email addresses. This is demonstrated by example:
* ~~~~~
* // Example of blacklist definition
* $config->wireMail('blacklist', [
* 'email@domain.com', // blacklist this email address
* '@host.domain.com', // blacklist all emails ending with @host.domain.com
* '@domain.com', // blacklist all emails ending with @domain.com
* 'domain.com', // blacklist any email address ending with domain.com (would include mydomain.com too).
* '.domain.com', // blacklist any email address at any host off domain.com (domain.com, my.domain.com, but NOT mydomain.com).
* '/something/', // blacklist any email containing "something". PCRE regex assumed when "/" is used as opening/closing delimiter.
* '/.+@really\.bad\.com$/', // another example of using a PCRE regular expression (blocks all "@really.bad.com").
* ]);
*
* // Test out the blacklist
* $email = 'somebody@bad-domain.com';
* $result = $mail->isBlacklistEmail($email, [ 'why' => true ]);
* if($result === false) {
* echo "<p>Email address is not blacklisted</p>";
* } else {
* echo "<p>Email is blacklisted by rule: $result</p>";
* }
* ~~~~~
*
* #property string module Name of WireMail module to use or blank to auto-detect. (default='')
* #property string from Default from email address, when none provided at runtime. (default=$config->adminEmail)
* #property string fromName Default from name string, when none provided at runtime. (default='')
* #property string newline What to use for newline if different from RFC standard of "\r\n" (optional).
* #property array headers Default additional headers to send in email, key=value. (default=[])
* #property array blacklist Email blacklist addresses or rules. (default=[])
*
* @var array
*
@@ -1031,6 +1276,7 @@ $config->wireMail = array(
'from' => '',
'fromName' => '',
'headers' => array(),
'blacklist' => array()
);
/**
@@ -1084,8 +1330,23 @@ $config->pageEdit = array(
*/
$config->pageAdd = array(
'noSuggestTemplates' => '',
);
);
/**
* MarkupQA (markup quality assurance) optional settings
*
* This is used by Textarea Fieldtype when enabled and using content-type HTML.
*
* #property array ignorePaths Paths that begin with any of these will be ignored and left alone (not abstracted), i.e. [ '/a/b/', '/c/d/' ]
* #property bool debug Show debugging info to superusers? (default=false). May also be specified in $config->debugMarkupQA.
*
* @var array
*
*/
$config->markupQA = array(
// 'ignorePaths' => [ "/some/path/", "/another/path/", "/and/so/on/" ],
// 'debug' => true,
);
/*** 9. MISC ************************************************************************************/
@@ -1149,7 +1410,16 @@ $config->adminEmail = '';
* @var string
*
*/
$config->fatalErrorHTML = "<p style='background:crimson;color:white;padding:0.5em;font-family:sans-serif;'><b>{message}</b><br /><br /><small>{why}</small></p>";
$config->fatalErrorHTML = "<p style='background:crimson;color:white;padding:1em;font-family:sans-serif;font-size:16px;line-height:20px;clear:both'>{message}<br /><br /><small>{why}</small></p>";
/**
* HTTP code to send for fatal error (typically 500 or 503)
*
* @var int
* @since 3.0.151
*
*/
$config->fatalErrorCode = 500;
/**
* Settings for modal windows
@@ -1250,12 +1520,139 @@ $config->lazyPageChunkSize = 250;
* Uncomment and paste into /site/config.php if you want to use this
*
* $config->InputfieldWrapper = array(
* 'useDependencies' => true,
* 'requiredLabel' => 'Missing required value',
* 'useDependencies' => true,
* 'requiredLabel' => 'Missing required value',
* 'columnWidthSpacing' => 0,
* );
*
*/
/**
* statusFiles: Optional automatic include files when ProcessWire reaches each status/state
*
* **Using status/state files:**
*
* - These files must be located in your /site/ directory, i.e. /site/ready.php.
* - If a file does not exist, PW will see that and ignore it.
* - For any state/status files that you dont need, it is preferable to make them
* blank or remove them, so that PW does not have to check if the file exists.
* - It is also preferable that included files have the ProcessWire namespace, and it is
* required that a `boot` file (if used) have the Processwire namespace.
* - The `init` and `ready` status files are called *after* autoload modules have had their
* corresponding methods (init or ready) called. Use `_init` or `_ready` (with leading
* underscore) as the keys to specify files that should instead be called *before* the state.
* - While php files in /site/ are blocked from direct access by the .htaccess file, its
* also a good idea to add `if(!defined("PROCESSWIRE")) die();` at the top of them.
*
* **Status files and available API variables:**
*
* - Included files receive current available ProcessWire API variables, locally scoped.
* - In the `boot` state, only $wire, $hooks, $config, $classLoader API variables are available.
* - In the `init` state, all core API variables are available, except for $page.
* - In the `ready`, `render`, `download` and `finished` states, all API variables are available.
* - In the `failed` state, an unknown set of API variables is available, so use isset() to check.
*
* **Description of statuses/states:**
*
* The statuses occur in this order:
*
* 1. The `boot` status occurs in the ProcessWire class constructor, after PW has initialized its
* class loader, loaded its config files, and initialized its hooks system. One use for this
* state is to add static hooks to methods that might be executed between boot and init, which
* would be missed by the time the init state is reached.
*
* 2. The `init` status occurs after ProcessWire has loaded all of its core API variables, except
* that the $page API variable has not yet been determined. At this point, all of the autoload
* modules have had their init() methods called as well.
*
* - If you want to target the state right before modules.init() methods are called, (rather
* than after), you can use `initBefore`.
*
* 3. The `ready` status is similar to the init status except that the current Page is now known,
* and the $page API variable is known and available. The ready file is included after autoload
* modules have had their ready() methods called.
*
* - If you want to limit your ready file to just be called for front-end (site) requests,
* you can use `readySite`.
*
* - If you want to limit your ready file to just be called for back-end (admin) requests with
* a logged-in user, you can use `readyAdmin`.
*
* - If you want to target the state right before modules.ready() methods are called, (rather
* than after), you can use `readyBefore`. This is the same as the init state, except that
* the current $page is available.
*
* 4. The `render` status occurs when a page is about to be rendered and the status is retained
* until the page has finished rendering. If using a status file for this, in addition to API
* variables, it will also receive a `$contentType` variable that contains the matched content-
* type header, or it may be blank for text/html content-type, or if not yet known. If externally
* bootstrapped it will contain the value “external”.
*
* 5. The `download` status occurs when a file is about to be sent as a download to the user.
* It occurs *instead* of a render status (rather than in addition to). If using an include file
* for this, in addition to API vars, it will also receive a `$downloadFile` variable containing
* the filename requested to be downloaded (string).
*
* 6. The `finished` status occurs after the request has been delivered and output sent. ProcessWire
* performs various maintenance tasks during this state.
*
* 7. The `failed` status occurs when the request has failed due an Exception being thrown.
* In addition to available API vars, it also receives these variables:
*
* - `$exception` (\Exception): The Exception that triggered the failed status, this is most
* commonly going to be a Wire404Exception, WirePermissionException or WireException.
* - `$reason` (string): Additional text info about error, beyond $exception->getMessage().
* - `$failPage` (Page|NullPage): Page where the error occurred
*
* **Defining status files:**
*
* You can define all of the status files at once using an array like the one this documentation
* is for, but chances are you want to set one or two rather than all of them. You can do so like
* this, after creating /site/boot.php and site/failed.php files (as examples):
* ~~~~~
* $config->statusFiles('boot', 'boot.php');
* $config->statusFiles('failed', 'failed.php');
* ~~~~~
*
* @since 3.0.142
* @var array
*
* #property string boot File to include for 'boot' state.
* #property string init File to include for 'init' state.
* #property string initBefore File to include right before 'init' state, before modules.init().
* #property string ready File to include for API 'ready' state.
* #property string readyBefore File to include right before 'ready'state, before modules.ready().
* #property string readySite File to include for 'ready' state on front-end/site only.
* #property string readyAdmin File to include for 'ready' state on back-end/admin only.
* #property string download File to include for API 'download' state (sending file to user).
* #property string render File to include for the 'render' state (always called before).
* #property string finished File to include for the 'finished' state.
* #property string failed File to include for the 'failed' state.
*
*/
$config->statusFiles = array(
'boot' => '',
'initBefore' => '',
'init' => 'init.php',
'readyBefore' => '',
'ready' => 'ready.php',
'readySite' => '',
'readyAdmin' => '',
'render' => '',
'download' => '',
'finished' => 'finished.php',
'failed' => '',
);
/**
* adminTemplates: Names of templates that ProcessWire should consider exclusive to the admin
*
* @since 3.0.142
* @var array
*
*/
$config->adminTemplates = array('admin');
/*** 10. RUNTIME ********************************************************************************
*
@@ -1287,6 +1684,20 @@ $config->modal = false;
*/
$config->external = false;
/**
* status: Current runtime status (corresponding to ProcessWire::status* constants)
*
*/
$config->status = 0;
/**
* admin: TRUE when current request is for a logged-in user in the admin, FALSE when not, 0 when not yet known
*
* @since 3.0.142
*
*/
$config->admin = 0;
/**
* cli: This is automatically set to TRUE when PW is booted as a command line (non HTTP) script.
*
@@ -1299,7 +1710,6 @@ $config->cli = false;
*/
$config->version = '';
/**
* versionName: This is automatically populated with the current PW version name (i.e. 2.5.0 dev)
*
@@ -1310,11 +1720,21 @@ $config->versionName = '';
* column width spacing for inputfields: used by some admin themes to communicate to InputfieldWrapper
*
* Value is null, 0, or 1 or higher. This should be kept at null in this file.
*
* This can also be specified with $config->InputfieldWrapper('columnWidthSpacing', 0); (3.0.158+)
*
*/
$config->inputfieldColumnWidthSpacing = null;
$config->inputfieldColumnWidthSpacing = null;
/**
* Populated to contain <link rel='next|prev'.../> tags for document head
*
* This is populated only after a MarkupPagerNav::render() has rendered pagination and is
* otherwise null.
*
* $config->pagerHeadTags = '';
*
*/
/*** 11. SYSTEM *********************************************************************************
*

View File

@@ -10,7 +10,7 @@
* This file is licensed under the MIT license.
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
* @property int|string $version Current admin theme version
@@ -62,6 +62,14 @@ abstract class AdminTheme extends WireData implements Module {
*/
protected $bodyClasses = array();
/**
* General purpose classes indexed by name
*
* @var array
*
*/
protected $classes = array();
/**
* Extra markup regions
*
@@ -122,8 +130,10 @@ abstract class AdminTheme extends WireData implements Module {
$config = $this->wire('config');
/** @var Session $session */
$session = $this->wire('session');
/** @var User $user */
$user = $this->wire('user');
/** @var string $adminTheme */
$adminTheme = $this->wire('user')->admin_theme;
$adminTheme = $user->admin_theme;
if($adminTheme) {
// there is user specified admin theme
@@ -138,14 +148,14 @@ abstract class AdminTheme extends WireData implements Module {
// adjust $config adminThumbOptions[scale] for auto detect when requested
$o = $config->adminThumbOptions;
if($o && isset($o['scale']) && $o['scale'] === 1) {
$o['scale'] = $session->hidpi ? 0.5 : 1.0;
$o['scale'] = $session->get('hidpi') ? 0.5 : 1.0;
$config->adminThumbOptions = $o;
}
$config->js('modals', $config->modals);
if($session->hidpi) $this->addBodyClass('hidpi-device');
if($session->touch) $this->addBodyClass('touch-device');
if($session->get('hidpi')) $this->addBodyClass('hidpi-device');
if($session->get('touch')) $this->addBodyClass('touch-device');
$this->addBodyClass($this->className());
}
@@ -155,6 +165,25 @@ abstract class AdminTheme extends WireData implements Module {
return parent::get($key);
}
/**
* Get predefined translated label by key for labels shared among admin themes
*
* @param string $key
* @param string $val Value to return if label not available
* @return string
* @since 3.0.162
*
*/
public function getLabel($key, $val = '') {
switch($key) {
case 'search-help': return $this->_('help'); // Localized term to type for search-engine help (3+ chars)
case 'search-tip': return $this->_('Try “help”'); // // Search tip (indicating your translated “help” term)
case 'advanced-mode': return $this->_('Advanced Mode');
case 'debug': return $this->_('Debug');
}
return $val;
}
/**
* Returns true if this admin theme is the one that will be used for this request
*
@@ -232,7 +261,7 @@ abstract class AdminTheme extends WireData implements Module {
*
*/
public function addBodyClass($className) {
$this->bodyClasses[$className] = $className;
$this->addClass('body', $className);
}
/**
@@ -242,7 +271,75 @@ abstract class AdminTheme extends WireData implements Module {
*
*/
public function getBodyClass() {
return trim(implode(' ', $this->bodyClasses));
return $this->getClass('body');
}
/**
* Return class for a given named item or blank if none available
*
* Omit the first argument to return all classes in an array.
*
* @param string $name Tag or item name, i.e. “input”, or omit to return all defined [tags=classes]
* @param bool $getArray Specify true to return array of class name(s) rather than string (default=false). $tagName argument required.
* @return string|array Returns string or array of class names, or array of all [tags=classes] or $tagName argument is empty.
*
*/
public function getClass($name = '', $getArray = false) {
if(empty($name)) {
return $this->classes;
} else if(isset($this->classes[$name])) {
return $getArray ? explode(' ', $this->classes[$name]) : $this->classes[$name];
} else {
return $getArray ? array() : '';
}
}
/**
* Add class for given named item
*
* Default behavior is to merge classes if existing classes are already present for given item $name.
*
* #pw-internal
*
* @param string $name
* @param string|array $class
* @param bool $replace Specify true to replace any existing classes rather than merging them
*
*/
public function addClass($name, $class, $replace = false) {
if(is_array($class)) {
foreach($class as $c) {
$this->addClass($name, $c);
}
} else if(!$replace && isset($this->classes[$name])) {
$classes = $this->classes[$name];
if(strpos($classes, $class) !== false) {
// avoid re-adding class if it is already present
if(array_search($class, explode(' ', $classes)) !== false) return;
}
$this->classes[$name] = trim($classes . ' ' . ltrim($class));
} else {
$this->classes[$name] = trim($class);
}
}
/**
* Set classes for multiple tags
*
* #pw-internal
*
* @param array $classes Array of strings (class names) where keys are tag names
* @param bool $replace Specify true to replace any existing classes rather than merge them (default=false)
*
*/
public function setClasses(array $classes, $replace = false) {
if($replace || empty($this->classes)) {
$this->classes = $classes;
} else {
foreach($classes as $name => $class) {
$this->addClass($name, $class);
}
}
}
/**

View File

@@ -9,7 +9,7 @@
* @property bool $isSuperuser
* @property bool $isEditor
* @property bool $isLoggedIn
* @property bool $isModal
* @property bool|string $isModal
* @property bool|int $useAsLogin
* @method array getUserNavArray()
*
@@ -61,7 +61,11 @@ abstract class AdminThemeFramework extends AdminTheme {
public function __construct() {
parent::__construct();
$this->set('useAsLogin', false);
}
public function wired() {
$this->sanitizer = $this->wire('sanitizer');
parent::wired();
}
/**
@@ -270,6 +274,7 @@ abstract class AdminThemeFramework extends AdminTheme {
if($this->isModal === 'inline') $classes[] = 'modal-inline';
if($this->wire('input')->urlSegment1) $classes[] = 'hasUrlSegments';
if($process) $classes[] = $process->className();
if(!$this->isLoggedIn) $classes[] = 'pw-guest';
return implode(' ', $classes);
}
@@ -650,6 +655,7 @@ abstract class AdminThemeFramework extends AdminTheme {
$text = $notice->text;
$allowMarkup = $notice->flags & Notice::allowMarkup;
$groupByType = $options['groupByType'] && !($notice->flags & Notice::noGroup) && !($notice instanceof NoticeError);
if($allowMarkup) {
// leave $text alone
@@ -657,6 +663,7 @@ abstract class AdminThemeFramework extends AdminTheme {
// unencode + re-encode entities, just in case module already entity some or all of output
if(strpos($text, '&') !== false) $text = $this->sanitizer->unentities($text);
$text = $this->sanitizer->entities($text);
$text = nl2br($text);
}
if($notice instanceof NoticeError) {
@@ -693,7 +700,7 @@ abstract class AdminThemeFramework extends AdminTheme {
'{text}' => $text,
);
if($options['groupByType']) {
if($groupByType) {
if(!isset($noticesArray[$noticeType])) $noticesArray[$noticeType] = array();
$noticesArray[$noticeType][] = $replacements;
} else {

View File

@@ -210,7 +210,7 @@ class CacheFile extends Wire {
foreach($dir as $file) {
if($file->isDir() || $file->isDot()) continue;
//if(strpos($file->getFilename(), self::cacheFileExtension)) @unlink($file->getPathname());
if(self::isCacheFile($file->getPathname())) @unlink($file->getPathname());
if(self::isCacheFile($file->getPathname())) $this->wire('files')->unlink($file->getPathname());
}
return @rmdir($this->path);
@@ -223,7 +223,7 @@ class CacheFile extends Wire {
*
*/
protected function removeFilename($filename) {
@unlink($filename);
$this->wire('files')->unlink($filename);
}
@@ -250,7 +250,7 @@ class CacheFile extends Wire {
$numRemoved += self::removeAll($pathname, true);
} else if($file->isFile() && (self::isCacheFile($pathname) || ($file->getFilename() == self::globalExpireFilename))) {
if(unlink($pathname)) $numRemoved++;
if(wire('files')->unlink($pathname)) $numRemoved++;
}
}

View File

@@ -8,7 +8,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
* #pw-summary Holds ProcessWire configuration settings as defined in /wire/config.php and /site/config.php.
@@ -22,6 +22,7 @@
*
* @property bool $ajax If the current request is an ajax (asynchronous javascript) request, this is set to true. #pw-group-runtime
* @property bool|int $modal If the current request is in a modal window, this is set to a positive number. False if not. #pw-group-runtime
* @property bool|int $admin Is current request for logged-in user in admin? True, false, or 0 if not yet known. @since 3.0.142 #pw-group-runtime
* @property string $httpHost Current HTTP host name. #pw-group-HTTP-and-input
* @property bool $https If the current request is an HTTPS request, this is set to true. #pw-group-runtime
* @property string $version Current ProcessWire version string (i.e. "2.2.3") #pw-group-system #pw-group-runtime
@@ -30,7 +31,7 @@
* @property FilenameArray $styles Array used by ProcessWire admin to keep track of what stylesheet files its template should load. It will be blank otherwise. Feel free to use it for the same purpose in your own sites. #pw-group-runtime
* @property FilenameArray $scripts Array used by ProcessWire admin to keep track of what javascript files its template should load. It will be blank otherwise. Feel free to use it for the same purpose in your own sites. #pw-group-runtime
*
* @property Paths $urls Items from $config->urls reflect the http path one would use to load a given location in the web browser. URLs retrieved from $config->urls always end with a trailing slash. #pw-group-runtime
* @property Paths $urls Items from $config->urls reflect the http path one would use to load a given location in the web browser. URLs retrieved from $config->urls always end with a trailing slash. This is the same as the $urls API variable. #pw-group-runtime #pw-group-URLs
* @property Paths $paths All of what can be accessed from $config->urls can also be accessed from $config->paths, with one important difference: the returned value is the full disk path on the server. There are also a few items in $config->paths that aren't in $config->urls. All entries in $config->paths always end with a trailing slash. #pw-group-runtime
*
* @property string $templateExtension Default is 'php' #pw-group-template-files
@@ -39,10 +40,13 @@
*
* @property bool $protectCSRF Enables CSRF (cross site request forgery) protection on all PW forms, recommended for security. #pw-group-HTTP-and-input
*
* @property array $imageSizes Predefined image sizes (and options) indexed by name. See /wire/config.php for example. (3.0.151+) #pw-group-images
* @property array $imageSizerOptions Options to set image sizing defaults. Please see the /wire/config.php file for all options and defaults. #pw-group-images
* @property array $webpOptions Options for webp images. Please see /wire/config.php for all options. #pw-group-images
*
* @property bool $pagefileSecure When used, files in /site/assets/files/ will be protected with the same access as the page. Routines files through a passthrough script. #pw-group-files
* @property string $pagefileSecurePathPrefix One or more characters prefixed to the pathname of protected file dirs. This should be some prefix that the .htaccess file knows to block requests for. #pw-group-files
* @property string $pagefileUrlPrefix Deprecated property that was a string that prefixes filenames in PW URLs, becoming a shortcut to a pages files URL (do not use, here for backwards compatibility only). #pw-internal
*
* @property array $contentTypes Array of extensions and the associated MIME type for each (for template file output). #pw-group-template-files
* @property array $fileContentTypes Array of extensions and the associated MIME type for each (for file output). See /wire/config.php for details and defaults. #pw-group-files
@@ -61,28 +65,33 @@
* @property bool|callable $sessionAllow Are sessions allowed? Typically boolean true, unless provided a callable function that returns boolean. See /wire/config.php for an example. #pw-group-session
* @property int $sessionExpireSeconds How many seconds of inactivity before session expires? #pw-group-session
* @property bool $sessionChallenge Should login sessions have a challenge key? (for extra security, recommended) #pw-group-session
* @property bool $sessionFingerprint Should login sessions be tied to IP and user agent? 0 or false: Fingerprint off. 1 or true: Fingerprint on with default/recommended setting (currently 10). 2: Fingerprint only the remote IP. 4: Fingerprint only the forwarded/client IP (can be spoofed). 8: Fingerprint only the useragent. 10: Fingerprint the remote IP and useragent (default). 12: Fingerprint the forwarded/client IP and useragent. 14: Fingerprint the remote IP, forwarded/client IP and useragent (all). #pw-group-session
* @property int|bool $sessionFingerprint Should login sessions be tied to IP and user agent? 0 or false: Fingerprint off. 1 or true: Fingerprint on with default/recommended setting (currently 10). 2: Fingerprint only the remote IP. 4: Fingerprint only the forwarded/client IP (can be spoofed). 8: Fingerprint only the useragent. 10: Fingerprint the remote IP and useragent (default). 12: Fingerprint the forwarded/client IP and useragent. 14: Fingerprint the remote IP, forwarded/client IP and useragent (all). #pw-group-session
* @property int $sessionHistory Number of session entries to keep (default=0, which means off). #pw-group-session
* @property string $sessionForceIP Force the client IP address returned by $session->getIP() to be this rather than auto-detect (useful with load balancer). Use for setting value only. #pw-group-session
* @property array $loginDisabledRoles Array of role name(s) or ID(s) of roles where login is disallowed. #pw-group-session
*
* @property string $prependTemplateFile PHP file in /site/templates/ that will be loaded before each page's template file (default=none) #pw-group-template-files
* @property string $appendTemplateFile PHP file in /site/templates/ that will be loaded after each page's template file (default=none) #pw-group-template-files
* @property bool $templateCompile Allow use of compiled templates? #pw-group-template-files
*
* @property string $uploadUnzipCommand Shell command to unzip archives, used by WireUpload class. #pw-group-deprecated
* @property string $uploadUnzipCommand Shell command to unzip archives, used by WireUpload class (deprecated, no longer in use). #pw-internal
* @property string $uploadTmpDir Optionally override PHP's upload_tmp_dir with your own. Should include a trailing slash. #pw-group-files
* @property string $uploadBadExtensions Space separated list of file extensions that are always disallowed from uploads. #pw-group-files
*
* @property string $adminEmail Email address to send fatal error notifications to. #pw-group-system
* @property array $adminTemplates Names of templates that ProcessWire should consider exclusive to the admin. #pw-group-system @since 3.0.142
*
* @property string $pageNameCharset Character set for page names, must be 'ascii' (default, lowercase) or 'UTF8' (uppercase). #pw-group-URLs
* @property string $pageNameWhitelist Whitelist of characters allowed in UTF8 page names. #pw-group-URLs
* @property string $pageNameUntitled Name to use for untitled pages (default="untitled"). #pw-group-URLs
* @property string $pageNumUrlPrefix Prefix used for pagination URLs. Default is "page", resulting in "/page1", "/page2", etc. #pw-group-URLs
* @property array $pageNumUrlPrefixes Multiple prefixes that may be used for detecting pagination (internal use, for multi-language) #pw-group-URLs
* @property int $maxUrlSegments Maximum number of extra stacked URL segments allowed in a page's URL (including page numbers) #pw-group-URLs
* @property int $maxUrlSegmentLength Maximum length of any individual URL segment (default=128). #pw-group-URLs
* @property int $maxUrlDepth Maximum URL/path slashes (depth) for request URLs. (Min=10, Max=60) #pw-group-URLs
* @property string $wireInputOrder Order that variables with the $input API var are handled when you access $input->var. #pw-group-HTTP-and-input
* @property bool $wireInputLazy Specify true for $input API var to load input data in a lazy fashion and potentially use less memory. Default is false. #pw-group-HTTP-and-input
* @property array $cookieOptions Options for setting cookies from $input->cookie #pw-group-HTTP-and-input
*
* @property bool $advanced Special mode for ProcessWire system development. Not recommended for regular site development or production use. #pw-group-system
* @property bool $demo Special mode for demonstration use that causes POST requests to be disabled. Applies to core, but may not be safe with 3rd party modules. #pw-group-system
@@ -95,6 +104,7 @@
* @property array $adminThumbOptions Admin thumbnail image options #pw-group-images
* @property array $httpHosts HTTP hosts For added security, specify the host names ProcessWire should recognize. #pw-group-HTTP-and-input
* @property int $maxPageNum Maximum number of recognized paginations #pw-group-URLs
* @property bool|string|array $noHTTPS When boolean true, pages requiring HTTPS will not enforce it (useful for dev environments). May also specify hostname (string) or hostnames (array) to disable HTTPS for. #pw-group-HTTP-and-input
*
* @property string $dbHost Database host #pw-group-database
* @property string $dbName Database name #pw-group-database
@@ -120,17 +130,21 @@
* @property string $moduleServiceKey API key for modules web service #pw-group-modules
* @property bool $moduleCompile Allow use of compiled modules? #pw-group-modules
* @property array $wireMail Default WireMail module settings. #pw-group-modules
* @property array $moduleInstall Admin module install options you allow. #pw-group-modules
*
* @property array $substituteModules Associative array with names of substitute modules for when requested module doesn't exist #pw-group-modules
* @property array $logs Additional core logs to keep #pw-group-admin
* @property bool $logIP Include IP address in logs, when applicable? #pw-group-admin
* @property string $defaultAdminTheme Default admin theme: AdminThemeDefault or AdminThemeReno #pw-group-admin
* @property string $fatalErrorHTML HTML used for fatal error messages in HTTP mode. #pw-group-system
* @property int $fatalErrorCode HTTP code to send on fatal error (typically 500 or 503). #pw-group-system
* @property array $modals Settings for modal windows #pw-group-admin
* @property array $preloadCacheNames Cache names to preload at beginning of request #pw-group-system
* @property bool $allowExceptions Allow Exceptions to propagate? (default=false, specify true only if you implement your own exception handler) #pw-group-system
* @property bool $usePoweredBy Use the x-powered-by header? Set to false to disable. #pw-group-system
* @property bool $useFunctionsAPI Allow most API variables to be accessed as functions? (see /wire/core/FunctionsAPI.php) #pw-group-system
* @property bool $useMarkupRegions Enable support for front-end markup regions? #pw-group-system
* @property bool $usePageClasses Use custom Page classes in `/site/classes/[TemplateName]Page.php`? #pw-group-system #since 3.0.152
* @property int $lazyPageChunkSize Chunk size for for $pages->findMany() calls. #pw-group-system
*
* @property string $userAuthSalt Salt generated at install time to be used as a secondary/non-database salt for the password system. #pw-group-session
@@ -141,7 +155,12 @@
* @property bool $cli This is automatically set to TRUE when PW is booted as a command line (non HTTP) script. #pw-group-runtime
* @property string $versionName This is automatically populated with the current PW version name (i.e. 2.5.0 dev) #pw-group-runtime
* @property int $inputfieldColumnWidthSpacing Used by some admin themes to commmunicate to InputfieldWrapper at runtime. #pw-internal
* @property array InputfieldWrapper Settings specific to InputfieldWrapper class #pw-internal
* @property bool $debugMarkupQA Set to true to make the MarkupQA class report verbose debugging messages (to superusers). #pw-internal
* @property array $markupQA Optional settings for MarkupQA class used by FieldtypeTextarea module. #pw-group-modules
* @property string|null $pagerHeadTags Populated at runtime to contain `<link rel=prev|next />` tags for document head, after pagination has been rendered by MarkupPagerNav module. #pw-group-runtime
* @property array $statusFiles File inclusions for ProcessWires runtime statuses/states. #pw-group-system @since 3.0.142
* @property int $status Value of current system status/state corresponding to ProcessWire::status* constants. #pw-internal
*
* @property int $rootPageID Page ID of homepage (usually 1) #pw-group-system-IDs
* @property int $adminRootPageID Page ID of admin root page #pw-group-system-IDs
@@ -195,15 +214,161 @@ class Config extends WireData {
}
/**
* Alias for the url() method
* Get URL for requested resource or module or get all URLs if no argument
*
* #pw-internal
*
* @param string|Wire $for Predefined ProcessWire URLs property or module name
* @return null|string
* @return null|string|Paths
* @since 3.0.130
*
*/
public function urls($for) { return $this->url($for); }
public function urls($for = '') {
return $for === '' ? $this->urls : $this->url($for);
}
/**
* Given a directory to a named location, updates $config->paths and $config->urls for it
*
* - Paths relative to PW installation root should omit the leading slash, i.e. use `site/templates/` and NOT `/site/templates/`.
*
* - If specifying just the `$dir` argument, it updates both `$config->paths` and `$config->urls` for it.
*
* - If specifying both `$dir` and `$url` arguments, then `$dir` refers to `$config->paths` and `$url` refers to `$config->urls`.
*
* - The `$for` argument can be: `cache`, `logs`, `files`, `tmp`, `templates`, or one of your own. Other named locations may
* also work, but since they can potentially be used before PWs “ready” state, they may not be reliable.
*
* - **Warning:** anything that changes a system URL may make the URL no longer have the protection of the root .htaccess file.
* As a result, if you modify system URLs for anything on a live server, you should also update your .htaccess file to
* reflect your changes (while leaving existing rules for original URL in place).
*
* The following example would be in /site/init.php or /site/ready.php (or equivalent module method). In this example we
* are changing the location (path and URL) of our /site/templates/ to use a new version of the files in /site/dev-templates/
* so that we can test them out with user 'karen', while all other users on the site get our regular templates.
* ~~~~~
* // change templates path and URL to /site/dev-templates/ when user name is 'karen'
* if($user->name == 'karen') {
* $config->setLocation('templates', 'site/dev-templates/');
* }
* ~~~~~
*
* @param string $for Named location from `$config->paths` or `$config->urls`, one of: `cache`, `logs`, `files`, `tmp`, `templates`, or your own.
* @param string $dir Directory or URL to the location. Should be either a path or URL relative to current installation root (recommended),
* or an absolute disk path that resolves somewhere in current installation root. If specifying an absolute path outside of the installation
* root, then youll also want to provide the $url argument since PW wont know it. You may also specify a blank string for this argument
* if you only want to set the $url argument.
* @param string|bool $url If the $dir argument represents both the path and URL relative to site root, you can omit this argument.
* If path and URL cannot be derived from one another, or you only want to modify the $url (leaving $dir blank), you
* can specify the URL in this argument. Specify boolean false if you only want to set the $dir (path) and not detect the $url from it.
* @return self
* @throws WireException If request cannot be accommodated
* @since 3.0.141
*
*/
public function setLocation($for, $dir, $url = '') {
if($for === 'root') throw new WireException('Root path can only be changed at boot');
if(!empty($dir)) {
$rootPath = $this->paths->get('root');
// make sure path uses unix-style slashes
$dir = Paths::normalizeSeparators($dir);
// if given path is inclusive of root path, make path relative to site root
if(strpos($dir, $rootPath) === 0) $dir = substr($dir, strlen($rootPath));
// ensure trailing slash
if(substr($dir, -1) !== '/') $dir .= '/';
}
// now determine the URL to set
if($url === false) {
// arguments say to skip setting URL
} else if(empty($url)) {
// URL and path are the same relative to site root
if(!empty($dir)) $url = $dir;
} else {
// given a custom URL
$rootUrl = $this->urls->get('root');
// if URL begins at PW installation root, remove the root part of the URL
if(strpos($url, $rootUrl) === 0) $url = substr($url, strlen($rootUrl));
// ensure trailing slash
if(substr($url, -1) !== '/' && strpos($url, '?') === false && strpos($url, '#') === false) $url .= '/';
}
if(!empty($dir)) $this->paths->set($for, $dir);
if(!empty($url)) $this->urls->set($for, $url);
return $this;
}
/**
* Change or set just the server disk path for the named location (leaving URL as-is)
*
* - If you want to update both disk path and URL at the same time, or if URL and path are going to be the same relative to
* installation root, use the `setLocation()` method instead.
*
* - Paths relative to PW installation root should omit the leading slash, i.e. use `site/templates/` and NOT `/site/templates/`.
*
* - The `$for` argument can be: `cache`, `logs`, `files`, `tmp`, `templates`, or one of your own. Other named locations may
* also work, but since they can potentially be used before PWs “ready” state, they may not be reliable.
*
* @param string $for Named location from `$config->paths`, one of: `cache`, `logs`, `files`, `tmp`, `templates`, or your own.
* @param string $path Path relative to PW installation root (no leading slash), or absolute path if not.
* @return self
* @throws WireException
* @since 3.0.141
*
*/
public function setPath($for, $path) {
return $this->setLocation($for, $path, false);
}
/**
* Change or set just the URL for the named location (leaving server disk path as-is)
*
* - If you want to update both disk path and URL at the same time, or if URL and path are going to be the same relative to
* installation root, use the `setLocation()` method instead.
*
* - Paths relative to PW installation root should omit the leading slash, i.e. use `site/templates/` and NOT `/site/templates/`.
*
* - The `$for` argument can be: `cache`, `logs`, `files`, `tmp`, `templates`, or one of your own. Other named locations may
* also work, but since they can potentially be used before PWs “ready” state, they may not be reliable.
*
* - **Warning:** anything that changes a system URL may make the URL no longer have the protection of the root .htaccess file.
* As a result, if you modify system URLs for anything on a live server, you should also update your .htaccess file to
* reflect your changes (while leaving existing rules for original URL in place).
*
* The following examples would go in /site/ready.php.
*
* Lets say we created a symbolic link in our web root `/tiedostot/` (Finnish for “files”) that points to /site/assets/files/.
* We want all of our file URLs to appear as “/tiedostot/1234/img.jpg” rather than “/site/assets/files/1234/img.jpg”. We would
* change the URL for ProcessWires `$config->urls->files` to point there like this example below. (Please also see warning above)
* ~~~~~
* if($page->template != 'admin') {
* $config->setUrl('files', 'tiedostot/');
* }
* ~~~~~
* In this next example, we are changing all of our file URLs on the front-end to point a cookieless subdomain that maps all
* requests to the root path of https://files.domain.com to /site/assets/files/. The example works for CDNs as well.
* ~~~~~
* if($page->template != 'admin) {
* $config->setUrl('files', 'https://files.domain.com/');
* }
* ~~~~~
*
* @param string $for Named location from `$config->urls`, one of: `cache`, `logs`, `files`, `tmp`, `templates`, or your own.
* @param string $url URL relative to PW installation root (no leading slash) or absolute URL if not (optionally including scheme and domain).
* @return self
* @throws WireException
* @since 3.0.141
*
*/
public function setUrl($for, $url) {
return $this->setLocation($for, '', $url);
}
/**
* Get disk path for requested resource or module
@@ -227,15 +392,18 @@ class Config extends WireData {
}
/**
* Alias for the path() method
* Get disk path for requested resource or module or get all paths if no argument
*
* #pw-internal
*
* @param string $for Predefined ProcessWire paths property or module name
* @return null|string
* @return null|string|Paths
* @since 3.0.130
*
*/
public function paths($for) { return $this->path($for); }
public function paths($for = '') {
return $for === '' ? $this->paths : $this->path($for);
}
/**
* List of config keys that are also exported in javascript
@@ -402,5 +570,98 @@ class Config extends WireData {
return $this;
}
/**
* Return true if current PHP version is equal to or newer than the given version
*
* ~~~~~
* if($config->phpVersion('7.0.0')) {
* // PHP version is 7.x
* }
* ~~~~~
*
* #pw-group-tools
*
* @param string|null $minVersion
* @return bool
* @since 3.0.101
*
*/
public function phpVersion($minVersion) {
return version_compare(PHP_VERSION, $minVersion) >= 0;
}
/**
* Check if current ProcessWire version is equal to or newer than given version, or return current version
*
* If no version argument is given, it simply returns the current ProcessWire version (3.0.130+)
*
* ~~~~~
* if($config->version('3.0.100')) {
* // ProcessWire version is 3.0.100 or newer
* }
* ~~~~~
*
* #pw-group-tools
*
* @param string $minVersion Specify version string if you want to compare against current version
* @return bool|string Returns current version if no argument given (3.0.130+), OR boolean if given a version argument:
* - If given version is older than current, returns false.
* - If given version is equal to or newer than current, returns true.
* @since 3.0.106 with no-argument behavior added in 3.0.130
*
*/
public function version($minVersion = '') {
if($minVersion === '') return $this->version;
return version_compare($this->version, $minVersion) >= 0;
}
/**
* Was this ProcessWire installation installed after a particular date?
*
* #pw-group-tools
*
* @param int|string $date Unix timestamp or strtotime() compatible date string
* @return bool
* @see Config::installedBefore(), Config::installed
* @since 3.0.129
*
*/
public function installedAfter($date) {
if(!ctype_digit("$date")) $date = strtotime($date);
return $this->installed > $date;
}
/**
* Was this ProcessWire installation installed before a particular date?
*
* #pw-group-tools
*
* @param int|string $date Unix timestamp or strtotime() compatible date string
* @return bool
* @see Config::installedAfter(), Config::installed
* @since 3.0.129
*
*/
public function installedBefore($date) {
if(!ctype_digit("$date")) $date = strtotime($date);
return $this->installed < $date;
}
/**
* Set the current ProcessWire instance for this object (PW 3.0)
*
* #pw-internal
*
* @param ProcessWire $wire
*
*/
public function setWire(ProcessWire $wire) {
parent::setWire($wire);
$paths = $this->paths;
if($paths) $paths->setWire($wire);
$urls = $this->urls;
if($urls) $urls->setWire($wire);
}
}

View File

@@ -10,7 +10,7 @@
* of what other methods/objects have done to it. It also means being able
* to build a complex query without worrying about correct syntax placement.
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* This file is licensed under the MIT license
@@ -18,7 +18,12 @@
*
* @property array $where
* @property array $bindValues
* @properety array $bindIndex
* @property array $bindKeys
* @property array $bindOptions
* @property string $query
* @property string $sql
*
* @method $this where($sql, array $params = array())
*
*/
abstract class DatabaseQuery extends WireData {
@@ -32,63 +37,397 @@ abstract class DatabaseQuery extends WireData {
protected $bindValues = array();
/**
* Index of bound values per originating method
* Bound parameter types of name => \PDO::PARAM_* type constant
*
* Indexed by originating method, with values as the bound parameter names as in $bindValues.
* This is populated by the setupBindValues() method
* Populated only when a type is provided to the bindValue() call
*
* @var array
* @var array
*
*/
protected $bindIndex = array();
protected $bindTypes = array();
/**
* @var array
*
*/
protected $bindKeys = array();
/**
* Method names for building DB queries
*
* @var array
*
*/
protected $queryMethods = array();
/**
* @var int
*
*/
protected $instanceNum = 0;
/**
* @var int
*
*/
protected $keyNum = 0;
/**
* @var array
*
*/
protected $bindOptions = array(
'prefix' => 'pw', // prefix for auto-generated global keys
'suffix' => 'X', // 1-character suffix for auto-generated keys
'global' => false // globally unique among all bind keys in all instances?
);
/**
* @var int
*
*/
static $numInstances = 0;
/**
* Construct
*
*/
public function __construct() {
self::$numInstances++;
$this->instanceNum = self::$numInstances;
$this->addQueryMethod('where', " \nWHERE ", " \nAND ");
parent::__construct();
}
/**
* Add a query method
*
* #pw-internal
*
* @param string $name
* @param string $prepend Prepend first statement with this
* @param string $split Split multiple statements with this
* @param string $append Append this to last statement (if needed)
* @since 3.0.157
*
*/
protected function addQueryMethod($name, $prepend = '', $split = '', $append = '') {
$this->queryMethods[$name] = array($prepend, $split, $append);
$this->set($name, array());
}
/**
* Get or set a bind option
*
* @param string|bool $optionName One of 'prefix' or 'global', boolean true to get/set all
* @param null|int|string|array $optionValue Omit when getting, Specify option value to set, or array when setting all
* @return string|int|array
* @since 3.0.157
*
*/
public function bindOption($optionName, $optionValue = null) {
if($optionName === true) {
if(is_array($optionValue)) $this->bindOptions = array_merge($this->bindOptions, $optionValue);
return $this->bindOptions;
} else if($optionValue !== null) {
$this->bindOptions[$optionName] = $optionValue;
}
return isset($this->bindOptions[$optionName]) ? $this->bindOptions[$optionName] : null;
}
/**
* Bind a parameter value
*
* @param string $key Parameter name
* @param mixed $value Parameter value
* @param null|int|string Optionally specify value type: string, int, bool, null or PDO::PARAM_* constant.
* @return $this
*
*/
public function bindValue($key, $value) {
public function bindValue($key, $value, $type = null) {
if(strpos($key, ':') !== 0) $key = ":$key";
$this->bindValues[$key] = $value;
$this->bindKeys[$key] = $key;
if($type !== null) $this->setBindType($key, $type);
return $this;
}
/**
* Get bound parameter values, optionally for a specific method call
* Bind value and get unique key that refers to it in one step
*
* @param string $method
* @return array
* @param string|int|float $value
* @param null|int|string $type
* @return string
* @since 3.0.157
*
*/
public function getBindValues($method = '') {
if(empty($method)) return $this->bindValues;
if(!isset($this->bindIndex[$method])) return array();
$names = $this->bindIndex[$method];
$values = array();
foreach($names as $name) {
$values[$name] = $this->bindValues[$name];
public function bindValueGetKey($value, $type = null) {
$key = $this->getUniqueBindKey(array('value' => $value));
$this->bindValue($key, $value, $type);
return $key;
}
/**
* Get or set multiple parameter values
*
* #pw-internal
*
* @param array|null $bindValues Omit to get or specify array to set
* @return $this|array Returns array when getting or $this when setting
* @since 3.0.156
*
*/
public function bindValues($bindValues = null) {
if(is_array($bindValues)) {
foreach($bindValues as $key => $value) {
$this->bindValue($key, $value);
}
return $this;
} else {
return $this->bindValues;
}
return $values;
}
/**
* Set bind type
*
* #pw-internal
*
* @param string $key
* @param int|string $type
*
*/
public function setBindType($key, $type) {
if(is_int($type) || ctype_digit("$type")) {
$this->bindTypes[$key] = (int) $type;
}
switch(strtolower(substr($type, 0, 3))) {
case 'str': $type = \PDO::PARAM_STR; break;
case 'int': $type = \PDO::PARAM_INT; break;
case 'boo': $type = \PDO::PARAM_BOOL; break;
case 'nul': $type = \PDO::PARAM_NULL; break;
default: $type = null;
}
if($type !== null) $this->bindTypes[$key] = $type;
}
/**
* Get or set all bind types
*
* #pw-internal
*
* @param array|null $bindTypes Omit to get, or specify associative array of [ ":bindKey" => int ] to set
* @return array|$this Returns array when getting or $this when setting
* @since 3.0.157
*
*/
public function bindTypes($bindTypes = null) {
if(is_array($bindTypes)) {
$this->bindTypes = array_merge($this->bindTypes, $bindTypes); // set
return $this;
}
return $this->bindTypes; // get
}
/**
* Get a unique key to use for bind value
*
* Note if you given a `key` option, it will only be used if it is determined unique,
* otherwise itll auto-generate one. When using your specified key, it is the only
* option that applies, unless it is not unique and the method has to auto-generate one.
*
* @param array $options
* - `key` (string): Preferred bind key, or omit (blank) to auto-generate (digit only keys not accepted)
* - `value` (string|int): Value to use as part of the generated key
* - `prefix` (string): Prefix to override default
* - `global` (bool): Require globally unique among all instances?
* @return string Returns bind key/name in format ":name" (with leading colon)
* @since 3.0.156
*
*/
public function getUniqueBindKey(array $options = array()) {
if(empty($options['key'])) {
// auto-generate key
$key = ':';
$prefix = (isset($options['prefix']) ? $options['prefix'] : $this->bindOptions['prefix']);
$suffix = isset($option['suffix']) && $options['suffix'] ? $options['suffix'] : $this->bindOptions['suffix'];
$value = isset($options['value']) ? $options['value'] : null;
$global = isset($options['global']) ? $options['global'] : $this->bindOptions['global'];
if($global) $key .= $prefix . $this->instanceNum;
if($value !== null) {
if(is_int($value)) {
$key .= "i";
} else if(is_string($value)) {
$key .= "s";
} else if(is_array($value)) {
$key .= "a";
} else {
$key .= "o";
}
} else if($prefix && !$global) {
$key .= $prefix;
} else {
$key .= "v";
}
$n = 0;
$k = $key;
$key = $k . '0' . $suffix;
while(isset($this->bindKeys[$key]) && ++$n) {
$key = $k . $n . $suffix;
}
} else {
// provided key, make sure it is valid and unique (this part is not typically used)
$key = ltrim($options['key'], ':') . 'X';
if(!ctype_alnum(str_replace('_', '', $key))) $key = $this->wire('database')->escapeCol($key);
if(empty($key) || ctype_digit($key[0]) || isset($this->bindKeys[":$key"])) {
// if key is not valid, then auto-generate one instead
unset($options['key']);
$key = $this->getUniqueBindKey($options);
} else {
$key = ":$key";
}
}
$this->bindKeys[$key] = $key;
return $key;
}
/**
* Get bind values, with options
*
* - If given a \PDOStatement or DatabaseQuery, it is assumed to be the `query` option.
* - When copying, you may prefer to use the copyBindValuesTo() method instead (more readable).
*
* Note: The $options argument was added in 3.0.156, prior to this it was a $method argument,
* which was never used so has been removed.
*
* @param string|\PDOStatement|DatabaseQuery|array $options Optionally specify an option:
* - `query` (\PDOStatement|DatabaseQuery): Copy bind values to this query object (default=null)
* - `count` (bool): Get a count of values rather than array of values (default=false) 3.0.157+
* - `inSQL` (string): Only get bind values referenced in this given SQL statement
* @return array|int Returns one of the following:
* - Associative array in format [ ":column" => "value" ] where each "value" is int, string or NULL.
* - if `count` option specified as true then it returns a count of values instead.
*
*/
public function getBindValues($options = array()) {
$defaults = array(
'query' => is_object($options) ? $options : null,
'count' => false,
'inSQL' => '',
);
$options = is_array($options) ? array_merge($defaults, $options) : $defaults;
$query = $options['query'];
$bindValues = $this->bindValues;
if(!empty($options['inSQL'])) {
foreach(array_keys($bindValues) as $bindKey) {
if(strpos($options['inSQL'], $bindKey) === false) {
unset($bindValues[$bindKey]);
} else if(!preg_match('/' . $bindKey . '\b/', $options['inSQL'])) {
unset($bindValues[$bindKey]);
}
}
}
if(is_object($query)) {
if($query instanceof \PDOStatement) {
foreach($bindValues as $k => $v) {
$type = isset($this->bindTypes[$k]) ? $this->bindTypes[$k] : $this->pdoParamType($v);
$query->bindValue($k, $v, $type);
}
} else if($query instanceof DatabaseQuery && $query !== $this) {
$query->bindValues($bindValues);
$query->bindTypes($this->bindTypes);
}
}
return $options['count'] ? count($bindValues) : $bindValues;
}
/**
* Copy bind values from this query to another given DatabaseQuery or \PDOStatement
*
* This is a more readable interface to the getBindValues() method and does the same
* thing as passing a DatabaseQuery or PDOStatement to the getBindValues() method.
*
* @param DatabaseQuery|\PDOStatement $query
* @param array $options Additional options
* - `inSQL` (string): Only copy bind values that are referenced in given SQL string
* @return int Number of bind values that were copied
* @since 3.0.157
*
*/
public function copyBindValuesTo($query, array $options = array()) {
$options['query'] = $query;
if(!isset($options['count'])) $options['count'] = true;
return $this->getBindValues($options);
}
/**
* Copy queries from this DatabaseQuery to another DatabaseQuery
*
* If you want to copy bind values you should also call copyBindValuesTo($query) afterwards.
*
* @param DatabaseQuery $query Query to copy data to
* @param array $methods Optionally specify the names of methods to copy, otherwise all are copied
* @return int Total items copied
* @since 3.0.157
*
*/
public function copyTo(DatabaseQuery $query, array $methods = array()) {
$numCopied = 0;
if($query === $this) return 0;
if(!count($methods)) $methods = array_keys($this->queryMethods);
foreach($methods as $method) {
if($method === 'bindValues') continue;
$fromValues = $this->$method; // array
if(!is_array($fromValues)) continue; // nothing to import
$toValues = $query->$method;
if(!is_array($toValues)) continue; // query does not have this method
$query->set($method, array_merge($toValues, $fromValues));
$numCopied += count($fromValues);
}
return $numCopied;
}
/**
* Enables calling the various parts of a query as functions for a fluent interface.
*
* Examples (all in context of DatabaseQuerySelect):
* ~~~~~
* $query->select("id")->from("mytable")->orderby("name");
* ~~~~~
* To bind one or more named parameters, specify associative array as second argument:
* ~~~~~
* $query->where("name=:name", [ ':name' => $page->name ]);
* ~~~~~
* To bind one or more implied parameters, use question marks and specify regular array:
* ~~~~~
* $query->where("name=?, id=?", [ $page->name, $page->id ]);
* ~~~~~
* When there is only one implied parameter, specifying an array is optional:
* ~~~~~
* $query->where("name=?", $page->name);
* ~~~~~
*
* $query->select("id")->from("mytable")->orderby("name");
*
* To bind parameters, specify associative array as second argument:
*
* $query->where("name=:name", array(':name' => $page->name));
*
* To import query/method and bound values from another DatabaseQuery:
*
* $query->select($anotherQuery);
*
* The "select" may be any method supported by the class.
* The "select" or "where" methods above may be any method supported by the class.
* Implied parameters (using "?") was added in 3.0.157.
*
* @param string $method
* @param array $args
@@ -96,83 +435,142 @@ abstract class DatabaseQuery extends WireData {
*
*/
public function __call($method, $args) {
if(!$this->has($method)) return parent::__call($method, $args);
$curValue = $this->get($method);
$value = $args[0];
if(empty($value)) return $this;
// if(!$this->has($method)) return parent::__call($method, $args);
if(!isset($this->queryMethods[$method])) return parent::__call($method, $args);
if(!count($args)) return $this;
$curValue = $this->get($method);
if(!is_array($curValue)) $curValue = array();
$value = $args[0];
if(is_object($value) && $value instanceof DatabaseQuery) {
// if we've been given another DatabaseQuery, load from it's $method
// if we've been given another DatabaseQuery, load from its $method
// note that if using bindValues you should also copy them separately
// behavior deprecated in 3.l0.157+, please use the copyTo() method instead
/** @var DatabaseQuery $query */
$query = $value;
$value = $query->$method;
$value = $query->$method; // array
if(!is_array($value) || !count($value)) return $this; // nothing to import
$params = $query->getBindValues($method);
} else {
$params = isset($args[1]) && is_array($args[1]) ? $args[1] : null;
}
if(!empty($params)) {
$value = $this->setupBindValues($value, $params, $method);
} else if(is_string($value)) {
// value is SQL string, number or array
$params = isset($args[1]) ? $args[1] : null;
if($params !== null && !is_array($params)) $params = array($params);
if(is_array($params) && count($params)) $value = $this->methodBindValues($value, $params);
} else if(!empty($args[1])) {
throw new WireException("Argument error in $this::$method('string required here when using bind values', [ bind values ])");
}
if(is_array($value)) {
$curValue = array_merge($curValue, $value);
} else {
$curValue[] = trim($value, ", ");
}
$this->set($method, $curValue);
return $this;
}
/**
* Setup bound parameters for the given query, returning an updated $value if any renames needed to be made
* Setup bind params for the given SQL provided to method call
*
* @param string|array $sql
* @param array $params
* @param string $method Method name that the bound values are for
* This is only used when params are provided as part of a method call like:
* ~~~~~
* $query->where("foo=:bar", [ ":bar" => "baz" ]); // named
* $query->where("foo=?", [ "baz" ]); // implied
* ~~~~~
*
* #pw-internal
*
* @param string $sql
* @param array $values Bind values
* @return string
* @throws WireException
*
*/
public function setupBindValues($sql, array $params, $method) {
foreach($params as $name => $value) {
if(strpos($name, ':') !== 0) $name = ":$name";
$newName = $name;
$n = 0;
while(isset($this->bindValues[$newName])) {
$newName = $name . (++$n);
}
if($n) {
if(is_array($sql)) {
foreach($sql as $k => $v) {
if(strpos($v, $name) === false) continue;
$sql[$k] = preg_replace('/' . $name . '\b/', $newName, $v);
}
} else {
$sql = preg_replace('/' . $name . '\b/', $newName, $sql);
protected function methodBindValues($sql, array $values) {
$numImplied = 0;
$numNamed = 0;
$_sql = $sql;
if(!is_string($sql)) {
throw new WireException('methodBindValues requires a string for $sql argument');
}
foreach($values as $name => $value) {
if(is_int($name)) {
// implied parameter
$numImplied++;
if(strpos($sql, '?') === false) {
throw new WireException("No place for given param $name in: $_sql");
}
do {
$name = $this->getUniqueBindKey(array('value' => $value));
} while(strpos($sql, $name) !== false); // highly unlikely, but just in case
list($a, $b) = explode('?', $sql, 2);
$sql = $a . $name . $b;
} else {
// named parameter
$numNamed++;
if(strpos($name, ':') !== 0) $name = ":$name";
if(strpos($sql, $name) === false) {
throw new WireException("Param $name not found in: $_sql");
}
$name = $newName;
}
$this->bindValue($name, $value);
if(!isset($this->bindIndex[$method])) $this->bindIndex[$method] = array();
$this->bindIndex[$method][] = $name;
}
return $sql;
if($numImplied && strpos($sql, '?') !== false) {
throw new WireException("Missing implied “?” param in: $_sql");
} else if($numImplied && $numNamed) {
throw new WireException("You may not mix named and implied params in: $_sql");
}
return $sql;
}
/**
* @param string $key
* @param mixed $value
*
*/
public function __set($key, $value) {
if(is_array($this->$key)) $this->__call($key, array($value));
}
/**
* @param string $key
* @return array|mixed|null
*
*/
public function __get($key) {
if($key == 'query') return $this->getQuery();
else if($key == 'bindValues') return $this->bindValues;
else if($key == 'bindIndex') return $this->bindIndex;
else return parent::__get($key);
if($key === 'query' || $key === 'sql') {
return $this->getQuery();
} else if($key === 'bindValues') {
return $this->bindValues;
} else if($key === 'bindOptions') {
return $this->bindOptions;
} else if($key === 'bindKeys') {
return $this->bindKeys;
}
return parent::__get($key);
}
/**
* Merge the contents of current query with another (experimental/incomplete)
*
* #pw-internal
*
* @internal
* @param DatabaseQuery $query
* @return $this
* @deprecated
*
*/
public function merge(DatabaseQuery $query) {
@@ -184,22 +582,89 @@ abstract class DatabaseQuery extends WireData {
/**
* Generate the SQL query based on everything set in this DatabaseQuery object
*
* @return string
*
*/
abstract public function getQuery();
/**
* Get the WHERE portion of the query
*
* Get SQL query with bind params populated for debugging purposes (not to be used as actual query)
*
* @return string
*
*/
protected function getQueryWhere() {
if(!count($this->where)) return '';
$where = $this->where;
$sql = "\nWHERE " . array_shift($where) . " ";
foreach($where as $s) $sql .= "\nAND $s ";
public function getDebugQuery() {
$sql = $this->getQuery();
$suffix = $this->bindOptions['suffix'];
$database = $this->wire('database');
foreach($this->bindValues as $bindKey => $bindValue) {
if(is_string($bindValue)) $bindValue = $database->quote($bindValue);
if($bindKey[strlen($bindKey)-1] === $suffix) {
$sql = str_replace($bindKey, $bindValue, $sql);
} else {
$sql = preg_replace('/' . $bindKey . '\b/', $bindValue, $sql);
}
}
return $sql;
}
/**
* Return generated SQL for entire query or specific method
*
* @param string $method Optionally specify method name to get SQL for
* @return string
* @since 3.0.157
*
*/
public function getSQL($method = '') {
return $method ? $this->getQueryMethod($method) : $this->getQuery();
}
/**
* Return the generated SQL for specific query method
*
* @param string $method Specify method name to get SQL for, or blank string for entire query
* @return string
* @since 3.0.157
*
*/
public function getQueryMethod($method) {
if(!$method) return $this->getQuery();
if(!isset($this->queryMethods[$method])) return '';
$methodName = 'getQuery' . ucfirst($method);
if(method_exists($this, $methodName)) {
$sql = $this->$methodName();
} else {
list($prepend, $split, $append) = $this->queryMethods[$method];
$values = $this->$method;
if(!is_array($values)) return '';
foreach($values as $key => $value) {
if(!strlen(trim($value))) unset($values[$key]); // remove any blank values
}
if(!count($values)) return '';
$sql = trim(implode($split, $values));
if(!strlen($sql) || $sql === trim($split)) return '';
$sql = $prepend . $sql . $append;
}
return $sql;
}
/**
* Get the WHERE portion of the query
*
protected function getQueryWhere() {
$where = $this->where;
if(!count($where)) return '';
$sql = "\nWHERE " . implode(" \nAND ", $where) . " ";
return $sql;
}
*/
/**
* Prepare and return a PDOStatement
*
@@ -209,13 +674,35 @@ abstract class DatabaseQuery extends WireData {
public function prepare() {
$query = $this->wire('database')->prepare($this->getQuery());
foreach($this->bindValues as $key => $value) {
$query->bindValue($key, $value);
$type = isset($this->bindTypes[$key]) ? $this->bindTypes[$key] : $this->pdoParamType($value);
$query->bindValue($key, $value, $type);
}
return $query;
}
/**
* Get the PDO::PARAM_* type for given value
*
* @param string|int|null $value
* @return int
*
*/
protected function pdoParamType($value) {
if(is_int($value)) {
$type = \PDO::PARAM_INT;
} else if($value === null) {
$type = \PDO::PARAM_NULL;
} else {
$type = \PDO::PARAM_STR;
}
return $type;
}
/**
* Execute the query with the current database handle
*
* @return \PDOStatement
* @throws WireDatabaseQueryException|\PDOException
*
*/
public function execute() {
@@ -227,7 +714,7 @@ abstract class DatabaseQuery extends WireData {
$msg = $e->getMessage();
if(stripos($msg, 'MySQL server has gone away') !== false) $database->closeConnection();
if($this->wire('config')->allowExceptions) throw $e; // throw original
throw new WireException($msg); // throw WireException
throw new WireDatabaseQueryException($msg, $e->getCode(), $e);
}
return $query;
}

View File

@@ -46,19 +46,27 @@
*/
class DatabaseQuerySelect extends DatabaseQuery {
/**
* DB cache setting from $config
*
* @var null
*
*/
static $dbCache = null;
/**
* Setup the components of a SELECT query
*
*/
public function __construct() {
$this->set('select', array());
$this->set('join', array());
$this->set('from', array());
$this->set('leftjoin', array());
$this->set('where', array());
$this->set('orderby', array());
$this->set('groupby', array());
$this->set('limit', array());
parent::__construct();
$this->addQueryMethod('select', 'SELECT ', ', ');
$this->addQueryMethod('from', " \nFROM `", '`,`', '` ');
$this->addQueryMethod('join', " \nJOIN ", " \nJOIN ");
$this->addQueryMethod('leftjoin', " \nLEFT JOIN ", " \nLEFT JOIN ");
$this->addQueryMethod('orderby', " \nORDER BY ", ",");
$this->addQueryMethod('groupby', " \nGROUP BY ", ',');
$this->addQueryMethod('limit', " \nLIMIT ", ',');
$this->set('comment', '');
}
@@ -68,15 +76,16 @@ class DatabaseQuerySelect extends DatabaseQuery {
*/
public function getQuery() {
$sql =
$this->getQuerySelect() .
$this->getQueryFrom() .
$this->getQueryJoin($this->join, "JOIN") .
$this->getQueryJoin($this->leftjoin, "LEFT JOIN") .
$this->getQueryWhere() .
$this->getQueryGroupby() .
$this->getQueryOrderby() .
$this->getQueryLimit();
$sql = trim(
$this->getQueryMethod('select') .
$this->getQueryMethod('from') .
$this->getQueryMethod('join') .
$this->getQueryMethod('leftjoin') .
$this->getQueryMethod('where') .
$this->getQueryMethod('groupby') .
$this->getQueryMethod('orderby') .
$this->getQueryMethod('limit')
) . ' ';
if($this->get('comment') && $this->wire('config')->debug) {
// NOTE: PDO thinks ? and :str param identifiers in /* comments */ are real params
@@ -89,7 +98,7 @@ class DatabaseQuerySelect extends DatabaseQuery {
}
/**
* Add an 'order by' element to the query
* Add an ORDER BY section to the query
*
* @param string|array $value
* @param bool $prepend Should the value be prepended onto the existing value? default is to append rather than prepend.
@@ -124,10 +133,20 @@ class DatabaseQuerySelect extends DatabaseQuery {
return $this;
}
/**
* Get SELECT portion of SQL
*
* @return string
*
*/
protected function getQuerySelect() {
if(self::$dbCache === null) {
self::$dbCache = $this->wire('config')->dbCache === false ? false : true;
}
$sql = '';
$select = $this->select;
$sql = '';
// ensure that an SQL_CALC_FOUND_ROWS request comes first
while(($key = array_search("SQL_CALC_FOUND_ROWS", $select)) !== false) {
@@ -135,36 +154,17 @@ class DatabaseQuerySelect extends DatabaseQuery {
unset($select[$key]);
}
if(!$sql) $sql = "SELECT ";
// $config->dbCache option for debugging purposes
if($this->wire('config')->dbCache === false) $sql .= "SQL_NO_CACHE ";
foreach($select as $s) $sql .= "$s,";
$sql = rtrim($sql, ",") . " ";
return $sql;
}
protected function getQueryFrom() {
$sql = "\nFROM ";
foreach($this->from as $s) $sql .= "`$s`,";
$sql = rtrim($sql, ",") . " ";
return $sql;
}
protected function getQueryJoin(array $join, $type) {
$sql = '';
foreach($join as $s) $sql .= "\n$type $s ";
return $sql;
}
protected function getQueryOrderby() {
if(!count($this->orderby)) return '';
$sql = "\nORDER BY ";
foreach($this->orderby as $s) $sql .= "$s,";
$sql = rtrim($sql, ",") . " ";
return $sql;
if(self::$dbCache === false) $sql .= "SQL_NO_CACHE ";
return $sql . implode(',', $select) . ' ';
}
/**
* Get GROUP BY section of SQL
*
* @return string
*
*/
protected function getQueryGroupby() {
if(!count($this->groupby)) return '';
$sql = "\nGROUP BY ";
@@ -185,23 +185,31 @@ class DatabaseQuerySelect extends DatabaseQuery {
foreach($having as $n => $h) {
if($n > 0) $sql .= " AND ";
$sql .= $h;
}
}
$sql = rtrim($sql, ",") . " ";
return $sql;
return rtrim($sql, ",") . " ";
}
/**
* Get LIMIT section of SQL
*
* @return string
*
*/
protected function getQueryLimit() {
if(!count($this->limit)) return '';
$limit = $this->limit;
$sql = "\nLIMIT " . reset($limit) . " ";
return $sql;
$limit = reset($limit);
if(strpos($limit, ',') !== false) {
list($start, $limit) = explode(',', $limit);
$start = (int) trim($start);
$limit = (int) trim($limit);
$limit = "$start,$limit";
} else {
$limit = (int) $limit;
}
return "\nLIMIT $limit ";
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
/**
* ProcessWire DatabaseStopwords
*
* MySQL stopwords, primarily for use with filtering fulltext queries
* MySQL stopwords, primarily for use with filtering fulltext queries (MyISAM only)
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* https://processwire.com
@@ -49,8 +49,8 @@ class DatabaseStopwords {
"various", "very", "via", "viz", "vs", "want", "wants", "was", "wasn't", "way", "we", "we'd", "we'll", "we're", "we've", "welcome", "well", "went", "were",
"weren't", "what", "what's", "whatever", "when", "whence", "whenever", "where", "where's", "whereafter", "whereas", "whereby", "wherein", "whereupon",
"wherever", "whether", "which", "while", "whither", "who", "who's", "whoever", "whole", "whom", "whose", "why", "will", "willing", "wish", "with", "within",
"without", "won't", "wonder", "would", "would", "wouldn't", "yes", "yet", "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself",
"yourselves", "zero");
"without", "won't", "wonder", "would", "wouldn't", "yes", "yet", "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself", "yourselves", "zero"
);
/**
* Get all stopwords

View File

@@ -10,8 +10,14 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* ~~~~~
* $timer = Debug::startTimer();
* execute_some_code();
* $elapsed = Debug::stopTimer($timer);
* ~~~~~
*
*/
@@ -41,6 +47,37 @@ class Debug {
*/
static protected $savedTimerNotes = array();
/**
* Use hrtime()?
*
* @var null|bool
*
*/
static protected $useHrtime = null;
/**
* Key of last started timer
*
* @var string
*
*/
static protected $lastTimerKey = '';
/**
* Timer precision (digits after decimal)
*
* @var int
*
*/
static protected $timerSettings = array(
'useMS' => false, // use milliseconds?
'precision' => 4,
'precisionMS' => 1,
'useHrtime' => null,
'suffix' => '',
'suffixMS' => 'ms',
);
/**
* Measure time between two events
*
@@ -58,24 +95,114 @@ class Debug {
*
*/
static public function timer($key = '', $reset = false) {
// returns number of seconds elapsed since first call
if($reset && $key) self::removeTimer($key);
if(!$key || !isset(self::$timers[$key])) {
// start new timer
$startTime = -microtime(true);
if(!$key) {
$key = (string) $startTime;
while(isset(self::$timers[$key])) $key .= "0";
}
self::$timers[(string) $key] = $startTime;
$value = $key;
$value = self::startTimer($key);
} else {
// return existing timer
$value = number_format(self::$timers[$key] + microtime(true), 4);
$value = self::stopTimer($key, null, false);
}
return $value;
}
/**
* Start a new timer
*
* @param string $key Optionally specify name for new timer
* @return string
*
*/
static public function startTimer($key = '') {
if(self::$timerSettings['useHrtime'] === null) {
self::$timerSettings['useHrtime'] = function_exists("\\hrtime");
}
return $value;
$startTime = self::$timerSettings['useHrtime'] ? hrtime(true) : -microtime(true);
if($key === '') {
$key = (string) $startTime;
while(isset(self::$timers[$key])) $key .= "0";
}
$key = (string) $key;
self::$timers[$key] = $startTime;
self::$lastTimerKey = $key;
return $key;
}
/**
* Get elapsed time for given timer and stop
*
* @param string $key Timer key returned by startTimer(), or omit for last started timer
* @param null|int|string $option Specify override precision (int), suffix (string), or "ms" for milliseconds and suffix.
* @param bool $clear Also clear the timer? (default=true)
* @return string
* @since 3.0.158
*
*/
static public function stopTimer($key = '', $option = null, $clear = true) {
if(empty($key) && self::$lastTimerKey) $key = self::$lastTimerKey;
if(!isset(self::$timers[$key])) return '';
$value = self::$timers[$key];
$useMS = $option === 'ms' || (self::$timerSettings['useMS'] && $option !== 's');
$suffix = $useMS ? self::$timerSettings['suffixMS'] : self::$timerSettings['suffix'];
$precision = $useMS ? self::$timerSettings['precisionMS'] : self::$timerSettings['precision'];
if(self::$timerSettings['useHrtime']) {
// existing hrtime timer
$value = ((hrtime(true) - $value) / 1e+6) / 1000;
} else {
// existing microtime timer
$value = $value + microtime(true);
}
if($option === null) {
// no option specified
} else if(is_int($option)) {
// precision override
$precision = $option;
} else if(is_string($option)) {
// suffix specified
$suffix = $option;
}
if($useMS) {
$value = round($value * 1000, $precision);
} else {
$value = number_format($value, $precision);
}
if($clear) self::removeTimer($key);
if($suffix) $value .= $suffix;
return $value;
}
/**
* Get or set timer setting
*
* ~~~~~~
* // Example of changing precision to 2
* Debug::timerSetting('precision', 2);
* ~~~~~~
*
* @param string $key
* @param mixed|null $value
* @return mixed
* @since 3.0.154
*
*/
static public function timerSetting($key, $value = null) {
if($value !== null) self::$timerSettings[$key] = $value;
return self::$timerSettings[$key];
}
/**
@@ -85,17 +212,16 @@ class Debug {
*
* @param string $key
* @param string $note Optional note to include in getSavedTimer
* @return bool Returns false if timer didn't exist in the first place
* @return bool|string Returns elapsed time, or false if timer didn't exist
*
*/
static public function saveTimer($key, $note = '') {
if(!isset(self::$timers[$key])) return false;
self::$savedTimers[$key] = self::timer($key);
self::removeTimer($key);
self::$savedTimers[$key] = self::stopTimer($key);
if($note) self::$savedTimerNotes[$key] = $note;
return true;
return self::$savedTimers[$key];
}
/**
* Return the time recorded in the saved timer $key
*
@@ -153,5 +279,253 @@ class Debug {
static public function removeAll() {
self::$timers = array();
}
/**
* Get all active timers in array with timer name (key) and start time (value)
*
* @return array
* @since 3.0.158
*
*/
static public function getAll() {
return self::$timers;
}
/**
* Return a backtrace array that is simpler and more PW-specific relative to PHPs debug_backtrace
*
* @param array $options
* @return array|string
* @since 3.0.136
*
*/
static public function backtrace(array $options = array()) {
$defaults = array(
'limit' => 0, // the limit argument for the debug_backtrace call
'flags' => DEBUG_BACKTRACE_PROVIDE_OBJECT, // flags for PHP debug_backtrace method
'showHooks' => false, // show internal methods for hook calls?
'getString' => false, // get newline separated string rather than array?
'getCnt' => true, // get index number count (for getString only)
'getFile' => true, // get filename? true, false or 'basename'
'maxCount' => 10, // max size for arrays
'maxStrlen' => 100, // max length for strings
'maxDepth' => 5, // max allowed recursion depth when converting variables to strings
'ellipsis' => ' …', // show this ellipsis when a long value is truncated
'skipCalls' => array(), // method/function calls to skip
);
$options = array_merge($defaults, $options);
if($options['limit']) $options['limit']++;
$traces = @debug_backtrace($options['flags'], $options['limit']);
$config = wire('config');
$rootPath = ProcessWire::getRootPath(true);
$rootPath2 = $config && $config->paths ? $config->paths->root : $rootPath;
array_shift($traces); // shift of the simpleBacktrace call, which is not needed
$apiVars = array();
$result = array();
$cnt = 0;
foreach(wire('all') as $name => $value) {
if(!is_object($value)) continue;
$apiVars[wireClassName($value)] = '$' . $name;
}
foreach($traces as $n => $trace) {
if(!is_array($trace) || !isset($trace['function']) || !isset($trace['file'])) {
continue;
} else if(count($options['skipCalls']) && in_array($trace['function'], $options['skipCalls'])) {
continue;
}
$obj = null;
$class = '';
$type = '';
$args = $trace['args'];
$argStr = '';
$file = $trace['file'];
$basename = basename($file);
$function = $trace['function'];
$isHookableCall = false;
if(isset($trace['object'])) {
$obj = $trace['object'];
$class = wireClassName($obj);
} else if(isset($trace['class'])) {
$class = wireClassName($trace['class']);
}
if($class) {
$type = isset($trace['type']) ? $trace['type'] : '.';
}
if(!$options['showHooks']) {
if($basename === 'Wire.php' && !wireMethodExists('Wire', $function)) continue;
if($class === 'WireHooks' || $basename === 'WireHooks.php') continue;
}
if(strpos($function, '___') === 0) {
$isHookableCall = '___';
} else if($obj && !method_exists($obj, $function) && method_exists($obj, "___$function")) {
$isHookableCall = true;
}
if($type === '->' && isset($apiVars[$class])) {
// use API var name when available
if(strtolower($class) === strtolower(ltrim($apiVars[$class], '$'))) {
$class = $apiVars[$class];
} else {
$class = "$class " . $apiVars[$class];
}
}
if($basename === 'Wire.php' && $class !== 'Wire') {
$ref = new \ReflectionClass($trace['class']);
$file = $ref->getFileName();
}
// rootPath and rootPath2 can be different if one of them represented by a symlink
$file = str_replace($rootPath, '/', $file);
if($rootPath2 !== $rootPath) $file = str_replace($rootPath2, '/', $file);
if(($function === '__call' || $function == '_callMethod') && count($args)) {
$function = array_shift($args);
}
if(!$options['showHooks'] && $isHookableCall === '___') {
$function = substr($function, 3);
}
if(!empty($args)) {
$newArgs = array();
if($isHookableCall && count($args) === 1 && is_array($args[0])) {
$newArgs = $args[0];
}
foreach($args as $arg) {
if(is_object($arg)) {
$arg = wireClassName($arg) . ' $obj';
} else if(is_array($arg)) {
$count = count($arg);
if($count < 4) {
$arg = $count ? self::toStr($arg, array('maxDepth' => 2)) : '[]';
} else {
$arg = 'array(' . count($arg) . ')';
}
} else if(is_string($arg)) {
if(strlen("$arg") > $options['maxStrlen']) $arg = substr($arg, 0, $options['maxStrlen']) . ' …';
$arg = '"' . $arg . '"';
} else if(is_bool($arg)) {
$arg = $arg ? 'true' : 'false';
} else {
// leave as-is
}
$newArgs[] = $arg;
}
$argStr = implode(', ', $newArgs);
if($argStr === '[]') $argStr = '';
}
if($options['getFile'] === 'basename') $file = basename($file);
$call = "$class$type$function($argStr)";
$file = "$file:$trace[line]";
if($options['getString']) {
$str = '';
if($options['getCnt']) $str .= "$cnt. ";
$str .= "$file » $call";
$result[] = $str;
} else {
$result[] = array(
'file' => $file,
'call' => $call,
);
}
$cnt++;
}
if($options['getString']) $result = implode("\n", $result);
return $result;
}
/**
* Convert value to string for backtrace method
*
* @param $value
* @param array $options
* @return null|string
*
*/
static protected function toStr($value, array $options = array()) {
$defaults = array(
'maxCount' => 10, // max size for arrays
'maxStrlen' => 100, // max length for strings
'maxDepth' => 5,
'ellipsis' => ' …'
);
static $depth = 0;
$options = count($options) ? array_merge($defaults, $options) : $defaults;
$depth++;
if(is_object($value)) {
// object
$str = wireClassName($value);
if($str === 'HookEvent') {
$str .= ' $event';
} else if(method_exists($value, '__toString')) {
$value = (string) $value;
if($value !== $str) {
if(strlen($value) > $options['maxStrlen']) {
$value = substr($value, 0, $options['maxStrlen']) . $options['ellipsis'];
}
$str .= "($value)";
}
}
} else if(is_array($value)) {
// array
if(empty($value)) {
$str = '[]';
} else if($depth >= $options['maxDepth']) {
$str = "array(" . count($value) . ")";
} else {
$suffix = '';
if(count($value) > $options['maxCount']) {
$value = array_slice($value, 0, $options['maxCount']);
$suffix = $options['ellipsis'];
}
foreach($value as $k => $v) {
$value[$k] = self::toStr($v, $options);
}
$str = '[ ' . implode(', ', $value) . $suffix . ' ]';
}
} else if(is_string($value)) {
// string
if(strlen($value) > $options['maxStrlen']) {
$value = substr($value, 0, $options['maxStrlen']) . $options['ellipsis'];
}
$hasDQ = strpos($value, '"') !== false;
$hasSQ = strpos($value, "'") !== false;
if(($hasDQ && $hasSQ) || $hasSQ) {
$value = str_replace('"', '\\"', $value);
$str = '"' . $value . '"';
} else {
$str = "'$value'";
}
} else if(is_bool($value)) {
// true or false
$str = $value ? 'true' : 'false';
} else {
// int, float or other
$str = $value;
}
$depth--;
return $str;
}
}

View File

@@ -8,7 +8,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
*/
@@ -17,24 +17,149 @@
* Generic ProcessWire exception
*
*/
class WireException extends \Exception {}
class WireException extends \Exception {
/**
* Replace previously set message
*
* @param string $message
* @since 3.0.150
*
*/
protected function setMessage($message) {
$this->message = $message;
}
/**
* Replace previously set code
*
* @param int $code
* @since 3.0.150
*
*/
protected function setCode($code) {
$this->code = $code;
}
}
/**
* Triggered when access to a resource is not allowed
* Thrown when access to a resource is not allowed
*
*/
class WirePermissionException extends WireException {}
/**
* Triggered when a requested item does not exist and generates a fatal error
* Thrown when a requested page does not exist, or can be thrown manually to show the 404 page
*
*/
class Wire404Exception extends WireException {}
class Wire404Exception extends WireException {
/**
* 404 is because core determined requested resource by URL does not physically exist
*
* #pw-internal
*
*/
const codeNonexist = 404;
/**
* 404 is a result of a resource that might exist but there is no access
*
* Similar to a WirePermissionException except always still a 404 externally
*
* #pw-internal
*
*/
const codePermission = 4041;
/**
* 404 is a result of a secondary non-file asset that does not exist, even if page does
*
* For example: /foo/bar/?id=123 where /foo/bar/ exists but 123 points to non-existent asset.
*
* #pw-internal
*
*/
const codeSecondary = 4042;
/**
* 404 is a result of content not available in requested language
*
* #pw-internal
*
*/
const codeLanguage = 4043;
/**
* 404 is a result of a physical file that does not exist on the file system
*
* #pw-internal
*
*/
const codeFile = 4044;
/**
* 404 is a result of a front-end wire404() function call
*
* #pw-internal
*
*/
const codeFunction = 4045;
/**
* Anonymous 404 with no code provided
*
* #pw-internal
*
*/
const codeAnonymous = 0;
}
/**
* WireDatabaseException is the exception thrown by the Database class
*
* If you use this class without ProcessWire, change 'extends WireException' below to be just 'extends Exception'
* Thrown when ProcessWire is unable to connect to the database at boot
*
*/
class WireDatabaseException extends WireException {}
/**
* Thrown by DatabaseQuery classes on query exception
*
* May have \PDOException populated with call to its getPrevious(); method,
* in which can it also has same getCode() and getMessage() as \PDOException.
*
* @since 3.0.156
*
*/
class WireDatabaseQueryException extends WireException {}
/**
* Thrown when cross site request forgery detected by SessionCSRF::validate()
*
*/
class WireCSRFException extends WireException {}
/**
* Thrown when a requested Process or Process method is requested that doesnt exist
*
*/
class ProcessController404Exception extends Wire404Exception { }
/**
* Thrown when the user doesnt have access to execute the requested Process or method
*
*/
class ProcessControllerPermissionException extends WirePermissionException { }
/**
* Thrown by PageFinder when an error occurs trying to find pages
*
*/
class PageFinderException extends WireException { }
/**
* Thrown by PageFinder when it detects an error in the syntax of a given page-finding selector
*
*/
class PageFinderSyntaxException extends PageFinderException { }

View File

@@ -12,32 +12,40 @@
* #pw-body Field objects are managed by the `$fields` API variable.
* #pw-use-constants
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
* @property int $id Numeric ID of field in the database #pw-group-properties
* @property string $name Name of field #pw-group-properties
* @property string $table Database table used by the field #pw-group-properties
* @property string $prevTable Previously database table (if field was renamed) #pw-group-properties
* @property string $prevName Previously used name (if field was renamed), 3.0.164+ #pw-group-properties
* @property Fieldtype|null $type Fieldtype module that represents the type of this field #pw-group-properties
* @property Fieldtype $prevFieldtype Previous Fieldtype, of type was changed #pw-group-properties
* @property Fieldtype|null $prevFieldtype Previous Fieldtype, if type was changed #pw-group-properties
* @property int $flags Bitmask of flags used by this field #pw-group-properties
* @property-read string $flagsStr Names of flags used by this field (readonly) #pw-group-properties
* @property string $label Text string representing the label of the field #pw-group-properties
* @property string $description Longer description text for the field #pw-group-properties
* @property string $notes Additional notes text about the field #pw-group-properties
* @property string $icon Icon name used by the field, if applicable #pw-group-properties
* @property string $tags Tags that represent this field, if applicable (space separated string). #pw-group-properties
* @property-read array $tagList Same as $tags property, but as an array. #pw-group-properties
* @property bool $useRoles Whether or not access control is enabled #pw-group-access
* @property array $editRoles Role IDs with edit access, applicable only if access control is enabled. #pw-group-access
* @property array $viewRoles Role IDs with view access, applicable only if access control is enabled. #pw-group-access
* @property array|null $orderByCols Columns that WireArray values are sorted by (default=null), Example: "sort" or "-created". #pw-internal
* @property int|null $paginationLimit Used by paginated WireArray values to indicate limit to use during load. #pw-internal
* @property array $allowContexts Names of settings that are custom configured to be allowed for context. #pw-group-properties
* @property bool|int|null $flagUnique Non-empty value indicates request for, or presence of, Field::flagUnique flag. #pw-internal
* @property Fieldgroup|null $_contextFieldgroup Fieldgroup field is in context for or null if not in context. #pw-internal
*
* Common Inputfield properties that Field objects store:
* @property int|bool|null $required Whether or not this field is required during input #pw-group-properties
* @property string|null $requiredIf A selector-style string that defines the conditions under which input is required #pw-group-properties
* @property string|null $showIf A selector-style string that defines the conditions under which the Inputfield is shown #pw-group-properties
* @property int|null $columnWidth The Inputfield column width (percent) 10-100. #pw-group-properties
* @property int|null $collapsed The Inputfield 'collapsed' value (see Inputfield collapsed constants). #pw-group-properties
* @property int|null $textFormat The Inputfield 'textFormat' value (see Inputfield textFormat constants). #pw-group-properties
*
* @method bool viewable(Page $page = null, User $user = null) Is the field viewable on the given $page by the given $user? #pw-group-access
* @method bool editable(Page $page = null, User $user = null) Is the field editable on the given $page by the given $user? #pw-group-access
@@ -51,6 +59,7 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Field should be automatically joined to the page at page load time
*
* #pw-group-flags
*
*/
@@ -58,6 +67,7 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Field used by all fieldgroups - all fieldgroups required to contain this field
*
* #pw-group-flags
*
*/
@@ -65,6 +75,7 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Field is a system field and may not be deleted, have it's name changed, or be converted to non-system
*
* #pw-group-flags
*
*/
@@ -72,6 +83,7 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Field is permanent in any fieldgroups/templates where it exists - it may not be removed from them
*
* #pw-group-flags
*
*/
@@ -79,6 +91,7 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Field is access controlled
*
* #pw-group-flags
*
*/
@@ -88,6 +101,7 @@ class Field extends WireData implements Saveable, Exportable {
* If field is access controlled, this flag says that values are still front-end API accessible
*
* Without this flag, non-viewable values are made blank when output formatting is ON.
*
* #pw-group-flags
*
*/
@@ -97,13 +111,28 @@ class Field extends WireData implements Saveable, Exportable {
* If field is access controlled and user has no edit access, they can still view in the editor (if they have view permission)
*
* Without this flag, non-editable values are simply not shown in the editor at all.
*
* #pw-group-flags
*
*/
const flagAccessEditor = 128;
const flagAccessEditor = 128;
/**
* Field requires that the same value is not repeated more than once in its table 'data' column (when supported by Fieldtype)
*
* When this flag is set and there is a non-empty $flagUnique property on the field, then it indicates a unique index
* is currently present. When only this flag is present (no property), it indicates a request to remove the index and flag.
* When only the property is present (no flag), it indicates a pending request to add unique index and flag.
*
* #pw-group-flags
* @since 3.0.150
*
*/
const flagUnique = 256;
/**
* Field has been placed in a runtime state where it is contextual to a specific fieldgroup and is no longer saveable
*
* #pw-group-flags
*
*/
@@ -111,6 +140,7 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Set this flag to override system/permanent flags if necessary - once set, system/permanent flags can be removed, but not in the same set().
*
* #pw-group-flags
*
*/
@@ -118,6 +148,7 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Prefix for database tables
*
* #pw-internal
*
*/
@@ -136,10 +167,10 @@ class Field extends WireData implements Saveable, Exportable {
*/
protected $settings = array(
'id' => 0,
'type' => null,
'flags' => 0,
'name' => '',
'label' => '',
'flags' => 0,
'type' => null,
);
/**
@@ -149,18 +180,26 @@ class Field extends WireData implements Saveable, Exportable {
protected $prevTable;
/**
* A specifically set table name by setTable() for override purposes
* If the field name changed, this is the previous name
*
* @var string
*
*/
protected $setTable = '';
protected $prevName = '';
/**
* If the field type changed, this is the previous fieldtype so that it can be changed at save time
*
*/
protected $prevFieldtype;
/**
* A specifically set table name by setTable() for override purposes
*
* @var string
*
*/
protected $setTable = '';
/**
* Accessed properties, becomes array when set to true, null when set to false
@@ -203,6 +242,14 @@ class Field extends WireData implements Saveable, Exportable {
*/
protected $inputfieldSettings = array();
/**
* Tags assigned to this field, keys are lowercase version of tag, values can possibly contain mixed case
*
* @var null|array
*
*/
protected $tagList = null;
/**
* True if lowercase tables should be enforce, false if not (null = unset). Cached from $config
*
@@ -231,6 +278,9 @@ class Field extends WireData implements Saveable, Exportable {
} else if($key == 'prevTable') {
$this->prevTable = $value;
return $this;
} else if($key == 'prevName') {
$this->prevName = $value;
return $this;
} else if($key == 'prevFieldtype') {
$this->prevFieldtype = $value;
return $this;
@@ -341,15 +391,25 @@ class Field extends WireData implements Saveable, Exportable {
*
*/
public function get($key) {
if($key === 'type' && isset($this->settings['type'])) {
$value = $this->settings['type'];
if($value) $value->setLastAccessField($this);
return $value;
}
if($key == 'viewRoles') return $this->viewRoles;
else if($key == 'editRoles') return $this->editRoles;
else if($key == 'table') return $this->getTable();
else if($key == 'prevTable') return $this->prevTable;
else if($key == 'prevName') return $this->prevName;
else if($key == 'prevFieldtype') return $this->prevFieldtype;
else if(isset($this->settings[$key])) return $this->settings[$key];
else if($key == 'icon') return $this->getIcon(true);
else if($key == 'useRoles') return ($this->settings['flags'] & self::flagAccess) ? true : false;
else if($key == 'flags') return $this->settings['flags'];
else if($key == 'flagsStr') return $this->wire('fields')->getFlagNames($this->settings['flags'], true);
else if($key == 'tagList') return $this->getTags();
else if($key == 'tags') return $this->getTags(true);
$value = parent::get($key);
if($key === 'allowContexts' && !is_array($value)) $value = array();
@@ -521,7 +581,7 @@ class Field extends WireData implements Saveable, Exportable {
// populate import data
foreach($changes as $key => $change) {
$this->errors('clear all');
$this->set($key, $data[$key]);
if(isset($data[$key])) $this->set($key, $data[$key]);
if(!empty($data['errors'][$key])) {
$error = $data['errors'][$key];
// just in case they switched it to an array of multiple errors, convert back to string
@@ -569,7 +629,10 @@ class Field extends WireData implements Saveable, Exportable {
throw new WireException("You may not change the name of field '{$this->settings['name']}' because it is a system field.");
}
$this->trackChange('name');
if($this->settings['name']) $this->prevTable = $this->getTable(); // so that Fields can perform a table rename
if($this->settings['name']) {
$this->prevName = $this->settings['name'];
$this->prevTable = $this->getTable(); // so that Fields can perform a table rename
}
}
$this->settings['name'] = $name;
@@ -594,7 +657,7 @@ class Field extends WireData implements Saveable, Exportable {
} else if(is_string($type)) {
$typeStr = $type;
$fieldtypes = $this->wire('fieldtypes');
$fieldtypes = $this->wire('fieldtypes'); /** @var Fieldtypes $fieldtypes */
if(!$type = $fieldtypes->get($type)) {
$this->error("Fieldtype '$typeStr' does not exist");
return $this;
@@ -627,6 +690,59 @@ class Field extends WireData implements Saveable, Exportable {
return $this->type;
}
/**
* Get this field in context of a Page/Template
*
* #pw-group-retrieval
*
* @param Page|Template|Fieldgroup|string $for Specify Page, Template, or template name string
* @param string $namespace Optional namespace (internal use)
* @param bool $has Return boolean rather than Field to check if context exists? (default=false)
* @return Field|bool
* @since 3.0.162
* @see Fieldgroup::getFieldContext(), Field::hasContext()
*
*/
public function getContext($for, $namespace = '', $has = false) {
/** @var Fieldgroup|null $fieldgroup */
$fieldgroup = null;
if(is_string($for)) {
$for = $this->wire()->templates->get($for);
}
if($for instanceof Page) {
/** @var Page $context */
$template = $for instanceof NullPage ? null : $for->template;
if(!$template) throw new WireException('Page must have template to get context');
$fieldgroup = $template->fieldgroup;
} else if($for instanceof Template) {
/** @var Template $context */
$fieldgroup = $for->fieldgroup;
} else if($for instanceof Fieldgroup) {
$fieldgroup = $for;
}
if(!$fieldgroup) throw new WireException('Cannot get Fieldgroup for field context');
if($has) return $fieldgroup->hasFieldContext($this->id, $namespace);
return $fieldgroup->getFieldContext($this->id, $namespace);
}
/**
* Does this field have context settings for given Page/Template?
*
* #pw-group-retrieval
*
* @param Page|Template|Fieldgroup|string $for Specify Page, Template, or template name string
* @param string $namespace Optional namespace (internal use)
* @return Field|bool
* @since 3.0.163
* @see Field::getContext()
*
*/
public function hasContext($for, $namespace = '') {
return $this->getContext($for, $namespace, true);
}
/**
* Set the roles that are allowed to view or edit this field on pages.
*
@@ -1010,6 +1126,7 @@ class Field extends WireData implements Saveable, Exportable {
}
$inputfields->attr('title', $this->_('Input'));
$inputfields->attr('id+name', 'inputfieldConfig');
/** @var InputfieldWrapper $inputfieldInputfields */
$inputfieldInputfields = $inputfield->getConfigInputfields();
if(!$inputfieldInputfields) $inputfieldInputfields = $this->wire(new InputfieldWrapper());
$configArray = $inputfield->getConfigArray();
@@ -1249,6 +1366,124 @@ class Field extends WireData implements Saveable, Exportable {
return $this;
}
/**
* Get tags
*
* @param bool|string $getString Optionally specify true for space-separated string, or delimiter string (default=false)
* @return array|string Returns array of tags unless $getString option is requested
* @since 3.0.106
*
*/
public function getTags($getString = false) {
if($this->tagList === null) {
$tagList = $this->setTags(parent::get('tags'));
} else {
$tagList = $this->tagList;
}
if($getString !== false) {
$delimiter = $getString === true ? ' ' : $getString;
return implode($delimiter, $tagList);
}
return $tagList;
}
/**
* Set all tags
*
* #pw-internal
*
* @param array|string $tagList Array of tags to add (or space-separated string)
* @param bool $reindex Set to false to set given $tagsList exactly as-is (assumes it's already in correct format)
* @return array Array of tags that were set
* @since 3.0.106
*
*/
public function setTags($tagList, $reindex = true) {
if($tagList === null || $tagList === '') {
$tagList = array();
} else if(!is_array($tagList)) {
$tagList = explode(' ', $tagList);
}
if($reindex && count($tagList)) {
$tags = array();
foreach($tagList as $tag) {
$tag = trim($tag);
if(strlen($tag)) $tags[strtolower($tag)] = $tag;
}
$tagList = $tags;
}
if($this->tagList !== $tagList) {
$this->tagList = $tagList;
parent::set('tags', implode(' ', $tagList));
$this->wire('fields')->getTags('reset');
}
return $tagList;
}
/**
* Add one or more tags
*
* @param string $tag
* @return array Returns current tag list
* @since 3.0.106
*
*/
public function addTag($tag) {
$tagList = $this->getTags();
$tagList[strtolower($tag)] = $tag;
$this->setTags($tagList, false);
return $tagList;
}
/**
* Return true if this field has the given tag or false if not
*
* @param string $tag
* @return bool
* @since 3.0.106
*
*/
public function hasTag($tag) {
$tagList = $this->getTags();
return isset($tagList[strtolower(trim(ltrim($tag, '-')))]);
}
/**
* Remove a tag
*
* @param string $tag
* @return array Returns current tag list
* @since 3.0.106
*
*/
public function removeTag($tag) {
$tagList = $this->getTags();
$tag = strtolower($tag);
if(!isset($tagList[$tag])) return $tagList;
unset($tagList[$tag]);
return $this->setTags($tagList, false);
}
/**
* Get URL to edit field in the admin
*
* @param array|bool|string $options Specify array of options, string for find option, or bool for http option.
* - `find` (string): Name of field to find in editor form
* - `http` (bool): True to force inclusion of scheme and hostname
* @return string
* @since 3.0.151
*
*/
public function editUrl($options = array()) {
if(is_string($options)) $options = array('find' => $options);
if(is_bool($options)) $options = array('http' => $options);
if(!is_array($options)) $options = array();
$url = $this->wire('config')->urls(empty($options['http']) ? 'admin' : 'httpAdmin');
$url .= "setup/field/edit?id=$this->id";
if(!empty($options['find'])) $url .= '#find-' . $this->wire('sanitizer')->fieldName($options['find']);
return $url;
}
/**
* debugInfo PHP 5.6+ magic method
*
@@ -1258,9 +1493,11 @@ class Field extends WireData implements Saveable, Exportable {
*
*/
public function __debugInfo() {
$info = parent::__debugInfo();
$info['settings'] = $this->settings;
$info = $this->settings;
$info['flags'] = $info['flags'] ? "$this->flagsStr ($info[flags])" : "";
$info = array_merge($info, parent::__debugInfo());
if($this->prevTable) $info['prevTable'] = $this->prevTable;
if($this->prevName) $info['prevName'] = $this->prevName;
if($this->prevFieldtype) $info['prevFieldtype'] = (string) $this->prevFieldtype;
if(!empty($this->trackGets)) $info['trackGets'] = $this->trackGets;
if($this->useRoles) {
@@ -1270,5 +1507,14 @@ class Field extends WireData implements Saveable, Exportable {
return $info;
}
public function debugInfoSmall() {
return array(
'id' => $this->id,
'name' => $this->name,
'label' => $this->getLabel(),
'type' => $this->type ? wireClassName($this->type) : '',
);
}
}

View File

@@ -13,7 +13,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
*/
@@ -41,17 +41,31 @@ class FieldSelectorInfo extends Wire {
* CSV keywords from schema mapped to input types to auto-determine input type from schema
*
*/
protected $schemaToInput = array();
protected $schemaToInput = array();
/**
* Construct
*
*/
public function __construct() {
$ftNops = array();
$ftOps = Selectors::getOperators(array(
'compareType' => Selector::compareTypeFind,
'getValueType' => 'operator',
'getIndexType' => 'none',
));
foreach($ftOps as $op) {
$ftNops[] = "!$op";
}
$this->operators = array(
'number' => array('=', '!=', '>', '<', '>=', '<=', '=""', '!=""'),
'text' => array('=', '!=', '%=', '^=', '$=', '=""', '!=""'),
'fulltext' => array('%=', '*=', '~=', '^=', '$=', '=', '!=', '=""', '!=""', '!%=', '!*=', '!~=', '!^=', '!$='),
'text' => array('=', '!=', '%=', '%^=', '%$=', '=""', '!=""'),
'fulltext' => array_merge($ftOps, array('=', '!=', '=""', '!=""'), $ftNops),
'select' => array('=', '!=')
);
);
$this->infoTemplate = array(
// name of the field
@@ -68,7 +82,7 @@ class FieldSelectorInfo extends Wire {
'options' => array(),
// if field has subfields, this contains array of all above, indexed by subfield name (blank if not applicable)
'subfields' => array(),
);
);
$this->schemaToInput = array(
'TEXT,TINYTEXT,MEDIUMTEXT,LONGTEXT,VARCHAR,CHAR' => 'text',
@@ -76,8 +90,7 @@ class FieldSelectorInfo extends Wire {
'DATE' => 'date',
'INT,DECIMAL,FLOAT,DOUBLE' => 'number',
'ENUM,SET' => 'select',
);
);
}
/**
@@ -187,21 +200,19 @@ class FieldSelectorInfo extends Wire {
*
*/
public function getOperatorLabels() {
if(empty($this->operatorLabels)) $this->operatorLabels = array(
'=' => $this->_('Equals'),
'!=' => $this->_('Not Equals'),
'>' => $this->_('Greater Than'),
'<' => $this->_('Less Than'),
'>=' => $this->_('Greater Than or Equal'),
'<=' => $this->_('Less Than or Equal'),
'%=' => $this->_('Contains Text'),
'*=' => $this->_('Contains Phrase'),
'~=' => $this->_('Contains Words'),
'^=' => $this->_('Starts With'),
'$=' => $this->_('Ends With'),
'=""' => $this->_('Is Empty'),
'!=""' => $this->_('Is Not Empty')
);
if(!empty($this->operatorLabels)) return $this->operatorLabels;
$this->operatorLabels = Selectors::getOperators(array(
'getIndexType' => 'operator',
'getValueType' => 'label',
));
$this->operatorLabels['=""'] = $this->_('Is Empty');
$this->operatorLabels['!=""'] = $this->_('Is Not Empty');
foreach($this->operators as $operator) {
if(isset($this->operatorLabels[$operator])) continue;
if(strpos($operator, '!') !== 0) continue;
$op = ltrim($operator, '!');
$this->operatorLabels[$operator] = sprintf($this->_('Not: %s'), $this->operatorLabels[$op]);
}
return $this->operatorLabels;
}
}

View File

@@ -18,6 +18,8 @@
*
* @property int $id Fieldgroup database ID #pw-group-retrieval
* @property string $name Fieldgroup name #pw-group-retrieval
* @property array $fields_id Array of all field IDs in this Fieldgroup
* @property null|FieldsArray $removedFields Null when there are no removed fields, or FieldsArray when there are.
*
*/
class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupItems {
@@ -39,6 +41,8 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
/**
* Any fields that were removed from this instance are noted so that Fieldgroups::save() can delete unused data
*
* @var FieldsArray|null
*
*/
protected $removedFields = null;
@@ -264,6 +268,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
if($useFieldgroupContext && $value) {
$value->flags = $value->flags | Field::flagFieldgroupContext;
$value->setQuietly('_contextFieldgroup', $this);
}
return $value;
@@ -314,7 +319,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
}
/**
* Does this fieldgroup having the given field?
* Does this fieldgroup have the given field?
*
* #pw-group-retrieval
*
@@ -335,7 +340,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
* #pw-group-retrieval
*
* @param string|int $key Property name to retrieve, or Field name
* @return Field|string|int|null
* @return Field|string|int|null|array
*
*/
public function get($key) {
@@ -524,6 +529,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
$container = $this->wire(new InputfieldWrapper());
$containers = array();
$inFieldset = false;
$inHiddenFieldset = false;
$inModalGroup = '';
// for multiple named fields
@@ -566,6 +572,14 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
continue;
}
}
if($inHiddenFieldset) {
// we are in a modal group that should be skipped since all the inputs require the modal
if($field->name == $inHiddenFieldset . "_END") {
$inHiddenFieldset = false;
} else {
continue;
}
}
if($fieldName) {
// limit to specific field name
@@ -595,6 +609,10 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
// field requires modal
$inModalGroup = $field->name;
} else if($field->type instanceof FieldtypeFieldsetOpen && $field->collapsed == Inputfield::collapsedHidden) {
$inHiddenFieldset = $field->name;
continue;
} else if(!$flat && $field->type instanceof FieldtypeFieldsetOpen) {
// new fieldset in non-flat mode
if($field->type instanceof FieldtypeFieldsetClose) {
@@ -616,7 +634,10 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
if(!$inputfield) continue;
if($inputfield->collapsed == Inputfield::collapsedHidden) continue;
$inputfield->setAttribute('value', $page->get($field->name));
if(!$page instanceof NullPage) {
$value = $page->get($field->name);
$inputfield->setAttribute('value', $value);
}
if($multiMode) {
$fieldInputfields[$field->id] = $inputfield;

View File

@@ -3,7 +3,9 @@
/**
* ProcessWire Fieldgroups
*
* Maintains collections of Fieldgroup instances.
* #pw-summary Maintains collections of Fieldgroup object instances and represents the `$fieldgroups` API variable.
* #pw-body For full details on all methods available in a Fieldgroup, be sure to also see the `WireArray` class.
* #pw-var $fieldgroups
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* https://processwire.com
@@ -465,14 +467,19 @@ class Fieldgroups extends WireSaveableItemsLookup {
if(is_null($template)) $template = $this->wire('templates')->get($fieldgroup->name);
if(($field->flags & Field::flagGlobal) && (!$template || !$template->noGlobal)) {
return
"Field '$field' may not be removed from fieldgroup '{$this->name}' " .
"because it is globally required (Field::flagGlobal).";
if($template && $template->getConnectedField()) {
// if template has a 1-1 relationship with a field, noGlobal is not enforced
return false;
} else {
return
"Field '$field' may not be removed from fieldgroup '$fieldgroup->name' " .
"because it is globally required (Field::flagGlobal).";
}
}
if($field->flags & Field::flagPermanent) {
return
"Field '$field' may not be removed from fieldgroup '{$this->name}' " .
"Field '$field' may not be removed from fieldgroup '$fieldgroup->name' " .
"because it is permanent (Field::flagPermanent).";
}

View File

@@ -5,10 +5,19 @@
*
* Manages collection of ALL Field instances, not specific to any particular Fieldgroup
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
* #pw-summary Manages all custom fields in ProcessWire
* #pw-summary Manages all custom fields in ProcessWire, independently of any Fieldgroup.
* #pw-var $fields
* #pw-body =
* Each field returned is an object of type `Field`. The $fields API variable is iterable:
* ~~~~~
* foreach($fields as $field) {
* echo "<p>Name: $field->name, Type: $field->type, Label: $field->label</p>";
* }
* ~~~~~
* #pw-body
*
* @method Field|null get($key) Get a field by name or id
* @method bool changeFieldtype(Field $field1, $keepSettings = false)
@@ -37,6 +46,8 @@ class Fields extends WireSaveableItems {
static protected $nativeNamesSystem = array(
'child',
'children',
'count',
'check_access',
'created_users_id',
'created',
'createdUser',
@@ -44,11 +55,16 @@ class Fields extends WireSaveableItems {
'createdUsersID',
'data',
'description',
'editUrl',
'end',
'fieldgroup',
'fields',
'find',
'flags',
'get',
'has_parent',
'hasParent',
'httpUrl',
'id',
'include',
'isNew',
@@ -76,20 +92,52 @@ class Fields extends WireSaveableItems {
'templatePrevious',
'templates_id',
'url',
'_custom',
);
protected $flagNames = array();
/**
* Field names that are native/permanent to this instance of ProcessWire (configurable at runtime)
*
* Array indexes are the names and values are all boolean true.
*
*/
protected $nativeNamesLocal = array();
/**
* Cache of all tags for all fields, populated to array when asked for the first time
*
* @var array|null
*
*/
protected $tagList = null;
/**
* @var FieldsTableTools|null
*
*/
protected $tableTools = null;
/**
* Construct
*
*/
public function __construct() {
$this->fieldsArray = new FieldsArray();
$this->flagNames = array(
Field::flagAutojoin => 'autojoin',
Field::flagGlobal => 'global',
Field::flagSystem => 'system',
Field::flagPermanent => 'permanent',
Field::flagAccess => 'access',
Field::flagAccessAPI => 'access-api',
Field::flagAccessEditor => 'access-editor',
Field::flagFieldgroupContext => 'fieldgroup-context',
Field::flagSystemOverride => 'system-override',
);
// convert so that keys are names so that isset() can be used rather than in_array()
if(isset(self::$nativeNamesSystem[0])) self::$nativeNamesSystem = array_flip(self::$nativeNamesSystem);
}
/**
@@ -113,6 +161,46 @@ class Fields extends WireSaveableItems {
return $this->wire(new Field());
}
/**
* Make an item and populate with given data
*
* @param array $a Associative array of data to populate
* @return Saveable|Wire
* @throws WireException
* @since 3.0.146
*
*/
public function makeItem(array $a = array()) {
if(empty($a['type'])) return parent::makeItem($a);
/** @var Fieldtypes $fieldtypes */
$fieldtypes = $this->wire('fieldtypes');
if(!$fieldtypes) return parent::makeItem($a);
/** @var Fieldtype $fieldtype */
$fieldtype = $fieldtypes->get($a['type']);
if(!$fieldtype) return parent::makeItem($a);
$class = $fieldtype->getFieldClass($a);
if(empty($class) || $class === 'Field') return parent::makeItem($a);
if(strpos($class, "\\") === false) $class = wireClassName($class, true);
if(!class_exists($class)) return parent::makeItem($a);
/** @var Field $field */
$field = new $class();
$this->wire($field);
foreach($a as $key => $value) {
$field->$key = $value;
}
$field->resetTrackChanges(true);
return $field;
}
/**
* Per WireSaveableItems interface, return all available Field instances
*
@@ -178,9 +266,13 @@ class Fields extends WireSaveableItems {
$database->exec("RENAME TABLE `$prevTable` TO `tmp_$table`"); // QA
$database->exec("RENAME TABLE `tmp_$table` TO `$table`"); // QA
}
$item->type->renamedField($item, str_replace(Field::tablePrefix, '', $prevTable));
$item->prevTable = '';
}
if(!$isNew && $item->prevName && $item->prevName != $item->name) {
$item->type->renamedField($item, $item->prevName);
$item->prevName = '';
}
if($item->prevFieldtype && $item->prevFieldtype->name != $item->type->name) {
if(!$this->changeFieldtype($item)) {
@@ -207,6 +299,8 @@ class Fields extends WireSaveableItems {
}
}
}
$this->getTags('reset');
return true;
}
@@ -287,7 +381,6 @@ class Fields extends WireSaveableItems {
* @param Field|Saveable $item Field to clone
* @param string $name Optionally specify name for new cloned item
* @return bool|Saveable $item Returns the new clone on success, or false on failure
* @throws WireException
*
*/
public function ___clone(Saveable $item, $name = '') {
@@ -335,7 +428,7 @@ class Fields extends WireSaveableItems {
$field_id = (int) $field->id;
$fieldgroup_id = (int) $fieldgroup->id;
$database = $this->wire('database');
$database = $this->wire()->database;
$newValues = $field->getArray();
$oldValues = $fieldOriginal->getArray();
@@ -413,13 +506,12 @@ class Fields extends WireSaveableItems {
// if there is something in data, then JSON encode it. If it's empty then make it null.
$data = count($data) ? wireEncodeJSON($data, true) : null;
if(is_null($data)) {
$data = 'NULL';
$query = $database->prepare('UPDATE fieldgroups_fields SET data=:data WHERE fields_id=:field_id AND fieldgroups_id=:fieldgroup_id');
if(empty($data)) {
$query->bindValue(':data', null, \PDO::PARAM_NULL);
} else {
$data = "'" . $this->wire('database')->escapeStr($data) . "'";
$query->bindValue(':data', $data, \PDO::PARAM_STR);
}
$query = $database->prepare("UPDATE fieldgroups_fields SET data=$data WHERE fields_id=:field_id AND fieldgroups_id=:fieldgroup_id"); // QA
$query->bindValue(':field_id', $field_id, \PDO::PARAM_INT);
$query->bindValue(':fieldgroup_id', $fieldgroup_id, \PDO::PARAM_INT);
$result = $query->execute();
@@ -806,8 +898,8 @@ class Fields extends WireSaveableItems {
*
*/
public function isNative($name) {
if(in_array($name, self::$nativeNamesSystem)) return true;
if(in_array($name, $this->nativeNamesLocal)) return true;
if(isset(self::$nativeNamesSystem[$name])) return true;
if(isset($this->nativeNamesLocal[$name])) return true;
return false;
}
@@ -820,12 +912,106 @@ class Fields extends WireSaveableItems {
*
*/
public function setNative($name) {
$this->nativeNamesLocal[] = $name;
$this->nativeNamesLocal[$name] = true;
}
/**
* Get list of all tags used by fields
*
* - By default it returns an array of tag names where both keys and values are the tag names.
* - If you specify true for the `$getFields` argument, it returns an array where the keys are
* tag names and the values are arrays of field names in the tag.
* - If you specify "reset" for the `$getFields` argument it returns a blank array and resets
* internal tags cache.
*
* @param bool|string $getFieldNames Specify true to return associative array where keys are tags and values are field names
* …or specify the string "reset" to force getTags() to reset its cache, forcing it to reload on the next call.
* @return array
* @since 3.0.106
*
*/
public function getTags($getFieldNames = false) {
if($getFieldNames === 'reset') {
$this->tagList = null;
return array();
}
if($this->tagList === null) {
$tagList = array();
foreach($this as $field) {
/** @var Field $field */
$fieldTags = $field->getTags();
foreach($fieldTags as $tag) {
if(!isset($tagList[$tag])) $tagList[$tag] = array();
$tagList[$tag][] = $field->name;
}
}
ksort($tagList);
$this->tagList = $tagList;
}
if($getFieldNames) return $this->tagList;
$tagList = array();
foreach($this->tagList as $tag => $fieldNames) {
$tagList[$tag] = $tag;
}
return $tagList;
}
/**
* Return all fields that have the given $tag
*
* Returns an associative array of `['field_name' => 'field_name']` if `$getFieldNames` argument is true,
* or `['field_name => Field instance]` if not (which is the default).
*
* @param string $tag Tag to find fields for
* @param bool $getFieldNames If true, returns array of field names rather than Field objects (default=false).
* @return array Array of Field objects, or array of field names if requested. Array keys are always field names.
* @since 3.0.106
*
*/
public function findByTag($tag, $getFieldNames = false) {
$tags = $this->getTags(true);
$items = array();
if(!isset($tags[$tag])) return $items;
foreach($tags[$tag] as $fieldName) {
$items[$fieldName] = ($getFieldNames ? $fieldName : $this->get($fieldName));
}
ksort($items);
return $items;
}
/**
* Get all flag names or get all flag names for given flags or Field
*
* #pw-internal
*
* @param int|Field|null $flags Specify flags or Field or omit to get all flag names
* @param bool $getString Get a string of flag names rather than array? (default=false)
* @return array|string When array is returned, array is of strings indexed by flag value (int)
*
*/
public function getFlagNames($flags = null, $getString = false) {
if($flags === null) {
$a = $this->flagNames;
} else {
$a = array();
if($flags instanceof Field) $flags = $flags->flags;
foreach($this->flagNames as $flag => $name) {
if($flags & $flag) $a[$flag] = $name;
}
}
return $getString ? implode(' ', $a) : $a;
}
/**
* Overridden from WireSaveableItems to retain keys with 0 values and remove defaults we don't need saved
*
* #pw-internal
*
* @param array $value
* @return string of JSON
*
@@ -910,5 +1096,46 @@ class Fields extends WireSaveableItems {
*/
public function ___changeTypeReady(Saveable $item, Fieldtype $fromType, Fieldtype $toType) { }
/**
* Get Fieldtypes compatible (for type change) with given Field
*
* #pw-internal
*
* @param Field $field
* @return array Array of Fieldtype objects indexed by class name
* @since 3.0.140
*
*/
public function getCompatibleFieldtypes(Field $field) {
$fieldtype = $field->type;
if($fieldtype) {
// ask fieldtype what is compatible
$fieldtypes = $fieldtype->getCompatibleFieldtypes($field);
if(!$fieldtypes || !$fieldtypes instanceof WireArray) {
$fieldtypes = $this->wire(new Fieldtypes());
}
// ensure original is present
$fieldtypes->prepend($fieldtype);
} else {
// allow all
$fieldtypes = $this->wire('fieldtypes');
}
return $fieldtypes;
}
/**
* Get FieldsIndexTools instance
*
* #pw-internal
*
* @return FieldsTableTools
* @since 3.0.150
*
*/
public function tableTools() {
if($this->tableTools === null) $this->tableTools = $this->wire(new FieldsTableTools());
return $this->tableTools;
}
}

View File

@@ -0,0 +1,403 @@
<?php namespace ProcessWire;
/**
* ProcessWire Fields Table and Index tools
*
* #pw-summary Methods for managing DB tables and indexes for fields, and related methods. Accessed from `$fields->tableTools()`.
*
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* @since 3.0.150
*
* #pw-internal
*
*/
class FieldsTableTools extends Wire {
/**
* Find duplicate rows for a specific column in a fields table
*
* #pw-internal
*
* @param Field $field
* @param array $options
* - `column` (string): Name of column to find duplicate values in (default='data')
* - `value` (bool|string): Value to find duplicates of, or false to find all duplicate values (default=false)
* - `verbose` (bool): Include entire DB rows in returned result? (default=false)
* @return array Returns array of arrays where each item contains indexes of 'count' (int) and 'value' (int|string), plus,
* if the `verbose` option is true, returned value also adds a `rows` index (array) containing contents of entire matching DB rows.
*
*/
public function findDuplicateRows(Field $field, array $options = array()) {
$defaults = array(
'column' => 'data',
'value' => false,
'verbose' => false,
);
$options = array_merge($defaults, $options);
$result = array();
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$table = $database->escapeTable($field->getTable());
$col = $database->escapeCol($options['column']);
$sql = "SELECT $col, COUNT($col) FROM $table ";
if($options['value'] !== false) {
if($options['value'] === null) {
$sql .= "WHERE $col IS NULL ";
} else {
$sql .= "WHERE $col=:val ";
}
}
$sql .= "GROUP BY $col HAVING COUNT($col) > 1";
$query = $database->prepare($sql);
if($options['value'] !== false && $options['value'] !== null) {
$query->bindValue(':val', $options['value']);
}
$query->execute();
while($row = $query->fetch(\PDO::FETCH_NUM)) {
$result[] = array('value' => $row[0], 'count' => (int) $row[1]);
}
$query->closeCursor();
if($options['verbose']) {
foreach($result as $key => $item) {
$result[$key]['rows'] = array();
$sql = "SELECT * FROM $table WHERE $col=:val";
$query = $database->prepare($sql);
$query->bindValue(':val', $item['value']);
$query->execute();
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
$result[$key]['rows'][] = $row;
}
$query->closeCursor();
}
}
return $result;
}
/**
* Add or remove a unique index for a field on its 'data' column
*
* #pw-internal
*
* @param Field $field
* @param bool $add Specify false to remove index rather than add (default=true)
* @return bool|int Returns one of the following when adding index:
* - `true` (bool): When index successfully added.
* - `false` (bool): Index cannot be added because there are non-unique rows already present (not allowed).
* - `1` (int): Unique index already present so was not necessary (not needed).
* - `0` (int): Requested column does not exist in table so cannot be added as index (not allowed).
* Returns one of the following when removing index:
* - `true` (bool): When index successfully removed.
* - `false` (bool): When index failed to remove.
* - `1` (int): When remove index but there is no unique index to remove (not needed).
* - `0` (int): When remove index that is not one we have previously added (not allowed).
* @throws \PDOException When given invalid column name or unknown error condition
*
*/
public function setUniqueIndex(Field $field, $add = true) {
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$col = 'data';
$table = $database->escapeTable($field->getTable());
$uniqueIndexName = $this->hasUniqueIndex($field, $col);
$requireIndexName = $database->escapeCol($col . '_unique');
$action = ''; // whether to 'add' or 'remove' flag and property from Field
if($uniqueIndexName) {
// already has unique index for indicated column
if($add) {
// already has unique index name
$result = 1;
$action = 'add';
} else {
// remove requested
if($uniqueIndexName === $requireIndexName) {
// remove the unique index
$sql = "ALTER TABLE $table DROP INDEX `$requireIndexName`";
try {
$result = $database->exec($sql) !== false;
if($result) $action = 'remove';
} catch(\Exception $e) {
$result = false;
}
} else {
// unique index present but its not one we previously added
$result = 0;
$action = 'remove';
}
}
} else if($add) {
// no unique index yet exists for column, so add one
$col = $database->escapeCol($col);
$sql = "ALTER TABLE $table ADD UNIQUE `$requireIndexName` (`$col`)";
try {
$result = $database->exec($sql) !== false;
if($result) $action = 'add';
} catch(\Exception $e) {
$action = 'remove';
if($e->getCode() == 23000) {
// non unique rows already present
$result = false;
} else if($e->getCode() == 42000) {
// requested column does not exist
$result = 0;
} else {
throw $e;
}
}
} else {
// remove properties indicating unique
if($field->hasFlag(Field::flagUnique) || $field->flagUnique) $action = 'remove';
$result = 1;
}
if($action) {
$save = false;
if($action === 'add') {
if(!$field->hasFlag(Field::flagUnique)) $save = $field->addFlag(Field::flagUnique);
if(!$field->flagUnique) $save = $field->set('flagUnique', true);
} else if($action === 'remove') {
if($field->hasFlag(Field::flagUnique)) $save = $field->removeFlag(Field::flagUnique);
if($field->flagUnique) $save = $field->remove('flagUnique');
}
if($save) $field->save();
}
return $result;
}
/**
* Does given field have a unique index on column?
*
* #pw-internal
*
* @param Field $field
* @param string $col
* @return bool|string Returns index name when present, or boolean false when not
*
*/
public function hasUniqueIndex(Field $field, $col = 'data') {
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$table = $database->escapeTable($field->getTable());
$sql = "SHOW INDEX FROM $table";
$query = $database->prepare($sql);
$query->execute();
$has = false;
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
if($row['Column_name'] === $col && !$row['Non_unique']) {
$has = $row['Key_name'];
break;
}
}
$query->closeCursor();
return $has;
}
/**
* Check state of field unique 'data' index and update as needed
*
* @param Field $field
* @param bool $verbose Show messages when changes made? (default=true)
* @throws WireException
*
*/
public function checkUniqueIndex(Field $field, $verbose = true) {
static $checking = false;
if($checking) return;
$col = 'data';
$session = $this->wire('session');
if($verbose && !$session) return;
// is unique index requested?
$useUnique = (bool) $field->get('flagUnique');
// ise unique index already present?
$hasUnique = (bool) $field->hasFlag(Field::flagUnique);
if($useUnique === $hasUnique) return;
if(!$this->database->tableExists($field->getTable())) return;
$checking = true;
if($useUnique && !$hasUnique) {
// add unique index
$qty = $this->deleteEmptyRows($field, $col);
if($qty && $verbose) {
$session->message(sprintf($this->_('Deleted %d empty row(s) for field %s'), $qty, $field->name));
}
$result = $this->setUniqueIndex($field, true);
if($result === false && $verbose) {
$msg = $this->_('Unique index cannot be added yet because there are already non-unique row(s) present:') . ' ';
$rows = $this->findDuplicateRows($field, array('verbose' => true, 'column' => $col));
foreach($rows as $row) {
$ids = array();
foreach($row['rows'] as $a) {
$ids[] = $a['pages_id'];
}
$msg .= "\n$row[value]" .
sprintf($this->_('Appears %d times'), $row['count']) . ' ' .
sprintf($this->_('(pages: %s)'), implode(', ', $ids)) . ' ';
}
$session->error($msg, Notice::noGroup);
} else if($result && $verbose) {
$session->message($this->_('Added unique index'));
}
} else if($hasUnique && !$useUnique) {
// remove unique index
$result = $this->setUniqueIndex($field, false);
if($result && $verbose) $session->message($this->_('Removed unique index'));
}
$checking = false;
}
/**
* Delete rows having empty column value
*
* @param Field $field
* @param string $col Column name (default='data')
* @param bool $strict When true, delete not allowed if there are columns other than one given and 'pages_id' (default=true)
* @return bool|int Returns false if delete not allowed, otherwise returns int with # of rows deleted
* @throws WireException
*
*/
public function deleteEmptyRows(Field $field, $col = 'data', $strict = true) {
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$table = $database->escapeTable($field->getTable());
$fieldtype = $field->type;
$schema = $fieldtype->getDatabaseSchema($field);
$wheres = array();
$types = array(
'INT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT',
'TEXT', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT',
'DATE', 'TIME', 'DATETIME', 'TIMESTAMP',
'CHAR', 'VARCHAR',
);
unset($schema['keys'], $schema['pages_id'], $schema['xtra']);
if(!isset($schema[$col])) return false; // if there's no schema for this column, fail
if($strict && count($schema) > 1) return false; // if there are other columns too, fail
$type = strtoupper($schema[$col]);
$allowNull = strpos($type, 'NOT NULL') === false;
if(strpos($type, ' ')) list($type,) = explode(' ', $type, 2);
if(strpos($type, '(')) list($type,) = explode('(', $type, 2);
if(!in_array(trim($type), $types)) return false; // if not in allowed col types, fail
if($col !== 'data') {
$col = $database->escapeCol($this->sanitizer->fieldName($col));
if(empty($col)) return false;
}
if(strpos($type, 'INT') !== false) {
if($fieldtype->isEmptyValue($field, 0)) {
$wheres[] = "$col=0";
}
} else if($fieldtype->isEmptyValue($field, '')) {
$wheres[] = "$col=''";
}
if($allowNull) {
$wheres[] = "$col IS NULL";
}
if(count($wheres)) {
// delete empty rows matching our conditions
$sql = "DELETE FROM $table WHERE " . implode(' OR ', $wheres);
$query = $database->prepare($sql);
$result = $query->execute() ? $query->rowCount() : 0;
$query->closeCursor();
} else {
// no empty rows possible
$result = true;
}
return $result;
}
/**
* Create a checkbox Inputfield to configure unique value state
*
* @param Field $field
* @return InputfieldCheckbox
*
*/
public function getUniqueIndexInputfield(Field $field) {
$col = 'data';
$modules = $this->wire('modules'); /** @var Modules $modules */
if((bool) $field->flagUnique != $field->hasFlag(Field::flagUnique)) {
$this->checkUniqueIndex($field, true);
}
$f = $modules->get('InputfieldCheckbox'); /** @var InputfieldCheckbox $f */
$f->attr('name', "flagUnique");
$f->label = $this->_('Unique');
$f->icon = 'hand-stop-o';
$f->description = $this->_('When checked, a given value may not be used more than once in this field, and thus may not appear on more than one page.');
if($this->hasUniqueIndex($field, $col)) {
$f->attr('checked', 'checked');
if(!$field->hasFlag(Field::flagUnique)) $field->addFlag(Field::flagUnique);
if(!$field->flagUnique) $field->flagUnique = true;
}
return $f;
}
/**
* Does given value exist anywhere in field table?
*
* @param Field $field
* @param string|int $value
* @param string $col
* @return int Returns page ID where value exists, if found. Otherwise returns 0.
* @throws WireException
*
*/
public function valueExists(Field $field, $value, $col = 'data') {
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$table = $database->escapeTable($field->getTable());
if($col !== 'data') $col = $database->escapeCol($this->sanitizer->fieldName($col));
$sql = "SELECT pages_id FROM $table WHERE $col=:val LIMIT 1";
$query = $database->prepare($sql);
$query->bindValue(':val', $value);
$query->execute();
$pageId = $query->rowCount() ? (int) $query->fetchColumn() : 0;
$query->closeCursor();
return $pageId;
}
}

View File

@@ -77,6 +77,14 @@ abstract class Fieldtype extends WireData implements Module {
*/
protected $loadPageFieldFilters = null;
/**
* Field that last referenced this Fieldtype from $field->type
*
* @var Field|null
*
*/
protected $lastAccessField = null;
/**
* Construct
*
@@ -91,6 +99,30 @@ abstract class Fieldtype extends WireData implements Module {
*/
public function init() { }
/**
* Set last access field
*
* #pw-internal
*
* @param Field $field
*
*/
public function setLastAccessField(Field $field) {
$this->lastAccessField = $field;
}
/**
* Return field that last accessed this Fieldtype via $field->type
*
* #pw-internal
*
* @return null|Field
*
*/
public function getLastAccessField() {
return $this->lastAccessField;
}
/**
* Fieldtype modules are singular, in that only one instance is needed per request
*
@@ -462,6 +494,23 @@ abstract class Fieldtype extends WireData implements Module {
return '';
}
/**
* Is given value one that should cause the DB row(s) to be deleted rather than saved?
*
* Not applicable to Fieldtypes that override the savePageField() method with their own
* implementation, unless they also use this method.
*
* @param Page $page
* @param Field $field
* @param mixed $value
* @return bool
* @since 3.0.150
*
*/
public function isDeleteValue(Page $page, Field $field, $value) {
return $value === $this->getBlankValue($page, $field);
}
/**
* Return whether the given value is considered empty or not.
*
@@ -472,6 +521,16 @@ abstract class Fieldtype extends WireData implements Module {
* Example: an integer or text Fieldtype might not consider a "0" to be empty,
* whereas a Page reference would.
*
* This method is primarily used by the PageFinder::whereEmptyValuePossible()
* method to determine whether to include non-present (null) rows.
*
* 3.0.164+: If given a Selector object for $value, PageFinder is proposing
* handling the empty-value match condition internally rather than calling
* the Fieldtypes getMatchQuery() method. Return true if this Fieldtype would
* prefer to handle the match, or false if not. Fieldtype modules do not need
* to consider this unless they want to override the default empty value match
* behavior in PageFinder::whereEmptyValuePossible().
*
* #pw-group-finding
*
* @param Field $field
@@ -573,7 +632,7 @@ abstract class Fieldtype extends WireData implements Module {
*
* #pw-internal
*
* @param array Field $field
* @param Field $field
* @return array
*
*/
@@ -644,7 +703,7 @@ abstract class Fieldtype extends WireData implements Module {
*
* #pw-group-finding
*
* @param DatabaseQuerySelect $query
* @param PageFinderDatabaseQuerySelect $query
* @param string $table The table name to use
* @param string $subfield Name of the subfield (typically 'data', unless selector explicitly specified another)
* @param string $operator The comparison operator
@@ -662,9 +721,8 @@ abstract class Fieldtype extends WireData implements Module {
$table = $database->escapeTable($table);
$subfield = $database->escapeCol($subfield);
$quoteValue = $database->quote($value);
$query->where("{$table}.{$subfield}{$operator}$quoteValue"); // QA
$operator = $database->escapeOperator($operator, WireDatabasePDO::operatorTypeComparison);
$query->where("{$table}.{$subfield}{$operator}?", $value); // QA
return $query;
}
@@ -781,6 +839,21 @@ abstract class Fieldtype extends WireData implements Module {
return $schema;
}
/**
* Get class name to use Field objects of this type (must be class that extends Field class)
*
* Return blank if default class (Field) should be used.
*
* @param array $a Field data from DB (if needed)
* @return string Return class name or blank to use default Field class
* @since 3.0.146
*
*/
public function getFieldClass(array $a = array()) {
if($a) {} // ignore
return '';
}
/**
* Returns verbose array of database schema information
*
@@ -871,16 +944,73 @@ abstract class Fieldtype extends WireData implements Module {
/**
* Return trimmed database schema array of any parts that aren't needed for data loading
* Trim and/or filter database schema
*
* Returns schema with all native columns and schema settings removed,
* leaving just the custom columns for the schema, optionally filtered
* by given $options array.
*
* #pw-internal
*
* @param array $schema
* @param array $schema Schema from getDatabaseSchema() call (not verbose schema)
* @param array $options Additional options (since 3.0.158)
* - `trimMeta` (bool): Trim meta data from schema, like 'keys' and 'xtra'? (default=true)
* - `trimDefault` (bool): Trim default columns from schema, like 'pages_id' and 'sort'? (default=true)
* - `findDefaultNULL` (bool): When true, return all columns that specify NULL as their default.
* - `findAutoIncrement` (bool): When true, return all columns that specify AUTO_INCREMENT.
* - `findType` (string): Return all columns that match the given column type (i.e. "int", "varchar", "text")
* Precede types like "int" or "text" with "*" to match all of type (i.e. "*int" matches "tinyint", "int", etc.)
* @return array
*
*/
public function trimDatabaseSchema(array $schema) {
unset($schema['pages_id'], $schema['keys'], $schema['xtra'], $schema['sort']);
public function trimDatabaseSchema(array $schema, array $options = array()) {
$defaults = array(
'trimMeta' => true,
'trimDefault' => true, // trim default columns (like pages_id and sort) from result?
'findType' => '',
'findAutoIncrement' => false,
'findDefaultNULL' => false,
);
$options = array_merge($defaults, $options);
if($options['trimMeta']) unset($schema['keys'], $schema['xtra']);
if($options['trimDefault']) unset($schema['pages_id'], $schema['sort']);
$findType = $options['findType'] ? strtolower(trim($options['findType'], '*')) : false; // find in column type
$findAllType = $findType && $findType !== $options['findType']; // find all variations of findType
$useFind = $findType || $options['findAutoIncrement'] || $options['findDefaultNULL'];
// exit early if no finds are requested
if(!$useFind) return $schema;
foreach($schema as $colName => $colSchema) {
$match = null;
$colSchema = strtolower($colSchema) . ' ';
list($colType, $colMeta) = explode(' ', $colSchema, 2);
if($options['findAutoIncrement'] && $match !== false) {
$match = strpos($colMeta, 'auto_increment') !== false;
}
if($options['findDefaultNULL'] && $match !== false) {
$match = strpos($colMeta, 'default null') !== false;
}
if($findType && $match !== false && strpos($colType, $findType) !== false) {
if($colType === $findType || strpos($colType, "$findType ") === 0 || strpos($colType, "$findType(") === 0) {
$match = true; // exact match
} else if($findAllType && strpos($colType, $findType) === 0) {
$match = true; // partial match at front, i.e. "int" matching integer
} else if($findAllType && preg_match('/^[a-z]*' . $findType . '\b/i', $colType)) {
$match = true; // partial match at rear, i.e. "int" matching "tinyint", "smallint", "mediumint", etc.
}
}
if(!$match) unset($schema[$colName]);
}
return $schema;
}
@@ -920,16 +1050,18 @@ abstract class Fieldtype extends WireData implements Module {
if(!$page->id || !$field->id) return null;
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$page_id = (int) $page->id;
$schema = $this->getDatabaseSchema($field);
$table = $database->escapeTable($field->table);
$value = null;
$stmt = null;
/** @var DatabaseQuerySelect $query */
$query = $this->wire(new DatabaseQuerySelect());
$query = $this->getLoadQuery($field, $query);
$query->where("$table.pages_id='$page_id'");
$bindKey = $query->bindValueGetKey($page->id);
$query->where("$table.pages_id=$bindKey");
$query->from($table);
try {
@@ -980,6 +1112,7 @@ abstract class Fieldtype extends WireData implements Module {
*
*/
public function ___loadPageFieldFilter(Page $page, Field $field, $selector) {
if(false) throw new WireException(); // a gift for the ide
$this->setLoadPageFieldFilters($field, $selector);
$value = $this->loadPageField($page, $field);
$this->setLoadPageFieldFilters($field, null);
@@ -1071,7 +1204,7 @@ abstract class Fieldtype extends WireData implements Module {
* @param Page $page Page object to save.
* @param Field $field Field to retrieve from the page.
* @return bool True on success, false on DB save failure.
* @throws WireException
* @throws WireException|\PDOException|WireDatabaseException
*
*/
public function ___savePageField(Page $page, Field $field) {
@@ -1085,20 +1218,24 @@ abstract class Fieldtype extends WireData implements Module {
$database = $this->wire('database');
$value = $page->get($field->name);
// if the value is the same as the default, then remove the field from the database because it's redundant
if($value === $this->getBlankValue($page, $field)) return $this->deletePageField($page, $field);
// if the value is one that should be deleted, then remove the field from the database because it's redundant
if($this->isDeleteValue($page, $field, $value)) {
return $this->deletePageField($page, $field);
}
$value = $this->sleepValue($page, $field, $value);
$page_id = (int) $page->id;
$table = $database->escapeTable($field->table);
$schema = array();
$bindValues = array(':page_id' => $page_id);
if(is_array($value)) {
$sql1 = "INSERT INTO `$table` (pages_id";
$sql2 = "VALUES('$page_id'";
$sql2 = "VALUES(:page_id";
$sql3 = "ON DUPLICATE KEY UPDATE ";
$n = 0;
foreach($value as $k => $v) {
$k = $database->escapeCol($k);
@@ -1109,8 +1246,9 @@ abstract class Fieldtype extends WireData implements Module {
if(empty($schema)) $schema = $this->getDatabaseSchema($field);
$sql2 .= isset($schema[$k]) && stripos($schema[$k], ' DEFAULT NULL') ? ",NULL" : ",''";
} else {
$v = $database->escapeStr($v);
$sql2 .= ",'$v'";
$bindKey = ':v' . (++$n);
$bindValues[$bindKey] = $v;
$sql2 .= ",$bindKey";
}
$sql3 .= "`$k`=VALUES(`$k`), ";
@@ -1123,18 +1261,36 @@ abstract class Fieldtype extends WireData implements Module {
if(is_null($value)) {
// check if schema explicitly allows NULL
$schema = $this->getDatabaseSchema($field);
$value = isset($schema['data']) && stripos($schema['data'], ' DEFAULT NULL') ? "NULL" : "''";
$null = isset($schema['data']) && stripos($schema['data'], ' DEFAULT NULL') ? "NULL" : "''";
$sql = "INSERT INTO `$table` (pages_id, data) VALUES(:page_id, $null) ";
} else {
$value = "'" . $database->escapeStr($value) . "'";
$bindValues[":value"] = $value;
$sql = "INSERT INTO `$table` (pages_id, data) VALUES(:page_id, :value) ";
}
$sql = "INSERT INTO `$table` (pages_id, data) " .
"VALUES('$page_id', $value) " .
"ON DUPLICATE KEY UPDATE data=VALUES(data)";
$sql .= 'ON DUPLICATE KEY UPDATE data=VALUES(data)';
}
$query = $database->prepare($sql);
$result = $query->execute();
foreach($bindValues as $bindKey => $bindValue) {
if(is_int($bindValue)) {
$query->bindValue($bindKey, $bindValue, \PDO::PARAM_INT);
} else {
$query->bindValue($bindKey, $bindValue);
}
}
try {
$result = $query->execute();
} catch(\PDOException $e) {
if($e->getCode() == 23000) {
$message = sprintf($this->_('Value not allowed for field “%2$s” because it is already in use'), $field->name);
throw new WireDatabaseException($message, $e->getCode(), $e);
} else {
throw $e;
}
}
return $result;
}
@@ -1329,6 +1485,7 @@ abstract class Fieldtype extends WireData implements Module {
*
*/
public function ___install() {
if(false) throw new WireException(); // an offering for phpstorm
return true;
}
@@ -1371,6 +1528,7 @@ abstract class Fieldtype extends WireData implements Module {
*/
public function ___upgrade($fromVersion, $toVersion) {
// any code needed to upgrade between versions
if($fromVersion && $toVersion && false) throw new WireException(); // to make the ide stop complaining
}
/**

View File

@@ -42,6 +42,14 @@ abstract class FieldtypeMulti extends Fieldtype {
*/
protected static $getMatchQueryCount = 0;
/**
* Do we currently have a locked table?
*
* @var bool
*
*/
protected $lockedTable = false;
/**
* Modify the default schema provided by Fieldtype to include a 'sort' field, and integrate that into the primary key.
*
@@ -192,27 +200,28 @@ abstract class FieldtypeMulti extends Fieldtype {
}
return $values;
}
/**
* Per the Fieldtype interface, Save the given Field from the given Page to the database
*
* Because the number of values may have changed, this method plays it safe and deletes all the old values
* and reinserts them as new.
* and reinserts them as new.
*
* @param Page $page
* @param Field $field
* @return bool
* @throws \Exception|WireException on failure
* @throws \PDOException|WireException|WireDatabaseQueryException on failure
*
*/
public function ___savePageField(Page $page, Field $field) {
if(!$page->id || !$field->id) return false;
$database = $this->wire('database');
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
$config = $this->wire('config'); /** @var Config $config */
$useTransaction = $database->allowTransaction();
$values = $page->get($field->name);
$schema = array();
if(is_object($values)) {
if(!$values->isChanged() && !$page->isChanged($field->name)) return true;
} else if(!$page->isChanged($field->name)) {
@@ -224,83 +233,123 @@ abstract class FieldtypeMulti extends Fieldtype {
return $this->savePageFieldRows($page, $field, $values);
}
$values = $this->sleepValue($page, $field, $values);
$table = $database->escapeTable($field->table);
$page_id = (int) $page->id;
$values = $this->sleepValue($page, $field, $values);
$table = $database->escapeTable($field->table);
$page_id = (int) $page->id;
$schema = $this->getDatabaseSchema($field);
$useSort = isset($schema['sort']);
// since we don't manage IDs of existing values for multi fields, we delete the existing data and insert all of it again
$query = $database->prepare("DELETE FROM `$table` WHERE pages_id=:page_id"); // QA
$query->bindValue(":page_id", $page_id, \PDO::PARAM_INT);
$query->execute();
// use transaction when possible
if($useTransaction) $database->beginTransaction();
try {
// since we don't manage IDs of existing values for multi fields, we delete the existing data and insert all of it again
$query = $database->prepare("DELETE FROM `$table` WHERE pages_id=:page_id"); // QA
$query->bindValue(":page_id", $page_id, \PDO::PARAM_INT);
$query->execute();
} catch(\Exception $e) {
if($useTransaction) $database->rollBack();
if($config->allowExceptions) throw $e; // throw original
throw new WireDatabaseQueryException($e->getMessage(), $e->getCode(), $e);
}
if(count($values)) {
if(!count($values)) {
// no values to insert, exit early
if($useTransaction) $database->commit();
return true;
}
// get first value to find key definition
$value = reset($values);
// if the first value is not an associative (key indexed) array, then force it to be with 'data' as the key.
// this is to allow for this method to be able to save fields that have more than just a 'data' field,
// even though most instances will probably just use only the data field
// if the first value is not an associative (key indexed) array, then force it to be with 'data' as the key.
// this is to allow for this method to be able to save fields that have more than just a 'data' field,
// even though most instances will probably just use only the data field
$value = reset($values); // first value to find definitions
if(is_array($value)) {
unset($value['pages_id'], $value['sort']); // likely not present, but just in case
$keys = array_keys($value);
foreach($keys as $k => $v) $keys[$k] = $database->escapeTableCol($v);
} else {
$keys = array('data');
}
// $keys is just the columns unique to the Fieldtype
// whereas $cols is same as keys except it also has pages_id and sort
if(is_array($value)) {
$keys = array_keys($value);
foreach($keys as $k => $v) $keys[$k] = $database->escapeTableCol($v);
} else {
$keys = array('data');
$cols = array('pages_id');
if($useSort) $cols[] = 'sort';
foreach($keys as $col) $cols[] = $col;
$intCols = $this->trimDatabaseSchema($schema, array('findType' => '*int', 'trimDefault' => false));
$nullers = false;
$sql = "INSERT INTO `$table` (`" . implode('`, `', $cols) . "`) VALUES(:" . implode(', :', $cols) . ")";
$query = $database->prepare($sql);
$query->bindValue(':pages_id', $page_id, \PDO::PARAM_INT);
$sort = 0;
$result = true;
$exception = false;
// cycle through the values to generate the query
foreach($values as $value) {
if($useSort) {
$query->bindValue(':sort', $sort, \PDO::PARAM_INT);
}
$sql = "INSERT INTO `$table` (pages_id, sort, `" . implode('`, `', $keys) . "`) VALUES";
$sort = 0;
// if the value is not an associative array, then force it to be one
if(!is_array($value)) {
$value = array('data' => $value);
}
// cycle through the values to generate the query
foreach($values as $value) {
$sql .= "($page_id, $sort, ";
// if the value is not an associative array, then force it to be one
if(!is_array($value)) $value = array('data' => $value);
// cycle through the keys, which represent DB fields (i.e. data, description, etc.) and generate the insert query
foreach($keys as $key) {
$v = isset($value[$key]) ? $value[$key] : null;
if(is_null($v)) {
// value is NULL, determine how to handle it
if(empty($schema)) $schema = $this->getDatabaseSchema($field);
$useNULL = false;
if(isset($schema[$key])) {
if(stripos($schema[$key], ' DEFAULT NULL')) {
// use the default NULL value
$useNULL = true;
} else if(stripos($schema[$key], ' AUTO_INCREMENT')) {
// potentially a primary key, some SQL modes require NULL (rather than blank) for auto increment
$useNULL = true;
}
}
$sql .= $useNULL ? "NULL, " : "'', ";
// cycle through the keys, which represent DB fields (i.e. data, description, etc.) and generate the insert query
foreach($keys as $key) {
$val = isset($value[$key]) ? $value[$key] : null;
if($val === null) {
// null column
// some SQL modes require NULL for auto_increment primary key (rather than blank)
if(isset($schema[$key]) && $nullers === false) $nullers = array_merge(
$this->trimDatabaseSchema($schema, array('findDefaultNULL' => true)),
$this->trimDatabaseSchema($schema, array('findAutoIncrement' => true))
);
if($nullers && isset($nullers[$key])) {
$query->bindValue(":$key", null, \PDO::PARAM_NULL);
} else {
$sql .= "'" . $database->escapeStr("$v") . "', ";
$query->bindValue(":$key", '');
}
} else if(isset($intCols[$key])) {
// integer column
$query->bindValue(":$key", (int) $val, \PDO::PARAM_INT);
} else {
// string column
$query->bindValue(":$key", $val);
}
$sql = rtrim($sql, ", ") . "), ";
$sort++;
}
$sql = rtrim($sql, ", ");
$query = $database->prepare($sql);
}
try {
$result = $query->execute();
} catch(\Exception $e) {
if($this->wire('config')->allowExceptions) throw $e; // throw original
$msg = $e->getMessage();
if($this->wire('config')->debug && $this->wire('config')->advanced) $msg .= "\n$sql";
throw new WireException($msg); // throw WireException
$exception = $e;
}
return $result;
if($exception) break;
$sort++;
}
if($exception) {
/** @var \PDOException $exception */
if($useTransaction) $database->rollBack();
if($config->allowExceptions) throw $exception; // throw original
throw new WireDatabaseQueryException($exception->getMessage(), $exception->getCode(), $exception);
} else {
if($useTransaction) $database->commit();
}
return true;
return $result;
}
/**
* Load the given page field from the database table and return the value.
*
@@ -327,15 +376,17 @@ abstract class FieldtypeMulti extends Fieldtype {
if(!$page->id || !$field->id) return null;
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$page_id = (int) $page->id;
$schema = $this->getDatabaseSchema($field);
$table = $database->escapeTable($field->table);
$stmt = null;
/** @var DatabaseQuerySelect $query */
$query = $this->wire(new DatabaseQuerySelect());
$query = $this->getLoadQuery($field, $query);
$query->where("$table.pages_id='$page_id'");
$bindKey = $query->bindValueGetKey($page->id);
$query->where("$table.pages_id=$bindKey");
$query->from($table);
try {
@@ -526,7 +577,6 @@ abstract class FieldtypeMulti extends Fieldtype {
$table = $database->escapeTable($table);
// note the Fulltext class can handle non-text values as well (when using non-partial text matching operators)
$ft = new DatabaseQuerySelectFulltext($query);
$this->wire($ft);
$ft->match($table, $col, $operator, $value);
return $query;
}
@@ -606,35 +656,26 @@ abstract class FieldtypeMulti extends Fieldtype {
$hasInserts = false;
$sort = null;
$numSaved = 0;
$locked = false;
// sleep the values for storage
$sleepValue = $this->sleepValue($page, $field, $value);
try {
// attempt lock if possible
if($database->exec("LOCK TABLES `$table` WRITE"));
$locked = true;
} catch(\Exception $e) {
// nothing ever happened, it's all just stories
}
$this->lockForWriting($field);
if(isset($schema['sort'])) {
// determine if there are any INSERTs and what the next sort value(s) should be
// this is because "pages_id,sort" are generally a unique index with FieldtypeMulti
$maxSort = 0;
foreach($sleepValue as $v) {
if(!is_array($v)) continue;
$id = isset($v[$primaryKey]) ? $v[$primaryKey] : 0;
if(!$id) $hasInserts = true;
if(isset($v['sort']) && $v['sort'] > $maxSort) $maxSort = $v['sort'];
}
if($hasInserts) {
// determine max sort value for new items inserted
$sql = "SELECT MAX(sort) FROM `$table` WHERE pages_id=:pages_id";
$query = $database->prepare($sql);
$query->bindValue(':pages_id', $page->id, \PDO::PARAM_INT);
$query->execute();
$sort = (int) $query->fetchColumn();
$query->closeCursor();
$sort = $this->getMaxColumnValue($page, $field, 'sort', -1);
if($maxSort > $sort) $sort = $maxSort;
}
}
@@ -679,18 +720,82 @@ abstract class FieldtypeMulti extends Fieldtype {
$this->error($e->getMessage(), $this->wire('user')->isSuperuser() ? Notice::logOnly : Notice::log);
}
}
if($locked) {
try {
$database->exec("UNLOCK TABLES");
} catch(\Exception $e) {
// indeed there is no thing here
}
}
$this->unlockForWriting();
return $numSaved;
}
/**
* Lock field table for writing
*
* @param Field $field
* @return bool
*
*/
protected function lockForWriting(Field $field) {
$database = $this->wire('database');
$table = $database->escapeTable($field->getTable());
$locked = false;
try {
// attempt lock if possible
if($database->exec("LOCK TABLES `$table` WRITE") !== false) {
$this->lockedTable = true;
$locked = true;
}
} catch(\Exception $e) {
// ignore
}
return $locked;
}
/**
* Unlock for writing
*
* @return bool
*
*/
protected function unlockForWriting() {
$result = false;
if($this->lockedTable) try {
$this->wire('database')->exec("UNLOCK TABLES");
$this->lockedTable = false;
$result = true;
} catch(\Exception $e) {
// ignore
}
return $result;
}
/**
* Get max value of column for given Page and Field or boolean false (or specified $noValue) if no rows present
*
* @param Page $page
* @param Field $field
* @param string $column
* @param int|bool $noValue Return this value if there are no rows to count from (default=false)
* @return int|bool|mixed
* @throws WireException
* @since 3.0.154
*
*/
protected function getMaxColumnValue(Page $page, Field $field, $column, $noValue = false) {
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$table = $database->escapeTable($field->getTable());
$column = $database->escapeCol($column);
$sql = "SELECT MAX($column) FROM `$table` WHERE pages_id=:pages_id";
$query = $database->prepare($sql);
$query->bindValue(':pages_id', $page->id, \PDO::PARAM_INT);
$query->execute();
$value = $query->fetchColumn();
$query->closeCursor();
if($value === null) return $noValue;
if(!is_int($value) && ctype_digit(ltrim($value, '-'))) $value = (int) $value;
return $value;
}
/**
* Delete specific rows (for tables with single primary key)
*
@@ -755,7 +860,7 @@ abstract class FieldtypeMulti extends Fieldtype {
*
* Possible template method: If overridden, children should NOT call this parent method.
*
* @param DatabaseQuerySelect $query
* @param PageFinderDatabaseQuerySelect $query
* @param string $table The table name to use
* @param string $subfield Name of the field (typically 'data', unless selector explicitly specified another)
* @param string $operator The comparison operator
@@ -772,12 +877,14 @@ abstract class FieldtypeMulti extends Fieldtype {
$database = $this->wire('database');
$table = $database->escapeTable($table);
if($subfield === 'count' && (empty($value) || ctype_digit(ltrim("$value", '-')))
&& in_array($operator, array("=", "!=", ">", "<", ">=", "<="))) {
if($subfield === 'count'
&& (empty($value) || ctype_digit(ltrim("$value", '-')))
&& $database->isOperator($operator, WireDatabasePDO::operatorTypeComparison)) {
$value = (int) $value;
$t = $table . "_" . $n;
$c = $database->escapeTable($this->className()) . "_" . $n;
$operator = $database->escapeOperator($operator);
$query->select("$t.num_$t AS num_$t");
$query->leftjoin(
@@ -791,20 +898,22 @@ abstract class FieldtypeMulti extends Fieldtype {
(in_array($operator, array('>', '>=')) && $value < 0) ||
(in_array($operator, array('=', '>=')) && !$value)) {
// allow for possible zero values
$query->where("(num_$t{$operator}$value OR num_$t IS NULL)"); // QA
$bindKey = $query->bindValueGetKey($value);
$query->where("(num_$t{$operator}$bindKey OR num_$t IS NULL)"); // QA
} else {
// non zero values
$query->where("num_$t{$operator}$value"); // QA
$bindKey = $query->bindValueGetKey($value);
$query->where("num_$t{$operator}$bindKey"); // QA
}
// only allow matches using templates with the requested field
$templates = $field->getTemplates();
if(count($templates)) {
$sql = 'pages.templates_id IN(';
$ids = array();
foreach($templates as $template) {
$sql .= ((int) $template->id) . ',';
$ids[] = (int) $template->id;
}
$sql = rtrim($sql, ',') . ')';
$sql = 'pages.templates_id IN(' . implode(',', $ids) . ')'; // QA
} else {
$sql = 'pages.templates_id=0';
}

View File

@@ -10,6 +10,7 @@
*
*
*/
class Fieldtypes extends WireArray {
/**
@@ -19,12 +20,21 @@ class Fieldtypes extends WireArray {
protected $preloaded = false;
/**
* Construct this Fieldtypes object and load all Fieldtype modules
* Is this the $fieldtypes API var?
*
* @var bool
*
*/
protected $isAPI = false;
/**
* Construct the $fieldtypes API var (load all Fieldtype modules into it)
*
*/
public function init() {
foreach($this->wire('modules') as $module) {
if(strpos($module->className(), 'Fieldtype') === 0) {
$this->isAPI = true;
foreach($this->wire('modules') as $name => $module) {
if(strpos($name, 'Fieldtype') === 0) {
// if($module instanceof ModulePlaceholder) $module = $this->wire('modules')->get($module->className());
$this->add($module);
}
@@ -37,12 +47,13 @@ class Fieldtypes extends WireArray {
*/
protected function preload() {
if($this->preloaded) return;
$debug = $this->wire('config')->debug;
$debug = $this->isAPI && $this->wire('config')->debug;
if($debug) Debug::timer('Fieldtypes.preload');
foreach($this->data as $key => $module) {
$modules = $this->wire('modules'); /** @var Modules $modules */
foreach($this->data as $moduleName => $module) {
if($module instanceof ModulePlaceholder) {
$fieldtype = $this->wire('modules')->get($module->className());
$this->data[$key] = $fieldtype;
$fieldtype = $modules->getModule($moduleName);
$this->data[$moduleName] = $fieldtype;
}
}
if($debug) Debug::saveTimer('Fieldtypes.preload');
@@ -57,11 +68,13 @@ class Fieldtypes extends WireArray {
*
*/
public function isValidItem($item) {
return $item instanceof Fieldtype || $item instanceof ModulePlaceholder;
if($item instanceof Fieldtype) return true;
if($item instanceof ModulePlaceholder && strpos($item->className(), 'Fieldtype') === 0) return true;
return false;
}
/**
* Per the WireArray interface, keys must be strings (field names)
* Per the WireArray interface, keys must be strings (fieldtype class names)
*
* @param string|int $key
* @return bool
@@ -118,12 +131,13 @@ class Fieldtypes extends WireArray {
if(strpos($key, 'Fieldtype') !== 0) $key = "Fieldtype" . ucfirst($key);
if(!$fieldtype = parent::get($key)) {
$fieldtype = $this->wire('modules')->get($key);
$fieldtype = $this->wire('modules')->getModule($key);
if($fieldtype) $this->set($key, $fieldtype);
}
if($fieldtype instanceof ModulePlaceholder) {
$fieldtype = $this->wire('modules')->get($fieldtype->className());
$this->set($key, $fieldtype);
$fieldtype = $this->wire('modules')->getModule($fieldtype->className());
if($fieldtype) $this->set($key, $fieldtype);
}
return $fieldtype;
@@ -146,7 +160,7 @@ class Fieldtypes extends WireArray {
public function eq($num) { $this->preload(); return parent::eq($num); }
public function first() { $this->preload(); return parent::first(); }
public function last() { $this->preload(); return parent::last(); }
public function sort($properties) { $this->preload(); return parent::sort($properties); }
public function sort($properties, $flags = null) { $this->preload(); return parent::sort($properties, $flags); }
protected function filterData($selectors, $not = false) { $this->preload(); return parent::filterData($selectors, $not); }
public function makeCopy() { $this->preload(); return parent::makeCopy(); }
public function makeNew() { $this->preload(); return parent::makeNew(); }

View File

@@ -123,33 +123,43 @@ class FileCompiler extends Wire {
*
*/
public function __construct($sourcePath, array $options = array()) {
$this->options = array_merge($this->options, $options);
$globalOptions = $this->wire('config')->fileCompilerOptions;
if(strpos($sourcePath, '..') !== false) $sourcePath = realpath($sourcePath);
if(DIRECTORY_SEPARATOR != '/') $sourcePath = str_replace(DIRECTORY_SEPARATOR, '/', $sourcePath);
$this->sourcePath = rtrim($sourcePath, '/') . '/';
}
/**
* Wired to instance
*
*/
public function wired() {
/** @var Config $config */
$config = $this->wire('config');
$globalOptions = $config->fileCompilerOptions;
if(is_array($globalOptions)) {
$this->globalOptions = array_merge($this->globalOptions, $globalOptions);
}
if(!empty($this->globalOptions['extensions'])) {
$this->extensions = $this->globalOptions['extensions'];
}
if(empty($this->globalOptions['cachePath'])) {
$this->cachePath = $this->wire('config')->paths->cache . $this->className() . '/';
$this->cachePath = $config->paths->cache . $this->className() . '/';
} else {
$this->cachePath = rtrim($this->globalOptions['cachePath'], '/') . '/';
}
if(!strlen(__NAMESPACE__)) {
// when PW compiled without namespace support
$this->options['skipIfNamespace'] = false;
$this->options['namespace'] = true;
}
if(strpos($sourcePath, '..') !== false) $sourcePath = realpath($sourcePath);
if(DIRECTORY_SEPARATOR != '/') $sourcePath = str_replace(DIRECTORY_SEPARATOR, '/', $sourcePath);
$this->sourcePath = rtrim($sourcePath, '/') . '/';
parent::wired();
}
/**
@@ -159,6 +169,7 @@ class FileCompiler extends Wire {
*
*/
protected function init() {
if(!$this->isWired()) $this->wired();
static $preloaded = false;
$config = $this->wire('config');
@@ -388,7 +399,7 @@ class FileCompiler extends Wire {
$targetData = $this->compileData($targetData, $sourcePathname);
if(false !== file_put_contents($targetPathname, $targetData, LOCK_EX)) {
$this->chmod($targetPathname);
touch($targetPathname, filemtime($sourcePathname));
$this->touch($targetPathname, filemtime($sourcePathname));
$targetHash = md5_file($targetPathname);
$cacheData = array(
'source' => array(
@@ -977,7 +988,7 @@ class FileCompiler extends Wire {
copy($sourceFile, $targetFile);
$this->chmod($targetFile);
touch($targetFile, filemtime($sourceFile));
$this->touch($targetFile, filemtime($sourceFile));
$numCopied++;
}
@@ -1060,7 +1071,7 @@ class FileCompiler extends Wire {
// maintenance already run today
return false;
}
touch($lastRunFile);
$this->touch($lastRunFile);
$this->chmod($lastRunFile);
clearstatcache();
@@ -1110,14 +1121,14 @@ class FileCompiler extends Wire {
if(!file_exists($sourceFile)) {
// source file has been deleted
unlink($targetFile);
$this->wire('files')->unlink($targetFile, true);
if($useLog) $this->log("Maintenance/Remove target file: $targetURL$basename");
} else if(filemtime($sourceFile) != filemtime($targetFile)) {
} else if(filemtime($sourceFile) > filemtime($targetFile)) {
// source file has changed
copy($sourceFile, $targetFile);
$this->chmod($targetFile);
touch($targetFile, filemtime($sourceFile));
$this->touch($targetFile, filemtime($sourceFile));
if($useLog) $this->log("Maintenance/Copy new version of source file to target file: $sourceURL$basename => $targetURL$basename");
}
}
@@ -1162,5 +1173,29 @@ class FileCompiler extends Wire {
$this->exclusions[] = $pathname;
}
/**
* Same as PHP touch() but with fallbacks for cases where touch() does not work
*
* @param string $filename
* @param null|int $time
* @return bool
*
*/
protected function touch($filename, $time = null) {
if($time === null) {
$result = @touch($filename);
} else {
$result = @touch($filename, $time);
// try again, but without time
if(!$result) $result = @touch($filename);
}
if(!$result) {
// lastly try alternative method which should have same affect as touch without $time
$fp = fopen($filename, 'a');
$result = $fp !== false ? fclose($fp) : false;
}
return $result;
}
}

View File

@@ -11,15 +11,73 @@
*/
class FileLog extends Wire {
const defaultChunkSize = 12288;
const debug = false;
protected $logFilename = false;
protected $itemsLogged = array();
/**
* Default size of chunks used for reading from logs
*
*/
const defaultChunkSize = 12288;
/**
* Debug mode used during development of this class
*
*/
const debug = false;
/**
* Chunk size used when reading from logs and not overridden
*
* @var int
*
*/
protected $chunkSize = self::defaultChunkSize;
/**
* Full path to log file or false when not yet set
*
* @var bool|string
*
*/
protected $logFilename = false;
/**
* Log items saved during this request where array keys are md5 hash of log entries and values ignored
*
* @var array
*
*/
protected $itemsLogged = array();
/**
* Delimiter used in log entries
*
* @var string
*
*/
protected $delimeter = "\t";
/**
* Maximum allowed line length for a single log line
*
* @var int
*
*/
protected $maxLineLength = 8192;
/**
* File extension used for log files
*
* @var string
*
*/
protected $fileExtension = 'txt';
/**
* Path where log files are stored
*
* @var string
*
*/
protected $path = '';
/**
@@ -58,52 +116,159 @@ class FileLog extends Wire {
protected function cleanStr($str) {
$str = str_replace(array("\r\n", "\r", "\n"), ' ', trim($str));
if(strlen($str) > $this->maxLineLength) $str = substr($str, 0, $this->maxLineLength);
if(strpos($str, ' ^+') !== false) $str = str_replace(' ^=', ' ^ +', $str); // disallowed sequence
return $str;
}
/**
* Save the given log entry string
*
* @param $str
* @return bool Success state
* @param string $str
* @param array $options options to modify behavior (Added 3.0.143)
* - `allowDups` (bool): Allow duplicating same log entry in same runtime/request? (default=true)
* - `mergeDups` (int): Merge previous duplicate entries that also appear near end of file?
* To enable, specify int for quantity of bytes to consider from EOF, value of 1024 or higher (default=0, disabled)
* - `maxTries` (int): If log entry fails to save, maximum times to re-try (default=20)
* - `maxTriesDelay` (int): Micro seconds (millionths of a second) to delay between re-tries (default=2000)
* @return bool Success state: true if log written, false if not.
*
*/
public function save($str) {
if(!$this->logFilename) return false;
public function save($str, array $options = array()) {
$defaults = array(
'mergeDups' => 0,
'allowDups' => true,
'maxTries' => 20,
'maxTriesDelay' => 2000,
);
if(!$this->logFilename) return false;
$options = array_merge($defaults, $options);
$hash = md5($str);
// if we've already logged this during this instance, then don't do it again
if(in_array($hash, $this->itemsLogged)) return true;
$ts = date("Y-m-d H:i:s");
$str = $this->cleanStr($str);
$fp = fopen($this->logFilename, "a");
$line = $this->delimeter . $str; // log entry, excluding timestamp
$hasLock = false; // becomes true when lock obtained
$fp = false; // becomes resource when file is open
if($fp) {
$trys = 0;
$stop = false;
// if we've already logged this during this instance, then don't do it again
if(!$options['allowDups'] && isset($this->itemsLogged[$hash])) return true;
while(!$stop) {
if(flock($fp, LOCK_EX)) {
fwrite($fp, "$ts{$this->delimeter}$str\n");
flock($fp, LOCK_UN);
$this->itemsLogged[] = $hash;
$stop = true;
} else {
usleep(2000);
if($trys++ > 20) $stop = true;
}
}
// determine write mode
$mode = file_exists($this->logFilename) ? 'a' : 'w';
if($mode === 'a' && $options['mergeDups']) $mode = 'r+';
fclose($fp);
$this->wire('files')->chmod($this->logFilename);
return true;
} else {
// open the log file
for($tries = 0; $tries <= $options['maxTries']; $tries++) {
$fp = fopen($this->logFilename, $mode);
if($fp) break;
// if unable to open for reading/writing, see if we can open for append instead
if($mode === 'r+' && $tries > ($options['maxTries'] / 2)) $mode = 'a';
usleep($options['maxTriesDelay']);
}
// if unable to open, exit now
if(!$fp) return false;
// obtain a lock
for($tries = 0; $tries <= $options['maxTries']; $tries++) {
$hasLock = flock($fp, LOCK_EX);
if($hasLock) break;
usleep($options['maxTriesDelay']);
}
// if unable to obtain a lock, we cannot write to the log
if(!$hasLock) {
fclose($fp);
return false;
}
// if opened for reading and writing, merge duplicates of $line
if($mode === 'r+' && $options['mergeDups']) {
// do not repeat the same log entry in the same chunk
$chunkSize = (int) $options['mergeDups'];
if($chunkSize < 1024) $chunkSize = 1024;
fseek($fp, -1 * $chunkSize, SEEK_END);
$chunk = fread($fp, $chunkSize);
// check if our log line already appears in the immediate earlier chunk
if(strpos($chunk, $line) !== false) {
// this log entry already appears 1+ times within the last chunk of the file
// remove the duplicates and replace the chunk
$chunkLength = strlen($chunk);
$this->removeLineFromChunk($line, $chunk, $chunkSize);
fseek($fp, 0, SEEK_END);
$oldLength = ftell($fp);
$newLength = $chunkLength > $oldLength ? $oldLength - $chunkLength : 0;
ftruncate($fp, $newLength);
fseek($fp, 0, SEEK_END);
fwrite($fp, $chunk);
}
} else {
// already at EOF because we are appending or creating
}
// add the log line
$result = fwrite($fp, "$ts$line\n");
// release the lock and close the file
flock($fp, LOCK_UN);
fclose($fp);
if($result && !$options['allowDups']) $this->itemsLogged[$hash] = true;
// if we were creating the file, make sure it has the right permission
if($mode === 'w') {
$files = $this->wire('files'); /** @var WireFileTools $files */
$files->chmod($this->logFilename);
}
return (int) $result > 0;
}
/**
* Remove given $line from $chunk and add counter to end of $line indicating quantity that was removed
*
* @param string $line
* @param string $chunk
* @param int $chunkSize
* @since 3.0.143
*
*/
protected function removeLineFromChunk(&$line, &$chunk, $chunkSize) {
$qty = 0;
$chunkLines = explode("\n", $chunk);
foreach($chunkLines as $key => $chunkLine) {
$x = 1;
if($key === 0 && strlen($chunk) >= $chunkSize) continue; // skip first line since its likely a partial line
// check if line appears in this chunk line
if(strpos($chunkLine, $line) === false) continue;
// check if line also indicates a previous quantity that we should add to our quantity
if(strpos($chunkLine, ' ^+') !== false) {
list($chunkLine, $n) = explode(' ^+', $chunkLine, 2);
if(ctype_digit($n)) $x += (int) $n;
}
// verify that these are the same line
if(strpos(trim($chunkLine) . "\n", trim($line) . "\n") === false) continue;
// remove the line
unset($chunkLines[$key]);
// update the quantity
$qty += $x;
}
if($qty) {
// append quantity to line, i.e. “^+2” indicating 2 more indentical lines were above
$chunk = implode("\n", array_values($chunkLines));
$line .= " ^+$qty";
}
}
public function size() {
@@ -145,7 +310,7 @@ class FileLog extends Wire {
*
*/
protected function getChunkArray($chunkNum = 1, $chunkSize = 0, $reverse = true) {
if($chunkSize < 1) $chunkSize = self::defaultChunkSize;
if($chunkSize < 1) $chunkSize = $this->chunkSize;
$lines = explode("\n", $this->getChunk($chunkNum, $chunkSize, $reverse));
foreach($lines as $key => $line) {
$line = trim($line);
@@ -165,15 +330,16 @@ class FileLog extends Wire {
* Returned string is automatically adjusted at the beginning and
* ending to contain only full log lines.
*
* @param int $chunkNum Current pagination number (default=1)
* @param int $chunkSize Number of bytes to retrieve (default=12288)
* @param int $chunkNum Current chunk/pagination number (default=1, first)
* @param int $chunkSize Number of bytes to retrieve (default=0, which assigns default chunk size of 12288)
* @param bool $reverse True=pull from end of file, false=pull from beginning (default=true)
* @param bool $clean Get a clean chunk that starts at the beginning of a line? (default=true)
* @return string
*
*/
protected function getChunk($chunkNum = 1, $chunkSize = 0, $reverse = true) {
protected function getChunk($chunkNum = 1, $chunkSize = 0, $reverse = true, $clean = true) {
if($chunkSize < 1) $chunkSize = self::defaultChunkSize;
if($chunkSize < 1) $chunkSize = $this->chunkSize;
if($reverse) {
$offset = -1 * ($chunkSize * $chunkNum);
@@ -181,7 +347,9 @@ class FileLog extends Wire {
$offset = $chunkSize * ($chunkNum-1);
}
if(self::debug) $this->message("chunkNum=$chunkNum, chunkSize=$chunkSize, offset=$offset, filesize=" . filesize($this->logFilename));
if(self::debug) {
$this->message("chunkNum=$chunkNum, chunkSize=$chunkSize, offset=$offset, filesize=" . filesize($this->logFilename));
}
$data = '';
$totalChunks = $this->getTotalChunks($chunkSize);
@@ -190,23 +358,27 @@ class FileLog extends Wire {
if(!$fp = fopen($this->logFilename, "r")) return $data;
fseek($fp, $offset, ($reverse ? SEEK_END : SEEK_SET));
// make chunk include up to beginning of first line
fseek($fp, -1, SEEK_CUR);
while(ftell($fp) > 0) {
$chr = fread($fp, 1);
if($chr == "\n") break;
fseek($fp, -2, SEEK_CUR);
$data = $chr . $data;
if($clean) {
// make chunk include up to beginning of first line
fseek($fp, -1, SEEK_CUR);
while(ftell($fp) > 0) {
$chr = fread($fp, 1);
if($chr == "\n") break;
fseek($fp, -2, SEEK_CUR);
$data = $chr . $data;
}
fseek($fp, $offset, ($reverse ? SEEK_END : SEEK_SET));
}
// get the big part of the chunk
fseek($fp, $offset, ($reverse ? SEEK_END : SEEK_SET));
$data .= fread($fp, $chunkSize);
// remove last partial line
$pos = strrpos($data, "\n");
if($pos) $data = substr($data, 0, $pos);
if($clean) {
// remove last partial line
$pos = strrpos($data, "\n");
if($pos) $data = substr($data, 0, $pos);
}
fclose($fp);
@@ -221,9 +393,9 @@ class FileLog extends Wire {
*
*/
protected function getTotalChunks($chunkSize = 0) {
if($chunkSize < 1) $chunkSize = self::defaultChunkSize;
if($chunkSize < 1) $chunkSize = $this->chunkSize;
$filesize = filesize($this->logFilename);
return ceil($filesize / $chunkSize);
return $filesize > 0 ? ceil($filesize / $chunkSize) : 0;
}
/**
@@ -234,7 +406,7 @@ class FileLog extends Wire {
*/
public function getTotalLines() {
if(filesize($this->logFilename) < self::defaultChunkSize) {
if(filesize($this->logFilename) < $this->chunkSize) {
$data = file($this->logFilename);
return count($data);
}
@@ -243,7 +415,7 @@ class FileLog extends Wire {
$totalLines = 0;
while(!feof($fp)) {
$data = fread($fp, self::defaultChunkSize);
$data = fread($fp, $this->chunkSize);
$totalLines += substr_count($data, "\n");
}
@@ -323,7 +495,7 @@ class FileLog extends Wire {
$cnt = 0; // number that will be written or returned by this
$n = 0; // number total
$chunkNum = 0;
$totalChunks = $this->getTotalChunks(self::defaultChunkSize);
$totalChunks = $this->getTotalChunks($this->chunkSize);
$stopNow = false;
$chunkLineHashes = array();
@@ -451,11 +623,11 @@ class FileLog extends Wire {
fclose($fpr);
if($cnt) {
unlink($filename);
rename("$filename.new", $filename);
$this->wire('files')->unlink($filename, true);
$this->wire('files')->rename("$filename.new", $filename, true);
$this->wire('files')->chmod($filename);
} else {
@unlink("$filename.new");
$this->wire('files')->unlink("$filename.new", true);
}
return $cnt;
@@ -477,8 +649,8 @@ class FileLog extends Wire {
'dateTo' => time(),
));
if(file_exists($toFile)) {
unlink($this->logFilename);
rename($toFile, $this->logFilename);
$this->wire('files')->unlink($this->logFilename, true);
$this->wire('files')->rename($toFile, $this->logFilename, true);
return $qty;
}
return 0;
@@ -491,7 +663,7 @@ class FileLog extends Wire {
*
*/
public function delete() {
return @unlink($this->logFilename);
return $this->wire('files')->unlink($this->logFilename, true);
}
public function __toString() {
@@ -517,6 +689,19 @@ class FileLog extends Wire {
public function setFileExtension($ext) {
$this->fileExtension = $ext;
}
/**
* Get or set the default chunk size used when reading from logs and not overridden by method argument
*
* @param int $chunkSize Specify chunk size to set, or omit to get
* @return int
* @since 3.0.143
*
*/
public function chunkSize($chunkSize = 0) {
if($chunkSize > 0) $this->chunkSize = (int) $chunkSize;
return $this->chunkSize;
}
}

View File

@@ -10,7 +10,7 @@
* 3. Copy the getModuleInfo() method out of this class and update as appropriate.
* 4. Implement an isValidFile($filename) method, and you are done.
*
* EXAMPLE: /site/modules/FileValidatorSVG.module
* EXAMPLE: /site/modules/FileValidatorHTML.module
*
* class FileValidatorHTML extends FileValidatorModule {
* public static function getModuleInfo() {

View File

@@ -106,8 +106,8 @@ class FilenameArray implements \IteratorAggregate, \Countable {
*
*/
public function remove($filename) {
$key = array_search($filename, $this->data);
if($key !== false) unset($this->data[$key]);
$key = $this->getKey($filename);
unset($this->data[$key]);
return $this;
}

View File

@@ -10,7 +10,7 @@
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* @property ProcessWire $wire
@@ -37,6 +37,9 @@
* @property Config $config
* @property Fuel $fuel
* @property WireProfilerInterface $profiler
* @property WireFileTools $files
* @property WireMailTools $mail
* @property WireDateTime $datetime
*
*/
class Fuel implements \IteratorAggregate {
@@ -66,6 +69,18 @@ class Fuel implements \IteratorAggregate {
protected $requiredInterfaces = array(
'profiler' => 'WireProfilerInterface'
);
/**
* The most common API variable names, those likely to be called multiple times in any request
*
* @var array
*
*/
static protected $commonNames = array(
'page' => 1, 'pages' => 1, 'session' => 1, 'input' => 1, 'sanitizer' => 1,
'config' => 1, 'user' => 1, 'users' => 1, 'fields' => 1, 'templates' => 1,
'database' => 1, 'modules' => 1, 'hooks' => 1,
);
/**
* @param string $key API variable name to set - should be valid PHP variable name.
@@ -110,6 +125,10 @@ class Fuel implements \IteratorAggregate {
public function __get($key) {
return isset($this->data[$key]) ? $this->data[$key] : null;
}
public function get($key) {
return isset($this->data[$key]) ? $this->data[$key] : null;
}
public function getIterator() {
return new \ArrayObject($this->data);
@@ -119,4 +138,7 @@ class Fuel implements \IteratorAggregate {
return $this->data;
}
public static function isCommon($name) {
return isset(self::$commonNames[$name]);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,26 +3,36 @@
/**
* ProcessWire functions API maps function names to common API variables
*
* #pw-summary-Functions-API =
* Provides an alternative to the API variables by providing functions of the same
* name, with these benefits:
*
* - They are always in scope
* - Makes life simpler in an IDE that recognizes phpdoc, as it can more easily
* recognize the types an return values.
* - They are always in scope whether inside a function or outside of it.
* - They are self documenting with your IDE, unlike API $variables.
* - They cannot be accidentally overwritten the way variables can be.
* - They provider greater contrast between what are API-calls and variables.
* - In some cases it makes for shorter API calls.
* - Some of the functions provide arguments for useful shortcuts.
*
* The primary drawback is that the function calls are not mapped to a specific
* instance, so in a multi-instance environment it's possible these function calls
* may not be referring to the correct ProcessWire instance. For this reason, we
* think these functions are primarily useful for front-end/website usages, and
* not as useful for back-end and module development.
* These functions always refer to the current ProcessWire instance, so are intended
* primarily for front-end usage in template files (not for modules).
*
* Note: This file is only used if $config->useFunctionsAPI == true;
* If these functions are not working for you, you can enable them by setting
* `$config->useFunctionsAPI=true;` in your /site/config.php file.
*
* Regardless of whether the Functions API is enabled or not, you can also access
* any of these functions by prefixing the word `wire` to them and using the format
* `wireFunction()` i.e. `wirePages()`, `wireUser()`, etc.
* Or, if you do not
* #pw-summary-Functions-API
*
*/
/**
* Access the $pages API variable as a function
* Retrieve or save pages ($pages API variable as a function)
*
* Accessing `pages()` is exactly the same as accessing `$pages`. Though there are a couple of optional
* shortcuts available by providing an argument to this function.
*
* ~~~~
* // A call with no arguments returns the $pages API variable
@@ -39,12 +49,15 @@
* $page = pages("page-name");
* ~~~~
*
* @param string|array $selector Specify one of the following:
* #pw-group-Functions-API
*
* @param string|array|int $selector Specify one of the following:
* - Nothing, makes it return the $pages API variable.
* - Selector (string) to find matching pages, makes function return PageArray - equivalent to $pages->find("selector");
* - Page ID (int) to return a single matching Page - equivalent to $pages->get(123);
* - Page name (string) to return a single page having the given name - equivalent to $pages->get("name");
* @return Pages|PageArray|Page|NullPage
* @see Pages
*
*/
function pages($selector = '') {
@@ -52,19 +65,35 @@ function pages($selector = '') {
}
/**
* Access the $page API variable as a function
* Returns the current Page being viewed ($page API variable as a function)
*
* This function behaves the same as the `$page` API variable, though does support optional
* arguments as shortcuts for getting from the page or setting values to it.
*
* ~~~~
* $page = page(); // Simply get $page API var
* $body = page()->body; // Get body field value
* $body = page('body'); // Same as above
* $headline = page('headline|title'); // Get headline or title
* page('headline', 'Setting headline value'); // Set headline
*
* // Get the “body” field
* $body = page()->body; // direct syntax
* $body = page()->get('body'); // regular syntax
* $body = page('body'); // shortcut syntax
*
* // Get the “headline” field or fallback to “title
* $headline = page()->get('headline|title'); // regular syntax
* $headline = page('headline|title'); // shortcut syntax
*
* // Set the “headline” field
* page()->headline = 'My headline'; // direct syntax
* page()->set('headline', 'My headline'); // regular syntax
* page('headline', 'My headline'); // shortcut syntax
* ~~~~
*
* #pw-group-Functions-API
*
* @param string $key Optional property to get or set
* @param null $value Optional value to set
* @return Page|mixed
* @see Page
*
*/
function page($key = '', $value = null) {
@@ -72,19 +101,25 @@ function page($key = '', $value = null) {
}
/**
* Access the $config API variable as a function
* Access a ProcessWire configuration setting ($config API variable as a function)
*
* This function behaves the same as the `$config` API variable, though does support
* optional shortcut arguments for getting/setting values.
*
* ~~~~~
* $config = config(); // Simply get $config API var
* $debug = config()->debug; // Get value of debug
* $debug = config('debug'); // Same as above
* $debug = config('debug'); // Same as above, shortcut syntax
* config()->debug = true; // Set value of debug
* config('debug', true); // Same as above
* config('debug', true); // Same as above, shortcut syntax
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $key
* @param null $value
* @return Config|mixed
* @see Config
*
*/
function config($key = '', $value = null) {
@@ -92,7 +127,10 @@ function config($key = '', $value = null) {
}
/**
* Access the $modules API variable as a function
* Get a module, get module information, and much more ($modules API variable as a function)
*
* This function behaves the same as the `$modules` API variable, though does support
* an optional shortcut argument for getting a module.
*
* ~~~~~
* $modules = modules(); // Simply get $modules API var
@@ -100,8 +138,11 @@ function config($key = '', $value = null) {
* $module = modules('ModuleName'); // Shortcut to get a module
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $name Optionally retrieve the given module name
* @return Modules|Module|ConfigurableModule|null
* @see Modules
*
*/
function modules($name = '') {
@@ -109,11 +150,17 @@ function modules($name = '') {
}
/**
* Access the $user API variable as a function
* Get the currently logged in user ($user API variable as a function)
*
* This function behaves the same as the `$user` API variable, though does support
* optional shortcut arguments for getting or setting values.
*
* #pw-group-Functions-API
*
* @param string $key Optional property to get or set
* @param null $value Optional value to set
* @return User|mixed
* @see User
*
*/
function user($key = '', $value = null) {
@@ -121,13 +168,28 @@ function user($key = '', $value = null) {
}
/**
* Access the $users API variable as a function
* Get, find or save users ($users API variable as a function)
*
* See the pages() function for full usage details.
* This function behaves the same as the `$users` API variable, though does support
* an optional shortcut argument for getting a single user or finding multiple users.
*
* @param string|array $selector Optional selector to send to find() or get()
* ~~~~~~
* // Get a single user (regular and shortcut syntax)
* $u = users()->get('karen');
* $u = users('karen');
*
* // Find multiple users (regular and shortcut syntax)
* $us = users()->find('roles.name=editor');
* $us = users('roles.name=editor');
* ~~~~~~
*
* #pw-group-Functions-API
*
* @param string|array|int $selector Optional selector to send to find() or get()
* - Specify user name or ID to get and return that User
* - Specify a selector string to find all users matching selector (PageArray)
* @return Users|PageArray|User|mixed
* @see pages()
* @see pages(), Users
*
*/
function users($selector = '') {
@@ -135,11 +197,29 @@ function users($selector = '') {
}
/**
* Access the $session API variable as a function
* Get or set values in the current user session ($session API variable as a function)
*
* This function behaves the same as the `$session` API variable, though does support
* optional shortcut arguments for getting or setting values.
*
* ~~~~~
* // Get a value from the session
* $foo = session()->foo; // direct syntax
* $foo = session()->get('foo'); // regular syntax
* $foo = session('foo'); // shortcut syntax
*
* // Set a value to the session
* session()->foo = 'bar'; // direct syntax
* session()->set('foo', 'bar'); // regular syntax
* session('foo', 'bar'); // shortcut syntax
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $key Optional property to get or set
* @param null $value Optional value to set
* @return Session|mixed
* @return Session|null|string|array|int|float
* @see Session
*
*/
function session($key = '', $value = null) {
@@ -147,10 +227,21 @@ function session($key = '', $value = null) {
}
/**
* Access the $fields API variable as a function
* Get or save fields independent of templates ($fields API variable as as function)
*
* This function behaves the same as the `$fields` API variable, though does support
* an optional shortcut argument for getting a single field.
*
* ~~~~~
* $field = fields()->get('title'); // regular syntax
* $field = fields('title'); // shortcut syntax
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $name Optional field name to retrieve
* @return Fields|Field|null
* @see Fields
*
*/
function fields($name = '') {
@@ -158,10 +249,21 @@ function fields($name = '') {
}
/**
* Access the $templates API variable as a function
* Get or save templates ($templates API variable as a function)
*
* This function behaves the same as the `$templates` API variable, though does support
* an optional shortcut argument for getting a single template.
*
* ~~~~~~
* $t = templates()->get('basic-page'); // regular syntax
* $t = templates('basic-page'); // shortcut syntax
* ~~~~~~
*
* #pw-group-Functions-API
*
* @param string $name Optional template to retrieve
* @return Templates|Template|null
* @see Templates
*
*/
function templates($name = '') {
@@ -169,9 +271,12 @@ function templates($name = '') {
}
/**
* Access the $database API variable as a function
* Create and execute PDO database queries ($database API variable as a function)
*
* #pw-group-Functions-API
*
* @return WireDatabasePDO
* @see WireDatabasePDO
*
*/
function database() {
@@ -179,12 +284,28 @@ function database() {
}
/**
* Access the $permissions API varaible as a function
* Get, find or save permissions ($permissions API variable as a function)
*
* See the pages() function for usage details.
* Accessing `permissions()` is exactly the same as accessing `$permissions`. Though there are a couple of optional
* shortcuts available by providing an argument to this function.
*
* @param string $selector
* ~~~~~
* // Get a permission
* $p = permissions()->get('page-edit'); // regular syntax
* $p = permissions('page-edit'); // shortcut syntax
*
* // Find permissions
* $ps = permissions()->find('name^=page'); // regular syntax
* $ps = permissions('name^=page'); // shortcut syntax
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string|int $selector
* - Specify permission name or ID to retrieve that Permission (Permission)
* - Specify a selector string to return all permissions matching selector (PageArray)
* @return Permissions|Permission|PageArray|null|NullPage
* @see Permissions
*
*/
function permissions($selector = '') {
@@ -192,12 +313,18 @@ function permissions($selector = '') {
}
/**
* Access the $roles API varaible as a function
* Get, find or save roles ($roles API variable as a function)
*
* Accessing `roles()` is exactly the same as accessing `$roles`. Though there are a couple of optional
* shortcuts available by providing an argument to this function.
*
* See the pages() function for usage details.
* #pw-group-Functions-API
*
* @param string $selector
* @param string|int $selector
* - Specify name or ID of role to get (Role object)
* - Specify selector string matching roles to find (PageArray object)
* @return Roles|Role|PageArray|null|NullPage
* @see Roles
*
*/
function roles($selector = '') {
@@ -205,17 +332,21 @@ function roles($selector = '') {
}
/**
* Access the $sanitizer API variable as a function
* Sanitize variables and related string functions ($sanitizer API variable as a function)
*
* This behaves the same as the `$sanitizer` API variable but supports arguments as optional shortcuts.
*
* ~~~~~
* // Example usages
* $clean = sanitizer()->pageName($dirty);
* $clean = sanitizer('pageName', $dirty); // same as above
* $clean = sanitizer()->pageName($dirty); // regular syntax
* $clean = sanitizer('pageName', $dirty); // shortcut syntax
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $name Optionally enter a sanitizer function name
* @param string $value If $name populated, enter the value to sanitize
* @return Sanitizer|string|int|array|null|mixed
* @see Sanitizer
*
*/
function sanitizer($name = '', $value = '') {
@@ -223,18 +354,23 @@ function sanitizer($name = '', $value = '') {
}
/**
* Access the $datetime API variable as a function
* Access date and time related tools ($datetime API variable as a function)
*
* This behaves the same as the `$datetime` API variable except that you can optionally provide
* arguments as a shortcut to the `$datetime->formatDate()` method.
*
* ~~~~~
* // Example usages
* $str = datetime()->relativeTimeStr('2016-10-10');
* $str = datetime('Y-m-d');
* $str = datetime('Y-m-d', time());
* $str = datetime('Y-m-d'); // shortcut to formatDate method
* $str = datetime('Y-m-d', time()); // shortcut to formatDate method
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $format Optional date format
* @param string|int $value Optional date to format
* @return WireDateTime|string|int
* @see WireDateTime
*
*/
function datetime($format = '', $value = '') {
@@ -242,9 +378,14 @@ function datetime($format = '', $value = '') {
}
/**
* Access the $files API variable as a function
* Access tools for working on the file system ($files API variable as a function)
*
* This behaves identically to the `$files` API variable and as no optional arguments.
*
* #pw-group-Functions-API
*
* @return WireFileTools
* @see WireFileTools
*
*/
function files() {
@@ -252,16 +393,21 @@ function files() {
}
/**
* Access the $cache API variable as a function
* Get and save caches ($cache API variable as a function)
*
* If called with no arguments it returns the $cache API variable.
* If called with arguments, it can be used the same as `WireCache::get()`.
* This behaves the same as the $cache API variable but does support arguments as a
* shortcut for the `$cache->get()` method.
*
* - If called with no arguments it returns the $cache API variable.
* - If called with arguments, it can be used the same as `WireCache::get()`.
*
* #pw-group-Functions-API
*
* @param string $name
* @param callable|int|string|null $expire
* @param callable|int|string|null $func
* @return WireCache|string|array|PageArray|null
* @see WireCache::get()
* @see WireCache, WireCache::get()
*
*/
function cache($name = '', $expire = null, $func = null) {
@@ -269,19 +415,22 @@ function cache($name = '', $expire = null, $func = null) {
}
/**
* Access the $languages API variable as a function
* Access all installed languages in multi-language environment ($languages API variable as a function)
*
* Returns the $languages API variable, or a Language object if given a language name.
* Returns the `$languages` API variable, or a `Language` object if given a language name or ID.
*
* ~~~~
* // Examples
* $languages = languages(); // Languages if active, null if not
* $en = languages()->getDefault();
* $de = languages('de');
* $en = languages()->getDefault(); // Get default language
* $de = languages()->get('de'); // Get another language
* $de = languages('de'); // Get another language (shorcut syntax)
* ~~~~
*
* #pw-group-Functions-API
*
* @param string|int $name Optional Language name or ID for language to retrieve
* @return Languages|Language|NullPage|null
* @see Languages, Languages::get(), Language
*
*/
function languages($name = '') {
@@ -289,35 +438,52 @@ function languages($name = '') {
}
/**
* Access the $input API variable as a function
* Access GET, POST or COOKIE input variables and more ($input API variable as a function)
*
* - Default behavior is to return the $input API var.
* - If given just a $type (like "get" or "post"), it will return a WireInputData object for that type.
* - If given a $type and $key it will return the input variable.
* - If all arguments given, the returned value will also be run through the given sanitizer.
* - Default behavior with no arguments is to return the `$input` API variable.
* - If given just a `$type` argument (like get or post), it will return a `WireInputData` object for that type.
* - If given a `$type` and `$key` arguments, it will return the value, or null if not present.
* - If `$sanitizer` argument given, the returned value will also be run through the given sanitizer.
* - If the `$sanitizer` argument is an array, the returned input value must be within the given list, or null if not (3.0.125+).
* - If `$fallback` argument given, it will return the fallback value if input value was not present or not valid (3.0.125+).
* - See the `WireInput::get()` method for all options.
*
* ~~~~~
* // Examples
* // Can be used the same way as the $input API variable
* // In examples below the “post” can also be “get” or “cookie”
* $input = input(); // Returns $input API var (WireInput)
* $post = input('post'); // Returns $input->post (WireInputData)
* $value = input('get', 'sort'); // Returns $input->get('sort');
* $value = input('get', 'sort', 'fieldName'); // Returns $input->get('sort') run through $sanitizer->fieldName().
* $post = input()->post(); // Returns $input->post (WireInputData instance)
* $foo = input()->post('foo'); // Returns POST variable “foo”
* $bar = input()->post('bar', 'text'); // Returns “bar” after text sanitizer (3.0.125+)
* $s = input()->post('s', ['foo', 'bar', 'baz']); // POST var “s” must match given list (3.0.125+)
*
* // You can also move the arguments all to the function call if you prefer:
* $s = input('get', 'sort'); // Returns GET var “sort”
* $s = input('get', 'sort', 'fieldName'); // Returns “sort” after “fieldName” sanitizer
* $s = input('get', 'sort', ['title', 'created']); // Require sort to be one in given array (3.0.125+)
* $s = input('get', 'sort', ['title', 'created'], 'title'); // Same as above, fallback to 'title' (3.0.125+)
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $type Optionally indicate "get", "post", "cookie" or "whitelist"
* @param string $key If getting a value, specify name of property containing value
* @param string $sanitizer Optionally specify sanitizer name to run value through
* @return WireInput|WireInputData array|string|int|null
* @param string $key If getting a value, specify name of input property containing value
* @param string $sanitizer Optionally specify sanitizer name to run value through, or in 3.0.125+ may also be an array of allowed values.
* @param string|int|null $fallback Value to fallback to if input not present or invalid
* @return WireInput|WireInputData|array|string|int|null
* @see WireInput
*
*/
function input($type = '', $key = '', $sanitizer = '') {
return wireInput($type, $key, $sanitizer);
function input($type = '', $key = '', $sanitizer = null, $fallback = null) {
return wireInput($type, $key, $sanitizer, $fallback);
}
/**
* Access the $input->get API variable as a function
*
* This is the same as the input() function except that the $type "get" is already implied.
* This is the same as the input() function except that the $type "get" is already implied.
*
* #pw-internal
*
* @param string $key
* @param string $sanitizer
@@ -332,6 +498,8 @@ function inputGet($key = '', $sanitizer = '') {
* Access the $input->post API variable as a function
*
* This is the same as the input() function except that the $type "post" is already implied.
*
* #pw-internal
*
* @param string $key
* @param string $sanitizer
@@ -346,6 +514,8 @@ function inputPost($key = '', $sanitizer = '') {
* Access the $input->cookie API variable as a function
*
* This is the same as the input() function except that the $type "cookie" is already implied.
*
* #pw-internal
*
* @param string $key
* @param string $sanitizer
@@ -357,10 +527,21 @@ function inputCookie($key = '', $sanitizer = '') {
}
/**
* Function that returns a $config->urls->[name] value o
* Get one of any named system URLs (shortcut to the $config API variable “urls” property)
*
* URLs always have a trailing slash.
*
* ~~~~~
* // you can use either syntax below, where “templates” can be the name for any system URL
* $url = urls()->templates;
* $url = urls('templates');
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $key
* @return null|Paths|string
* @see Config::urls()
*
*/
function urls($key = '') {
@@ -368,11 +549,22 @@ function urls($key = '') {
}
/**
* Function that returns a $config->paths->[name] value o
* Get one of any named server disk paths (shortcut to the $config API variable “paths” property)
*
* Paths always have a trailing slash.
*
* ~~~~~
* // you can use either syntax below, where “templates” can be the name for any system URL
* $path = paths()->templates;
* $path = paths('templates');
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $key
* @return null|Paths|string
*
* @see Config::paths()
*
*/
function paths($key = '') {
return wirePaths($key);
@@ -380,6 +572,8 @@ function paths($key = '') {
/**
* Start or stop a profiler event or return WireProfilerInterface instance
*
* #pw-internal
*
* @param string|array|object|null $name Name of event to start or event to stop
* @param null|object|string $source If starting an event, optional source of event (object)
@@ -392,7 +586,18 @@ function profiler($name = null, $source = null, $data = array()) {
}
/**
* Get or set a region for front-end output
* Get or set an output region (primarily for front-end output usage)
*
* This function is an convenience for storing markup that ultimately gets output in a _main.php file
* (or whatever file `$config->appendTemplateFile` is set to). It is an alternative to passing variables
* between included files and provides an interface for setting, appending, prepending and ultimately
* getting markup (or other strings) for output. Its designed for use the the “Delayed Output” strategy,
* though does not necessarily require it.
*
* This function can also be accessed as `wireRegion()`, and that function is always available
* regardless of whether the Functions API is enabled or not.
*
* *Note: unlike other functions in the Functions API, this function is not related to API variables.*
*
* ~~~~~
* // define a region
@@ -415,13 +620,16 @@ function profiler($name = null, $source = null, $data = array()) {
*
* // clear all regions
* region('*', '');
*
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string $key Name of region to get or set.
* - Specify "*" to retrieve all defined regions in an array.
* - Prepend a "+" to the region name to have it prepend your given value to any existing value.
* - Append a "+" to the region name to have it append your given value to any existing value.
* - Prepend a "++" to region name to make future calls without "+" automatically prepend.
* - Append a "++" to region name to make future calls without "+" to automatically append.
* @param null|string $value If setting a region, the text that you want to set.
* @return string|null|bool|array Returns string of text when getting a region, NULL if region not set, or TRUE if setting region.
*
@@ -430,3 +638,46 @@ function region($key = '', $value = null) {
return wireRegion($key, $value);
}
/**
* Get or set a runtime site setting
*
* This is a simple helper function for maintaining runtime settings in a site profile.
* It simply sets and gets settings that you define. It is preferable to using ProcessWires
* `$config` or `config()` API var/function because it is not used to store anything else for
* ProcessWire. It is also preferable to using a variable (or variables) because it is always
* in scope and accessible anywhere in your template files, even within existing functions.
*
* *Note: unlike other functions in the Functions API, this function is not related to API variables.*
*
* ~~~~~
* // set a setting named “foo” to value “bar”
* setting('foo', 'bar');
*
* // get a setting named “foo”
* $value = setting('foo');
*
* // set or replace multiple settings
* setting([
* 'foo' => 'value',
* 'bar' => 123,
* 'baz' => [ 'foo', 'bar', 'baz' ]
* ]);
*
* // get all settings in associative array
* $a = setting();
*
* // to unset a setting
* setting(false, 'foo');
* ~~~~~
*
* #pw-group-Functions-API
*
* @param string|array $name Setting name, or array to set multiple
* @param string|int|array|float|mixed $value Value to set, or omit if getting value of $name (default=null)
* @return array|string|int|bool|mixed|null
*
*/
function setting($name = '', $value = null) {
return wireSetting($name, $value);
}

View File

@@ -35,7 +35,7 @@
function _wirePagesAPI($_apiVar, $selector) {
/** @var Pages|PagesType $pages */
$pages = wire($_apiVar);
$pages = is_object($_apiVar) ? $_apiVar : wire($_apiVar);
if(!$pages) return null;
if(!$selector) return $pages;
@@ -374,26 +374,30 @@ function wireLanguages($name = '') {
*
* ~~~~~
* // Examples
* $input = input(); // Returns $input API var (WireInput)
* $post = input('post'); // Returns $input->post (WireInputData)
* $value = input('get', 'sort'); // Returns $input->get('sort');
* $value = input('get', 'sort', 'fieldName'); // Returns $input->get('sort') run through $sanitizer->fieldName().
* $input = wireInput(); // Returns $input API var (WireInput)
* $post = wireInput('post'); // Returns $input->post (WireInputData)
* $post = wireInput()->post(); // Same as above
* $value = wireInput('get', 'sort'); // Returns $input->get('sort');
* $value = wireInput('get', 'sort', 'fieldName'); // Returns $input->get('sort') run through $sanitizer->fieldName().
* $value = wireInput('get', 'sort', 'fieldName', 'title'); // Same as above but fallback to 'title' if no sort is present (3.0.125)
* $value = wireInput('get', 'sort', ['title', 'created', 'likes'], 'title'); // Require value to be one given or fallback to 'title' (3.0.125+)
* $value = wireInput()->get('sort', ['title', 'created', 'likes'], 'title'); // Same as above (3.0.125)
* ~~~~~
*
* @param string $type Optionally indicate "get", "post", "cookie" or "whitelist"
* @param string $key If getting a value, specify name of property containing value
* @param string $sanitizer Optionally specify sanitizer name to run value through
* @return WireInput|WireInputData array|string|int|null
* @param string $sanitizer Optionally specify sanitizer name to run value through, or array containing whitelist of allowed values (3.0.125).
* @param mixed $fallback Fallback value to return rather than null if value not present or does not validate (3.0.125+)
* @return WireInput|WireInputData|array|string|int|null
*
*/
function wireInput($type = '', $key = '', $sanitizer = '') {
function wireInput($type = '', $key = '', $sanitizer = null, $fallback = null) {
/** @var WireInput $input */
$input = wire('input');
if(!strlen($type)) return $input;
$type = strtolower($type);
if(!strlen($key)) return $input->$type;
$value = $input->$type($key);
if(strlen($sanitizer)) $value = wireSanitizer($sanitizer, $value);
$value = $input->$type($key, $sanitizer, $fallback);
return $value;
}
@@ -402,13 +406,14 @@ function wireInput($type = '', $key = '', $sanitizer = '') {
*
* This is the same as the input() function except that the $type "get" is already implied.
*
* @param string $key
* @param string $sanitizer
* @param string $key Name of input variable to get
* @param string $sanitizer Optionally specify sanitizer name to run value through, or array containing whitelist of allowed values (3.0.125+).
* @param mixed $fallback Fallback value to return rather than null if value not present or does not validate (3.0.125+)
* @return WireInputData|string|int|array|null
*
*/
function wireInputGet($key = '', $sanitizer = '') {
return wireInput('get', $key, $sanitizer);
function wireInputGet($key = '', $sanitizer = null, $fallback = null) {
return wireInput('get', $key, $sanitizer, $fallback);
}
/**
@@ -416,13 +421,14 @@ function wireInputGet($key = '', $sanitizer = '') {
*
* This is the same as the input() function except that the $type "post" is already implied.
*
* @param string $key
* @param string $sanitizer
* @param string $key Name of input variable to get
* @param string $sanitizer Optionally specify sanitizer name to run value through, or array containing whitelist of allowed values (3.0.125).
* @param mixed $fallback Fallback value to return rather than null if value not present or does not validate (3.0.125+)
* @return WireInputData|string|int|array|null
*
*/
function wireInputPost($key = '', $sanitizer = '') {
return wireInput('post', $key, $sanitizer);
function wireInputPost($key = '', $sanitizer = null, $fallback = null) {
return wireInput('post', $key, $sanitizer, $fallback);
}
/**
@@ -430,13 +436,14 @@ function wireInputPost($key = '', $sanitizer = '') {
*
* This is the same as the input() function except that the $type "cookie" is already implied.
*
* @param string $key
* @param string $sanitizer
* @param string $key Name of input variable to get
* @param string $sanitizer Optionally specify sanitizer name to run value through, or array containing whitelist of allowed values (3.0.125+).
* @param mixed $fallback Fallback value to return rather than null if value not present or does not validate (3.0.125+)
* @return WireInputData|string|int|array|null
*
*/
function wireInputCookie($key = '', $sanitizer = '') {
return wireInput('cookie', $key, $sanitizer);
function wireInputCookie($key = '', $sanitizer = null, $fallback = null) {
return wireInput('cookie', $key, $sanitizer, $fallback);
}
/**
@@ -504,3 +511,87 @@ function wirePaths($key = '') {
return wire('config')->paths($key);
}
/**
* Get or set a runtime site setting
*
* This is a simple helper function for maintaining runtime settings in a site profile.
* It simply sets and gets settings that you define. It is preferable to using ProcessWires
* `$config` or `config()` API var/function because it is not used to store anything else for
* ProcessWire. It is also preferable to using a variable (or variables) because it is always
* in scope and accessible anywhere in your template files, even within existing functions.
*
* ~~~~~
* // set a setting named “foo” to value “bar”
* setting('foo', 'bar');
*
* // get a setting named “foo”
* $value = setting('foo');
*
* // set or replace multiple settings
* setting([
* 'foo' => 'value',
* 'bar' => 123,
* 'baz' => [ 'foo', 'bar', 'baz' ]
* ]);
*
* // get all settings in associative array
* $a = setting();
*
* // to unset a setting
* setting(false, 'foo');
* ~~~~~
*
* @param string|array $name Setting name, or array to set multiple
* @param string|int|array|float|mixed $value Value to set, or omit if getting value of $name (default=null)
* @return array|string|int|bool|mixed|null
*
*/
function wireSetting($name = '', $value = null) {
static $settings = array();
if($name === '') return $settings;
if(is_array($name)) return $settings = array_merge($settings, $name);
if($name === false) { unset($settings[(string) $value]); return null; }
if($value !== null) $settings[$name] = $value;
return isset($settings[$name]) ? $settings[$name] : null;
}
/**
* Return array of functions available from the functions API
*
* Returned array is shortVersion => longVersion
*
* @return array
*
*/
function _wireFunctionsAPI() {
$names = array(
'cache' => 'wireCache',
'config' => 'wireConfig',
'database' => 'wireDatabase',
'datetime' => 'wireDatetime',
'fields' => 'wireFields',
'files' => 'wireFiles',
'input' => 'wireInput',
'inputGet' => 'wireInputGet',
'inputPost' => 'wireInputPost',
'inputCookie' => 'wireInputCookie',
'languages' => 'wireLanguages',
'modules' => 'wireModules',
'page' => 'wirePage',
'pages' => 'wirePages',
'paths' => 'wirePaths',
'permissions' => 'wirePermissions',
'profiler' => 'wireProfiler',
'region' => 'wireRegion',
'roles' => 'wireRoles',
'sanitizer' => 'wireSanitizer',
'setting' => 'wireSetting',
'session' => 'wireSession',
'templates' => 'wireTemplates',
'urls' => 'wireUrls',
'user' => 'wireUser',
'users' => 'wireUsers',
);
return $names;
}

View File

@@ -207,12 +207,12 @@ class HookEvent extends WireData {
* $event->removeHook(null);
* ~~~~~
*
* @param string|null $hookId
* @param string|HookEvent|null $hookId
* @return HookEvent|WireData $this
*
*/
public function removeHook($hookId) {
if(empty($hookId)) {
if(empty($hookId) || $hookId === $this) {
if($this->object && $this->id) {
$this->object->removeHook($this->id);
}

View File

@@ -135,7 +135,7 @@ class ImageInspector extends WireData {
if(is_array($additionalInfo) && $parseAppmarker) {
$appmarker = array();
foreach($additionalInfo as $k => $v) {
$appmarker[$k] = substr($v, 0, strpos($v, null));
$appmarker[$k] = substr($v, 0, strpos($v, chr(0)));
}
$this->info['appmarker'] = $appmarker;
if(isset($additionalInfo['APP13'])) {
@@ -158,7 +158,7 @@ class ImageInspector extends WireData {
* Check orientation (@horst)
*
* @param array
* @return bool
* @return array
* @todo there is already a checkOrientation method in ImageSizerEngine - do we need both?
*
*/
@@ -175,7 +175,13 @@ class ImageInspector extends WireData {
'8' => array(90, 0)
);
$result = array('orientation' => 0, 'rotate' => 0, 'flip' => 0);
if(!function_exists('exif_read_data')) return $result;
$supportedExifMimeTypes = array('image/jpeg', 'image/tiff'); // hardcoded by PHP
$mime = isset($this->info['mime']) ? $this->info['mime'] : 'no';
if(!function_exists('exif_read_data') || !in_array($mime, $supportedExifMimeTypes)) {
return $result;
}
$exif = @exif_read_data($filename, 'IFD0');
if(!is_array($exif)
|| !isset($exif['Orientation'])
@@ -273,8 +279,6 @@ class ImageInspector extends WireData {
/**
* parse JPEG Image and collect information into $this->info
*
* @return bool
*
*/
protected function loadImageInfoJpg() {
}

View File

@@ -11,7 +11,7 @@
*
* Other user contributions as noted.
*
* Copyright (C) 2016 by Horst Nogajski and Ryan Cramer
* Copyright (C) 2016-2019 by Horst Nogajski and Ryan Cramer
* This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/
*
* @method bool resize($targetWidth, $targetHeight = 0)
@@ -55,6 +55,14 @@ class ImageSizer extends Wire {
*/
static protected $knownEngines = null;
/**
* Names of engines that failed the supported() checks
*
* @var array
*
*/
protected $failedEngineNames = array();
/**
* Module/class name of engine that must only be used (for cases where you want to force a specific engine)
*
@@ -84,7 +92,6 @@ class ImageSizer extends Wire {
*
* @param string $filename Filename to resize. Omit only if instantiating class for a getEngines() call.
* @param array $options Initial options to the engine.
* @throws WireException
*
*/
public function __construct($filename = '', $options = array()) {
@@ -108,15 +115,21 @@ class ImageSizer extends Wire {
self::$knownEngines = array();
$modules = $this->wire('modules');
$engines = $modules->find("className^=ImageSizerEngine");
$engines = $modules->findByPrefix('ImageSizerEngine');
$numEngines = count($engines);
foreach($engines as $module) {
$moduleName = $module->className();
foreach($engines as $moduleName) {
if(!$modules->isInstalled($moduleName)) continue;
if(count($engines) > 1) {
$configData = $modules->getModuleConfigData($moduleName);
if($numEngines > 1) {
$configData = $modules->getConfig($moduleName);
$priority = isset($configData['enginePriority']) ? (int) $configData['enginePriority'] : 0;
while(isset(self::$knownEngines[$priority])) $priority++;
// multiply by 10 to ensure two priority 1 engines don't get mixed up with a priority 2 engine
// for instance, two priority 1 engines become 10 and 11, rather than 1 and 2, as a priority 1
// engine incremented to 2 could otherwise be confused with a priority 2 engine
$priority *= 10;
while(isset(self::$knownEngines[$priority])) {
$priority++;
}
} else {
$priority = 0;
}
@@ -124,9 +137,61 @@ class ImageSizer extends Wire {
}
if(count(self::$knownEngines) > 1) ksort(self::$knownEngines);
self::$knownEngines[] = $this->defaultEngineName;
return self::$knownEngines;
}
/**
* Get array of information for all ImageSizer engines (or optionally a specific ImageSizer engine)
*
* Returns array of arrays indexed by engine name, each with the following:
*
* - `name` (string): engine name
* - `title` (string): engine title
* - `class` (string): PHP class name for engine
* - `summary` (string): Single sentence summary of the engine
* - `author` (string): Authr name (if available)
* - `moduleVersion` (string): Version of the module that powers this engine
* - `libraryVersion` (string): Version of the library that powers this engine
* - `sources` (array): Supported formats for source images it reads (i.e. JPG, JPEG, PNG, PNG24, GIF, GIF87, etc.)
* - `targets` (array): Supported formats for target images it creates (i.e. JPG, PNG, PNG24, WEBP, etc.)
* - `quality` (int): Current quality setting configured with the engine
* - `sharpening` (string): Current sharpening setting configured with the engine
* - `priority` (int): Engine priority (lower is higher priority)
* - `runOrder` (int): Order ImageSizer will try this engine in relative to others (lower runs first), derived from priority.
*
* @param string $name Specify engine name to get info just for that engine or omit to get info for all engines (default)
* @return array Array of arrays indexed by engine name, or if $name specified then just array of info for that engine.
* Returns empty array on error.
* @since 3.0.138
*
*/
public function getEngineInfo($name = '') {
$infos = array();
$engineNames = $name ? array($name) : $this->getEngines();
$prefix = 'ImageSizerEngine';
if($name && stripos($name, $prefix) === 0) {
$name = str_replace($prefix, '', $name);
}
foreach($engineNames as $priority => $engineName) {
$shortName = str_replace($prefix, '', $engineName);
if($name && $shortName !== $name) continue;
$engine = $this->getEngine($engineName);
if(!$engine) continue;
$info = $engine->getEngineInfo();
$info['runOrder'] = $priority;
$infos[$shortName] = $info;
}
// if one engine requested reduce array to just that engine
if($name) $infos = isset($infos[$name]) ? $infos[$name] : array();
return $infos;
}
/**
* Instantiate an ImageSizerEngine
@@ -152,21 +217,43 @@ class ImageSizer extends Wire {
}
$engine = null;
$bestFallbackEngine = null; // first engine that was supported but failed webp check
$engineNames = $this->getEngines();
// find first supported engine, according to knownEngines priority
foreach($this->getEngines() as $engineName) {
foreach($engineNames as $engineName) {
if($this->forceEngineName && $engineName != $this->forceEngineName) continue;
$e = $this->wire('modules')->get($engineName);
$e = $this->getEngine($engineName);
if(!$e) continue;
/** @var ImageSizerEngine $e */
$e->prepare($filename, $options, $inspectionResult);
if($e->supported()) {
$supported = $e->supported();
if($supported && !empty($options['webpAdd']) && !$e->supported('webp')) {
// engine does not support requested webp extra image
if(!$bestFallbackEngine) $bestFallbackEngine = $e;
} else if($supported) {
// found supported engine
$engine = $e;
break;
}
}
$this->failedEngineNames[$engineName] = $engineName;
}
if(!$engine) {
// fallback to default
$engine = $this->newDefaultImageSizerEngine($filename, $options, $inspectionResult);
if(!$engine) {
// no engine found
if($bestFallbackEngine) {
// if there is a next best fallback, use it
$engine = $bestFallbackEngine;
} else {
// otherwise fallback to default
$engine = $this->newDefaultImageSizerEngine($filename, $options, $inspectionResult);
}
}
return $engine;
@@ -294,11 +381,10 @@ class ImageSizer extends Wire {
*
*/
public function setModified($modified) {
if($this->engine) $this->engine->modified = $modified ? true : false;
if($this->engine) $this->engine->setModified($modified ? true : false);
return $this;
}
// setters (@todo phpdocs)
public function setAutoRotation($value = true) { return $this->setOptions(array('autoRotation', $value)); }
public function setCropExtra($value) { return $this->setOptions(array('cropExtra', $value)); }
public function setCropping($cropping = true) { return $this->setOptions(array('cropping', $cropping)); }
@@ -312,7 +398,6 @@ class ImageSizer extends Wire {
public function setTimeLimit($value = 30) { return $this->setOptions(array('timeLimit', $value)); }
public function setUpscaling($value = true) { return $this->setOptions(array('upscaling', $value)); }
public function setUseUSM($value = true) { return $this->setOptions(array('useUSM', $value)); }
public function getWidth() {
$image = $this->getEngine()->get('image');
return $image['width'];
@@ -321,21 +406,34 @@ class ImageSizer extends Wire {
$image = $this->getEngine()->get('image');
return $image['height'];
}
public function getFilename() { return $this->getEngine()->filename; }
public function getExtension() { return $this->getEngine()->extension; }
public function getImageType() { return $this->getEngine()->imageType; }
public function isModified() { return $this->getEngine()->modified; }
public function getFilename() { return $this->getEngine()->getFilename(); }
public function getExtension() { return $this->getEngine()->getExtension(); }
public function getImageType() { return $this->getEngine()->getImageType(); }
public function isModified() { return $this->getEngine()->getModified(); }
public function getOptions() { return $this->getEngine()->getOptions(); }
public function getFailedEngineNames() { return $this->failedEngineNames; }
/**
* Get the current ImageSizerEngine
*
* @return ImageSizerEngine
* @param string $engineName Optionally specify a specific engine name to get a new instance of that engine
* When used, returned engine is in an unprepared state (no filename assigned, etc.). Since 3.0.138.
* @return ImageSizerEngine|null Returns ImageSizerEngine or null only if requested $engineName is not found.
* If no $engineName is specified this method may return an existing instance from a previous call.
* @throws WireException
*
*/
public function getEngine() {
public function getEngine($engineName = '') {
if($engineName) {
if($engineName === $this->defaultEngineName) {
$engineClass = __NAMESPACE__ . "\\$engineName";
$engine = $this->wire(new $engineClass());
} else {
$engine = $this->wire('modules')->get($engineName);
}
return $engine;
}
if($this->engine) return $this->engine;
@@ -533,7 +631,7 @@ class ImageSizer extends Wire {
} else {
return null;
}
$sizer = new ImageSizerEngineGD($filename);
$sizer = new ImageSizerEngineGD();
if($wire) $wire->wire($sizer);
$result = false !== $sizer->writeBackIPTC($filename) ? true : false;
return $result;

View File

@@ -3,7 +3,7 @@
/**
* ImageSizer Engine Module (Abstract)
*
* Copyright (C) 2016 by Horst Nogajski and Ryan Cramer
* Copyright (C) 2016-2019 by Horst Nogajski and Ryan Cramer
* This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/
*
* @property bool $autoRotation
@@ -18,6 +18,10 @@
* @property string $flip
* @property bool $useUSM
* @property int $enginePriority Priority for use among other ImageSizerEngine modules (0=disabled, 1=first, 2=second, 3=and so on)
* @property bool $webpAdd
* @property int $webpQuality
* @property bool|null $webpResult
* @property bool|null $webpOnly
*
*/
abstract class ImageSizerEngine extends WireData implements Module, ConfigurableModule {
@@ -60,6 +64,38 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*/
protected $quality = 90;
/**
* WebP Image quality setting, 1..100
*
* @var int
*
*/
protected $webpQuality = 90;
/**
* Also create a WebP Image with this variation?
*
* @var bool
*
*/
protected $webpAdd = false;
/**
* Only create the webp file?
*
* @var bool
*
*/
protected $webpOnly = false;
/**
* webp result (null=not known or not applicable)
*
* @var bool|null
*
*/
protected $webpResult = null;
/**
* Image interlace setting, false or true
*
@@ -219,6 +255,8 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
'cropping',
'interlace',
'quality',
'webpQuality',
'webpAdd',
'sharpening',
'defaultGamma',
'scale',
@@ -448,6 +486,64 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
return $this->validSourceImageFormats();
}
/**
* Get an array of image file formats this ImageSizerModule can use as source or target
*
* Unless using the $type argument, returned array contains 'source' and 'target' indexes,
* each an array of image file types/extensions in uppercase.
*
* @param string $type Specify 'source' or 'target' to get just those formats, or omit to get all.
* @return array
* @since 3.0.138
*
*/
public function getSupportedFormats($type = '') {
$a = array(
'source' => $this->validSourceImageFormats(),
'target' => $this->validTargetImageFormats()
);
return $type && isset($a[$type]) ? $a[$type] : $a;
}
/**
* Get array of information about this engine
*
* @return array
* @since 3.0.138
*
*/
public function getEngineInfo() {
$formats = $this->getSupportedFormats();
$moduleName = $this->className();
$className = $this->className(true);
if(is_callable("$className::getModuleInfo")) {
$moduleInfo = $className::getModuleInfo();
} else {
$moduleInfo = $this->wire('modules')->getModuleInfoVerbose($className);
}
if(!is_array($moduleInfo)) $moduleInfo = array();
$info = array(
'name' => str_replace('ImageSizerEngine', '', $moduleName),
'title' => isset($moduleInfo['title']) ? $moduleInfo['title'] : '',
'class' => $moduleName,
'summary' => isset($moduleInfo['summary']) ? $moduleInfo['summary'] : '',
'author' => isset($moduleInfo['author']) ? $moduleInfo['author'] : '',
'moduleVersion' => isset($moduleInfo['version']) ? $moduleInfo['version'] : '',
'libraryVersion' => $this->getLibraryVersion(),
'priority' => $this->enginePriority,
'sources' => $formats['source'],
'targets' => $formats['target'],
'quality' => $this->quality,
'sharpening' => $this->sharpening,
);
return $info;
}
/*************************************************************************************************
* COMMON IMPLEMENTATION METHODS
*
@@ -552,9 +648,9 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
$dest = preg_replace('/\.' . $extension . '$/', '_tmp.' . $extension, $filename);
if(strlen($content) == @file_put_contents($dest, $content, \LOCK_EX)) {
// on success we replace the file
unlink($filename);
rename($dest, $filename);
wireChmod($filename);
$this->wire('files')->unlink($filename);
$this->wire('files')->rename($dest, $filename);
$this->wire('files')->chmod($filename);
return true;
} else {
// it was created a temp diskfile but not with all data in it
@@ -669,28 +765,28 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
if($targetWidth == $originalTargetWidth && 1 + $targetWidth == $pWidth) $pWidth = $pWidth - 1;
if($targetHeight == $originalTargetHeight && 1 + $targetHeight == $pHeight) $pHeight = $pHeight - 1;
if(!$this->upscaling) {
// we are going to shoot for something smaller than the target
while($pWidth > $img['width'] || $pHeight > $img['height']) {
// favor the smallest dimension
if($pWidth > $img['width']) {
$pWidth = $img['width'];
$pHeight = $this->getProportionalHeight($pWidth);
}
if($pHeight > $img['height']) {
$pHeight = $img['height'];
$pWidth = $this->getProportionalWidth($pHeight);
}
if($targetWidth > $pWidth) $targetWidth = $pWidth;
if($targetHeight > $pHeight) $targetHeight = $pHeight;
if(!$this->cropping) {
$targetWidth = $pWidth;
$targetHeight = $pHeight;
}
if(!$this->upscaling && ($img['width'] < $targetWidth || $img['height'] < $targetHeight)) {
// via @horst-n PR #118:
// upscaling is not allowed and we have one or both dimensions to small,
// we scale down the target dimensions to fit within the image dimensions,
// with respect to the target dimensions ratio
$ratioSource = $img['height'] / $img['width'];
$ratioTarget = !$this->cropping ? $ratioSource : $targetHeight / $targetWidth;
if($ratioSource >= $ratioTarget) {
// ratio is equal or target fits into source
$pWidth = $targetWidth = $img['width'];
$pHeight = $img['height'];
$targetHeight = ceil($pWidth * $ratioTarget);
} else {
// target doesn't fit into source
$pHeight = $targetHeight = $img['height'];
$pWidth = $img['width'];
$targetWidth = ceil($pHeight / $ratioTarget);
}
if($this->cropping) {
// we have to disable any sharpening method here,
// as the source will not be resized, only cropped
$this->sharpening = 'none';
}
}
@@ -819,7 +915,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
* disable cropping, specify boolean false. To enable cropping with default (center), you may also specify
* boolean true.
*
* @return $this
* @return self
*
*/
public function setCropping($cropping = true) {
@@ -834,7 +930,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param array $value containing 4 params (x y w h) indexed or associative
*
* @return $this
* @return self
* @throws WireException when given invalid value
*
*/
@@ -878,14 +974,51 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param int $n
*
* @return $this
* @return self
*
*/
public function setQuality($n) {
$n = (int) $n;
if($n < 1) $n = 1;
if($n > 100) $n = 100;
$this->quality = (int) $n;
$this->quality = $this->getIntegerValue($n, 1, 100);
return $this;
}
/**
* Set the image quality 1-100 for WebP output, where 100 is highest quality
*
* @param int $n
*
* @return self
*
*/
public function setWebpQuality($n) {
$this->webpQuality = $this->getIntegerValue($n, 1, 100);
return $this;
}
/**
* Set flag to also create a webp file or not
*
* @param bool $webpAdd
* @param bool|null $webpOnly
* @return self
*
*/
public function setWebpAdd($webpAdd, $webpOnly = null) {
$this->webpAdd = (bool) $webpAdd;
if(is_bool($webpOnly)) $this->webpOnly = $webpOnly;
return $this;
}
/**
* Set flag to only create a webp file
*
* @param bool value$
* @return self
*
*/
public function setWebpOnly($value) {
$this->webpOnly = (bool) $value;
if($this->webpOnly) $this->webpAdd = true; // webpAdd required for webpOnly
return $this;
}
@@ -928,7 +1061,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param mixed $value
*
* @return $this
* @return self
* @throws WireException
*
*/
@@ -957,7 +1090,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param bool $value Whether to auto-rotate or not (default = true)
*
* @return $this
* @return self
*
*/
public function setAutoRotation($value = true) {
@@ -970,7 +1103,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param bool $value Whether to upscale or not (default = true)
*
* @return $this
* @return self
*
*/
public function setUpscaling($value = true) {
@@ -983,7 +1116,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param bool $value Whether to upscale or not (default = true)
*
* @return $this
* @return self
*
*/
public function setInterlace($value = true) {
@@ -996,7 +1129,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param float|int $value 0.5 to 4.0 or -1 to disable
*
* @return $this
* @return self
* @throws WireException when given invalid value
*
*/
@@ -1016,7 +1149,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param int $value 10 to 60 recommended, default is 30
*
* @return $this
* @return self
*
*/
public function setTimeLimit($value = 30) {
@@ -1042,7 +1175,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param float $scale
*
* @return $this
* @return self
*
*/
public function setScale($scale) {
@@ -1057,7 +1190,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param bool $hidpi True or false (default=true)
*
* @return $this
* @return self
*
*/
public function setHidpi($hidpi = true) {
@@ -1071,7 +1204,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param $degrees
*
* @return $this
* @return self
*
*/
public function setRotate($degrees) {
@@ -1089,7 +1222,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param $flip
*
* @return $this
* @return self
*
*/
public function setFlip($flip) {
@@ -1103,7 +1236,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param bool $value Whether to USM is used or not (default = true)
*
* @return $this
* @return self
*
*/
public function setUseUSM($value = true) {
@@ -1116,6 +1249,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param array $options May contain the following (show with default values):
* 'quality' => 90,
* 'webpQuality' => 90,
* 'cropping' => true,
* 'upscaling' => true,
* 'autoRotation' => true,
@@ -1125,7 +1259,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
* 'rotate' => 0 (90, 180, 270 or negative versions of those)
* 'flip' => '', (vertical|horizontal)
*
* @return $this
* @return self
*
*/
public function setOptions(array $options) {
@@ -1148,6 +1282,15 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
case 'quality':
$this->setQuality($value);
break;
case 'webpQuality':
$this->setWebpQuality($value);
break;
case 'webpAdd':
$this->setWebpAdd($value);
break;
case 'webpOnly':
$this->setWebpOnly($value);
break;
case 'cropping':
$this->setCropping($value);
break;
@@ -1196,6 +1339,25 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
if(in_array(strtolower($value), array('0', 'off', 'false', 'no', 'n', 'none'))) return false;
return ((int) $value) > 0;
}
/**
* Get integer value within given range
*
* @param int $n Number to require in given range
* @param int $min Minimum allowed number
* @param int $max Maximum allowed number
* @return int
*
*/
protected function getIntegerValue($n, $min, $max) {
$n = (int) $n;
if($n < $min) {
$n = $min;
} else if($n > $max) {
$n = $max;
}
return $n;
}
/**
* Return an array of the current options
@@ -1207,6 +1369,9 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
$options = array(
'quality' => $this->quality,
'webpQuality' => $this->webpQuality,
'webpAdd' => $this->webpAdd,
'webpOnly' => $this->webpOnly,
'cropping' => $this->cropping,
'upscaling' => $this->upscaling,
'interlace' => $this->interlace,
@@ -1247,6 +1412,8 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
'options'
);
if($key === 'webpResult') return $this->webpResult;
if($key === 'webpOnly') return $this->webpOnly;
if(in_array($key, $keys)) return $this->$key;
if(in_array($key, $this->optionNames)) return $this->$key;
if(isset($this->options[$key])) return $this->options[$key];
@@ -1387,7 +1554,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* @param bool $modified
*
* @return $this
* @return self
*
*/
public function setModified($modified) {
@@ -1395,6 +1562,16 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
return $this;
}
/**
* Get whether the image was modified
*
* @return bool
*
*/
public function getModified() {
return $this->modified;
}
/**
* Check if cropping is needed, if yes, populate x- and y-position to params $w1 and $h1
*
@@ -1538,13 +1715,17 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
return false; // fallback or failed
}
// all went well, copy back the temp file, remove the temp file
if(!@copy($this->tmpFile, $this->filename)) return false; // fallback or failed
wireChmod($this->filename);
@unlink($this->tmpFile);
// post processing: IPTC, setModified and reload ImageInfo
$this->writeBackIPTC($this->filename, false);
if($this->webpOnly) {
$this->wire('files')->unlink($this->tmpFile);
} else {
// all went well, copy back the temp file,
if(!@copy($this->tmpFile, $this->filename)) return false; // fallback or failed
$this->wire('files')->chmod($this->filename);
// remove the temp file
$this->wire('files')->unlink($this->tmpFile);
// post processing: IPTC, setModified and reload ImageInfo
$this->writeBackIPTC($this->filename, false);
}
$this->setModified($this->modified);
$this->loadImageInfo($this->filename, true);
@@ -1590,13 +1771,13 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
if($result) {
// success
if($tmpFilename != $dstFilename) {
if(is_file($dstFilename)) unlink($dstFilename);
rename($tmpFilename, $dstFilename);
if(is_file($dstFilename)) $this->wire('files')->unlink($dstFilename);
$this->wire('files')->rename($tmpFilename, $dstFilename);
}
wireChmod($dstFilename);
$this->wire('files')->chmod($dstFilename);
} else {
// fail
if(is_file($tmpFilename)) unlink($tmpFilename);
if(is_file($tmpFilename)) $this->wire('files')->unlink($tmpFilename);
}
return $result;
@@ -1860,6 +2041,17 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
return $this->moduleConfigData;
}
/**
* Get library version string
*
* @return string Returns version string or blank string if not applicable/available
* @since 3.0.138
*
*/
public function getLibraryVersion() {
return '';
}
/**
* Module configuration
*

View File

@@ -8,14 +8,25 @@
*
* Other user contributions as noted.
*
* Copyright (C) 2016 by Horst Nogajski and Ryan Cramer
* Copyright (C) 2016-2019 by Horst Nogajski and Ryan Cramer
* This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/
*
* https://processwire.com
*
* @method bool imSaveReady($im, $filename)
*
*/
class ImageSizerEngineGD extends ImageSizerEngine {
public static function getModuleInfo() {
return array(
'title' => 'GD Image Sizer',
'version' => 1,
'summary' => "Uses PHPs built-in GD library to resize images.",
'author' => 'Horst Nogajski',
);
}
/**
* @var string
*
@@ -34,6 +45,14 @@ class ImageSizerEngineGD extends ImageSizerEngine {
*/
protected $gammaLinearized;
/**
* Webp support available?
*
* @var bool|null
*
*/
static protected $webpSupport = null;
/**
* Get formats GD and resize
*
@@ -44,6 +63,30 @@ class ImageSizerEngineGD extends ImageSizerEngine {
return array('JPG', 'JPEG', 'PNG', 'GIF');
}
/**
* Get an array of image file extensions this ImageSizerModule can create
*
* @return array of uppercase file extensions, i.e. ['PNG', 'JPG']
*
*/
protected function validTargetImageFormats() {
$formats = $this->validSourceImageFormats();
if($this->supported('webp')) $formats[] = 'WEBP';
return $formats;
}
/**
* Get library version string
*
* @return string Returns version string or blank string if not applicable/available
* @since 3.0.138
*
*/
public function getLibraryVersion() {
$gd = gd_info();
return isset($gd['GD Version']) ? $gd['GD Version'] : '';
}
/**
* Return whether or not GD can proceed - Is the current image(sub)format supported?
*
@@ -57,6 +100,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
// and if it passes the mandatory requirements, we check particularly aspects here
switch($action) {
case 'imageformat':
// compare current imagefile infos fetched from ImageInspector
$requested = $this->getImageInfo(false);
@@ -69,7 +113,16 @@ class ImageSizerEngineGD extends ImageSizerEngine {
return true;
}
break;
case 'webp':
if(self::$webpSupport === null) {
// only call it once
$gd = gd_info();
self::$webpSupport = isset($gd['WebP Support']) ? $gd['WebP Support'] : false;
}
return self::$webpSupport;
break;
case 'install':
/*
$gd = gd_info();
@@ -77,6 +130,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$png = isset($gd['PNG Support']) ? $gd['PNG Support'] : false;
$gif = isset($gd['GIF Read Support']) && isset($gd['GIF Create Support']) ? $gd['GIF Create Support'] : false;
$freetype = isset($gd['FreeType Support']) ? $gd['FreeType Support'] : false;
$webp = isset($gd['WebP Support']) ? $gd['WebP Support'] : false;
$this->config->gdReady = true;
*/
return true;
@@ -218,14 +272,14 @@ class ImageSizerEngineGD extends ImageSizerEngine {
// current version is already the desired result, we only may have to compress JPEGs but leave GIF and PNG as is:
if(!$isModified && ($this->imageType == \IMAGETYPE_PNG || $this->imageType == \IMAGETYPE_GIF)) {
if(!$isModified && !$this->webpOnly && !$this->webpAdd && ($this->imageType == \IMAGETYPE_PNG || $this->imageType == \IMAGETYPE_GIF)) {
$result = @copy($srcFilename, $dstFilename);
if(isset($image) && is_resource($image)) @imagedestroy($image); // clean up
if(isset($image)) $image = null;
return $result; // early return !
}
if(isset($image)) $image = null;
return $result; // early return !
}
// process JPEGs
// process JPEGs
if(self::checkMemoryForImage(array(imagesx($image), imagesy($image), 3)) === false) {
throw new WireException(basename($srcFilename) . " - not enough memory to copy the final image");
}
@@ -246,7 +300,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$this->prepareImageLayer($thumb, $image);
imagecopyresampled($thumb, $image, 0, 0, 0, 0, $finalWidth, $finalHeight, $this->image['width'], $this->image['height']);
} else if(4 == $resizeMethod) { // 4 = resize and crop with aspect ratio
} else if(4 == $resizeMethod) { // 4 = resize and crop with aspect ratio, - or crop without resizing ($upscaling == false)
// we have to scale up or down and to _crop_
@@ -259,20 +313,6 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$sourceWidth = $this->image['width'];
$sourceHeight = $this->image['height'];
/*
* @todo figure out how to make zoom setting adjust coordinates to imagecopyresampled() calls
$zoom = is_array($this->cropping) && isset($this->cropping[2]) ? $this->cropping[2] : 0;
if($zoom > 1) {
$zoom = $zoom * 0.01;
$sourceWidth -= $sourceWidth * $zoom;
$sourceHeight -= $sourceHeight * $zoom;
$sourceX = $this->image['width'] - ($sourceWidth / 2);
$sourceY = $this->image['height'] - ($sourceHeight / 2);
$bgX = 0;
$bgY = 0;
}
*/
$thumb2 = imagecreatetruecolor($bgWidth, $bgHeight);
$this->prepareImageLayer($thumb2, $image);
imagecopyresampled(
@@ -336,41 +376,91 @@ class ImageSizerEngineGD extends ImageSizerEngine {
}
}
// optionally apply interlace bit to the final image.
// this will result in progressive JPEGs
if($this->interlace && \IMAGETYPE_JPEG == $this->imageType) {
if(0 == imageinterlace($thumb, 1)) {
// log that setting the interlace bit has failed ?
// ...
}
}
// write to file
$result = false;
// write to file(s)
if(file_exists($dstFilename)) $this->wire('files')->unlink($dstFilename);
$result = null; // null=not yet known
switch($this->imageType) {
case \IMAGETYPE_GIF:
// correct gamma from linearized 1.0 back to 2.0
$this->gammaCorrection($thumb, false);
$result = imagegif($thumb, $dstFilename);
// save the final GIF image file
if($this->imSaveReady($thumb, $srcFilename)) $result = imagegif($thumb, $dstFilename);
break;
case \IMAGETYPE_PNG:
// optionally correct gamma from linearized 1.0 back to 2.0
if(!$this->hasAlphaChannel()) $this->gammaCorrection($thumb, false);
// always use highest compression level for PNG (9) per @horst
$result = imagepng($thumb, $dstFilename, 9);
// save the final PNG image file and always use highest compression level (9) per @horst
if($this->imSaveReady($thumb, $srcFilename)) $result = imagepng($thumb, $dstFilename, 9);
break;
case \IMAGETYPE_JPEG:
// correct gamma from linearized 1.0 back to 2.0
$this->gammaCorrection($thumb, false);
$result = imagejpeg($thumb, $dstFilename, $this->quality);
if($this->imSaveReady($thumb, $srcFilename)) {
// optionally apply interlace bit to the final image. this will result in progressive JPEGs
if($this->interlace) {
if(0 == imageinterlace($thumb, 1)) {
// log that setting the interlace bit has failed ?
// ...
}
}
// save the final JPEG image file
$result = imagejpeg($thumb, $dstFilename, $this->quality);
}
break;
default:
$result = false;
}
// release the last GD image object
if(isset($thumb) && is_resource($thumb)) @imagedestroy($thumb);
if(isset($thumb)) $thumb = null;
if($result === null) $result = $this->webpResult; // if webpOnly option used
return $result;
}
/**
* Called before saving of image, returns true if save should proceed, false if not
*
* Also Creates a webp file when settings indicate it should.
*
* @param resource $im
* @param string $filename Source filename
* @return bool
*
*/
protected function ___imSaveReady($im, $filename) {
if($this->webpOnly || $this->webpAdd) {
$this->webpResult = $this->imSaveWebP($im, $filename, $this->webpQuality);
}
return $this->webpOnly ? false : true;
}
/**
* Create WebP image (@horst)
* Is requested by image options: ["webpAdd" => true] OR ["webpOnly" => true]
*
* @param resource $im
* @param string $filename
* @param int $quality
*
* @return boolean true | false
*
*/
protected function imSaveWebP($im, $filename, $quality = 90) {
if(!function_exists('imagewebp')) return false;
$path_parts = pathinfo($filename);
$webpFilename = $path_parts['dirname'] . '/' . $path_parts['filename'] . '.webp';
if(file_exists($webpFilename)) $this->wire('files')->unlink($webpFilename);
return imagewebp($im, $webpFilename, $quality);
}
/**
* Rotate image (@horst)
*
@@ -523,7 +613,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
* with mode = true it linearizes an image to 1
* with mode = false it set it back to the originating gamma value
*
* @param GD -image-resource $image
* @param resource $image
* @param bool $mode
*
*/
@@ -758,8 +848,8 @@ class ImageSizerEngineGD extends ImageSizerEngine {
*
* Intended for use by the resize() method
*
* @param GD -resource $im, destination resource needs to be prepared
* @param GD -resource $image, with GIF we need to read from source resource
* @param resource $im, destination resource needs to be prepared
* @param resource $image, with GIF we need to read from source resource
*
*/
protected function prepareImageLayer(&$im, &$image) {

View File

@@ -16,7 +16,7 @@
* An Inputfield is typically associated with a Fieldtype module when used for ProcessWire fields.
* Most Inputfields can also be used on their own.
*
* #pw-order-groups attribute-methods,attribute-properties,settings,traversal,labels,appearance,behavior,other,output,input,states
* #pw-order-groups attribute-methods,attribute-properties,settings,traversal,labels,appearance,uikit,behavior,other,output,input,states
* #pw-use-constants
* #pw-summary Inputfield is the base class for modules that collect user input for fields.
* #pw-summary-attribute-properties These properties are retrieved or manipulated via the attribute methods above.
@@ -25,6 +25,7 @@
* #pw-summary-skipLabel-constants Constants allowed for the `Inputfield::skipLabel` property.
* #pw-summary-renderValue-constants Options for `Inputfield::renderValueFlags` property, applicable `Inputfield::renderValue()` method call.
* #pw-summary-module Methods primarily of interest during module development.
* #pw-summary-uikit Settings for Inputfields recognized and used by AdminThemeUikit.
*
* #pw-body =
* ~~~~~
@@ -45,16 +46,31 @@
* @property mixed $value HTML 'value' attribute for the Inputfield. #pw-group-attribute-properties
* @property string $class HTML 'class' attribute for the Inputfield. #pw-group-attribute-properties
*
* @method string|Inputfield name($name = null) Get or set the name attribute. @since 3.0.110 #pw-group-attribute-methods
* @method string|Inputfield id($id = null) Get or set the id attribute. @since 3.0.110 #pw-group-attribute-methods
* @method string|Inputfield class($class = null) Get class attribute or add a class to the class attribute. @since 3.0.110 #pw-group-attribute-methods
*
* LABELS & CONTENT
* ================
* @property string $label Primary label text that appears above the input. #pw-group-labels
* @property string $description Optional description that appears under label to provide more detailed information. #pw-group-labels
* @property string $notes Optional notes that appear under input area to provide additional notes. #pw-group-labels
* @property string $detail Optional text details that appear under notes. @since 3.0.140 #pw-group-labels
* @property string $icon Optional font-awesome icon name to accompany label (excluding the "fa-") part). #pw-group-labels
* @property string $requiredLabel Optional custom label to display when missing required value. @since 3.0.98 #pw-group-labels
* @property string $head Optional text that appears below label but above description (only used by some Inputfields). #pw-internal
* @property string|null $prependMarkup Optional markup to prepend to the Inputfield content container. #pw-group-other
* @property string|null $appendMarkup Optional markup to append to the Inputfield content container. #pw-group-other
*
* @method string|Inputfield label($label = null) Get or set the 'label' property via method. @since 3.0.110 #pw-group-labels
* @method string|Inputfield description($description = null) Get or set the 'description' property via method. @since 3.0.110 #pw-group-labels
* @method string|Inputfield notes($notes = null) Get or set the 'notes' property via method. @since 3.0.110 #pw-group-labels
* @method string|Inputfield icon($icon = null) Get or set the 'icon' property via method. @since 3.0.110 #pw-group-labels
* @method string|Inputfield requiredLabel($requiredLabel = null) Get or set the 'requiredLabel' property via method. @since 3.0.110 #pw-group-labels
* @method string|Inputfield head($head = null) Get or set the 'head' property via method. @since 3.0.110 #pw-group-labels
* @method string|Inputfield prependMarkup($markup = null) Get or set the 'prependMarkup' property via method. @since 3.0.110 #pw-group-labels
* @method string|Inputfield appendMarkup($markup = null) Get or set the 'appendMarkup' property via method. @since 3.0.110 #pw-group-labels
*
* APPEARANCE
* ==========
* @property int $collapsed Whether the field is collapsed or visible, using one of the "collapsed" constants. #pw-group-appearance
@@ -62,10 +78,25 @@
* @property int $columnWidth Width of column for this Inputfield 10-100 percent. 0 is assumed to be 100 (default). #pw-group-appearance
* @property int $skipLabel Skip display of the label? See the "skipLabel" constants for options. #pw-group-appearance
*
* @method int|Inputfield collapsed($collapsed = null) Get or set collapsed property via method. @since 3.0.110 #pw-group-appearance
* @method string|Inputfield showIf($showIf = null) Get or set showIf selector property via method. @since 3.0.110 #pw-group-appearance
* @method int|Inputfield columnWidth($columnWidth = null) Get or set columnWidth property via method. @since 3.0.110 #pw-group-appearance
* @method int|Inputfield skipLabel($skipLabel = null) Get or set the skipLabel constant property via method. @since 3.0.110 #pw-group-appearance
*
* UIKIT THEME
* ===========
* @property bool|string $themeOffset Offset/margin for Inputfield, one of 's', 'm', or 'l'. #pw-group-uikit
* @property string $themeBorder Border style for Inputfield, one of 'none', 'card', 'hide' or 'line'. #pw-group-uikit
* @property string $themeInputSize Input size height/font within Inputfield, one of 's', 'm', or 'l'. #pw-group-uikit
* @property string $themeInputWidth Input width for text-type inputs, one of 'xs', 's', 'm', 'l', or 'f' (for full-width). #pw-group-uikit
* @property string $themeColor Color theme for Inputfield, one of 'primary', 'secondary', 'warning', 'danger', 'success', 'highlight', 'none'. #pw-group-uikit
* @property bool $themeBlank Makes <input> element display with no minimal container / no border when true. #pw-group-uikit
*
* SETTINGS & BEHAVIOR
* ===================
* @property int|bool $required Set to true (or 1) to make input required, or false (or 0) to make not required (default=0). #pw-group-behavior
* @property string $requiredIf Optional conditions under which input is required (selector string). #pw-group-behavior
* @property int|bool|null $requiredAttr Use HTML5 “required” attribute when used by Inputfield and $required is true? Default=null. #pw-group-behavior
* @property InputfieldWrapper|null $parent The parent InputfieldWrapper for this Inputfield or null if not set. #pw-internal
* @property null|bool|Fieldtype $hasFieldtype The Fieldtype using this Inputfield, or boolean false when known not to have a Fieldtype, or null when not known. #pw-group-other
* @property null|Field $hasField The Field object associated with this Inputfield, or null when not applicable or not known. #pw-group-other
@@ -77,6 +108,15 @@
* @property string $wrapClass Optional class name (CSS) to apply to the HTML element wrapping the Inputfield. #pw-group-other
* @property string $headerClass Optional class name (CSS) to apply to the InputfieldHeader element #pw-group-other
* @property string $contentClass Optional class name (CSS) to apply to the InputfieldContent element #pw-group-other
* @property int|null $textFormat Text format to use for description/notes text in Inputfield (see textFormat constants) #pw-group-output
*
* @method string|Inputfield required($required = null) Get or set required state. @since 3.0.110 #pw-group-behavior
* @method string|Inputfield requiredIf($requiredIf = null) Get or set required-if selector. @since 3.0.110 #pw-group-behavior
*
* @method string|Inputfield wrapClass($class = null) Get wrapper class attribute or add a class to it. @since 3.0.110 #pw-group-other
* @method string|Inputfield headerClass($class = null) Get header class attribute or add a class to it. @since 3.0.110 #pw-group-other
* @method string|Inputfield contentClass($class = null) Get content class attribute or add a class to it. @since 3.0.110 #pw-group-other
*
*
* HOOKABLE METHODS
* ================
@@ -185,7 +225,7 @@ abstract class Inputfield extends WireData implements Module {
const skipLabelFor = true;
/**
* Don't use a label header element at all (basically, skip the label)
* Don't show a visible header (likewise, do not show the label)
* #pw-group-skipLabel-constants
*
*/
@@ -198,6 +238,14 @@ abstract class Inputfield extends WireData implements Module {
*/
const skipLabelBlank = 4;
/**
* Do not render any markup for the header/label at all
* #pw-group-skipLabel-constants
* @since 3.0.139
*
*/
const skipLabelMarkup = 8;
/**
* Plain text: no type of markdown or HTML allowed
* #pw-group-textFormat-constants
@@ -261,7 +309,7 @@ abstract class Inputfield extends WireData implements Module {
*/
/**
* Attributes specified for the XHTML output, like class, rows, cols, etc.
* Attributes specified for the HTML output, like class, rows, cols, etc.
*
*/
protected $attributes = array();
@@ -304,22 +352,25 @@ abstract class Inputfield extends WireData implements Module {
self::$numInstances++;
$this->set('label', ''); // primary clikable label
$this->set('description', ''); // descriptive copy, below label
$this->set('label', ''); // primary clickable label
$this->set('description', ''); // descriptive copy, below label
$this->set('icon', ''); // optional icon name to accompany label
$this->set('notes', ''); // highlighted descriptive copy, below output of input field
$this->set('head', ''); // below label, above description
$this->set('required', 0); // set to 1 to make value required for this field
$this->set('notes', ''); // highlighted descriptive copy, below output of input field
$this->set('detail', ''); // text details that appear below notes
$this->set('head', ''); // below label, above description
$this->set('required', 0); // set to 1 to make value required for this field
$this->set('requiredIf', ''); // optional conditions to make it required
$this->set('collapsed', ''); // see the collapsed* constants at top of class (use blank string for unset value)
$this->set('showIf', ''); // optional conditions selector
$this->set('columnWidth', ''); // percent width of the field. blank or 0 = 100.
$this->set('collapsed', ''); // see the collapsed* constants at top of class (use blank string for unset value)
$this->set('showIf', ''); // optional conditions selector
$this->set('columnWidth', ''); // percent width of the field. blank or 0 = 100.
$this->set('skipLabel', self::skipLabelNo); // See the skipLabel constants
$this->set('wrapClass', ''); // optional class to apply to the Inputfield wrapper (contains InputfieldHeader + InputfieldContent)
$this->set('headerClass', ''); // optional class to apply to InputfieldHeader wrapper
$this->set('contentClass', ''); // optional class to apply to InputfieldContent wrapper
$this->set('textFormat', self::textFormatBasic); // format applied to description and notes
$this->set('renderValueFlags', 0); // see renderValue* constants, applicable to renderValue mode only
$this->set('prependMarkup', ''); // markup to prepend to Inputfield output
$this->set('appendMarkup', ''); // markup to append to Inputfield output
// default ID attribute if no 'id' attribute set
$this->defaultID = $this->className() . self::$numInstances;
@@ -424,12 +475,15 @@ abstract class Inputfield extends WireData implements Module {
*
*/
public function get($key) {
if($key == 'label' && !parent::get('label')) {
if($key === 'label') {
$value = parent::get('label');
if(strlen($value)) return $value;
if($this->skipLabel & self::skipLabelBlank) return '';
return $this->attributes['name'];
}
if($key == 'attributes') return $this->attributes;
if($key == 'parent') return $this->parent;
if($key === 'name' || $key === 'value' || $key === 'id') return $this->getAttribute($key);
if($key === 'attributes') return $this->attributes;
if($key === 'parent') return $this->parent;
if(($value = $this->wire($key)) !== null) return $value;
if(array_key_exists($key, $this->attributes)) return $this->attributes[$key];
return parent::get($key);
@@ -466,6 +520,18 @@ abstract class Inputfield extends WireData implements Module {
return $this;
}
/**
* Unset any previously set parent
*
* #pw-internal
* @return $this
*
*/
public function unsetParent() {
$this->parent = null;
return $this;
}
/**
* Get this Inputfields parent InputfieldWrapper, or NULL if it doesnt have one.
*
@@ -489,6 +555,7 @@ abstract class Inputfield extends WireData implements Module {
*
*/
public function getParents() {
/** @var InputfieldWrapper|null $parent */
$parent = $this->getParent();
if(!$parent) return array();
$parents = array($parent);
@@ -496,6 +563,80 @@ abstract class Inputfield extends WireData implements Module {
return $parents;
}
/**
* Get or set parent of Inputfield
*
* This convenience method performs the same thing as getParent() and setParent().
*
* To get parent, specify no arguments. It will return null if no parent assigned, or an
* InputfieldWrapper instance of the parent.
*
* To set parent, specify an InputfieldWrapper for the $parent argument. The return value
* is the current Inputfield for fluent interface.
*
* #pw-group-traversal
*
* @param null|InputfieldWrapper $parent
* @return null|Inputfield|InputfieldWrapper
* @since 3.0.110
*
*/
public function parent($parent = null) {
if($parent === null) {
return $this->getParent();
} else {
return $this->setParent($parent);
}
}
/**
* Get array of all parents of this Inputfield
*
* This is identical to and an alias of the getParents() method.
*
* #pw-group-traversal
*
* @return array
* @since 3.0.110
*
*/
public function parents() {
return $this->getParents();
}
/**
* Get the root parent InputfieldWrapper element (farthest parent, commonly InputfieldForm)
*
* This returns null only if Inputfield it is called from has not yet been added to an InputfieldWrapper.
*
* #pw-group-traversal
*
* @return InputfieldForm|InputfieldWrapper|null
* @since 3.0.106
*
*/
public function getRootParent() {
$parents = $this->getParents();
return count($parents) ? end($parents) : null;
}
/**
* Get the InputfieldForm element that contains this field or null if not yet defined
*
* This is the same as the `getRootParent()` method except that it returns null if root parent
* is not an InputfieldForm.
*
* #pw-group-traversal
*
* @return InputfieldForm|null
* @since 3.0.106
*
*/
public function getForm() {
$form = $this instanceof InputfieldForm ? $this : $this->getRootParent();
return ($form instanceof InputfieldForm ? $form : null);
}
/**
* Set an attribute
*
@@ -523,21 +664,46 @@ abstract class Inputfield extends WireData implements Module {
* - Name of attribute (string)
* - Names of attributes (array)
* - String with names of attributes split by "+" or "|"
* @param string|int|array $value Value of attribute to set.
* @param string|int|array|bool $value Value of attribute to set.
* @return $this
* @see Inputfield::attr(), Inputfield::removeAttr(), Inputfield::addClass()
*
*/
public function setAttribute($key, $value) {
if(is_array($key)) $keys = $key;
else if(strpos($key, '+') !== false) $keys = explode('+', $key);
else if(strpos($key, '|') !== false) $keys = explode('|', $key);
else $keys = array($key);
if(is_array($key)) {
$keys = $key;
} else if(strpos($key, '+') !== false) {
$keys = explode('+', $key);
} else if(strpos($key, '|') !== false) {
$keys = explode('|', $key);
} else {
$keys = array($key);
}
if(is_bool($value) && !in_array($key, array('name', 'id', 'class', 'value', 'type'))) {
$booleanValue = $value;
} else {
$booleanValue = null;
}
foreach($keys as $key) {
if($key == 'name' && strlen($value)) {
if(!ctype_alpha("$key")) $key = $this->wire('sanitizer')->attrName($key);
if(empty($key)) continue;
if($booleanValue !== null) {
if($booleanValue === true) {
// boolean true attribute sets value as attribute name (i.e. checked='checked')
$value = $key;
} else if($booleanValue === false) {
// boolean false attribute implies remove attribute
$this->removeAttribute($key);
continue;
}
}
if($key === 'name' && strlen($value)) {
$idAttr = $this->getAttribute('id');
$nameAttr = $this->getAttribute('name');
if($idAttr == $this->defaultID || $idAttr == $nameAttr || $idAttr == "Inputfield_$nameAttr") {
@@ -546,7 +712,9 @@ abstract class Inputfield extends WireData implements Module {
}
}
if(!array_key_exists($key, $this->attributes)) $this->attributes[$key] = '';
if(!array_key_exists($key, $this->attributes)) {
$this->attributes[$key] = '';
}
if(is_array($this->attributes[$key]) && !is_array($value)) {
@@ -644,7 +812,7 @@ abstract class Inputfield extends WireData implements Module {
* - Aassociative array to set multiple attributes.
* - String with attributes split by "+" or "|" to set them all to have the same value.
* - Specify boolean true to get all attributes in an associative array.
* @param string|int|null $value Value to set (if setting), omit otherwise.
* @param string|int|bool|null $value Value to set (if setting), omit otherwise.
* @return Inputfield|array|string|int|object|float If setting an attribute, it returns this instance. If getting an attribute, the attribute is returned.
* @see Inputfield::removeAttr(), Inputfield::addClass(), Inputfield::removeClass()
*
@@ -681,6 +849,39 @@ abstract class Inputfield extends WireData implements Module {
return $this->setAttribute('value', $value);
}
/**
* If method call resulted in no handler, this hookable method is called.
*
* We use this to allow for attributes and properties to be set via method, useful primarily
* for fluent interface calls.
*
* #pw-internal
*
* @param string $method Requested method name
* @param array $arguments Arguments provided
* @return null|mixed Return value of method (if applicable)
* @throws WireException
* @since 3.0.110
*
*/
protected function ___callUnknown($method, $arguments) {
$arg = isset($arguments[0]) ? $arguments[0] : null;
if(isset($this->attributes[$method])) {
// get or set an attribute
return $arg === null ? $this->getAttribute($method) : $this->setAttribute($method, $arg);
} else if(($value = $this->getSetting($method)) !== null) {
// get or set a setting
if($arg === null) return $value;
if(stripos($method, 'class') !== false) {
// i.e. class, wrapClass, contentClass, etc.
return $this->addClass($arg, $method);
} else {
return $this->set($method, $arg);
}
}
return parent::___callUnknown($method, $arguments);
}
/**
* Get all attributes specified for this Inputfield
*
@@ -690,11 +891,13 @@ abstract class Inputfield extends WireData implements Module {
*
*/
public function getAttributes() {
$attributes = array();
foreach($this->attributes as $key => $value) {
$attributes[$key] = $value;
$attrs = $this->attributes;
if(!isset($attrs['required']) && $this->getSetting('required') && $this->getSetting('requiredAttr')) {
if(!$this->getSetting('showIf') && !$this->getSetting('requiredIf')) {
$attrs['required'] = 'required';
}
}
return $attributes;
return $attrs;
}
/**
@@ -905,7 +1108,7 @@ abstract class Inputfield extends WireData implements Module {
*
* #pw-group-attribute-methods
*
* @param string $class Class name you want to remove or specify one of the following:
* @param string|array $class Class name you want to remove or specify one of the following:
* - Single class name to remove.
* - Space-separated class names you want to remove (Since 3.0.16).
* - Array of class names you want to remove (Since 3.0.16).
@@ -981,12 +1184,16 @@ abstract class Inputfield extends WireData implements Module {
}
foreach($attributes as $attr => $value) {
// skip over empty attributes
if(!is_array($value) && !strlen("$value") && (!$value = $this->attr($attr))) continue;
// if an attribute has multiple values (like class), then bundle them into a string separated by spaces
if(is_array($value)) $value = implode(' ', $value);
if(is_array($value)) {
// if an attribute has multiple values (like class), then bundle them into a string separated by spaces
$value = implode(' ', $value);
} else if(!strlen("$value") && strpos($attr, 'data-') !== 0) {
// skip over empty non-data attributes that are not arrays
// if(!$value = $this->attr($attr))) continue; // was in 3.0.132 and earlier
continue;
}
$str .= "$attr=\"" . htmlspecialchars($value, ENT_QUOTES, "UTF-8") . '" ';
}
@@ -1069,7 +1276,7 @@ abstract class Inputfield extends WireData implements Module {
* @param bool $renderValueMode
*
*/
public function ___renderReadyHook(Inputfield $parent = null, $renderValueMode) { }
public function ___renderReadyHook(Inputfield $parent = null, $renderValueMode = false) { }
/**
* This hook was replaced by renderReady
@@ -1085,7 +1292,7 @@ abstract class Inputfield extends WireData implements Module {
/**
* Process input for this Inputfield directly from the POST (or GET) variables
*
* This method should pull the value from the given `$input` ragument, sanitize/validate it, and
* This method should pull the value from the given `$input` argument, sanitize/validate it, and
* populate it to the `value` attribute of this Inputfield.
*
* Inputfield modules should implement this method if the built-in one here doesn't solve their need.
@@ -1300,6 +1507,22 @@ abstract class Inputfield extends WireData implements Module {
$field->description = $this->_("If checked, a value will be required for this field.");
$field->collapsed = $this->getSetting('required') ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$fields->add($field);
$requiredAttr = $this->getSetting('requiredAttr');
if($requiredAttr !== null) {
// Inputfield must have set requiredAttr to some non-null value before this will appear as option in config
$field->columnWidth = 50; // required checkbox
/** @var InputfieldCheckbox $f */
$f = $this->modules->get('InputfieldCheckbox');
$f->attr('name', 'requiredAttr');
$f->label = $this->_('Also use HTML5 “required” attribute?');
$f->showIf = "required=1, showIf='', requiredIf=''";
$f->description = $this->_('Use only on fields *always* visible to the user.');
$f->icon = 'html5';
$f->columnWidth = 50;
if($requiredAttr) $f->attr('checked', 'checked');
$fields->add($f);
}
/** @var InputfieldText $field */
$field = $this->modules->get('InputfieldText');
@@ -1377,6 +1600,7 @@ abstract class Inputfield extends WireData implements Module {
'columnWidth',
'required',
'requiredIf',
'requiredAttr',
'showIf'
);
}
@@ -1457,7 +1681,9 @@ abstract class Inputfield extends WireData implements Module {
$errors[] = $text;
$this->wire('session')->set($key, $errors);
}
$text .= $this->name ? " ($this->name)" : "";
$label = $this->getSetting('label');
if(empty($label)) $label= $this->attr('name');
if(strlen($label)) $text .= " - $label";
return parent::error($text, $flags);
}
@@ -1568,9 +1794,12 @@ abstract class Inputfield extends WireData implements Module {
*/
public function entityEncode($str, $markdown = false) {
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
// if already encoded, then un-encode it
if(strpos($str, '&') !== false && preg_match('/&(#\d+|[a-zA-Z]+);/', $str)) {
$str = html_entity_decode($str, ENT_QUOTES, "UTF-8");
$str = $sanitizer->unentities($str);
}
if($markdown && $markdown !== self::textFormatNone) {
@@ -1582,17 +1811,17 @@ abstract class Inputfield extends WireData implements Module {
if(!$textFormat) $textFormat = self::textFormatBasic;
if($textFormat & self::textFormatBasic) {
// only basic markdown allowed (default behavior)
$str = $this->wire('sanitizer')->entitiesMarkdown($str, array('allowBrackets' => true));
$str = $sanitizer->entitiesMarkdown($str, array('allowBrackets' => true));
} else if($textFormat & self::textFormatMarkdown) {
// full markdown, plus HTML is also allowed
$str = $this->wire('sanitizer')->entitiesMarkdown($str, array('fullMarkdown' => true));
$str = $sanitizer->entitiesMarkdown($str, array('fullMarkdown' => true));
} else {
// nothing allowed, text fully entity encoded regardless of $markdown request
$str = $this->wire('sanitizer')->entities($str);
$str = $sanitizer->entities($str);
}
} else {
$str = $this->wire('sanitizer')->entities($str);
$str = $sanitizer->entities($str);
}
return $str;

View File

@@ -24,6 +24,7 @@
* @property InputfieldsArray|null $children Inputfield instances that are direct children of this InputfieldWrapper. #pw-group-properties
*
* @method string renderInputfield(Inputfield $inputfield, $renderValueMode = false) #pw-group-output
* @method Inputfield new($typeName, $name = '', $label = '', array $settings = array()) #pw-group-manipulation
*
*/
@@ -51,6 +52,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
'item_description' => "<p class='description'>{out}</p>",
'item_head' => "<h2>{out}</h2>",
'item_notes' => "<p class='notes'>{out}</p>",
'item_detail' => "<p class='detail'>{out}</p>",
'item_icon' => "<i class='fa fa-fw fa-{name}'></i> ",
'item_toggle' => "<i class='toggle-icon fa fa-fw fa-angle-down' data-to='fa-angle-down fa-angle-right'></i>",
// ALSO:
@@ -99,7 +101,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
* Label displayed when a value is required but missing
*
*/
protected $requiredLabel = '';
protected $requiredLabel = 'Missing required value';
/**
* Whether or not column width is handled internally
@@ -115,26 +117,42 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
*/
public function __construct() {
parent::__construct();
$this->children = new InputfieldsArray();
$this->children = new InputfieldsArray();
$this->set('skipLabel', Inputfield::skipLabelFor);
$this->requiredLabel = $this->_('Missing required value');
$columnWidthSpacing = $this->wire('config')->inputfieldColumnWidthSpacing;
$columnWidthSpacing = is_null($columnWidthSpacing) ? 1 : (int) $columnWidthSpacing;
$this->set('columnWidthSpacing', $columnWidthSpacing);
$this->set('useDependencies', true); // whether or not to use consider field dependencies during processing
// allow optional override of any above settings with a $config->InputfieldWrapper array.
$settings = $this->wire('config')->InputfieldWrapper;
if(is_array($settings)) foreach($settings as $key => $value) {
if($key == 'requiredLabel') {
$this->requiredLabel = $value;
} else if($key == 'useColumnWidth') {
$this->useColumnWidth = $value;
} else {
$this->set($key, $value);
}
}
$this->set('renderValueMode', false);
$this->set('quietMode', false); // suppress label, description and notes
$this->set('columnWidthSpacing', 0);
}
public function wired() {
/** @var Config $config */
$config = $this->wire('config');
$this->wire($this->children);
$this->requiredLabel = $this->_('Missing required value');
$columnWidthSpacing = $config->inputfieldColumnWidthSpacing;
$columnWidthSpacing = is_null($columnWidthSpacing) ? 1 : (int) $columnWidthSpacing;
if($columnWidthSpacing > 0) $this->set('columnWidthSpacing', $columnWidthSpacing);
$columnWidthSpacing = null;
$settings = $config->InputfieldWrapper;
if(is_array($settings)) {
foreach($settings as $key => $value) {
if($key == 'requiredLabel') {
$this->requiredLabel = $value;
} else if($key == 'useColumnWidth') {
$this->useColumnWidth = $value;
} else {
$this->set($key, $value);
}
}
}
parent::wired();
}
/**
@@ -185,23 +203,87 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
/**
* Add an Inputfield item as a child (also accepts array definition)
*
* Since 3.0.110: If given a string value, it is assumed to be an Inputfield type that you
* want to add. In that case, it will create the Inputfield and return it instead of $this.
*
* #pw-group-manipulation
*
* @param Inputfield|array $item
* @return $this
* @param Inputfield|array|string $item
* @return Inputfield|InputfieldWrapper|$this
* @see InputfieldWrapper::import()
*
*/
public function add($item) {
if(is_array($item)) {
if(is_string($item)) {
return $this->___new($item);
} else if(is_array($item)) {
$this->importArray($item);
} else {
$item->setParent($this);
$this->children->add($item);
$item->setParent($this);
}
return $this;
}
/**
* Create a new Inputfield, add it to this InputfieldWrapper, and return the new Inputfield
*
* - Only the $typeName argument is required.
* - You may optionally substitute the $settings argument for the $name or $label arguments.
* - You may optionally substitute Inputfield “description” property for $settings argument.
*
* #pw-group-manipulation
*
* @param string $typeName Inputfield type, i.e. “InputfieldCheckbox” or just “checkbox” for short.
* @param string|array $name Name of input (or substitute $settings here).
* @param string|array $label Label for input (or substitute $settings here).
* @param array|string $settings Settings to add to Inputfield (optional). Or if string, assumed to be “description”.
* @return Inputfield|InputfieldSelect|InputfieldWrapper An Inputfield instance ready to populate with additional properties/attributes.
* @throws WireException If you request an unknown Inputfield type
* @since 3.0.110
*
*/
public function ___new($typeName, $name = '', $label = '', $settings = array()) {
if(is_array($name)) {
$settings = $name;
$name = '';
} else if(is_array($label)) {
$settings = $label;
$label = '';
}
if(strpos($typeName, 'Inputfield') !== 0) {
$typeName = "Inputfield" . ucfirst($typeName);
}
/** @var Inputfield|InputfieldSelect|InputfieldWrapper $inputfield */
$inputfield = $this->wire('modules')->getModule($typeName);
if(!$inputfield && wireClassExists($typeName)) {
$inputfield = $this->wire(new $typeName());
}
if(!$inputfield || !$inputfield instanceof Inputfield) {
throw new WireException("Unknown Inputfield type: $typeName");
}
if(strlen($name)) $inputfield->attr('name', $name);
if(strlen($label)) $inputfield->label = $label;
if(is_array($settings)) {
foreach($settings as $key => $value) {
$inputfield->set($key, $value);
}
} else if(is_string($settings)) {
$inputfield->description = $settings;
}
$this->add($inputfield);
return $inputfield;
}
/**
* Import the given Inputfield items as children
*
@@ -351,12 +433,101 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
array_push($wrappers, $inputfield);
}
$inputfield->unsetParent();
$wrapper->add($inputfield);
}
return $children;
}
/**
* Cached class parents indexed by Inputfield class name
*
* @var array
*
*/
static protected $classParents = array();
/**
* Get array of parent Inputfield classes for given Inputfield (excluding the base Inputfield class)
*
* @param Inputfield|string $inputfield
* @return array
*
*/
protected function classParents($inputfield) {
$p = &self::$classParents;
$c = is_object($inputfield) ? $inputfield->className() : $inputfield;
if(!isset($p[$c])) {
$p[$c] = array();
foreach(wireClassParents($inputfield) as $parentClass) {
if(strpos($parentClass, 'Inputfield') !== 0 || $parentClass === 'Inputfield') break;
$p[$c][] = $parentClass;
}
}
return $p[$c];
}
/**
* Prepare Inputfield for attributes used during rendering
*
* #pw-internal
*
* @param Inputfield $inputfield
* @param array $markup
* @param array $classes
* @since 3.0.144
*
*/
private function attributeInputfield(Inputfield $inputfield, &$markup, &$classes) {
$inputfieldClass = $inputfield->className();
$markupTemplate = array('attr' => array(), 'wrapAttr' => array(), 'set' => array());
$markupKeys = array($inputfieldClass, "name=$inputfield->name", "id=$inputfield->id");
$classKeys = array('class', 'wrapClass', 'headerClass', 'contentClass');
$addClasses = array();
$attr = array();
$wrapAttr = array();
$sets = array();
foreach($markupKeys as $key) {
if(isset($markup[$key])) $markup = array_merge($markup, $markup[$key]);
if(isset($classes[$key])) $classes = array_merge($classes, $classes[$key]);
}
foreach(array_merge($this->classParents($inputfield), $markupKeys) as $key) {
if(!isset($markup[$key])) continue;
$markupParent = array_merge($markupTemplate, $markup[$key]);
foreach($classKeys as $classKey) {
if(!empty($markupParent[$classKey])) {
$addClasses[$classKey] = $markupParent[$classKey];
}
}
foreach($markupParent['attr'] as $k => $v) {
$attr[$k] = $v;
}
foreach($markupParent['wrapAttr'] as $k => $v) {
$wrapAttr[$k] = $v;
}
foreach($markupParent['set'] as $k => $v) {
$sets[$k] = $v;
}
}
foreach($attr as $attrName => $attrVal) {
$inputfield->attr($attrName, $attrVal);
}
foreach($wrapAttr as $attrName => $attrVal) {
$inputfield->wrapAttr($attrName, $attrVal);
}
foreach($addClasses as $classKey => $class) {
$inputfield->addClass($class, $classKey);
}
foreach($sets as $setName => $setVal) {
$inputfield->set($setName, $setVal);
}
}
/**
* Render this Inputfield and the output of its children.
*
@@ -381,7 +552,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$useColumnWidth = $this->useColumnWidth;
$renderAjaxInputfield = $this->wire('config')->ajax ? $this->wire('input')->get('renderInputfieldAjax') : null;
if(isset($_classes['form']) && strpos($_classes['form'], 'InputfieldFormNoWidths') !== false) {
if($useColumnWidth === true && isset($_classes['form']) && strpos($_classes['form'], 'InputfieldFormNoWidths') !== false) {
$useColumnWidth = false;
}
@@ -403,10 +574,9 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
}
if($skip && !empty($parents)) continue;
}
$inputfieldClass = $inputfield->className();
$markup = isset($_markup[$inputfieldClass]) ? array_merge($_markup, $_markup[$inputfieldClass]) : $_markup;
$classes = isset($_classes[$inputfieldClass]) ? array_merge($_classes, $_classes[$inputfieldClass]) : $_classes;
list($markup, $classes) = array($_markup, $_classes);
$this->attributeInputfield($inputfield, $markup, $classes);
$renderValueMode = $this->getSetting('renderValueMode');
$collapsed = (int) $inputfield->getSetting('collapsed');
@@ -416,7 +586,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
if($collapsed == Inputfield::collapsedHidden) continue;
if($collapsed == Inputfield::collapsedNoLocked || $collapsed == Inputfield::collapsedYesLocked) $renderValueMode = true;
$ffOut = $this->renderInputfield($inputfield, $renderValueMode);
if(!strlen($ffOut)) continue;
$collapsed = (int) $inputfield->getSetting('collapsed'); // retrieve again after render
@@ -431,11 +601,16 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
}
} else $errors = array();
foreach(array('error', 'description', 'head', 'notes') as $property) {
foreach(array('error', 'description', 'head', 'notes', 'detail') as $property) {
$text = $property == 'error' ? $errorsOut : $inputfield->getSetting($property);
if($property === 'detail' && !is_string($text)) continue; // may not be necessary
if(!empty($text) && !$quietMode) {
$text = nl2br($entityEncodeText ? $inputfield->entityEncode($text, true) : $text);
$text = str_replace('{out}', $text, $markup["item_$property"]);
if($entityEncodeText) {
$text = $inputfield->entityEncode($text, true);
}
if($inputfield->textFormat != Inputfield::textFormatMarkdown) {
$text = str_replace('{out}', nl2br($text), $markup["item_$property"]);
}
} else {
$text = '';
}
@@ -444,7 +619,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$markup['item_content'] = str_replace($_property, $text, $markup['item_content']);
} else if(strpos($markup['item_label'], $_property) !== false) {
$markup['item_label'] = str_replace($_property, $text, $markup['item_label']);
} else if($text && $property == 'notes') {
} else if($text && ($property == 'notes' || $property == 'detail')) {
$ffOut .= $text;
} else if($text) {
$ffOut = $text . $ffOut;
@@ -458,14 +633,14 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
if($appendMarkup) $ffOut .= $appendMarkup;
}
// The inputfield's classname is always used in it's LI wrapper
// The inputfield classname is always used in its wrapping element
$ffAttrs = array(
'class' => str_replace(
array('{class}', '{name}'),
array($inputfield->className(), $inputfield->attr('name')
),
$classes['item'])
);
array('{class}', '{name}'),
array($inputfield->className(), $inputfield->attr('name')
),
$classes['item'])
);
if($inputfield instanceof InputfieldItemList) $ffAttrs['class'] .= " InputfieldItemList";
if($collapsed) $ffAttrs['class'] .= " collapsed$collapsed";
@@ -480,9 +655,9 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$ffAttrs['class'] .= ' ' . $classes['item_required_if'];
}
if($collapsed) {
if($collapsed && $collapsed !== Inputfield::collapsedNever) {
$isEmpty = $inputfield->isEmpty();
if(($isEmpty && $inputfield instanceof InputfieldWrapper) ||
if(($isEmpty && $inputfield instanceof InputfieldWrapper && $collapsed !== Inputfield::collapsedPopulated) ||
$collapsed === Inputfield::collapsedYes ||
$collapsed === Inputfield::collapsedYesLocked ||
$collapsed === true ||
@@ -503,100 +678,116 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
}
}
}
// if inputfield produced no output, then move to next
if(!$ffOut) continue;
// if the inputfield resulted in output, wrap it in an LI
if($ffOut) {
$attrs = '';
$label = $inputfield->getSetting('label');
if(!strlen($label) && $inputfield->getSetting('skipLabel') != Inputfield::skipLabelBlank) {
$label = $inputfield->attr('name');
// wrap the inputfield output
$attrs = '';
$label = $inputfield->getSetting('label');
$skipLabel = $inputfield->getSetting('skipLabel');
$skipLabel = is_bool($skipLabel) || empty($skipLabel) ? (bool) $skipLabel : (int) $skipLabel; // force as bool or int
if(!strlen($label) && $skipLabel !== Inputfield::skipLabelBlank && $inputfield->className() != 'InputfieldWrapper') {
$label = $inputfield->attr('name');
}
if(($label || $quietMode) && $skipLabel !== Inputfield::skipLabelMarkup) {
$for = $skipLabel || $quietMode ? '' : $inputfield->attr('id');
// if $inputfield has a property of entityEncodeLabel with a value of boolean FALSE, we don't entity encode
$entityEncodeLabel = $inputfield->getSetting('entityEncodeLabel');
if(is_int($entityEncodeLabel) && $entityEncodeLabel >= Inputfield::textFormatBasic) {
// uses an Inputfield::textFormat constant
$label = $inputfield->entityEncode($label, $entityEncodeLabel);
} else if($entityEncodeLabel !== false) {
$label = $inputfield->entityEncode($label);
}
if($label || $quietMode) {
$for = $inputfield->getSetting('skipLabel') || $quietMode ? '' : $inputfield->attr('id');
// if $inputfield has a property of entityEncodeLabel with a value of boolean FALSE, we don't entity encode
$entityEncodeLabel = $inputfield->getSetting('entityEncodeLabel');
if(is_int($entityEncodeLabel) && $entityEncodeLabel >= Inputfield::textFormatBasic) {
// uses an Inputfield::textFormat constant
$label = $inputfield->entityEncode($label, $entityEncodeLabel);
} else if($entityEncodeLabel !== false) {
$label = $inputfield->entityEncode($label);
}
$icon = $inputfield->getSetting('icon');
$icon = $icon ? str_replace('{name}', $this->wire('sanitizer')->name(str_replace(array('icon-', 'fa-'), '', $icon)), $markup['item_icon']) : '';
$toggle = $collapsed == Inputfield::collapsedNever ? '' : $markup['item_toggle'];
if($toggle && strpos($toggle, 'title=') === false) {
$toggle = str_replace("class=", "title='" . $this->_('Toggle open/close') . "' class=", $toggle);
}
if($inputfield->getSetting('skipLabel') === Inputfield::skipLabelHeader || $quietMode) {
// label only shows when field is collapsed
$label = str_replace('{out}', $icon . $label . $toggle, $markup['item_label_hidden']);
} else {
// label always visible
$label = str_replace(array('{for}', '{out}'), array($for, $icon . $label . $toggle), $markup['item_label']);
}
$headerClass = trim($inputfield->getSetting('headerClass') . " $classes[item_label]");
if($headerClass) {
if(strpos($label, '{class}') !== false) {
$label = str_replace('{class}', ' ' . $headerClass, $label);
} else {
$label = preg_replace('/( class=[\'"][^\'"]+)/', '$1 ' . $headerClass, $label, 1);
}
} else if(strpos($label, '{class}') !== false) {
$label = str_replace('{class}', '', $label);
}
$icon = $inputfield->getSetting('icon');
$icon = $icon ? str_replace('{name}', $this->wire('sanitizer')->name(str_replace(array('icon-', 'fa-'), '', $icon)), $markup['item_icon']) : '';
$toggle = $collapsed == Inputfield::collapsedNever ? '' : $markup['item_toggle'];
if($toggle && strpos($toggle, 'title=') === false) {
$toggle = str_replace("class=", "title='" . $this->_('Toggle open/close') . "' class=", $toggle);
}
if($skipLabel === Inputfield::skipLabelHeader || $quietMode) {
// label only shows when field is collapsed
$label = str_replace('{out}', $icon . $label . $toggle, $markup['item_label_hidden']);
} else {
// no header
// $inputfield->addClass('InputfieldNoHeader', 'wrapClass');
// label always visible
$label = str_replace(array('{for}', '{out}'), array($for, $icon . $label . $toggle), $markup['item_label']);
}
$columnWidth = (int) $inputfield->getSetting('columnWidth');
$columnWidthAdjusted = $columnWidth + ($columnWidthTotal ? -1 * $columnWidthSpacing : 0);
if($columnWidth >= 9 && $columnWidth <= 100) {
$ffAttrs['class'] .= ' ' . $classes['item_column_width'];
if(!$columnWidthTotal) $ffAttrs['class'] .= ' ' . $classes['item_column_width_first'];
if($useColumnWidth) {
$ffAttrs['style'] = "width: $columnWidthAdjusted%;";
$headerClass = trim($inputfield->getSetting('headerClass') . " $classes[item_label]");
if($headerClass) {
if(strpos($label, '{class}') !== false) {
$label = str_replace('{class}', ' ' . $headerClass, $label);
} else {
$ffAttrs['data-colwidth'] = "$columnWidthAdjusted%";
$label = preg_replace('/( class=[\'"][^\'"]+)/', '$1 ' . $headerClass, $label, 1);
}
$columnWidthTotal += $columnWidth;
//if($columnWidthTotal >= 100 && !$requiredIf) $columnWidthTotal = 0; // requiredIf meant to be a showIf?
if($columnWidthTotal >= 100) $columnWidthTotal = 0;
} else {
$columnWidthTotal = 0;
} else if(strpos($label, '{class}') !== false) {
$label = str_replace('{class}', '', $label);
}
if(!isset($ffAttrs['id'])) $ffAttrs['id'] = 'wrap_' . $inputfield->attr('id');
$ffAttrs['class'] = str_replace('Inputfield_ ', '', $ffAttrs['class']);
$wrapClass = $inputfield->getSetting('wrapClass');
if($wrapClass) $ffAttrs['class'] .= " " . $wrapClass;
foreach($inputfield->wrapAttr() as $k => $v) {
if(!empty($ffAttrs[$k])) {
$ffAttrs[$k] .= " $v";
} else {
$ffAttrs[$k] = $v;
}
}
foreach($ffAttrs as $k => $v) {
$k = $this->entityEncode($k);
$v = $this->entityEncode(trim($v));
$attrs .= " $k='$v'";
}
$markupItemContent = $markup['item_content'];
$contentClass = trim($inputfield->getSetting('contentClass') . " $classes[item_content]");
if($contentClass) {
if(strpos($markupItemContent, '{class}') !== false) {
$markupItemContent = str_replace('{class}', ' ' . $contentClass, $markupItemContent);
} else {
$markupItemContent = preg_replace('/( class=[\'"][^\'"]+)/', '$1 ' . $contentClass, $markupItemContent, 1);
}
} else if(strpos($markupItemContent, '{class}') !== false) {
$markupItemContent = str_replace('{class}', '', $markupItemContent);
}
if($inputfield->className() != 'InputfieldWrapper') $ffOut = str_replace('{out}', $ffOut, $markupItemContent);
$out .= str_replace(array('{attrs}', '{out}'), array(trim($attrs), $label . $ffOut), $markup['item']);
$lastInputfield = $inputfield;
} // if($ffOut)
} else if($skipLabel === Inputfield::skipLabelMarkup) {
// no header and no markup for header
$label = '';
} else {
// no header
// $inputfield->addClass('InputfieldNoHeader', 'wrapClass');
}
}
$columnWidth = (int) $inputfield->getSetting('columnWidth');
$columnWidthAdjusted = $columnWidth;
if($columnWidthSpacing) {
$columnWidthAdjusted = $columnWidth + ($columnWidthTotal ? -1 * $columnWidthSpacing : 0);
}
if($columnWidth >= 9 && $columnWidth <= 100) {
$ffAttrs['class'] .= ' ' . $classes['item_column_width'];
if(!$columnWidthTotal) {
$ffAttrs['class'] .= ' ' . $classes['item_column_width_first'];
}
$columnWidthTotal += $columnWidth;
if(!$useColumnWidth || $useColumnWidth > 1) {
if($columnWidthTotal >= 95 && $columnWidthTotal < 100) {
$columnWidthAdjusted += (100 - $columnWidthTotal);
$columnWidthTotal = 100;
}
$ffAttrs['data-colwidth'] = "$columnWidthAdjusted%";
}
if($useColumnWidth) {
$ffAttrs['style'] = "width: $columnWidthAdjusted%;";
}
//if($columnWidthTotal >= 100 && !$requiredIf) $columnWidthTotal = 0; // requiredIf meant to be a showIf?
if($columnWidthTotal >= 100) $columnWidthTotal = 0;
} else {
$columnWidthTotal = 0;
}
if(!isset($ffAttrs['id'])) $ffAttrs['id'] = 'wrap_' . $inputfield->attr('id');
$ffAttrs['class'] = str_replace('Inputfield_ ', '', $ffAttrs['class']);
$wrapClass = $inputfield->getSetting('wrapClass');
if($wrapClass) $ffAttrs['class'] .= " " . $wrapClass;
foreach($inputfield->wrapAttr() as $k => $v) {
if(!empty($ffAttrs[$k])) {
$ffAttrs[$k] .= " $v";
} else {
$ffAttrs[$k] = $v;
}
}
foreach($ffAttrs as $k => $v) {
$k = $this->entityEncode($k);
$v = $this->entityEncode(trim($v));
$attrs .= " $k='$v'";
}
$markupItemContent = $markup['item_content'];
$contentClass = trim($inputfield->getSetting('contentClass') . " $classes[item_content]");
if($contentClass) {
if(strpos($markupItemContent, '{class}') !== false) {
$markupItemContent = str_replace('{class}', ' ' . $contentClass, $markupItemContent);
} else {
$markupItemContent = preg_replace('/( class=[\'"][^\'"]+)/', '$1 ' . $contentClass, $markupItemContent, 1);
}
} else if(strpos($markupItemContent, '{class}') !== false) {
$markupItemContent = str_replace('{class}', '', $markupItemContent);
}
if($inputfield->className() != 'InputfieldWrapper') $ffOut = str_replace('{out}', $ffOut, $markupItemContent);
$out .= str_replace(array('{attrs}', '{out}'), array(trim($attrs), $label . $ffOut), $markup['item']);
$lastInputfield = $inputfield;
} // foreach($children as $inputfield)
if($out) {
$ulClass = $classes['list'];
@@ -625,6 +816,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
*
*/
public function ___renderValue() {
if(!count($this->children)) return '';
$this->addClass('InputfieldRenderValueMode');
$this->set('renderValueMode', true);
$out = $this->render();
@@ -648,9 +840,10 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
*/
public function ___renderInputfield(Inputfield $inputfield, $renderValueMode = false) {
$inputfieldID = $inputfield->attr('id');
$collapsed = $inputfield->getSetting('collapsed');
$ajaxInputfield = $collapsed == Inputfield::collapsedYesAjax ||
($collapsed == Inputfield::collapsedBlankAjax && $inputfield->isEmpty());
$ajaxInputfield = $collapsed == Inputfield::collapsedYesAjax || ($collapsed == Inputfield::collapsedBlankAjax && $inputfield->isEmpty());
$ajaxHiddenInput = "<input type='hidden' name='processInputfieldAjax[]' value='$inputfieldID' />";
$ajaxID = $this->wire('config')->ajax ? $this->wire('input')->get('renderInputfieldAjax') : '';
$required = $inputfield->getSetting('required');
@@ -660,6 +853,8 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$ajaxInputfield = false;
if($collapsed == Inputfield::collapsedYesAjax) $inputfield->collapsed = Inputfield::collapsedYes;
if($collapsed == Inputfield::collapsedBlankAjax) $inputfield->collapsed = Inputfield::collapsedBlank;
// indicate to next processInput that this field can be processed
$inputfield->appendMarkup .= $ajaxHiddenInput;
}
$restoreValue = null; // value to restore, if we happen to modify it before render (renderValueMode only)
@@ -689,8 +884,6 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
if($ajaxInputfield) {
$inputfieldID = $inputfield->attr('id');
if($ajaxID && $ajaxID == $inputfieldID) {
// render ajax inputfield
$editable = $inputfield->editable();
@@ -698,7 +891,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
echo $inputfield->renderValue();
} else {
echo $inputfield->render();
echo "<input type='hidden' name='processInputfieldAjax[]' value='$inputfieldID' />";
echo $ajaxHiddenInput;
}
exit;
@@ -723,7 +916,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$inputfield->resetTrackChanges();
}
if(is_null($out)) return '';
if(!strlen($out)) $out = '&nbsp;'; // prevent output from being skipped over
if(!strlen($out) && !$inputfield instanceof InputfieldWrapper) $out = '&nbsp;'; // prevent output from being skipped over
return $out;
}
@@ -795,7 +988,9 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
// check if a value is required and field is empty, trigger an error if so
if($child->name && $child->getSetting('required') && $child->isEmpty()) {
$child->error($this->requiredLabel);
$requiredLabel = $child->getSetting('requiredLabel');
if(empty($requiredLabel)) $requiredLabel = $this->requiredLabel;
$child->error($requiredLabel);
}
}
@@ -883,6 +1078,34 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
return $empty;
}
/**
* Return Inputfields in this wrapper that are required and have empty values
*
* This method includes all children up through the tree, not just direct children.
*
* #pw-internal
*
* @param bool $required Only include empty Inputfields that are required? (default=true)
* @return array of Inputfield instances indexed by name attributes
*
*/
public function getEmpty($required = true) {
$a = array();
static $n = 0;
foreach($this->children as $child) {
if($child instanceof InputfieldWrapper) {
$a = array_merge($a, $child->getEmpty($required));
} else {
if($required && !$child->getSetting('required')) continue;
if(!$child->isEmpty()) continue;
$name = $child->attr('name');
if(empty($name)) $name = "_unknown" . (++$n);
$a[$name] = $child;
}
}
return $a;
}
/**
* Return an array of errors that occurred on any of the children during input processing.
*
@@ -924,6 +1147,61 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
}
}
/**
* Find an Inputfield below this one that has the given name
*
* This is an alternative to the `getChildByName()` method, with more options for when you need it.
* For instance, it can also accept a selector string or numeric index for the $name argument, and you
* can optionally disable the $recursive behavior.
*
* #pw-group-retrieval-and-traversal
*
* @param string|int $name Name or selector string of child to find, omit for first child, or specify zero-based index of child to return.
* @param bool $recursive Find child recursively? Looks for child in this wrapper, and all other wrappers below it. (default=true)
* @return Inputfield|null Returns Inputfield instance if found, or null if not.
* @since 3.0.110
*
*/
public function child($name = '', $recursive = true) {
$child = null;
if(!$this->children->count()) {
// no child possible
} else if(empty($name)) {
// first child
$child = $this->children->first();
} else if(is_int($name)) {
// number index
$child = $this->children->eq($name);
} else if($this->wire('sanitizer')->name($name) === $name) {
// child by name
$wrappers = array();
foreach($this->children as $f) {
if($f->getAttribute('name') === $name) {
$child = $f;
break;
} else if($recursive && $f instanceof InputfieldWrapper) {
$wrappers[] = $f;
}
}
if(!$child && $recursive && count($wrappers)) {
foreach($wrappers as $wrapper) {
$child = $wrapper->child($name, $recursive);
if($child) break;
}
}
} else if(Selectors::stringHasSelector($name)) {
// first child matching selector string
$child = $this->children("$name, limit=1")->first();
}
return $child;
}
/**
* Return all children Inputfields (alias of children method)
*
@@ -1055,7 +1333,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
* #pw-internal
*
* @param bool $trackChanges
* @return $this
* @return Inputfield|InputfieldWrapper
*
*/
public function setTrackChanges($trackChanges = true) {
@@ -1069,7 +1347,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
* #pw-internal
*
* @param bool $trackChanges
* @return $this
* @return Inputfield|InputfieldWrapper
*
*/
public function resetTrackChanges($trackChanges = true) {

View File

@@ -19,6 +19,7 @@
* Item must have a gettable/settable 'id' property for this interface as well
*
* @property int $id
* @property string $name
*
*/
interface Saveable {
@@ -326,6 +327,12 @@ interface WirePageEditor {
public function getPage();
}
/**
* Interface shared by all ProcessWire Null objects
*
*/
interface WireNull { }
/**
* Interface that indicates the object supports its items being paginated
*

View File

@@ -3,31 +3,178 @@
/**
* ProcessWire Language Functions
*
* Provide GetText like language translation functions to ProcessWire
* #pw-summary-translation Provides GetText-like language translation functions to ProcessWire.
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
*
*/
/**
* Perform a language translation
*
* @param string $text Text for translation.
* @param string $textdomain Textdomain for the text, may be class name, filename, or something made up by you. If omitted, a debug backtrace will attempt to determine it automatically.
* @param string $context Name of context - DO NOT USE with this function for translation as it won't be parsed for translation. Use only with the _x() function, which will be parsed.
* @return string Translated text or original text if translation not available.
* This function enables you to specify static text as the argument, and that text becomes translatable (for each language)
* with ProcessWires built-in language translation tools. This function also works just fine if you do not have multi-language
* support installed, though in that case it just returns the text that it is given.
*
* For full documentation, please see the [Code Internationalization (i18n)](https://processwire.com/docs/multi-language-support/code-i18n/)
* documentation page at ProcessWire.com.
*
* This function is very similar to the GNU gettext `_('text');` function and essentially does the same thing, except that it is
* native to ProcessWire and not using GNU gettext.
*
* Use this `__('text')` function for common translations, use the `_x('text', 'context')` function for translations
* that also require additional context, and use the `_n('singular', 'plural', $n)` function for translations that should
* changed based on whether a value `$n` would require a singular or plural tense.
*
* ### Additional behaviors
*
* - You can optionally specify a “textdomain” as the second argument. The textdomain represents the source file of the
* translation. Most often it would be the current file, so the argument can be omitted, or you can specify `__FILE__`.
* But in some cases you may want to use a translation from another file, and the textdomain argument enables you to.
*
* - When in a class (or module) it is preferable to use `$this->_('text');` rather than `__('text');`, as it is slightly
* more efficient to do so.
*
* - A PHP comment, i.e. `// comment` that appears somewhere after a translation call (on the same line) is used as an
* additional description for the person translating text. Another comment after that, i.e. `// comment1 // comment2`
* is used as an additional secondary note for the person translating the text.
*
* - It is also possible to provide multiple acceptable phrases for translations, useful when making minor changes to
* an existing text where you do not want an previous translation to be abandoned. To do so, provide an array argument
* (using bracket syntax) with the multiple phrases as values, where the first is the newest phrase. This feature was
* added in ProcessWire 3.0.151. See examples below for usage details.
*
* ### Limitations
*
* - The function call (and translatable text within it) cannot span more than one line. If your translatable text is long
* enough to require multiple lines, split them into multiple calls (like one per sentence).
*
* - There cannot be more than one `__('text')` function call per line in the PHP code.
*
* - The provided text argument must be one string of static text. It cannot contain PHP variables or concatenation. To populate
* dynamic values you should use PHPs `sprintf()` (see examples below).
*
* ~~~~~~
* // Standard way to make static text translatable
* echo __('This is translatable text');
*
* // Optionally specify current file as textdomain (same result as above)
* echo __('This is translatable text', __FILE__);
*
* // Specify another file as textdomain (will use translation from that file)
* echo __('This is translatable text', '/site/templates/_init.php');
*
* // Using placeholders to populate dynamic values in translatable text:
* echo sprintf(__('You are reading the %s page'), $page->title);
* echo sprintf(__('%d is the current page ID'), $page->id);
* echo sprintf(__('Today is %1$s and the time is %2$s'), date('l'), date('g:i a'));
*
* // Providing a description via PHP comment to translator
* echo __('Welcome friend!'); // Friendly message for new users
*
* // Providing a description AND extra note via PHP comments to translator
* echo __('Welcome friend!'); // Friendly message for new users // Must be short!
*
* // In ProcessWire 3.0.151+ you can change existing phrases without automatically
* // abandoning the translations for them. To use, include both new and old phrase.
* // Specify PHP array (bracket syntax required) with 2+ phrases you accept trans-
* // lations for where the first is the newest/current text to translate. This array
* // replaces the $text argument of this function. Must be on 1 line.
* __([ 'New text', 'Old text' ]);
*
* // The above can also be used with _x() and _n() calls as well.
* _x([ 'Canada Goose', 'Canadian Goose' ], 'bird');
* ~~~~~
*
* #pw-group-translation
*
* @param string|array|bool $text Text for translation.
* @param string|array $textdomain Textdomain for the text, may be class name, filename, or something made up by you.
* If omitted, a debug backtrace will attempt to determine it automatically.
* @param string|bool|array $context Name of context - DO NOT USE with this function for translation as it will not be parsed for translation.
* Use only with the `_x()` function, which will be parsed.
* @return string|array Translated text or original text if translation not available. Returns array only if getting/setting options.
* @see _x(), _n()
* @link https://processwire.com/docs/multi-language-support/code-i18n/
*
*/
function __($text, $textdomain = null, $context = '') {
if(!wire('languages')) return $text;
if(!$language = wire('user')->language) return $text;
/** @var Language $language */
if(!$language->id) return $text;
if(is_null($textdomain)) {
if(defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
static $options = array(
'entityEncode' => null, // true=always, false=never, 1=only if not already encoded, null=undefined (backwards compatible behavior)
'translations' => false, // fallback translations to use when live translation not available ['Original text' => 'Translated text']
'replacements' => false, // global replacements (no textdomain), becomes array once set
'_useLimit' => null, // internal use: use limit argument for debug_backtrace call
);
$textArray = false;
$encode = $options['entityEncode'];
$user = wire('user');
$language = $user ? $user->language : null; /** @var Language $language */
if(!is_string($text)) {
// getting/setting options or translating with multiple phrases accepted
if(is_array($text)) {
// multiple translations accepted for text, with 1st being newest
$textArray = $text;
$text = reset($textArray);
} else if($text === true) {
// setting (or getting) custom option
list($option, $values) = array($textdomain, $context);
if($option === 'replacements' || $option === 'translations') {
// setting or getting global 'replacements' or 'translations'
// if not given any values to set then return current value
if(!is_array($values)) return $options[$option] ? $options[$option] : array();
// merge with existing 'replacements' or 'translations'
$options[$option] = $options[$option] === false ? $values : array_merge($options[$option], $values);
// return current value
return $options[$option];
} else if(is_array($option)) {
// translations options implied by array in $option/$textdomain argument (support legacy behavior)
return __(true, 'translations', $option);
} else {
// set and get other options
if($context !== '') $options[$option] = $values;
return isset($options[$option]) ? $options[$option] : null;
}
} else if(is_object($text)) {
$text = (string) $text;
} else {
// unknown custom option
}
}
// check if global replacement should be used
if($options['replacements'] !== false && isset($options['replacements'][$text])) {
$value = $options['replacements'][$text];
// array for replacement means only apply to named context, ie. 'text' => [ 'replacement', 'context' ]
if(is_array($value)) $value = isset($value[1]) && $value[1] === $context ? $value[0] : $text;
// false for $language on the next line ensures the $text value is returned in next if() statement
if($value !== $text) list($text, $language) = array($value, false);
}
// if multi-language not installed or not available then just return given text
if(!$language || !wire('languages') || !$language->id) {
return $encode ? htmlspecialchars($text, ENT_QUOTES, 'UTF-8', $encode === true) : $text;
}
// if _useLimit option not yet defined, define it
if($options['_useLimit'] === null) {
$options['_useLimit'] = version_compare(PHP_VERSION, '5.4.0') >= 0;
}
// do we need to determine the textdomain?
if($textdomain === null) {
// no specific textdomain provided, so determine automatically
if($options['_useLimit']) {
// PHP 5.4.0 or newer
$traces = @debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
} else if(defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
// PHP 5.3.6 or newer
$traces = @debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} else {
// older PHP (deprecated)
$traces = @debug_backtrace();
}
if(isset($traces[0]) && $traces[0]['file'] != __FILE__) {
@@ -40,27 +187,72 @@ function __($text, $textdomain = null, $context = '') {
// common translation
$textdomain = 'wire/modules/LanguageSupport/LanguageTranslator.php';
}
$value = $language->translator()->getTranslation($textdomain, $text, $context);
// are multiple translatable phrases available in $textArray?
if($textArray) {
// translations for multiple phrases accepted (current, previous, etc.)
$value = null;
foreach($textArray as $n => $t) {
$tr = $language->translator()->getTranslation($textdomain, $t, $context);
if(!$n && $language->isDefault()) {
$value = strlen($tr) ? $tr : $t;
break; // default language, do not use alternates
}
if($t === $tr || !strlen($tr)) continue; // if not translated, start over
$value = $tr;
break;
}
if($value === null) $value = $text;
} else {
// get translation for single phrase $text
$value = $language->translator()->getTranslation($textdomain, $text, $context);
}
if($value === "=") {
// translator has indicated that translated value should be same as source value
$value = $text;
} else if($value === "+") {
// translator has indicated we should use common translation value if available
$v = $language->translator()->commonTranslation($text);
$value = empty($v) ? $text : $v;
} else {
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
// regular translation
// if translated value same as original check if alternate available in pre-defined translations
if($value === $text && $options['translations'] !== false && isset($options['translations']["$text"])) {
$value = $options['translations']["$text"];
// array for translation means only apply to named context, ie. 'old text' => [ 'new text', 'context' ]
if(is_array($value)) $value = isset($value[1]) && $value[1] === $context ? $value[0] : $text;
}
// force original behavior fallback if encode mode not set (i.e. encode when translation available)
if($encode === null) $encode = 1;
}
if($encode) $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8', $encode === true);
return $value;
}
/**
* Perform a language translation in a specific context
*
* Used when to text strings might be the same in English, but different in other languages.
* Used when two or more text strings might be the same in default language, but different in other languages.
* This enables you to limit the context of the translation to a named context, like "button" or "headline" or
* whatever name you decide to use.
*
* ~~~~~
* echo _x('Click for more', 'button');
* echo _x('Click for more', 'text-link');
* ~~~~~
*
* #pw-group-translation
*
* @param string $text Text for translation.
* @param string $context Name of context
* @param string $textdomain Textdomain for the text, may be class name, filename, or something made up by you. If omitted, a debug backtrace will attempt to determine automatically.
* @param string $textdomain Textdomain for the text, may be class name, filename, or something made up by you.
* If omitted, a debug backtrace will attempt to determine automatically.
* @return string Translated text or original text if translation not available.
* @see __(), _n()
* @link https://processwire.com/docs/multi-language-support/code-i18n/
*
*/
function _x($text, $context, $textdomain = null) {
@@ -70,15 +262,164 @@ function _x($text, $context, $textdomain = null) {
/**
* Perform a language translation with singular and plural versions
*
* ~~~~~
* $items = array(...);
* $qty = count($items);
* echo _n('Found one item', 'Found multiple items', $qty);
* echo sprintf(_n('Found one item', 'Found %d items', $qty), $qty);
* ~~~~~
*
* #pw-group-translation
*
* @param string $textSingular Singular version of text (when there is 1 item)
* @param string $textPlural Plural version of text (when there are multiple items or 0 items)
* @param int $count Quantity of items, should be 0 or more.
* @param string $textdomain Textdomain for the text, may be class name, filename, or something made up by you. If omitted, a debug backtrace will attempt to determine automatically.
* @param string $textdomain Textdomain for the text, may be class name, filename, or something made up by you.
* If omitted, a debug backtrace will attempt to determine automatically.
* @return string Translated text or original text if translation not available.
* @see __(), _x()
* @link https://processwire.com/docs/multi-language-support/code-i18n/
*
*/
function _n($textSingular, $textPlural, $count, $textdomain = null) {
return $count == 1 ? __($textSingular, $textdomain) : __($textPlural, $textdomain);
}
/**
* Set entity encoding state for language translation function calls
*
* The function affects behavior of future `__()`, `_x()` and `_n()` calls.
*
* The following can be used for the `$value` argument:
*
* - `true` (bool): Entity encoding ON
* - `false` (bool): Entity encoding OFF
* - `1` (int): Entity encode only if not already
* - `null` (null): Entity encoding undefined
*
* To get current entity encoding state, call this function with no arguments.
*
* #pw-group-translation
*
* @param bool|int|string|null $value
* @return bool|int|string|null
* @since 3.0.154 Versions 3.0.125 to 3.0.153 can use __(true, 'entityEncode', $value);
*
*/
function wireLangEntityEncode($value = '') {
return __(true, 'encode', $value);
}
/**
* Set predefined fallbaack translation values
*
* These predefined translations are used when an existing translation is
* not available, enabling you to provide translations programmatically.
*
* These translations will be used if the text is not translated in the
* admin. The translations are not specific to any textdomain and thus can
* serve as a fallback for any file. The array you provide should be
* associative, where the keys contain the text to translate, and the
* values contain the translation (see examples).
*
* The function affects behavior of future `__()`, `_x()` and `_n()` calls,
* and their objected-oriented equivalents.
*
* ~~~~~
* // Return 'Hola' when text is 'Hello' and 'Mundo' when text is 'World'
* if($user->language->name == 'es') {
* wireLangTranslations([
* 'Hello' => 'Hola',
* 'World' => 'Mundo'
* ]);
* }
*
* // Setting predefined translations with context
* wireLangTranslations([
* // would apply only to a _x('Search', 'nav'); call (context)
* 'Search' => [ 'Buscar', 'nav' ]
* ]);
* ~~~~~
*
* #pw-group-translation
*
* @param array $values
* @return array
* @since 3.0.154 Versions 3.0.125 to 3.0.153 can use __(true, array $values);
*
*/
function wireLangTranslations(array $values = array()) {
return __(true, 'translations', $values);
}
/**
* Set global translation replacement values
*
* This option enables you to replace text sent to translation calls
* like `__('text')` with your own replacement text. This is similar
* to the `wireTranslateValues()` function except that it applies
* regardless of whether or not a translation is available for the
* phrase. It overrides rather than serves as a fallback.
*
* This function works whether ProcessWire multi-language support is
* installed or not, so it can also be useful for selectively replacing
* phrases in core or modules.
*
* Note that this applies globally to all translations that match,
* regardless of language. As a result, you would typically surround
* this in an if() statement to make sure you are in the desired state
* before you apply the replacements.
*
* The function affects behavior of future `__()`, `_x()` and `_n()`
* calls, as well as their object-oriented equivalents.
*
* This function should ideally be called from a /site/init.php file
* (before PW has booted) to ensure that your replacements will be
* available to any translation calls. However, it can be called from
* anywhere youd like, so long as it is before the translation calls
* that you are looking to replace.
*
* ~~~~~
* // The following example replaces the labels of all the Tabs in the
* // Page editor (and anywhere else labels used):
*
* wireLangReplacements([
* 'Content' => 'Data',
* 'Children' => 'Family',
* 'Settings' => 'Details',
* 'Delete' => 'Trash',
* 'View' => 'See',
* ]);
*
* // If you wanted to be sure the above replacements applied only
* // to the Page editor, then you would place it in /site/ready.php
* // or /site/templates/admin.php and surround with an if() statement:
*
* if($page->process == 'ProcessPageEdit') {
* wireLangReplacements([
* 'Content' => 'Data', // and so on
* ]);
* }
*
* // To make the replacement apply only for a specific _x() context, specify the
* // translated value in an array with text first and context second, like the
* // following example that replaces 'URL' with 'Path' when the context call
* // specifed 'relative-url' as context, i.e. _x('URL', 'relative-url');
*
* wireLangReplacements([
* 'URL' => [ 'Path', 'relative-url' ],
* ]);
* ~~~~~
*
* #pw-group-translation
*
* @param array $values
* @return array|string
* @since 3.0.154
*
*/
function wireLangReplacements(array $values) {
return __(true, 'replacements', $values);
}

View File

@@ -56,7 +56,17 @@ class MarkupFieldtype extends WireData implements Module {
* @var bool
*
*/
protected $renderIsUseless = false;
protected $renderIsUseless = false;
/**
* Properties that are potentially linkable to source page in markup
*
* @var array
*
*/
protected $linkableProperties = array(
'name', 'url', 'httpUrl', 'path', 'title',
);
/**
* Construct the MarkupFieldtype
@@ -102,7 +112,22 @@ class MarkupFieldtype extends WireData implements Module {
$valid = false;
if($value instanceof PageArray) {
// PageArray object: get array of property value from each item
$value = $value->explode($property, array('getMethod' => 'getFormatted'));
$field = $this->wire('fields')->get($property);
if(is_object($field) && $field->type) {
$a = array();
foreach($value as $page) {
$v = $page->getFormatted($property);
$v = $field->type->markupValue($page, $field, $v);
if($this->isLinkablePageProperty($page, $property)) {
$a[] = "<a href='$page->url'>$v</a>";
} else {
$a[] = $v;
}
}
return $this->arrayToString($a, false);
} else {
$value = $value->explode($property, array('getMethod' => 'getFormatted'));
}
$valid = true;
} else if($value instanceof WireArray) {
@@ -118,7 +143,7 @@ class MarkupFieldtype extends WireData implements Module {
if(is_object($field) && $field->type) return $field->type->markupValue($page, $field, $value);
$valid = true;
} else if($value instanceof LanguagesValueInterface) {
/** @var LanguaagesValueInterface $value */
/** @var LanguagesValueInterface $value */
/** @var Languages $languages */
$languages = $this->wire('languages');
if($property) {
@@ -126,8 +151,10 @@ class MarkupFieldtype extends WireData implements Module {
$languageID = $languages->getDefault()->id;
} else if(is_string($property) && preg_match('/^data(\d+)$/', $property, $matches)) {
$languageID = (int) $matches[1];
} else {
$languageID = 0;
}
$value = $value->getLanguageValue($languageID);
$value = $languageID ? $value->getLanguageValue($languageID) : (string) $value;
} else {
$value = (string) $value;
}
@@ -236,10 +263,11 @@ class MarkupFieldtype extends WireData implements Module {
* Convert any value to a string
*
* @param mixed $value
* @param bool $encode
* @return string
*
*/
protected function valueToString($value) {
protected function valueToString($value, $encode = true) {
if(is_object($value) && ($value instanceof Pagefiles || $value instanceof Pagefile)) {
return $this->objectToString($value);
} else if(WireArray::iterable($value)) {
@@ -247,7 +275,7 @@ class MarkupFieldtype extends WireData implements Module {
} else if(is_object($value)) {
return $this->objectToString($value);
} else {
return $this->wire('sanitizer')->entities1($value);
return $encode ? $this->wire('sanitizer')->entities1($value) : $value;
}
}
@@ -255,14 +283,15 @@ class MarkupFieldtype extends WireData implements Module {
* Render an unknown array or WireArray to a string
*
* @param array|WireArray $value
* @param bool $encode
* @return string
*
*/
protected function arrayToString($value) {
protected function arrayToString($value, $encode = true) {
if(!count($value)) return '';
$out = "<ul class='MarkupFieldtype'>";
foreach($value as $v) {
$out .= "<li>" . $this->valueToString($v) . "</li>";
$out .= "<li>" . $this->valueToString($v, $encode) . "</li>";
}
$out .= "</ul>";
return $out;
@@ -276,8 +305,16 @@ class MarkupFieldtype extends WireData implements Module {
*
*/
protected function objectToString($value) {
if($value instanceof WireArray && !$value->count()) return '';
if($value instanceof Page) return $value->get('title|name');
if($value instanceof WireArray) {
if(!$value->count()) return '';
}
if($value instanceof Page) {
if($value->viewable()) {
return "<a href='$value->url'>" . $value->get('title|name') . "</a>";
} else {
return $value->get('title|name');
}
}
if($value instanceof Pagefiles || $value instanceof Pagefile) {
$out = $this->renderInputfieldValue($value);
} else {
@@ -329,6 +366,20 @@ class MarkupFieldtype extends WireData implements Module {
return $out;
}
/**
* Is the given page property/field name one that should be linked to the source page in output?
*
* @param Page $page
* @param $property
* @return bool
*
*/
protected function isLinkablePageProperty(Page $page, $property) {
if(!in_array($property, $this->linkableProperties)) return false;
if(!$page->viewable($property)) return false;
return true;
}
/**
* The string value of a MarkupFieldtype is always the fully rendered field
*

View File

@@ -17,7 +17,7 @@
*
* Runtime errors are logged to: /site/assets/logs/markup-qa-errors.txt
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
*/
@@ -45,22 +45,24 @@ class MarkupQA extends Wire {
protected $field;
/**
* Whether or not to track verbose info to $page
* Markup QA custom settings
*
* $page->_markupQA = array('field_name' => array(counts)))
* Can be specified in $config->markupQA = [ ... ]
*
* @var bool
* - `ignorePaths` (array): Starting paths that should be ignored during link abstraction. Any paths that begin
* with one of these will be left alone. Note these are paths rather than URLs, so if the site runs off a
* subdirectory, then you should exclude the subdirectory in these paths.
* - `debug` (bool): Show debugging info to superusers? (default=false). May also be specified in $config->debugMarkupQA=true;
* - `verbose` (bool): Whether or not to track verbose info to $page: `$page->_markupQA = [ 'field_name' => [ counts ]]`
*
* @var array
*
*/
protected $verbose = false;
/**
* Whether verbose debug mode is active
*
* @var bool
*
*/
protected $debug = false;
protected $settings = array(
'ignorePaths' => array(),
'debug' => false,
'verbose' => false,
);
/**
* Construct
@@ -70,23 +72,24 @@ class MarkupQA extends Wire {
*
*/
public function __construct(Page $page = null, Field $field = null) {
if($page) $this->setPage($page);
if($field) $this->setField($field);
$this->assetsURL = $this->wire('config')->urls->assets;
if($this->wire('config')->debugMarkupQA) {
$user = $this->wire('user');
if($user) $this->debug = $user->isSuperuser();
if($page) {
$this->setPage($page);
$page->wire($this);
}
}
/**
* Enable or disable verbose mode
*
* @param bool $verbose
*
*/
public function setVerbose($verbose) {
$this->verbose = $verbose ? true : false;
if($field) {
$this->setField($field);
if(!$page) $field->wire($this);
}
/** @var Config $config */
$config = $this->wire('config');
$this->assetsURL = $config->urls->assets;
$settings = $config->markupQA;
if(is_array($settings) && count($settings)) {
if(!empty($settings['ignorePaths'])) $this->ignorePaths($settings['ignorePaths']);
if(!empty($settings['debug'])) $this->debug(true);
if(!empty($settings['verbose'])) $this->verbose(true);
}
if($config->debugMarkupQA) $this->debug(true);
}
/**
@@ -109,6 +112,75 @@ class MarkupQA extends Wire {
$this->field = $field;
}
/**
* Get or set paths to ignore for link abstraction
*
* To get ignored paths call function with no arguments. Otherwise you are setting them.
*
* @param array|string|null $paths Array of paths or string of one path, or CSV or newline separated string of multiple paths.
* @param bool $replace True to replace all existing paths, or false to merge with existing paths (default=false)
* @return array Returns array of current ignore paths
* @throws WireException if given invalid $paths argument
*
*/
public function ignorePaths($paths = null, $replace = false) {
if($paths === null) {
return $this->settings['ignorePaths'];
} else if(is_string($paths)) {
// string of one path or CSV/newline separated multiple paths
$paths = trim($paths);
if(strpos($paths, "\n")) {
$paths = explode("\n", $paths);
} else if(strpos($paths, ",")) {
$paths = explode(",", $paths);
} else {
$paths = strlen($paths) ? array($paths) : array();
}
foreach($paths as $k => $v) $paths[$k] = trim($v); // remove any remaining whitespace
}
if(!is_array($paths)) throw new WireException('setIgnorePaths() requires array or string');
if(!$replace) $paths = array_merge($this->settings['ignorePaths'], $paths);
$this->settings['ignorePaths'] = $paths;
return $paths;
}
/**
* Get or set debug status
*
* Applies only if current user is a superuser
*
* @param bool|null $set Omit this argument to get or specify bool to set
* @return bool
*
*/
public function debug($set = null) {
if(is_bool($set)) {
if($set === true) {
$user = $this->wire('user');
if(!$user || !$user->isSuperuser()) $set = false;
}
$this->settings['debug'] = $set;
}
return $this->settings['debug'];
}
/**
* Get or set verbose state
*
* Whether or not to set/track verbose information to page, i.e.
* `$page->_markupQA = array('field_name' => array(counts))`
*
* When getting, if $page or $field have not been populated, verbose is always false.
*
* @param bool|null $set Omit this argument to get or specify bool to set
* @return bool
*
*/
public function verbose($set = null) {
if(is_bool($set)) $this->settings['verbose'] = $set;
return $this->settings['verbose'] && $this->page && $this->field ? true : false;
}
/**
* Wakeup URLs in href or src attributes for presentation
*
@@ -166,7 +238,7 @@ class MarkupQA extends Wire {
// sleep
$value = str_ireplace(array_keys($replacements), array_values($replacements), $value);
if($this->verbose && $this->page && $this->field) {
if($this->verbose()) {
$info = $this->page->get('_markupQA');
if(!is_array($info)) $info = array();
if(!is_array($info[$this->field->name])) $info[$this->field->name] = array();
@@ -231,7 +303,7 @@ class MarkupQA extends Wire {
}
if($path && $path != $_path) {
if($this->debug) $this->message("MarkupQA absoluteToRelative converted: $_path => $path");
if($this->debug()) $this->message("MarkupQA absoluteToRelative converted: $_path => $path");
}
return $path;
@@ -252,7 +324,7 @@ class MarkupQA extends Wire {
// if there is already a data-pwid attribute present, then links are already asleep
if(strpos($value, 'href=') === false || strpos($value, 'data-pwid=')) return;
$info = $this->verbose ? $this->page->get('_markupQA') : array();
$info = $this->verbose() ? $this->page->get('_markupQA') : array();
if(!is_array($info)) $info = array();
if(isset($info[$this->field->name])) {
$counts = $info[$this->field->name];
@@ -265,6 +337,7 @@ class MarkupQA extends Wire {
'other' => 0,
'unresolved' => 0,
'nohttp' => 0,
'ignored' => 0,
);
}
@@ -279,6 +352,8 @@ class MarkupQA extends Wire {
$replacements = array();
$languages = $this->wire('languages');
$debug = $this->debug();
if($languages && !$this->wire('modules')->isInstalled('LanguageSupportPageNames')) $languages = null;
foreach($matches[3] as $key => $path) {
@@ -297,7 +372,7 @@ class MarkupQA extends Wire {
list($x, $host) = explode('//', $href);
if($host != $this->wire('config')->httpHost && !in_array($host, $this->wire('config')->httpHosts)) {
$counts['external']++;
if($this->debug) $this->message("MarkupQA sleepLinks skipping because hostname: $host");
if($debug) $this->message("MarkupQA sleepLinks skipping because hostname: $host");
// external hostname, which we will skip over
continue;
}
@@ -322,7 +397,19 @@ class MarkupQA extends Wire {
continue;
}
}
// check if this path is in the ignored paths list
$ignored = false;
foreach($this->ignorePaths() as $ignorePath) {
if(strpos($path, $ignorePath) !== 0) continue;
if($debug) $this->message("MarkupQA sleepLinks skipped $path because it matches ignored path $ignorePath");
$counts['ignored']++;
$ignored = true;
break;
}
if($ignored) continue;
// get the page ID for the path
$pageID = $this->wire('pages')->getByPath($path, array(
'getID' => true,
'useLanguages' => $languages ? true : false,
@@ -342,7 +429,7 @@ class MarkupQA extends Wire {
}
$replacements[$full] = "$start\tdata-pwid=$pwid$href$path$end";
$counts['internal']++;
if($this->debug) {
if($debug) {
$langName = $language ? $language->name : 'n/a';
$this->message(
"MarkupQA sleepLinks (field=$this->field, page={$this->page->path}, lang=$langName): " .
@@ -353,7 +440,7 @@ class MarkupQA extends Wire {
// did not resolve to a page, see if it resolves to a file or directory
$file = $this->wire('config')->paths->root . ltrim($path, '/');
if(file_exists($file)) {
if($this->debug) $this->message("MarkupQA sleepLinks link resolved to a file: $path");
if($debug) $this->message("MarkupQA sleepLinks link resolved to a file: $path");
$counts['files']++;
} else {
$parts = explode('/', trim($path, '/'));
@@ -375,7 +462,7 @@ class MarkupQA extends Wire {
}
$info[$this->field->name] = $counts;
if($this->verbose) $this->page->setQuietly('_markupQA', $info);
if($this->verbose()) $this->page->setQuietly('_markupQA', $info);
}
/**
@@ -405,6 +492,9 @@ class MarkupQA extends Wire {
$replacements = array();
$languages = $this->wire('languages');
$rootURL = $this->wire('config')->urls->root;
$adminURL = $this->wire('config')->urls->admin;
$adminPath = $rootURL === '/' ? $adminURL : str_replace($rootURL, '/', $adminURL);
$debug = $this->debug();
foreach($matches[2] as $key => $pwid) {
@@ -414,7 +504,8 @@ class MarkupQA extends Wire {
$pageID = $pwid;
$languageID = 0;
}
$pageID = (int) $pageID;
$full = $matches[0][$key];
$start = $matches[1][$key];
$href = $matches[3][$key];
@@ -427,7 +518,7 @@ class MarkupQA extends Wire {
$language = null;
}
$livePath = $this->wire('pages')->getPath((int) $pageID, array(
$livePath = $this->wire('pages')->getPath($pageID, array(
'language' => $language
));
@@ -436,24 +527,38 @@ class MarkupQA extends Wire {
$href = ' ' . ltrim($href); // immunity to wakeupUrls(), replacing tab with space
}
$langName = $this->debug && $language ? $language->name : '';
$langName = $debug && $language ? $language->name : '';
if($livePath) {
$ignore = false;
foreach($this->ignorePaths() as $ignorePath) {
if(strpos($livePath, $ignorePath) !== 0) continue;
if($debug) $this->message("MarkupQA wakeupLinks path $livePath matches ignored path $ignorePath");
$ignore = true;
break;
}
if($path && substr($path, -1) != '/') {
// no trailing slash, retain the editors wishes here
$livePath = rtrim($livePath, '/');
}
if(strpos($livePath, '/trash/') !== false) {
if($ignore) {
// path should be ignored and left as-is
} else if(strpos($livePath, '/trash/') !== false) {
// linked page is in trash, we won't update it but we'll produce a warning
$this->linkWarning("$path => $livePath (" . $this->_('it is in the trash') . ')');
continue;
} else if(strpos($livePath, $adminPath) === 0) {
// do not update paths that point in admin
$this->linkWarning("$path => $livePath (" . $this->_('points to the admin') . ')');
continue;
} else if($livePath != $path) {
// path differs from what's in the markup and should be updated
if($this->debug) $this->warning(
if($debug) $this->warning(
"MarkupQA wakeupLinks PATH UPDATED (field=$this->field, page={$this->page->path}, " .
"language=$langName): $path => $livePath"
);
$path = $livePath;
} else if($this->debug) {
} else if($debug) {
$this->message("MarkupQA wakeupLinks no changes (field=$this->field, language=$langName): $path => $livePath");
}
} else {
@@ -469,6 +574,108 @@ class MarkupQA extends Wire {
}
}
/**
* Find pages linking to another
*
* @param Page $page Page to find links to, or omit to use page specified in constructor
* @param array $fieldNames Field names to look in or omit to use field specified in constructor
* @param string $selector Optional selector to use as a filter
* @param array $options Additional options
* - `getIDs` (bool): Return array of page IDs rather than Page instances. (default=false)
* - `getCount` (bool): Return a total count (int) of found pages rather than Page instances. (default=false)
* - `confirm` (bool): Confirm that the links are present by looking at the actual page field data. (default=true)
* You can specify false for this option to make it perform faster, but with a potentially less accurate result.
* @return PageArray|array|int
*
*/
public function findLinks(Page $page = null, $fieldNames = array(), $selector = '', array $options = array()) {
$defaults = array(
'getIDs' => false,
'getCount' => false,
'confirm' => true
);
$options = array_merge($defaults, $options);
if($options['getIDs']) {
$result = array();
} else if($options['getCount']) {
$result = 0;
} else {
$result = $this->wire('pages')->newPageArray();
}
if(!$page) $page = $this->page;
if(!$page) return $result;
if(empty($fieldNames)) {
if($this->field) $fieldNames[] = $this->field->name;
if(empty($fieldNames)) return $result;
}
if($selector === true) $selector = "include=all";
$op = strlen("$page->id") > 3 ? "~=" : "%=";
$selector = implode('|', $fieldNames) . "$op'$page->id', id!=$page->id, $selector";
$selector = trim($selector, ', ');
// find pages
if($options['getCount'] && !$options['confirm']) {
// just return a count
return $this->wire('pages')->count($selector);
} else {
// find the IDs
$checkIDs = array();
$foundIDs = $this->wire('pages')->findIDs($selector);
if(!count($foundIDs)) return $result;
if($options['confirm']) {
$checkIDs = array_flip($foundIDs);
$foundIDs = array();
}
}
// confirm results
foreach($fieldNames as $fieldName) {
if(!count($checkIDs)) break;
$field = $this->wire('fields')->get($fieldName);
if(!$field) continue;
$table = $field->getTable();
$ids = implode(',', array_keys($checkIDs));
$sql = "SELECT * FROM `$table` WHERE `pages_id` IN($ids)";
$query = $this->wire('database')->prepare($sql);
$query->execute();
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
$pageID = (int) $row['pages_id'];
if(isset($foundIDs[$pageID])) continue;
$row = implode(' ', $row);
$find = "data-pwid=$page->id";
// first check if it might be there
if(!strpos($row, $find)) continue;
// then confirm with a more accurate check
if(!strpos($row, "$find ") && !strpos($row, "$find\t") && !strpos($row, "$find-")) continue;
// at this point we have confirmed that this item links to $page
unset($checkIDs[$pageID]);
$foundIDs[$pageID] = $pageID;
}
$query->closeCursor();
}
if(count($foundIDs)) {
if($options['getIDs']) {
$result = $foundIDs;
} else if($options['getCount']) {
$result = count($foundIDs);
} else {
$result = $this->wire('pages')->getById($foundIDs);
}
}
return $result;
}
/**
* Display and log a warning about a path that didn't resolve
*
@@ -481,11 +688,11 @@ class MarkupQA extends Wire {
$this->warning(sprintf(
$this->_('Unable to resolve link on page %1$s in field "%2$s": %3$s'),
$this->page->path,
$this->field->name,
$this->field->getLabel(),
$path
));
}
if($this->verbose || $logWarning) {
if($this->verbose() || $logWarning) {
$this->error("Unable to resolve link: $path");
}
}
@@ -533,7 +740,7 @@ class MarkupQA extends Wire {
$user = $this->wire('user');
$attrStrings = explode(' ', $img); // array of strings like "key=value"
if($this->verbose) {
if($this->verbose()) {
$markupQA = $this->page->get('_markupQA');
if(!is_array($markupQA)) $markupQA = array();
if(!isset($markupQA[$this->field->name])) $markupQA[$this->field->name] = array();
@@ -554,7 +761,7 @@ class MarkupQA extends Wire {
list($name, $val) = explode('=', $attr);
$name = strtolower($name);
$val = trim($val, "\"' ");
$val = trim($val, "\"'> ");
if($name == 'alt' && !strlen($val)) {
$replaceAlt = $attr;
@@ -577,7 +784,7 @@ class MarkupQA extends Wire {
if(file_exists($this->page->filesManager()->path() . basename($src))) {
// file exists, but we just don't know what it is - leave it alone
} else {
$this->error("Image file no longer exists: " . basename($src) . ")");
$this->error("Image file no longer exists: $src");
if($this->page->of()) $value = str_replace($img, '', $value);
$info['img_unresolved']++;
}
@@ -679,7 +886,7 @@ class MarkupQA extends Wire {
// new name differs from what is in text. Rename file to be consistent with text.
rename($newPagefile->filename(), $pathname);
}
if($this->debug || $this->wire('config')->debug) {
if($this->debug() || $this->wire('config')->debug) {
$this->message($this->_('Re-created image variation') . " - $newPagefile->name");
}
$pagefile = $newPagefile; // for next iteration
@@ -705,7 +912,7 @@ class MarkupQA extends Wire {
*
*/
public function error($text, $flags = 0) {
$logText = "$text (page={$this->page->path}, field={$this->field->name})";
$logText = "$text (field={$this->field->name}, id={$this->page->id}, path={$this->page->path})";
$this->wire('log')->save(self::errorLogName, $logText);
/*
if($this->wire('modules')->isInstalled('SystemNotifications')) {
@@ -720,4 +927,37 @@ class MarkupQA extends Wire {
*/
return $this;
}
/**
* Get or set a setting
*
* @param string $key Setting name to get or set, or omit to get all settings
* @param string|array|int|null $value Setting value to set, or omit when getting setting
* @return string|array|int|null|$this Returns value of $key
*
public function setting($key = null, $value = null) {
if($key === null) return $this->settings; // return all
if($value === null) return isset($this->settings[$key]) ? $this->settings[$key] : null; // return one
if($key === 'ignorePaths') return $this->ignorePaths($value); // set specific
$this->settings[$key] = $value; // set
return $value;
}
*/
/**
* Enable or disable verbose mode
*
* Sets whether or not to set/track verbose information to page, i.e.
* `$page->_markupQA = array('field_name' => array(counts))`
*
* #pw-internal
*
* @param bool $verbose
* @deprecated use verbose() method instead
*
*/
public function setVerbose($verbose) {
$this->settings['verbose'] = $verbose ? true : false;
}
}

View File

@@ -5,7 +5,7 @@
*
* Provides the base interfaces required by modules.
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
* #pw-summary Module is the primary PHP interface for module types in ProcessWire.
@@ -140,11 +140,12 @@
*
* ~~~~~
* {
* title: 'Your Module title',
* version: 1,
* // and so on
* "title": "Your Module Title",
* "version": 1
* }
* ~~~~~
* Note: The example JSON above just shows "title" and "version", but you would
* likely add more than that as needed, like shown in the static version above.
*
* -----------------------------------------------------------------
*
@@ -199,7 +200,7 @@
* something like an Inputfield module would not be singular. When not specified, modules that extend an
* existing base type typically inherit the singular setting from the module they extend.
*
* - `autoload` (boolean|string|callable): Should this module load automatically at boot? (default=false).
* - `autoload` (boolean|string|callable|int): Should this module load automatically at boot? (default=false).
* This is good for modules that attach hooks or that need to otherwise load on every single
* request. Autoload is typically specified as a boolean true or false. Below are the different ways
* autoload can be specified:
@@ -214,6 +215,14 @@
* - **Callable function:** The module will automatically load only if the given callable function
* returns true.
*
* - **Integer:** If given integer 2 or higher, it will autoload the module before other autoload
* modules (in /site/modules/). Higher numbers autoload before lower numbers.
*
* - `searchable` (string): When present, indicates that module implements a search() method
* consistent with the SearchableModule interface. The value of the 'searchable' property should
* be the name that the search results are referred to, using ascii characters of a-z, 0-9, and
* underscore. See the SearchableModule interface in this file for more details.
*
* -----------------------------------------------------------------------------------------------
*
* ## Module Methods
@@ -323,7 +332,7 @@
* to support the module from one version to another. The previous known version ($fromVersion) and new
* version ($toVersion) are provided as arguments.
*
* If implemented, uninstall() methods typically are defined hookable as `public function ___upgrade(...)`.
* If implemented, upgrade() methods typically are defined hookable as `public function ___upgrade(...)`.
* If the upgrade cannot proceed for some reason, this method should throw a `WireException`.
*
*
@@ -488,3 +497,95 @@ interface _Module {
public function getModuleConfigArray();
}
/**
* Interface SearchableModule
*
* Interface for modules that implement a method and expected array return value
* for completing basic text searches (primarily for admin search engine).
*
* It is optional to add this interface to "implements" section of the module class definition.
* However, you must specify a "searchable: name" property in your getModuleInfo() method in
* order for ProcessWire to recognize the module is searchable. See below for more info:
*
* ~~~~~~
* public static function getModuleInfo() {
* return array(
* 'searchable' => 'name',
*
* // You'll need the above 'searchable' property returned by your getModuleInfo().
* // The value of 'name' should be the name by which search results should be referred to
* // if the user wants to limit the search to this module. For instance, if your module
* // was called “ProcessWidgets”, youd probably choose the name “widgets” for this.
* // If the module represents an API variable, the name should be the same as the API variable.
* // ...
* );
* }
* ~~~~~
*
*/
interface SearchableModule {
/**
* Search for items containing $text and return an array representation of them
*
* You may also implement this method as hookable, i.e. ___search(), but note that youll
* want to skip the "implements SearchableModule" in your class definition.
*
* Must return PHP array in the format below. For each item in the 'items' array, Only the 'title'
* and 'url' properties are required for each item (the rest are optional).
*
* $result = array(
* 'title' => 'Title of these items',
* 'total' => 999, // total number of items found, or omit if pagination not supported or active
* 'url' => '', // optional URL to view all items, or omit for a PW-generated one
* 'properties' => array(), // optional list of supported search properties, only looked for if $options['info'] === true;
* 'items' => array(
* [0] => array(
* 'id' => 123, // Unique ID of item (optional)
* 'name' => 'Name of item', // (optional)
* 'title' => 'Title of item', // (*required)
* 'subtitle' => 'Secondary/subtitle of item', // (optional)
* 'summary' => 'Summary or description of item', // (optional)
* 'url' => 'URL to view or edit the item', // (*required)
* 'icon' => 'Optional icon name to represent the item, i.e. "gear" or "fa-gear"', // (optional)
* 'group' => 'Optionally group with other items having this group name, overrides $result[title]', // (optional)
* 'status' => int, // refers to Page status, omit if not a Page item (optional)
* 'modified' => int, // modified date of item as unix timestamp (optional)
* [1] => array(
* ...
* ),
* ),
* );
*
* PLEASE NOTE:
* When ProcessWire calls this method, if the module is not already loaded (autoload),
* it instantiates the module but DOES NOT call the init() or ready() methods. Thats because the
* search method is generally self contained. If you need either of those methods to be called,
* and your module is not autoload, you should call the method(s) from your search() method.
*
* About the optional “properties” index:
* If ProcessWire calls your search() method with $options['info'] == true; then it is likely wanting to see
* what properties are available for search. For instance, properties for a Module search might be:
* [ 'name', 'title', 'summary' ]. Implementation of the properties index is optional, and for PWs informational
* purposes only.
*
* @param string $text Text to search for
* @param array $options Options array provided to search() calls:
* - `edit` (bool): True if any 'url' returned should be to edit rather than view items, where access allows. (default=true)
* - `multilang` (bool): If true, search all languages rather than just current (default=true).
* - `start` (int): Start index (0-based), if pagination active (default=0).
* - `limit` (int): Limit to this many items, or 0 for no limit. (default=0).
* - `type` (string): If search should only be of a specific type, i.e. "pages", "modules", etc. then it is
* specified here. This corresponds with the getModuleInfo()['searchable'] name or item 'group' property.
* Note that ProcessWire wont call your search() method if the type cannot match this search.
* - `operator` (string): Selector operator type requested, if more than one is supported (default is %=).
* - `property` (string): If search should limit to a particular property/field, it is named here.
* - `verbose` (bool): True if output can optionally be more verbose, false if not. (default=false)
* - `debug` (bool): True if DEBUG option was specified in query. (default=false)
* - `help` (bool): True if we are just querying for help/info and are not using the search results. (default=false)
* @return array
*
*/
public function search($text, array $options = array());
}

View File

@@ -10,7 +10,7 @@
* in order to save resources. As a result, anything iterating through these Modules should check to make sure it's not a ModulePlaceholder
* before using it. If it's a ModulePlaceholder, then the real Module can be instantiated/retrieved by $modules->get($className).
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
* #pw-summary Loads and manages all modules in ProcessWire.
@@ -250,6 +250,22 @@ class Modules extends WireArray {
*/
protected $coreModulesDir = '';
/**
* Array of moduleName => order to indicate autoload order when necessary
*
* @var array
*
*/
protected $autoloadOrders = array();
/**
* Are we currently refreshing?
*
* @var bool
*
*/
protected $refreshing = false;
/**
* Properties that only appear in 'verbose' moduleInfo
*
@@ -264,6 +280,7 @@ class Modules extends WireArray {
'core',
'versionStr',
'permissions',
'searchable',
'page',
);
@@ -293,8 +310,13 @@ class Modules extends WireArray {
*
*/
public function __construct($path) {
parent::__construct();
$this->addPath($path);
}
public function wired() {
$this->coreModulesDir = '/' . $this->wire('config')->urls->data('modules');
parent::wired();
}
/**
@@ -750,6 +772,7 @@ class Modules extends WireArray {
*
*/
protected function loadModulesTable() {
$this->autoloadOrders = array();
$database = $this->wire('database');
// we use SELECT * so that this select won't be broken by future DB schema additions
// Currently: id, class, flags, data, with created added at sysupdate 7
@@ -764,7 +787,8 @@ class Modules extends WireArray {
$class = $row['class'];
$this->moduleIDs[$class] = $moduleID;
$this->moduleFlags[$moduleID] = $flags;
$loadSettings = ($flags & self::flagsAutoload) || ($flags & self::flagsDuplicate) || ($class == 'SystemUpdater');
$autoload = $flags & self::flagsAutoload;
$loadSettings = $autoload || ($flags & self::flagsDuplicate) || ($class == 'SystemUpdater');
if($loadSettings) {
// preload config data for autoload modules since we'll need it again very soon
@@ -782,6 +806,14 @@ class Modules extends WireArray {
$this->createdDates[$moduleID] = $row['created'];
}
if($autoload && !empty($this->moduleInfoCache[$moduleID]['autoload'])) {
$autoload = $this->moduleInfoCache[$moduleID]['autoload'];
if(is_int($autoload) && $autoload > 1) {
// autoload specifies an order > 1, indicating it should load before others
$this->autoloadOrders[$class] = $autoload;
}
}
unset($row['data']); // info we don't want stored in modulesTableCache
$this->modulesTableCache[$class] = $row;
}
@@ -797,12 +829,13 @@ class Modules extends WireArray {
*/
protected function load($path) {
$config = $this->wire('config');
$debugKey = $this->debug ? $this->debugTimerStart("load($path)") : null;
$installed =& $this->modulesTableCache;
$modulesLoaded = array();
$modulesDelayed = array();
$modulesRequired = array();
$rootPath = $this->wire('config')->paths->root;
$rootPath = $config->paths->root;
$basePath = substr($path, strlen($rootPath));
foreach($this->findModuleFiles($path, true) as $pathname) {
@@ -818,7 +851,7 @@ class Modules extends WireArray {
$requires = array();
$name = $moduleName;
$moduleName = $this->loadModule($path, $pathname, $requires, $installed);
if(!$this->wire('config')->paths->get($name)) $this->setConfigPaths($name, dirname($basePath . $pathname));
if(!$config->paths->get($name)) $this->setConfigPaths($name, dirname($basePath . $pathname));
if(!$moduleName) continue;
if(count($requires)) {
@@ -997,7 +1030,8 @@ class Modules extends WireArray {
} else if($autoload) {
$this->includeModuleFile($pathname, $basename);
if(!($info['flags'] & self::flagsDisabled)) {
$module = $this->newModule($basename);
$module = $this->refreshing ? parent::get($basename) : null;
if(!$module) $module = $this->newModule($basename);
}
}
}
@@ -1029,6 +1063,7 @@ class Modules extends WireArray {
static $startPath;
static $callNum = 0;
static $prependFiles = array();
$callNum++;
$config = $this->wire('config');
@@ -1052,6 +1087,11 @@ class Modules extends WireArray {
}
$files = array();
$autoloadOrders = null;
if(count($this->autoloadOrders) && $path !== $config->paths->modules) {
$autoloadOrders = &$this->autoloadOrders;
}
try {
$dir = new \DirectoryIterator($path);
@@ -1069,7 +1109,6 @@ class Modules extends WireArray {
if(DIRECTORY_SEPARATOR != '/') {
$pathname = str_replace(DIRECTORY_SEPARATOR, '/', $pathname);
$filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename);
}
if(strpos($pathname, '/.') !== false) {
@@ -1083,16 +1122,30 @@ class Modules extends WireArray {
}
// if the filename doesn't end with .module or .module.php, then stop and move onto the next
if(!strpos($filename, '.module')) continue;
if(substr($filename, -7) !== '.module' && substr($filename, -11) !== '.module.php') {
continue;
$extension = $file->getExtension();
if($extension !== 'module' && $extension !== 'php') continue;
list($moduleName, $extension) = explode('.', $filename, 2);
if($extension !== 'module' && $extension !== 'module.php') continue;
$pathname = str_replace($startPath, '', $pathname);
if($autoloadOrders !== null && isset($autoloadOrders[$moduleName])) {
$prependFiles[$pathname] = $autoloadOrders[$moduleName];
} else {
$files[] = $pathname;
}
$files[] = str_replace($startPath, '', $pathname);
}
if($level == 0 && $dir !== null) {
if($cache && $cacheName) $cache->save($cacheName, implode("\n", $files), WireCache::expireNever);
if(!empty($prependFiles)) {
// one or more non-core modules must be loaded first in a specific order
arsort($prependFiles);
$files = array_merge(array_keys($prependFiles), $files);
$prependFiles = array();
}
if($cache && $cacheName) {
$cache->save($cacheName, implode("\n", $files), WireCache::expireReserved);
}
}
return $files;
@@ -1287,7 +1340,6 @@ class Modules extends WireArray {
try {
if(!$this->initModule($module, array('clearSettings' => false, 'throw' => true))) {
return empty($options['returnError']) ? null : "Module '$module' failed init";
$module = null;
}
} catch(\Exception $e) {
if(empty($options['noThrow'])) throw $e;
@@ -1469,6 +1521,7 @@ class Modules extends WireArray {
*
* @param string $file
* @param string $moduleName
* @return bool
*
*/
protected function includeModuleFile($file, $moduleName) {
@@ -1480,11 +1533,11 @@ class Modules extends WireArray {
if($wire1 !== $wire2) {
// multi-instance is active, don't autoload module if class already exists
// first do a fast check, which should catch any core modules
if(class_exists(__NAMESPACE__ . "\\$moduleName", false)) return;
if(class_exists(__NAMESPACE__ . "\\$moduleName", false)) return true;
// next do a slower check, figuring out namespace
$ns = $this->getModuleNamespace($moduleName, array('file' => $file));
$className = trim($ns, "\\") . "\\$moduleName";
if(class_exists($className, false)) return;
if(class_exists($className, false)) return true;
// if this point is reached, module is not yet in memory in either instance
// temporarily set the $wire instance to 2nd instance during include()
ProcessWire::setCurrentInstance($wire2);
@@ -1495,11 +1548,15 @@ class Modules extends WireArray {
if($file) {
/** @noinspection PhpIncludeInspection */
include_once($file);
$success = @include_once($file);
} else {
$success = false;
}
// set instance back, if multi-instance
if($wire1 !== $wire2) ProcessWire::setCurrentInstance($wire1);
return (bool) $success;
}
/**
@@ -1508,7 +1565,7 @@ class Modules extends WireArray {
* #pw-internal Almost always recommend using findByPrefix() instead
*
* @param string $selector Selector string
* @return Modules WireArray of found modules, instantiated and ready-to-use
* @return WireArray of found modules, instantiated and ready-to-use
*
*/
public function find($selector) {
@@ -1527,7 +1584,8 @@ class Modules extends WireArray {
*
* By default this method returns module class names matching the given prefix.
* To instead retrieve instantiated (ready-to-use) modules, specify boolean true
* for the second argument.
* for the second argument. Regardless of `$load` argument all returned arrays
* are indexed by module name.
*
* ~~~~~
* // Retrieve array of all Textformatter module names
@@ -1538,24 +1596,140 @@ class Modules extends WireArray {
* ~~~~~
*
* @param string $prefix Specify prefix, i.e. "Process", "Fieldtype", "Inputfield", etc.
* @param bool $instantiate Specify true to return Module instances, or false to return class names (default=false)
* @param bool|int $load Specify one of the following (all indexed by module name):
* - Boolean true to return array of instantiated modules.
* - Boolean false to return array of module names (default).
* - Integer 1 to return array of module info for each matching module.
* - Integer 2 to return array of verbose module info for each matching module.
* - Integer 3 to return array of Module or ModulePlaceholder objects (whatever current state is). Added 3.0.146.
* @return array Returns array of module class names or Module objects. In either case, array indexes are class names.
*
*/
public function findByPrefix($prefix, $instantiate = false) {
public function findByPrefix($prefix, $load = false) {
$results = array();
foreach($this as $key => $value) {
$className = wireClassName($value->className(), false);
if(strpos($className, $prefix) !== 0) continue;
if($instantiate) {
$results[$className] = $this->get($className);
foreach($this as $moduleName => $value) {
if(stripos($moduleName, $prefix) !== 0) continue;
if($load === false) {
$results[$moduleName] = $moduleName;
} else if($load === true) {
$results[$moduleName] = $this->getModule($moduleName);
} else if($load === 1) {
$results[$moduleName] = $this->getModuleInfo($moduleName);
} else if($load === 2) {
$results[$moduleName] = $this->getModuleInfoVerbose($moduleName);
} else if($load === 3) {
$results[$moduleName] = $value;
} else {
$results[$className] = $className;
$results[$moduleName] = $moduleName;
}
}
return $results;
}
/**
* Find modules by matching a property or properties in their module info
*
* @param string|array $selector Specify one of the following:
* - Selector string to match module info.
* - Array of [ 'property' => 'value' ] to match in module info (this is not a selector array).
* - Name of property that will match module if not empty in module info.
* @param bool|int $load Specify one of the following:
* - Boolean true to return array of instantiated modules.
* - Boolean false to return array of module names (default).
* - Integer 1 to return array of module info for each matching module.
* - Integer 2 to return array of verbose module info for each matching module.
* @return array Array of modules, module names or module info arrays, indexed by module name.
*
*/
public function findByInfo($selector, $load = false) {
$selectors = null;
$infos = null;
$keys = null;
$results = array();
$verbose = $load === 2;
$properties = array();
$has = '';
if(is_array($selector)) {
// find matching all values in array
$keys = $selector;
$properties = array_keys($keys);
} if(!ctype_alnum($selector) && Selectors::stringHasOperator($selector)) {
// find by selectors
$selectors = new Selectors($selector);
if(!$verbose) foreach($selectors as $s) {
$properties = array_merge($properties, $s->fields());
}
} else {
// find non-empty property
$has = $selector;
$properties[] = $has;
}
// check if any verbose properties are part of the find
if(!$verbose) foreach($properties as $property) {
if(!in_array($property, $this->moduleInfoVerboseKeys)) continue;
$verbose = true;
break;
}
$moduleInfoOptions = array(
'verbose' => $verbose,
'minify' => false
);
foreach($this->getModuleInfo('*', $moduleInfoOptions) as $info) {
$isMatch = false;
if($has) {
// simply check if property is non-empty
if(!empty($info[$has])) $isMatch = true;
} else if($selectors) {
// match selector
$total = 0;
$n = 0;
foreach($selectors as $selector) {
$total++;
$values = array();
foreach($selector->fields() as $property) {
if(isset($info[$property])) $values[] = $info[$property];
}
if($selector->matches($values)) $n++;
}
if($n && $n === $total) $isMatch = true;
} else if($keys) {
// match all values in $keys array
$n = 0;
foreach($keys as $key => $value) {
if($value === '*') {
// match any non-empty value
if(!empty($info[$key])) $n++;
} else {
// match exact value
if(isset($info[$key]) && $value == $info[$key]) $n++;
}
}
if($n && $n === count($keys)) $isMatch = true;
}
if($isMatch) {
$moduleName = $info['name'];
if(is_int($load)) {
$results[$moduleName] = $info;
} else if($load === true) {
$results[$moduleName] = $this->getModule($moduleName);
} else {
$results[$moduleName] = $moduleName;
}
}
}
return $results;
}
/**
* Get an associative array [name => path] for all modules that arent currently installed.
*
@@ -1729,7 +1903,7 @@ class Modules extends WireArray {
$this->add($module);
unset($this->installable[$class]);
// note: the module's install is called here because it may need to know it's module ID for installation of permissions, etc.
// note: the module's install is called here because it may need to know its module ID for installation of permissions, etc.
if(method_exists($module, '___install') || method_exists($module, 'install')) {
try {
/** @var _Module $module */
@@ -1909,9 +2083,13 @@ class Modules extends WireArray {
$success = false;
$reason = $this->isDeleteable($class, true);
if($reason !== true) throw new WireException($reason);
$siteModulesPath = $this->wire('config')->paths->siteModules;
$filename = $this->installable[$class];
$basename = basename($filename);
/** @var WireFileTools $fileTools */
$fileTools = $this->wire('files');
// double check that $class is consistent with the actual $basename
if($basename === "$class.module" || $basename === "$class.module.php") {
@@ -1968,7 +2146,7 @@ class Modules extends WireArray {
continue;
}
if($file->isDir()) {
$dirs[] = $file->getPathname();
$dirs[] = $fileTools->unixDirName($file->getPathname());
continue;
}
if(in_array($file->getBasename(), $files)) continue; // skip known files
@@ -2002,7 +2180,7 @@ class Modules extends WireArray {
foreach($files as $file) {
$file = "$path/$file";
if(!file_exists($file)) continue;
if(unlink($file)) {
if($this->wire('files')->unlink($file, $siteModulesPath)) {
$this->message("Removed file: $file", Notice::debug);
} else {
$this->error("Unable to remove file: $file", Notice::debug);
@@ -2420,13 +2598,14 @@ class Modules extends WireArray {
* @param $className
* @return array Only includes module info specified in the module file itself.
*
*/
protected function getModuleInfoInternalSafe($className) {
// future addition
// load file, preg_split by /^\s*(public|private|protected)[^;{]+function\s*([^)]*)[^{]*{/
// isolate the one that starts has getModuleInfo in matches[1]
// parse data from matches[2]
return array();
}
*/
/**
* Retrieve module info for system properties: PHP or ProcessWire
@@ -2477,19 +2656,20 @@ class Modules extends WireArray {
* - `name` (string): module class name.
* - `title` (string): module title.
* - `version` (int): module version.
* - `icon` (string): Optional icon name (excluding the "fa - ") part.
* - `icon` (string): Optional icon name (excluding the "fa-") part.
* - `requires` (array): module names required by this module.
* - `requiresVersions` (array): required module versionsmodule name is key, value is array($operator, $version).
* - `installs` (array): module names that this module installs.
* - `permission` (string): permission name required to execute this module.
* - `autoload` (bool): true if module is autoload, false if not.
* - `singular` (bool): true if module is singular, false if not.
* - `created` (int): unix - timestamp of date/time module added to system (for uninstalled modules, it is the file date).
* - `created` (int): unix-timestamp of date/time module added to system (for uninstalled modules, it is the file date).
* - `installed` (bool): is the module currently installed? (boolean, or null when not determined)
* - `configurable` (bool|int): true or positive number when the module is configurable.
* - `namespace` (string): PHP namespace that module lives in.
*
* The following properties are also included when "verbose" mode is requested. When not in verbose mode, these properties are present but blank:
* The following properties are also included when "verbose" mode is requested. When not in verbose mode, these
* properties are present but blank:
*
* - `versionStr` (string): formatted module version string.
* - `file` (string): module filename from PW installation root, or false when it can't be found.
@@ -2497,10 +2677,11 @@ class Modules extends WireArray {
* - `author` (string): module author, when specified.
* - `summary` (string): summary of what this module does.
* - `href` (string): URL to module details (when specified).
* - `permissions` (array): permissions installed by this module, associative array ('permission - name' => 'Description').
* - `permissions` (array): permissions installed by this module, associative array ('permission-name' => 'Description').
* - `page` (array): definition of page to create for Process module (see Process class)
*
* The following properties appear only for "Process" modules. See the Process class for more details:
* The following properties appear only for "Process" modules, and only if specified by module.
* See the Process class for more details:
*
* - `nav` (array): navigation definition
* - `useNavJSON` (bool): whether the Process module provides JSON navigation
@@ -2515,28 +2696,45 @@ class Modules extends WireArray {
* $moduleInfo = $modules->getModuleInfoVerbose('MarkupAdminDataTable');
* ~~~~~
*
* @param string|Module|int $class May be class name, module instance, or module ID.
* Specify "*" or "all" to retrieve module info for all modules.
* @param string|Module|int $class Specify one of the following:
* - Module object instance
* - Module class name (string)
* - Module ID (int)
* - To get info for ALL modules, specify `*` or `all`.
* - To get system information, specify `ProcessWire` or `PHP`.
* - To get a blank module info template, specify `info`.
* @param array $options Optional options to modify behavior of what gets returned
* - `verbose` (bool): Makes the info also include additional properties (they will be usually blank without this option specified).
* - `noCache` (bool): prevents use of cache to retrieve the module info.
* - `noInclude` (bool): prevents include() of the module file, applicable only if it hasn't already been included.
* - `verbose` (bool): Makes the info also include verbose properties, which are otherwise blank. (default=false)
* - `minify` (bool): Remove non-applicable and properties that match defaults? (default=false, or true when getting `all`)
* - `noCache` (bool): prevents use of cache to retrieve the module info. (default=false)
* @return array Associative array of module information
* @throws WireException when a module exists but has no means of returning module info
* @see Modules::getModuleInfoVerbose()
* @todo move all getModuleInfo methods to their own ModuleInfo class and break this method down further.
* @todo move all getModuleInfo methods to their own ModuleInfo class and break this method down further.
*
*/
public function getModuleInfo($class, array $options = array()) {
if(!isset($options['verbose'])) $options['verbose'] = false;
if(!isset($options['noCache'])) $options['noCache'] = false;
$getAll = $class === '*' || $class === 'all';
$getSystem = $class === 'ProcessWire' || $class === 'PHP' || $class === 'info';
$defaults = array(
'verbose' => false,
'minify' => $getAll,
'noCache' => false,
'noInclude' => false,
);
$options = array_merge($defaults, $options);
$info = array();
$module = $class;
$moduleName = $this->getModuleClass($module);
$moduleID = (string) $this->getModuleID($module); // typecast to string for cache
$moduleName = '';
$moduleID = 0;
$fromCache = false; // was the data loaded from cache?
if(!$getAll && !$getSystem) {
$moduleName = $this->getModuleClass($module);
$moduleID = (string) $this->getModuleID($module); // typecast to string for cache
}
static $infoTemplate = array(
// module database ID
@@ -2576,7 +2774,9 @@ class Modules extends WireArray {
// is the module currently installed? (boolean, or null when not determined)
'installed' => null,
// this is set to true when the module is configurable, false when it's not, and null when it's not determined
'configurable' => null,
'configurable' => null,
// verbose mode only: true when module implements SearchableModule interface, or null|false when not
'searchable' => null,
// namespace that module lives in (string)
'namespace' => null,
// verbose mode only: this is set to the module filename (from PW installation root), false when it can't be found, null when it hasn't been determined
@@ -2590,8 +2790,30 @@ class Modules extends WireArray {
// 'page' => array(), // page to create for Process module: see Process.php
// 'permissionMethod' => string or callable // method to call to determine permission: see Process.php
);
if($module instanceof Module) {
if($getAll) {
if(empty($this->moduleInfoCache)) $this->loadModuleInfoCache();
$modulesInfo = $this->moduleInfoCache;
if($options['verbose']) {
if(empty($this->moduleInfoCacheVerbose)) $this->loadModuleInfoCacheVerbose();
foreach($this->moduleInfoCacheVerbose as $moduleID => $moduleInfoVerbose) {
$modulesInfo[$moduleID] = array_merge($modulesInfo[$moduleID], $moduleInfoVerbose);
}
}
if(!$options['minify']) {
foreach($modulesInfo as $moduleID => $info) {
$modulesInfo[$moduleID] = array_merge($infoTemplate, $info);
}
}
return $modulesInfo;
} else if($getSystem) {
// module is a system
if($class === 'info') return $infoTemplate;
$info = $this->getModuleInfoSystem($module);
return $options['minify'] ? $info : array_merge($infoTemplate, $info);
} else if($module instanceof Module) {
// module is an instance
// $moduleName = method_exists($module, 'className') ? $module->className() : get_class($module);
// return from cache if available
@@ -2604,28 +2826,12 @@ class Modules extends WireArray {
if(!count($info)) $info = $this->getModuleInfoInternal($module);
}
} else if($module == 'PHP' || $module == 'ProcessWire') {
// module is a system
$info = $this->getModuleInfoSystem($module);
return array_merge($infoTemplate, $info);
} else if($module === '*' || $module === 'all') {
if(empty($this->moduleInfoCache)) $this->loadModuleInfoCache();
$modulesInfo = $this->moduleInfoCache;
if($options['verbose']) {
if(empty($this->moduleInfoCacheVerbose)) $this->loadModuleInfoCacheVerbose();
foreach($this->moduleInfoCacheVerbose as $moduleID => $moduleInfoVerbose) {
$modulesInfo[$moduleID] = array_merge($modulesInfo[$moduleID], $moduleInfoVerbose);
}
}
return $modulesInfo;
} else {
// module is a class name or ID
if(ctype_digit("$module")) $module = $moduleName;
// return from cache if available
// return from cache if available (as it almost always should be)
if(empty($options['noCache']) && !empty($this->moduleInfoCache[$moduleID])) {
$info = $this->moduleInfoCache[$moduleID];
$fromCache = true;
@@ -2666,10 +2872,11 @@ class Modules extends WireArray {
return $info;
}
$info = array_merge($infoTemplate, $info);
if(!$options['minify']) $info = array_merge($infoTemplate, $info);
$info['id'] = (int) $moduleID;
if($fromCache) {
// since cache is loaded at init(), this is the most common scenario
if($options['verbose']) {
if(empty($this->moduleInfoCacheVerbose)) $this->loadModuleInfoCacheVerbose();
@@ -2683,7 +2890,7 @@ class Modules extends WireArray {
if(is_null($info['singular'])) $info['singular'] = false;
if(is_null($info['configurable'])) $info['configurable'] = false;
if(is_null($info['core'])) $info['core'] = false;
if(is_null($info['installed'])) $info['installed'] = true;
if(is_null($info['installed'])) $info['installed'] = true;
if(is_null($info['namespace'])) $info['namespace'] = strlen(__NAMESPACE__) ? "\\" . __NAMESPACE__ . "\\" : "";
if(!empty($info['requiresVersions'])) $info['requires'] = array_keys($info['requiresVersions']);
if($moduleName == 'SystemUpdater') $info['configurable'] = 1; // fallback, just in case
@@ -2692,8 +2899,9 @@ class Modules extends WireArray {
// are already accounted for in the cached module info
} else {
// if $info[requires] or $info[installs] isn't already an array, make it one
// not from cache, only likely to occur when refreshing modules info caches
// if $info[requires] isn't already an array, make it one
if(!is_array($info['requires'])) {
$info['requires'] = str_replace(' ', '', $info['requires']); // remove whitespace
if(strpos($info['requires'], ',') !== false) {
@@ -2718,6 +2926,7 @@ class Modules extends WireArray {
}
// what does it install?
// if $info[installs] isn't already an array, make it one
if(!is_array($info['installs'])) {
$info['installs'] = str_replace(' ', '', $info['installs']); // remove whitespace
if(strpos($info['installs'], ',') !== false) {
@@ -2728,7 +2937,7 @@ class Modules extends WireArray {
}
// misc
$info['versionStr'] = $this->formatVersion($info['version']); // versionStr
if($options['verbose']) $info['versionStr'] = $this->formatVersion($info['version']); // versionStr
$info['name'] = $moduleName; // module name
// module configurable?
@@ -2784,6 +2993,18 @@ class Modules extends WireArray {
// the file property is not stored in the verbose cache, but provided as a verbose key
$info['file'] = $this->getModuleFile($moduleName);
if($info['file']) $info['core'] = strpos($info['file'], $this->coreModulesDir) !== false; // is it core?
} else {
// module info may still contain verbose keys with undefined values
}
if($options['minify']) {
// when minify, any values that match defaults from infoTemplate are removed
if(!$options['verbose']) foreach($this->moduleInfoVerboseKeys as $key) unset($info[$key]);
foreach($info as $key => $value) {
if(!array_key_exists($key, $infoTemplate)) continue;
if($value !== $infoTemplate[$key]) continue;
unset($info[$key]);
}
}
// if($this->debug) $this->message("getModuleInfo($moduleName) " . ($fromCache ? "CACHE" : "NO-CACHE"));
@@ -2794,7 +3015,7 @@ class Modules extends WireArray {
/**
* Returns a verbose array of information for a Module
*
* This is the same as what's returned by `Modules::getModuleInfo()` except that it has the following additional properties:
* This is the same as whats returned by `Modules::getModuleInfo()` except that it has the following additional properties:
*
* - `versionStr` (string): formatted module version string.
* - `file` (string): module filename from PW installation root, or false when it can't be found.
@@ -2810,7 +3031,6 @@ class Modules extends WireArray {
* - `noCache` (bool): prevents use of cache to retrieve the module info
* - `noInclude` (bool): prevents include() of the module file, applicable only if it hasn't already been included
* @return array Associative array of module information
* @throws WireException when a module exists but has no means of returning module info
* @see Modules::getModuleInfo()
*
*/
@@ -2820,6 +3040,30 @@ class Modules extends WireArray {
return $info;
}
/**
* Get just a single property of module info
*
* @param Module|string $class Module instance or module name
* @param string $property Name of property to get
* @param array $options Additional options (see getModuleInfo method for options)
* @return mixed|null Returns value of property or null if not found
* @since 3.0.107
*
*/
public function getModuleInfoProperty($class, $property, array $options = array()) {
if(in_array($property, $this->moduleInfoVerboseKeys)) {
$info = $this->getModuleInfoVerbose($class, $options);
$info['verbose'] = true;
} else {
$info = $this->getModuleInfo($class, $options);
}
if(!isset($info[$property]) && empty($info['verbose'])) {
// try again, just in case we can find it in verbose data
$info = $this->getModuleInfoVerbose($class, $options);
}
return isset($info[$property]) ? $info[$property] : null;
}
/**
* Get an array of all unique, non-default, non-root module namespaces mapped to directory names
*
@@ -2991,6 +3235,27 @@ class Modules extends WireArray {
public function getModuleConfigData($className) {
return $this->getConfig($className);
}
/**
* Return the URL where the module can be edited, configured or uninstalled
*
* If module is not installed, it just returns the URL to ProcessModule.
*
* #pw-group-configuration
*
* @param string|Module $className
* @param bool $collapseInfo
* @return string
*
*/
public function getModuleEditUrl($className, $collapseInfo = true) {
if(!is_string($className)) $className = $this->getModuleClass($className);
$url = $this->wire('config')->urls->admin . 'module/';
if(empty($className) || !$this->isInstalled($className)) return $url;
$url .= "edit/?name=$className";
if($collapseInfo) $url .= "&collapse_info=1";
return $url;
}
/**
* Given a module name, return an associative array of configuration data for it
@@ -3003,6 +3268,9 @@ class Modules extends WireArray {
* $data = $modules->getConfig('HelloWorld');
* $data['greeting'] = 'Hello World! How are you today?';
* $modules->saveConfig('HelloWorld', $data);
*
* // Getting just one property 'apiKey' from module config data
* @apiKey = $modules->getConfig('HelloWorld', 'apiKey');
* ~~~~~~
*
* #pw-group-configuration
@@ -3010,8 +3278,8 @@ class Modules extends WireArray {
*
* @param string|Module $class
* @param string $property Optionally just get value for a specific property (omit to get all config)
* @return array Module configuration data
* @see Modules::saveConfig()
* @return array|string|int|float Module configuration data, returns array unless a specific $property was requested
* @see Modules::saveConfig()
* @since 3.0.16 Use method getModuleConfigData() with same arguments for prior versions (can also be used on any version).
*
*/
@@ -3020,7 +3288,9 @@ class Modules extends WireArray {
$emptyReturn = $property ? null : array();
$className = $class;
if(is_object($className)) $className = wireClassName($className->className(), false);
if(!$id = $this->moduleIDs[$className]) return $emptyReturn;
if(!isset($this->moduleIDs[$className])) return $emptyReturn;
$id = $this->moduleIDs[$className];
if(!$id) return $emptyReturn;
if(!isset($this->configData[$id])) return $emptyReturn; // module has no config data
if(is_array($this->configData[$id])) {
@@ -3045,14 +3315,6 @@ class Modules extends WireArray {
return $data;
}
/**
* Alias of getConfig() for backwards compatibility
*
* @param string|Module $className
* @return array
*
*/
/**
* Get the path + filename (or optionally URL) for this module
*
@@ -3259,7 +3521,11 @@ class Modules extends WireArray {
if($moduleInstance && $moduleInstance instanceof ConfigurableModule) {
// re-try because moduleInfo may be temporarily incorrect for this request because of change in moduleInfo format
// this is due to reports of ProcessChangelogHooks not getting config data temporarily between 2.6.11 => 2.6.12
$this->error("Configurable module check failed for $className, retrying...", Notice::debug);
$this->error(
"Configurable module check failed for $className. " .
"If this error persists, please do a Modules > Refresh.",
Notice::debug
);
$useCache = false;
} else {
return false;
@@ -3615,6 +3881,7 @@ class Modules extends WireArray {
// we allow for option of no return statement in the method
$module = $this->getModule($moduleName);
$fields = $this->wire(new InputfieldWrapper());
$fields->setParent($form);
$_fields = $module->getModuleConfigInputfields($fields);
if($_fields instanceof InputfieldWrapper) $fields = $_fields;
unset($_fields);
@@ -3878,7 +4145,7 @@ class Modules extends WireArray {
}
/**
* Is the given namespace a unique recognized module namespace? If yes, returns the path to it. If not, returns boolean false;
* Is the given namespace a unique recognized module namespace? If yes, returns the path to it. If not, returns boolean false.
*
* #pw-internal
*
@@ -3904,10 +4171,13 @@ class Modules extends WireArray {
if($this->wire('config')->systemVersion < 6) {
return;
}
$this->refreshing = true;
$this->clearModuleInfoCache();
$this->loadModulesTable();
foreach($this->paths as $path) $this->findModuleFiles($path, false);
foreach($this->paths as $path) $this->load($path);
if($this->duplicates()->numNewDuplicates() > 0) $this->duplicates()->updateDuplicates(); // PR#1020
$this->refreshing = false;
}
/**
@@ -4355,7 +4625,7 @@ class Modules extends WireArray {
if(!in_array($id, $this->moduleIDs)) unset($this->modulesLastVersions[$id]);
}
if(count($this->modulesLastVersions)) {
$this->wire('cache')->save(self::moduleLastVersionsCacheName, $this->modulesLastVersions, WireCache::expireNever);
$this->wire('cache')->save(self::moduleLastVersionsCacheName, $this->modulesLastVersions, WireCache::expireReserved);
} else {
$this->wire('cache')->delete(self::moduleLastVersionsCacheName);
}
@@ -4599,7 +4869,7 @@ class Modules extends WireArray {
}
}
}
$this->wire('cache')->save($cacheName, $data, WireCache::expireNever);
$this->wire('cache')->save($cacheName, $data, WireCache::expireReserved);
}
$this->log('Saved module info caches');
@@ -4693,7 +4963,7 @@ class Modules extends WireArray {
* #pw-internal
*
* @param Module|int|string $module Module object or class name
* @return array Returns number of files that were added
* @return int Returns number of files that were added
*
*/
public function loadModuleFileAssets($module) {
@@ -4771,7 +5041,7 @@ class Modules extends WireArray {
*
* @param array|Wire|string $text
* @param int $flags
* @return $this
* @return Modules|WireArray
*
*/
public function error($text, $flags = 0) {
@@ -4792,11 +5062,14 @@ class Modules extends WireArray {
*/
public function compile($moduleName, $file = '', $namespace = null) {
static $allowCompile = null;
if($allowCompile === null) $allowCompile = $this->wire('config')->moduleCompile;
// if not given a file, track it down
if(empty($file)) $file = $this->getModuleFile($moduleName);
// don't compile when module compilation is disabled
if(!$this->wire('config')->moduleCompile) return $file;
if(!$allowCompile) return $file;
// don't compile core modules
if(strpos($file, $this->coreModulesDir) !== false) return $file;
@@ -4822,7 +5095,8 @@ class Modules extends WireArray {
// compile if necessary
if($compile) {
$compiler = new FileCompiler(dirname($file));
/** @var FileCompiler $compiler */
$compiler = $this->wire(new FileCompiler(dirname($file)));
$compiledFile = $compiler->compile(basename($file));
if($compiledFile) $file = $compiledFile;
}

View File

@@ -10,18 +10,29 @@
* Base class that holds a message, source class, and timestamp.
* Contains notices/messages used by the application to the user.
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* @property string $text Text of notice
* @property string $class Class of notice
* @property int $timestamp When the notice was generated
* @property int $flags Optional flags bitmask of Notice::debug and/or Notice::warning
* @property string $icon
* @property int $timestamp Unix timestamp of when the notice was generated
* @property int $flags Bitmask using any of the Notice::constants
* @property-read $flagsArray Current flags as an array where indexes are int flags and values are flag names (since 3.0.149)
* @property-read $flagsStr Current flags as string of flag names (since 3.0.149)
* @property string $icon Name of icon to use with Notice
* @property string $idStr Unique ID string for Notice
* @property int $qty Number of times this Notice was added.
*
*/
abstract class Notice extends WireData {
/**
* Flag indicates notice should prepend (rather than append) to any existing notices
*
* @since 3.0.135
*
*/
const prepend = 1;
/**
* Flag indicates the notice is for when debug mode is on only
@@ -58,30 +69,271 @@ abstract class Notice extends WireData {
*
*/
const allowMarkup = 32;
/**
* Make notice anonymous (not tied to a particular class)
*
* @since 3.0.135
*
*/
const anonymous = 65536;
/**
* Indicate notice should not group/collapse with others of the same type (when supported by admin theme)
*
* @since 3.0.146
*
*/
const noGroup = 131072;
/**
* Ignore notice unless it will be seen by a logged-in user
*
* @since 3.0.149
*
*/
const login = 262144;
/**
* Ignore notice unless user is somewhere in the admin (login page included)
*
* @since 3.0.149
*
*/
const admin = 524288;
/**
* Ignore notice unless current user is a superuser
*
* @since 3.0.149
*
*/
const superuser = 1048576;
/**
* Make notice persist in session until removed with $notices->removeNotice() call
*
* (not yet fully implemented)
*
* #pw-internal
*
* @since 3.0.149
* @todo still needs an interactive way to remove
*
*/
const persist = 2097152;
/**
* Flag integers to flag names
*
* @var array
* @since 3.0.149
*
*/
static protected $flagNames = array(
self::prepend => 'prepend',
self::debug => 'debug',
self::log => 'log',
self::logOnly => 'logOnly',
self::allowMarkup => 'allowMarkup',
self::anonymous => 'anonymous',
self::noGroup => 'noGroup',
self::login => 'login',
self::admin => 'admin',
self::superuser => 'superuser',
self::persist => 'persist',
);
/**
* Create the Notice
*
* As of version 3.0.149 the $flags argument can also be specified as a space separated
* string or array of flag names. Previous versions only accepted flags integer.
*
* @param string $text Notification text
* @param int $flags Flags
* @param int|string|array $flags Flags Flags for Notice
*
*/
public function __construct($text, $flags = 0) {
$this->set('text', $text);
$this->set('class', '');
$this->set('timestamp', time());
$this->set('flags', $flags);
parent::__construct();
$this->set('icon', '');
$this->set('class', '');
$this->set('timestamp', time());
$this->set('flags', 0);
$this->set('text', $text);
$this->set('qty', 0);
if($flags !== 0) $this->flags($flags);
}
/**
* Get the notice log
* Set property
*
* @param string $key
* @param mixed $value
* @return $this|WireData
*
*/
public function set($key, $value) {
if($key === 'text' && is_string($value) && strpos($value, 'icon-') === 0 && strpos($value, ' ')) {
list($icon, $value) = explode(' ', $value, 2);
list(,$icon) = explode('-', $icon, 2);
$icon = $this->wire('sanitizer')->name($icon);
if(strlen($icon)) $this->set('icon', $icon);
} else if($key === 'flags') {
$this->flags($value);
return $this;
}
return parent::set($key, $value);
}
/**
* Get property
*
* @param string $key
* @return mixed
*
*/
public function get($key) {
if($key === 'flagsArray') return $this->flagNames(parent::get('flags'));
if($key === 'flagsStr') return $this->flagNames(parent::get('flags'), true);
if($key === 'idStr') return $this->getIdStr();
return parent::get($key);
}
/**
* Get or set flags
*
* @param string|int|array|null $value Accepts flags integer, or array of flag names, or space-separated string of flag names
* @return int
* @since 3.0.149
*
*/
public function flags($value = null) {
if($value === null) return parent::get('flags'); // get flags
$flags = 0;
if(is_int($value)) {
$flags = $value;
} else if(is_string($value)) {
if(ctype_digit($value)) {
$flags = (int) $value;
} else {
if(strpos($value, ',') !== false) $value = str_replace(array(', ', ','), ' ', $value);
$value = explode(' ', $value);
}
}
if(is_array($value)) {
foreach($value as $flag) {
if(empty($flag)) continue;
$flag = $this->flag($flag);
if($flag) $flags = $flags | $flag;
}
}
parent::set('flags', $flags);
return $flags;
}
/**
* Given flag name or int, return flag int
*
* @param string|int $name
* @return int
*
*/
protected function flag($name) {
if(is_int($name)) return $name;
$name = trim($name);
if(ctype_digit("$name")) return (int) $name;
$flag = array_search(strtolower($name), array_map('strtolower', self::$flagNames));
return $flag ? $flag : 0;
}
/**
* Get string of names for given flags integer
*
* @param null|int $flags Specify flags integer or omit to return all flag names (default=null)
* @param bool $getString Get a space separated string rather than an array (default=false)
* @return array|string
* @since 3.0.149
*
*/
protected function flagNames($flags = null, $getString = false) {
if($flags === null) return self::$flagNames;
if(!is_int($flags)) return '';
$flagNames = array();
foreach(self::$flagNames as $flag => $flagName) {
if($flags & $flag) $flagNames[$flag] = $flagName;
}
return $getString ? implode(' ', $flagNames) : $flagNames;
}
/**
* Add a flag
*
* @param int|string $flag
* @since 3.0.149
*
*/
public function addFlag($flag) {
$flag = $this->flag($flag);
if($flag && !($this->flags & $flag)) $this->flags = $this->flags | $flag;
}
/**
* Remove a flag
*
* @param int|string $flag
* @since 3.0.149
*
*/
public function removeFlag($flag) {
$flag = $this->flag($flag);
if($flag && ($this->flags & $flag)) $this->flags = $this->flags & ~$flag;
}
/**
* Does this Notice have given flag?
*
* @param int|string $flag
* @return bool
* @since 3.0.149
*
*/
public function hasFlag($flag) {
$flag = $this->flag($flag);
return $flag ? $this->flags & $flag : false;
}
/**
* Get the name for this type of Notice
*
* This name is used for notice logs when Notice::log or Notice::logOnly flag is used.
*
* @return string Name of log (basename)
*
*/
abstract public function getName();
/**
* Get a unique ID string based on properties of this Notice to identify it among others
*
* #pw-internal
*
* @return string
* @since 3.0.149
*
*/
public function getIdStr() {
$prefix = substr(str_replace('otice', '', $this->className()), 0, 2);
$idStr = $prefix . md5("$prefix$this->flags$this->class$this->text");
return $idStr;
}
public function __toString() {
return (string) $this->text;
}
@@ -169,6 +421,17 @@ class Notices extends WireArray {
const logAllNotices = false; // for debugging/dev purposes
/**
* Initialize Notices API var
*
* #pw-internal
*
*/
public function init() {
// @todo
// $this->loadStoredNotices();
}
/**
* #pw-internal
*
@@ -190,6 +453,67 @@ class Notices extends WireArray {
return $this->wire(new NoticeMessage(''));
}
/**
* Allow given Notice to be added?
*
* @param Notice $item
* @return bool
*
*/
protected function allowNotice(Notice $item) {
$user = $this->wire('user'); /** @var User $user */
if($item->flags & Notice::debug) {
if(!$this->wire('config')->debug) return false;
}
if($item->flags & Notice::superuser) {
if(!$user || !$user->isSuperuser()) return false;
}
if($item->flags & Notice::login) {
if(!$user || !$user->isLoggedin()) return false;
}
if($item->flags & Notice::admin) {
$page = $this->wire('page'); /** @var Page|null $page */
if(!$page || !$page->template || $page->template->name != 'admin') return false;
}
if($this->isDuplicate($item)) {
$item->qty = $item->qty+1;
return false;
}
if(self::logAllNotices || ($item->flags & Notice::log) || ($item->flags & Notice::logOnly)) {
$this->addLog($item);
$item->flags = $item->flags & ~Notice::log; // remove log flag, to prevent it from being logged again
if($item->flags & Notice::logOnly) return false;
}
return true;
}
/**
* Format Notice text
*
* @param Notice $item
*
*/
protected function formatNotice(Notice $item) {
$text = $item->text;
if(is_array($text)) {
$item->text = "<pre>" . trim(print_r($this->sanitizeArray($text), true)) . "</pre>";
$item->flags = $item->flags | Notice::allowMarkup;
} else if(is_object($text) && $text instanceof Wire) {
$item->text = "<pre>" . $this->wire('sanitizer')->entities(print_r($text, true)) . "</pre>";
$item->flags = $item->flags | Notice::allowMarkup;
} else if(is_object($text)) {
$item->text = (string) $text;
}
}
/**
* Add a Notice object
*
@@ -198,55 +522,149 @@ class Notices extends WireArray {
* ~~~~
*
* @param Notice $item
* @return $this
* @return Notices|WireArray
*
*/
public function add($item) {
if($item->flags & Notice::debug) {
if(!$this->wire('config')->debug) return $this;
if(!($item instanceof Notice)) {
$item = new NoticeError("You attempted to add a non-Notice object to \$notices: $item", Notice::debug);
}
if(is_array($item->text)) {
$item->text = "<pre>" . trim(print_r($this->sanitizeArray($item->text), true)) . "</pre>";
$item->flags = $item->flags | Notice::allowMarkup;
} else if(is_object($item->text) && $item->text instanceof Wire) {
$item->text = "<pre>" . $this->wire('sanitizer')->entities(print_r($item->text, true)) . "</pre>";
$item->flags = $item->flag | Notice::allowMarkup;
} else if(is_object($item->text)) {
$item->text = (string) $item->text;
}
if(!$this->allowNotice($item)) return $this;
// check for duplicates
$dup = false;
foreach($this as $notice) {
if($notice->text == $item->text && $notice->flags == $item->flags) $dup = true;
}
$item->qty = $item->qty+1;
$this->formatNotice($item);
if($dup) return $this;
if(($item->flags & Notice::warning) && !$item instanceof NoticeWarning) {
// if given a warning of either NoticeMessage or NoticeError, convert it to a NoticeWarning
// this is in support of legacy code, as NoticeWarning didn't used to exist
$warning = $this->wire(new NoticeWarning($item->text, $item->flags));
$warning->class = $item->class;
$warning->timestamp = $item->timestamp;
$item = $warning;
}
if(self::logAllNotices || ($item->flags & Notice::log) || ($item->flags & Notice::logOnly)) {
$this->addLog($item);
$item->flags = $item->flags & ~Notice::log; // remove log flag, to prevent it from being logged again
if($item->flags & Notice::logOnly) return $this;
if($item->flags & Notice::anonymous) {
$item->set('class', '');
}
return parent::add($item);
if($item->flags & Notice::persist) {
$this->storeNotice($item);
}
if($item->flags & Notice::prepend) {
return parent::prepend($item);
} else {
return parent::add($item);
}
}
/**
* Store a persist Notice in Session
*
* @param Notice $item
* @return bool
*
*/
protected function storeNotice(Notice $item) {
/** @var Session $session */
$session = $this->wire('session');
if(!$session) return false;
$items = $session->getFor($this, 'items');
if(!is_array($items)) $items = array();
$str = $this->noticeToStr($item);
$idStr = $item->getIdStr();
if(isset($items[$idStr])) return false;
$items[$idStr] = $str;
$session->setFor($this, 'items', $items);
return true;
}
/**
* Load persist Notices stored in Session
*
* @return int Number of Notices loaded
*
*/
protected function loadStoredNotices() {
$session = $this->wire('session');
$items = $session->getFor($this, 'items');
$qty = 0;
if(empty($items) || !is_array($items)) return $qty;
foreach($items as $idStr => $str) {
if(!is_string($str)) continue;
$item = $this->strToNotice($str);
if(!$item) continue;
$persist = $item->hasFlag(Notice::persist) ? Notice::persist : 0;
// temporarily remove persist flag so Notice does not get re-stored when added
if($persist) $item->removeFlag($persist);
$this->add($item);
if($persist) $item->addFlag($persist);
$item->set('_idStr', $idStr);
$qty++;
}
protected function addLog($item) {
return $qty;
}
/**
* Remove a Notice
*
* Like the remove() method but also removes persist notices.
*
* @param string|Notice $item Accepts a Notice object or Notice ID string.
* @return self
* @since 3.0.149
*
*/
public function removeNotice($item) {
if($item instanceof Notice) {
$idStr = $item->get('_idStr|idStr');
} else if(is_string($item)) {
$idStr = $item;
$item = $this->getByIdStr($idStr);
} else {
return $this;
}
if($item) parent::remove($item);
$session = $this->wire('session');
$items = $session->getFor($this, 'items');
if(is_array($items) && isset($items[$idStr])) {
unset($items[$idStr]);
$session->setFor($this, 'items', $items);
}
return $this;
}
/**
* Is the given Notice a duplicate of one already here?
*
* @param Notice $item
* @return bool|Notice Returns Notice that it duplicate sor false if not a duplicate
*
*/
protected function isDuplicate(Notice $item) {
$duplicate = false;
foreach($this as $notice) {
/** @var Notice $notice */
if($notice === $item) {
$duplicate = $notice;
break;
}
if($notice->className() === $item->className() && $notice->flags === $item->flags
&& $notice->icon === $item->icon && $notice->text === $item->text) {
$duplicate = $notice;
break;
}
}
return $duplicate;
}
/**
* Add Notice to log
*
* @param Notice $item
*
*/
protected function addLog(Notice $item) {
/** @var Notice $item */
$text = $item->text;
if($item->flags & Notice::allowMarkup && strpos($text, '&') !== false) {
if(strpos($text, '&') !== false) {
$text = $this->wire('sanitizer')->unentities($text);
}
if($this->wire('config')->debug && $item->class) $text .= " ($item->class)";
@@ -335,4 +753,76 @@ class Notices extends WireArray {
}
return $n;
}
/**
* Get a Notice by ID string
*
* #pw-internal
*
* @param string $idStr
* @return Notice|null
* @since 3.0.149
*
*/
protected function getByIdStr($idStr) {
$notice = null;
if(strlen($idStr) < 33) return null;
$prefix = substr($idStr, 0, 1);
foreach($this as $item) {
/** @var Notice $item */
if(strpos($item->className(), $prefix) !== 0) continue;
if($item->getIdStr() !== $idStr) continue;
$notice = $item;
break;
}
return $notice;
}
/**
* Export Notice object to string
*
* #pw-internal
*
* @param Notice $item
* @return string
* @since 3.0.149
*
*/
protected function noticeToStr(Notice $item) {
$type = str_replace('Notice', '', $item->className());
$a = array(
'type' => $type,
'flags' => $item->flags,
'timestamp' => $item->timestamp,
'class' => $item->class,
'icon' => $item->icon,
'text' => $item->text,
);
return implode(';', $a);
}
/**
* Import Notice object from string
*
* #pw-internal
*
* @param string $str
* @return Notice|null
* @since 3.0.149
*
*/
protected function strToNotice($str) {
if(substr_count($str, ';') < 5) return null;
list($type, $flags, $timestamp, $class, $icon, $text) = explode(';', $str, 6);
$type = __NAMESPACE__ . "\\Notice$type";
if(!wireClassExists($type)) return null;
/** @var Notice $item */
$item = new $type($text, (int) $flags);
$item->setArray(array(
'timestamp' => (int) $timestamp,
'class' => $class,
'icon' => $icon,
));
return $item;
}
}

View File

@@ -10,7 +10,7 @@
*
*/
class NullField extends Field {
class NullField extends Field implements WireNull {
public function get($key) {
if($key == 'id') return 0;
if($key == 'name') return '';

View File

@@ -32,7 +32,7 @@
*
*/
class NullPage extends Page {
class NullPage extends Page implements WireNull {
/**
* #pw-internal
*

View File

@@ -42,7 +42,7 @@ class PWGIFLZW {
}
function LZWCommand(&$data, $bInit) {
if($bInit) {
$this->SetCodeSize = ord($data{0});
$this->SetCodeSize = ord($data[0]);
$data = substr($data, 1);
$this->CodeSize = $this->SetCodeSize + 1;
$this->ClearCode = 1 << $this->SetCodeSize;
@@ -147,11 +147,11 @@ class PWGIFLZW {
}
$this->Buf[0] = $this->Buf[$this->LastByte - 2];
$this->Buf[1] = $this->Buf[$this->LastByte - 1];
$Count = ord($data{0});
$Count = ord($data[0]);
$data = substr($data, 1);
if($Count) {
for($i = 0; $i < $Count; $i++) {
$this->Buf[2 + $i] = ord($data{$i});
$this->Buf[2 + $i] = ord($data[$i]);
}
$data = substr($data, $Count);
} else {
@@ -187,7 +187,7 @@ class PWGIFCOLORTABLE {
return false;
}
if($this->extended) {
$this->m_arColors[] = (ord($rgb{2}) << 16) + (ord($rgb{1}) << 8) + ord($rgb{0});
$this->m_arColors[] = (ord($rgb[2]) << 16) + (ord($rgb[1]) << 8) + ord($rgb[0]);
}
$this->m_nColors++;
}
@@ -310,7 +310,7 @@ class PWGIFIMAGEHEADER {
if(!$this->m_nWidth || !$this->m_nHeight) {
return false;
}
$b = ord($lpData{8});
$b = ord($lpData[8]);
$this->m_bLocalClr = ($b & 0x80) ? true : false;
$this->m_bInterlace = ($b & 0x40) ? true : false;
$this->m_bSorted = ($b & 0x20) ? true : false;
@@ -355,7 +355,7 @@ class PWGIFIMAGE {
public function load($data, &$datLen) {
$datLen = 0;
while(true) {
$b = ord($data{0});
$b = ord($data[0]);
$data = substr($data, 1);
$datLen++;
switch($b) {
@@ -399,20 +399,20 @@ class PWGIFIMAGE {
}
function skipExt(&$data, &$extLen) {
$extLen = 0;
$b = ord($data{0});
$b = ord($data[0]);
$data = substr($data, 1);
$extLen++;
switch($b) {
case 0xF9: // Graphic Control
$b = ord($data{1});
$b = ord($data[1]);
$this->m_disp = ($b & 0x1C) >> 2;
$this->m_bUser = ($b & 0x02) ? true : false;
$this->m_bTrans = ($b & 0x01) ? true : false;
$this->m_nDelay = $this->w2i(substr($data, 2, 2));
$this->m_nTrans = ord($data{4});
$this->m_nTrans = ord($data[4]);
break;
case 0xFE: // Comment
$this->m_lpComm = substr($data, 1, ord($data{0}));
$this->m_lpComm = substr($data, 1, ord($data[0]));
break;
case 0x01: // Plain text
break;
@@ -420,13 +420,13 @@ class PWGIFIMAGE {
break;
}
// SKIP DEFAULT AS DEFS MAY CHANGE
$b = ord($data{0});
$b = ord($data[0]);
$data = substr($data, 1);
$extLen++;
while($b > 0) {
$data = substr($data, $b);
$extLen += $b;
$b = ord($data{0});
$b = ord($data[0]);
$data = substr($data, 1);
$extLen++;
}

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,8 @@ class PageAccess {
public function getAccessParent(Page $page, $type = 'view', $level = 0) {
if(!$page->id) return $page->wire('pages')->newNullPage();
if(!in_array($type, $this->types)) $type = $this->getType($type);
if($page->template->useRoles || $page->id === 1) {
if($page->id === 1 || $page->template->useRoles) {
// found an access parent
if($type != 'view' && $level > 0 && $page->template->noInherit != 0) {
// access parent prohibits inheritance of edit-related permissions
@@ -78,8 +79,18 @@ class PageAccess {
}
return $page;
}
$parent = $page->parent();
if($parent->id) return $this->getAccessParent($parent, $type, $level+1);
$parent = null;
if($type === 'edit' && $page->isTrash() && $page->id != $page->wire('config')->trashPageID) {
// pages in trash have an edit access parent as whatever it was prior to being trashed
$info = $page->wire('pages')->trasher()->parseTrashPageName($page->name);
if(!empty($info['parent_id'])) $parent = $page->wire('pages')->get((int) $info['parent_id']);
}
if(!$parent || !$parent->id) $parent = $page->parent();
if($parent->id) return $this->getAccessParent($parent, $type, $level + 1);
return $page->wire('pages')->newNullPage();
}

View File

@@ -13,15 +13,22 @@
* specifically on managing Page objects.
*
* PageArray is returned by all API methods in ProcessWire that can return more than one page at once.
* `$pages->find()` and `$page->children()` are common examples.
* `$pages->find()` and `$page->children()` are common examples that return PageArray.
*
* The recommended way to create a new PageArray is to use the `$pages->newPageArray()` method:
* You can create a new PageArray using any of the methods below:
* ~~~~~
* $pageArray = $pages->newPageArray();
* // the most common way to create a new PageArray and add a $page to it
* $a = new PageArray();
* $a->add($page);
*
* // ProcessWire 3.0.123+ can also create PageArray like this:
* $a = PageArray(); // create blank
* $a = PageArray($page); // create + add one page
* $a = PageArray([ $page1, $page2, $page3 ]); // create + add pages
* ~~~~~
* #pw-body
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
* @method string getMarkup($key = null) Render a simple/default markup value for each item #pw-internal
@@ -36,7 +43,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
/**
* Reference to the selectors that led to this PageArray, if applicable
*
* @var Selectors
* @var Selectors|string
*
*/
protected $selectors = null;
@@ -108,6 +115,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
*/
public function getItemKey($item) {
if(!$item instanceof Page) return null;
if(!$this->duplicateChecking) return parent::getItemKey($item);
// first see if we can determine key from our index
$id = $item->id;
@@ -157,6 +165,23 @@ class PageArray extends PaginatedArray implements WirePaginatable {
public function makeBlankItem() {
return $this->wire('pages')->newPage();
}
/**
* Creates a new blank instance of this PageArray, for internal use.
*
* #pw-internal
*
* @return PageArray
*
*/
public function makeNew() {
$class = get_class($this);
/** @var PageArray $newArray */
$newArray = $this->wire(new $class());
// $newArray->finderOptions($this->finderOptions());
if($this->lazyLoad) $newArray->_lazy(true);
return $newArray;
}
/**
* Import the provided pages into this PageArray.
@@ -419,12 +444,14 @@ class PageArray extends PaginatedArray implements WirePaginatable {
*
* #pw-internal
*
* @param Selectors $selectors
* @param Selectors|string $selectors Option to add as string added in 3.0.142
* @return $this
*
*/
public function setSelectors(Selectors $selectors) {
$this->selectors = $selectors;
public function setSelectors($selectors) {
if(is_string($selectors) || $selectors instanceof Selectors || $selectors === null) {
$this->selectors = $selectors;
}
return $this;
}
@@ -439,11 +466,15 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* echo $products->getSelectors(); // outputs the selector above
* ~~~~~
*
* @return Selectors|null Returns Selectors object if available, or null if not.
* @param bool $getString Specify true to get selector string rather than Selectors object (default=false) added in 3.0.142
* @return Selectors|string|null Returns Selectors object if available, or null if not. Always return string if $getString argument is true.
*
*/
public function getSelectors() {
return $this->selectors;
public function getSelectors($getString = false) {
if($getString) return (string) $this->selectors;
if($this->selectors === null) return null;
if(is_string($this->selectors)) $this->selectors = $this->wire(new Selectors($this->selectors));
return $this->selectors;
}
/**
@@ -452,7 +483,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* This is applicable to and destructive to the WireArray.
*
* @param string|Selectors|array $selectors AttributeSelector string to use as the filter.
* @param bool $not Make this a "not" filter? (default is false)
* @param bool|int $not Make this a "not" filter? Use int 1 for "not all". (default is false)
* @return PageArray|WireArray reference to current [filtered] PageArray
*
*/
@@ -487,6 +518,23 @@ class PageArray extends PaginatedArray implements WirePaginatable {
return parent::not($selector);
}
/**
* Like the base get() method but can only return Page objects (whether Page or NullPage)
*
* @param int|string|array $key Provide any of the following:
* - Key of Page to retrieve.
* - A selector string or selector array, to return the first item that matches the selector.
* - A string containing the "name" property of any Page, and the matching Page will be returned.
* @return Page|NullPage
* @since 3.0.162
* @see WireArray::get()
*
*/
public function getPage($key) {
$value = $this->get($key);
return $value instanceof Page ? $value : $this->wire()->pages->newNullPage();
}
/**
* Find all pages in this PageArray that match the given selector (non-destructive)
*
@@ -496,6 +544,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
*
* @param string $selector AttributeSelector string.
* @return PageArray|WireArray New PageArray instance
* @see WireArray::find()
*
*/
public function find($selector) {
@@ -503,18 +552,86 @@ class PageArray extends PaginatedArray implements WirePaginatable {
}
/**
* Same as find, but returns a single Page rather than PageArray or FALSE if empty.
* Same as find() method, but returns a single Page rather than PageArray or FALSE if empty.
*
* #pw-internal
*
* @param string $selector
* @return Page|bool
* @see WireArray::findOne()
*
*/
public function findOne($selector) {
return parent::findOne($selector);
}
/**
* Same as find() or findOne() methods, but always returns a Page (whether Page or NullPage)
*
* @param string $selector
* @return Page|NullPage
* @since 3.0.162
*
*/
public function findOnePage($selector) {
$value = parent::findOne($selector);
return $value instanceof Page ? $value : $this->wire()->pages->newNullPage();
}
/**
* Get Page from this PageArray having given name, or return NullPage if not present
*
* @param string $name
* @return NullPage|Page
* @since 3.0.162
*
*/
public function getPageByName($name) {
return $this->getPageByProperty('name', $name, true);
}
/**
* Get Page from this PageArray having given ID, or return NullPage if not present
*
* @param int $id
* @return NullPage|Page
* @since 3.0.162
*
*/
public function getPageByID($id) {
$id = (int) $id;
if(isset($this->keyIndex[$id])) {
$k = $this->keyIndex[$id];
if(isset($this->data[$k]) && $this->data[$k]->id === $id) return $this->data[$k];
}
return $this->getPageByProperty('id', (int) $id, true);
}
/**
* Get first found Page object matching property/value, or return NullPage if not present in this PageArray
*
* #pw-internal
*
* @param string $property Name of page property or field
* @param string|mixed $value Value to match
* @param bool $strict Match value with strict type enforcement? (default=false)
* @return Page|NullPage
* @since 3.0.162
*
*/
public function getPageByProperty($property, $value, $strict = false) {
$foundPage = null;
foreach($this->data as $item) {
if($strict) {
if($item->get($property) === $value) $foundPage = $item;
} else {
if($item->get($property) == $value) $foundPage = $item;
}
if($foundPage) break;
}
return $foundPage ? $foundPage : $this->wire()->pages->newNullPage();
}
/**
* Prepare selectors for filtering
*
@@ -637,12 +754,12 @@ class PageArray extends PaginatedArray implements WirePaginatable {
*
* #pw-internal
*
* @param array $options
* @param array|null $options Specify array to set or omit this argument to get
* @return array
*
*/
public function finderOptions(array $options = array()) {
$this->finderOptions = $options;
public function finderOptions($options = null) {
if(is_array($options)) $this->finderOptions = $options;
return $this->finderOptions;
}

View File

@@ -5,7 +5,7 @@
*
* Provides implementation for Page comparison functions.
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
*/
@@ -13,30 +13,174 @@
class PageComparison {
/**
* Does this page have the specified status number or template name?
* Is this page of the given type? (status, template, etc.)
*
* See status flag constants at top of Page class
*
* @param Page $page
* @param int|string|Selectors $status Status number or Template name or selector string/object
* @param int|string|array|Selectors|Page|Template $status One of the following:
* - Status expressed as int (using Page::status* constants)
* - Status expressed as string/name, i.e. "hidden" (3.0.150+)
* - Template name, indicating page type
* - Page or Template object (3.0.150+)
* - Selector string or Selectors object that must match
* - Array of any of the above where all have to match (3.0.150+)
* @return bool
*
*/
public function is(Page $page, $status) {
$is = false;
if(is_string($status) && ctype_digit($status)) {
$status = (int) $status;
}
if(is_int($status)) {
return ((bool) ($page->status & $status));
// status flag integer
$is = $page->status & $status;
} else if(is_object($status)) {
// Page, Template or Selectors object
if($status instanceof Page && $status->id === $page->id) {
$is = true;
} else if($status instanceof Template && "$page->template" === "$status") {
$is = true;
} else if($status instanceof Selectors || $status instanceof Selector) {
$is = $page->matches($status);
}
} else if(is_array($status)) {
// array where all items have to match an is() call
$n = 0;
foreach($status as $val) {
if(is_array($val)) break; // no multi-dimensional
if($this->is($page, $val)) $n++;
}
if($n && count($status) === $n) $is = true;
} else if(is_string($status) && $page->wire('sanitizer')->name($status) == $status) {
// valid template name or status name
if($page->template->name == $status) return true;
} else if(is_string($status) && $page->wire('sanitizer')->name($status) === $status) {
// name string (status name or template name)
$statuses = Page::getStatuses();
if(isset($statuses[$status])) {
// status name
$is = $page->status & $statuses[$status];
} else if("$page->template" === $status) {
// template name
$is = true;
}
} else if(is_string($status) && strpos($status, 'Page::status') === 0) {
// literal constant name in string
$status = __NAMESPACE__ . "\\$status";
$status = constant($status);
$is = $page->status & $status;
} else if($page->matches($status)) {
// Selectors object or selector string
return true;
$is = true;
}
return false;
return $is;
}
/**
* If value is available for $key return or call $yes condition (with optional $no condition)
*
* This merges the capabilities of an if() statement, get() and getMarkup() methods in one,
* plus some useful PW type-specific logic, providing a useful output shortcut.
*
* See phpdoc in `Page::if()` for full details.
*
* @param Page $page
* @param string|bool|int $key Name of field to check, selector string to evaluate, or boolean/int to evalute
* @param string|callable|mixed $yes If value for $key is present, return or call this
* @param string|callable|mixed $no If value for $key is empty, return or call this
* @return mixed|string|bool
* @since 3.0.126
*
*/
public function _if(Page $page, $key, $yes = '', $no = '') {
/** @var Sanitizer $sanitizer */
$sanitizer = $page->wire('sanitizer');
// if only given a key argument, we will be returning a boolean
if($yes === '' && $no === '') list($yes, $no) = array(true, false);
if(is_string($key)) $key = trim($key);
if(is_bool($key) || is_int($key)) {
// boolean or int
$val = $key;
$action = empty($val) ? $no : $yes;
} else if(is_array($key)) {
// PHP array
$val = $key;
$action = count($val) ? $no : $yes;
} else if(ctype_digit(ltrim("$key", '-'))) {
// integer or string value of digits, or Wire instance string value of digits
$val = (int) "$key";
$action = empty($val) ? $no : $yes;
} else if(is_string($key) && wireEmpty($key)) {
// empty string
$val = $key;
$action = $no;
} else if(!ctype_alnum("$key") && Selectors::stringHasOperator($key)) {
// selector string
$val = $page->matches($key) ? 1 : 0;
$action = $val ? $yes : $no;
} else {
// field name or other format string accepted by $page->get()
$val = $page->get($key);
$action = wireEmpty($val) ? $no : $yes;
}
if(is_string($action)) {
// action is a string
$getValue = false;
$tools = $sanitizer->getTextTools();
if(($action === 'value' || $action === 'val') && !$page->template->fieldgroup->hasField($action)) {
// implicit 'value' or 'val' maps back to name specified in $key argument
$getValue = $key;
}
if(empty($action)) {
$result = $action;
} else if($getValue) {
$result = $page->get($getValue);
} else if($tools->hasPlaceholders($action)) {
// action is a getMarkup() string
$keyIsFieldName = $sanitizer->fieldName($key) === $key;
$act = $action;
// if value placeholders present, replace them with field name placeholders
foreach(array('{value}', '{val}') as $tag) {
// string with {val} or {value} has that tag replaced with the {field_name}
if(strpos($action, $tag) === false) continue;
// if val or value is actually the name of a field in the system, then do not override it
if($page->hasField(trim($tag, '{}'))) continue;
$action = str_replace($tag, ($keyIsFieldName ? '{' . $key . '}' : $val), $action);
}
$result = $act === $action || $tools->hasPlaceholders($action) ? $page->getMarkup($action) : $action;
} else if($sanitizer->fieldSubfield($action, -1) === $action && $page->hasField($sanitizer->fieldSubfield($action, 0))) {
// action is another field name that we want to get the value for
$result = $page->get($action);
} else {
// action is just a string to return
$result = $action;
}
} else if(is_callable($action)) {
// action is callable
$result = call_user_func_array($action, array($val, $key, $page));
} else {
// action is a number, array or object
$result = $action;
}
return $result;
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -22,43 +22,87 @@ class PageTraversal {
* @param Page $page
* @param bool|string|int|array $selector
* When not specified, result includes all children without conditions, same as $page->numChildren property.
* When a string or array, a selector is assumed and quantity will be counted based on selector.
* When a string or array, a selector is assumed and quantity will be counted based on selector.
* When boolean true, number includes only visible children (excludes unpublished, hidden, no-access, etc.)
* When boolean false, number includes all children without conditions, including unpublished, hidden, no-access, etc.
* When integer 1 number includes viewable children (as opposed to visible, viewable includes hidden pages + it also includes unpublished pages if user has page-edit permission).
* @param array $options
* - `descendants` (bool): Use descendants rather than direct children
* @return int Number of children
*
*/
public function numChildren(Page $page, $selector = null) {
public function numChildren(Page $page, $selector = null, array $options = array()) {
$descendants = empty($options['descendants']) ? false : true;
$parentType = $descendants ? 'has_parent' : 'parent_id';
if(is_bool($selector)) {
// onlyVisible takes the place of selector
$onlyVisible = $selector;
if(!$onlyVisible) return $page->get('numChildren');
return $page->_pages('count', "parent_id=$page->id");
$numChildren = $page->get('numChildren');
if(!$numChildren) {
return 0;
} else if($onlyVisible) {
return $page->_pages('count', "$parentType=$page->id");
} else if($descendants) {
return $this->numDescendants($page);
} else {
return $numChildren;
}
} else if($selector === 1) {
// viewable pages only
$numChildren = $page->get('numChildren');
if(!$numChildren) return 0;
if($page->wire('user')->isSuperuser()) return $numChildren;
if($page->wire('user')->hasPermission('page-edit')) {
return $page->_pages('count', "parent_id=$page->id, include=unpublished");
if($page->wire('user')->isSuperuser()) {
if($descendants) return $this->numDescendants($page);
return $numChildren;
} else if($page->wire('user')->hasPermission('page-edit')) {
return $page->_pages('count', "$parentType=$page->id, include=unpublished");
} else {
return $page->_pages('count', "$parentType=$page->id, include=hidden");
}
return $page->_pages('count', "parent_id=$page->id, include=hidden");
} else if(empty($selector) || (!is_string($selector) && !is_array($selector))) {
return $page->get('numChildren');
// no selector provided
if($descendants) return $this->numDescendants($page);
return $page->get('numChildren');
} else {
// selector string or array provided
if(is_string($selector)) {
$selector = "parent_id=$page->id, $selector";
$selector = "$parentType=$page->id, $selector";
} else if(is_array($selector)) {
$selector["parent_id"] = $page->id;
$selector[$parentType] = $page->id;
}
return $page->_pages('count', $selector);
}
}
/**
* Return number of descendants, optionally with conditions
*
* Use this over $page->numDescendants property when you want to specify a selector or when you want the result to
* include only visible descendants. See the options for the $selector argument.
*
* @param Page $page
* @param bool|string|int|array $selector
* When not specified, result includes all descendants without conditions, same as $page->numDescendants property.
* When a string or array, a selector is assumed and quantity will be counted based on selector.
* When boolean true, number includes only visible descendants (excludes unpublished, hidden, no-access, etc.)
* When boolean false, number includes all descendants without conditions, including unpublished, hidden, no-access, etc.
* When integer 1 number includes viewable descendants (as opposed to visible, viewable includes hidden pages + it also includes unpublished pages if user has page-edit permission).
* @return int Number of descendants
*
*/
public function numDescendants(Page $page, $selector = null) {
if($selector === null) {
return $page->_pages('count', "has_parent=$page->id, include=all");
} else {
return $this->numChildren($page, $selector, array('descendants' => true));
}
}
/**
* Return this page's children pages, optionally filtered by a selector
*
@@ -116,18 +160,41 @@ class PageTraversal {
* Return this page's parent pages, or the parent pages matching the given selector.
*
* @param Page $page
* @param string|array $selector Optional selector string to filter parents by
* @param string|array|bool $selector Optional selector string to filter parents by or boolean true for reverse order
* @return PageArray
*
*/
public function parents(Page $page, $selector = '') {
$parents = $page->wire('pages')->newPageArray();
$parent = $page->parent();
$method = $selector === true ? 'add' : 'prepend';
while($parent && $parent->id) {
$parents->prepend($parent);
$parents->$method($parent);
$parent = $parent->parent();
}
return strlen($selector) ? $parents->filter($selector) : $parents;
return !is_bool($selector) && strlen($selector) ? $parents->filter($selector) : $parents;
}
/**
* Return number of parents (depth relative to homepage) that this page has, optionally filtered by a selector
*
* For example, homepage has 0 parents and root level pages have 1 parent (which is the homepage), and the
* number increases the deeper the page is in the pages structure.
*
* @param Page $page
* @param string $selector Optional selector to filter by (default='')
* @return int Number of parents
*
*/
public function numParents(Page $page, $selector = '') {
$num = 0;
$parent = $page->parent();
while($parent && $parent->id) {
if($selector !== '' && !$parent->matches($selector)) continue;
$num++;
$parent = $parent->parent();
}
return $num;
}
/**
@@ -216,11 +283,26 @@ class PageTraversal {
return $page->_pages('find', $selector, $options);
}
/**
* Get include mode specified in selector or blank if none
*
* @param string|array|Selectors $selector
* @return string
*
*/
protected function _getIncludeMode($selector) {
if(is_string($selector) && strpos($selector, 'include=') === false) return '';
if(is_array($selector)) return isset($selector['include']) ? $selector['include'] : '';
$selector = $selector instanceof Selectors ? $selector : new Selectors($selector);
$include = $selector->getSelectorByField('include');
return $include ? $include->value() : '';
}
/**
* Builds the PageFinder options for the _next() method
*
* @param Page $page
* @param string|array $selector
* @param string|array|Selectors $selector
* @param array $options
* @return array
*
@@ -232,7 +314,16 @@ class PageTraversal {
'startAfterID' => $options['prev'] ? 0 : $page->id,
'stopBeforeID' => $options['prev'] ? $page->id : 0,
'returnVerbose' => $options['all'] ? false : true,
'alwaysAllowIDs' => array(),
);
if($page->isUnpublished() || $page->isHidden()) {
// allow next() to still move forward even though it is hidden or unpublished
$includeMode = $this->_getIncludeMode($selector);
if(!$includeMode || ($includeMode === 'hidden' && $page->isUnpublished())) {
$fo['alwaysAllowIDs'][] = $page->id;
}
}
if(!$options['until']) return $fo;
@@ -280,7 +371,7 @@ class PageTraversal {
* Provides the core logic for next, prev, nextAll, prevAll, nextUntil, prevUntil
*
* @param Page $page
* @param string|array $selector Optional selector. When specified, will find nearest sibling(s) that match.
* @param string|array|Selectors $selector Optional selector. When specified, will find nearest sibling(s) that match.
* @param array $options Options to modify behavior
* - `prev` (bool): When true, previous siblings will be returned rather than next siblings.
* - `all` (bool): If true, returns all nextAll or prevAll rather than just single sibling (default=false).
@@ -305,12 +396,19 @@ class PageTraversal {
$parent = $page->parent();
if($options['until'] || $options['qty']) $options['all'] = true;
if(!$parent || !$parent->id) return $options['all'] ? $pages->newPageArray() : $pages->newNullPage();
if(!$parent || !$parent->id) {
if($options['qty']) return 0;
return $options['all'] ? $pages->newPageArray() : $pages->newNullPage();
}
if(is_array($selector)) {
$selector['parent_id'] = $parent->id;
} else {
} else if(is_string($selector)) {
$selector = trim("parent_id=$parent->id, $selector", ", ");
} else if($selector instanceof Selectors) {
$selector->add(new SelectorEqual('parent_id', $parent->id));
} else {
throw new WireException('Selector must be string, array or Selectors object');
}
$pageFinder = $pages->getPageFinder();
@@ -332,12 +430,16 @@ class PageTraversal {
} else {
$row = reset($rows);
$result = $pages->getById(array($row['id']), array(
'template' => $page->wire('templates')->get($row['templates_id']),
'parent_id' => $row['parent_id'],
'getOne' => true,
'cache' => $page->loaderCache
));
if($row && !empty($row['id'])) {
$result = $pages->getById(array($row['id']), array(
'template' => $page->wire('templates')->get($row['templates_id']),
'parent_id' => $row['parent_id'],
'getOne' => true,
'cache' => $page->loaderCache
));
} else {
$result = $pages->newNullPage();
}
}
return $result;
@@ -346,12 +448,26 @@ class PageTraversal {
/**
* Return the index/position of the given page relative to its siblings
*
* If given a hidden or unpublished page, that page would not usually be part of the group of siblings.
* As a result, such pages will return what the value would be if they were visible (as of 3.0.121). This
* may overlap with the index of other pages, since indexes are relative to visible pages, unless you
* specify an include mode (see next paragraph).
*
* If you want this method to include hidden/unpublished pages as part of the index numbers, then
* specify boolean true for the $selector argument (which implies "include=all") OR specify a
* selector of "include=hidden", "include=unpublished" or "include=all".
*
* @param Page $page
* @return int|NullPage|Page|PageArray
* @param string|array|bool|Selectors $selector Selector to apply or boolean true for "include=all" (since 3.0.121).
* - Boolean true to include hidden and unpublished pages as part of the index numbers (same as "include=all").
* - An "include=hidden", "include=unpublished" or "include=all" selector to include them in the index numbers.
* - A string selector or selector array to filter the criteria for the returned index number.
* @return int Returns index number (zero-based)
*
*/
public function index(Page $page) {
$index = $this->_next($page, '', array('prev' => true, 'all' => true, 'qty' => true));
public function index(Page $page, $selector = '') {
if($selector === true) $selector = "include=all";
$index = $this->_next($page, $selector, array('prev' => true, 'all' => true, 'qty' => 'index'));
return $index;
}
@@ -359,7 +475,7 @@ class PageTraversal {
* Return the next sibling page
*
* @param Page $page
* @param string $selector Optional selector. When specified, will find nearest next sibling that matches.
* @param string|array|Selectors $selector Optional selector. When specified, will find nearest next sibling that matches.
* @return Page|NullPage Returns the next sibling page, or a NullPage if none found.
*
*/
@@ -371,7 +487,7 @@ class PageTraversal {
* Return the previous sibling page
*
* @param Page $page
* @param string $selector Optional selector. When specified, will find nearest previous sibling that matches.
* @param string|array|Selectors $selector Optional selector. When specified, will find nearest previous sibling that matches.
* @return Page|NullPage Returns the previous sibling page, or a NullPage if none found.
*
*/
@@ -384,9 +500,9 @@ class PageTraversal {
* Return all sibling pages after this one, optionally matching a selector
*
* @param Page $page
* @param string $selector Optional selector. When specified, will filter the found siblings.
* @param string|array|Selectors $selector Optional selector. When specified, will filter the found siblings.
* @param array $options Options to pass to the _next() method
* @return Page|NullPage Returns all matching pages after this one.
* @return PageArray Returns all matching pages after this one.
*
*/
public function nextAll(Page $page, $selector = '', array $options = array()) {
@@ -399,9 +515,9 @@ class PageTraversal {
* Return all sibling pages prior to this one, optionally matching a selector
*
* @param Page $page
* @param string $selector Optional selector. When specified, will filter the found siblings.
* @param string|array|Selectors $selector Optional selector. When specified, will filter the found siblings.
* @param array $options Options to pass to the _next() method
* @return Page|NullPage Returns all matching pages after this one.
* @return PageArray Returns all matching pages after this one.
*
*/
public function prevAll(Page $page, $selector = '', array $options = array()) {
@@ -417,7 +533,7 @@ class PageTraversal {
* Return all sibling pages after this one until matching the one specified
*
* @param Page $page
* @param string|Page|array $selector May either be a selector or Page to stop at. Results will not include this.
* @param string|Page|array|Selectors $selector May either be a selector or Page to stop at. Results will not include this.
* @param string|array $filter Optional selector to filter matched pages by
* @param array $options Options to pass to the _next() method
* @return PageArray
@@ -457,9 +573,11 @@ class PageTraversal {
*
* You can specify an `$options` argument to this method with any of the following:
*
* - `pageNum` (int|string): Specify pagination number, or "+" for next pagination, or "-" for previous pagination.
* - `urlSegmentStr` (string): Specify a URL segment string to append.
* - `urlSegments` (array): Specify array of URL segments to append (may be used instead of urlSegmentStr).
* - `pageNum` (int|string|bool): Specify pagination number, "+" for next pagination, "-" for previous pagination, or true for current.
* - `urlSegmentStr` (string|bool): Specify a URL segment string to append, or true (3.0.155+) for current.
* - `urlSegments` (array|bool): Specify regular array of URL segments to append (may be used instead of urlSegmentStr).
* Specify boolean true for current URL segments (3.0.155+).
* Specify associative array (in 3.0.155+) to make both keys and values part of the URL segment string.
* - `data` (array): Array of key=value variables to form a query string.
* - `http` (bool): Specify true to make URL include scheme and hostname (default=false).
* - `language` (Language): Specify Language object to return URL in that Language.
@@ -510,9 +628,30 @@ class PageTraversal {
$sanitizer = $page->wire('sanitizer');
$language = null;
$url = null;
if($options['urlSegments'] === true || $options['urlSegmentStr'] === true) {
$options['urlSegments'] = $page->wire('input')->urlSegments();
}
if($options['pageNum'] === true) {
$options['pageNum'] = $page->wire('input')->pageNum();
}
if(count($options['urlSegments'])) {
$options['urlSegmentStr'] = implode('/', $options['urlSegments']);
$str = '';
if(is_string($options['urlSegments'][0])) {
// associative array converts to key/value style URL segments
foreach($options['urlSegments'] as $key => $value) {
$str .= "$key/$value/";
if(is_int($key)) $str = '';
if($str === '') break;
}
}
if(strlen($str)) {
$options['urlSegmentStr'] = rtrim($str, '/');
} else {
$options['urlSegmentStr'] = implode('/', $options['urlSegments']);
}
}
if($options['language'] && $page->wire('modules')->isInstalled('LanguageSupportPageNames')) {
@@ -538,7 +677,7 @@ class PageTraversal {
if(is_string($options['urlSegmentStr']) && strlen($options['urlSegmentStr'])) {
$url = rtrim($url, '/') . '/' . $sanitizer->pagePathNameUTF8(trim($options['urlSegmentStr'], '/'));
if($template->slashUrlSegments === '' || $template->slashUrlSegments) $url .= '/';
if($template->slashUrlSegments > -1) $url .= '/';
}
if($options['pageNum']) {
@@ -576,7 +715,263 @@ class PageTraversal {
return $url;
}
/**
* Return all URLs that this page can be accessed from (excluding URL segments and pagination)
*
* This includes the current page URL, any other language URLs (for which page is active), and
* any past (historical) URLs the page was previously available at (which will redirect to it).
*
* - Returned URLs do not include additional URL segments or pagination numbers.
* - Returned URLs are indexed by language name, i.e. “default”, “fr”, “es”, etc.
* - If multi-language URLs not installed, then index is just “default”.
* - Past URLs are indexed by language; then ISO-8601 date, i.e. “default;2016-08-11T07:44:43-04:00”,
* where the date represents the last date that URL was considered current.
* - If PagePathHistory core module is not installed then past/historical URLs are excluded.
* - You can disable past/historical or multi-language URLs by using the $options argument.
*
* @param Page $page
* @param array $options Options to modify default behavior:
* - `http` (bool): Make URLs include current scheme and hostname (default=false).
* - `past` (bool): Include past/historical URLs? (default=true)
* - `languages` (bool): Include other language URLs when supported/available? (default=true).
* - `language` (Language|int|string): Include only URLs for this language (default=null).
* Note: the `languages` option must be true if using the `language` option.
* @return array
*
*/
public function urls(Page $page, $options = array()) {
$defaults = array(
'http' => false,
'past' => true,
'languages' => true,
'language' => null,
);
/** @var Modules $modules */
$modules = $page->wire('modules');
$options = array_merge($defaults, $options);
$languages = $options['languages'] ? $page->wire('languages') : null;
$slashUrls = $page->template->slashUrls;
$httpHostUrl = $options['http'] ? $page->wire('input')->httpHostUrl() : '';
$urls = array();
if($options['language'] && $languages) {
if(!$options['language'] instanceof Page) {
$options['language'] = $languages->get($options['language']);
}
if($options['language'] && $options['language']->id) {
$languages = array($options['language']);
}
}
// include other language URLs
if($languages && $modules->isInstalled('LanguageSupportPageNames')) {
foreach($languages as $language) {
if(!$language->isDefault() && !$page->get("status$language")) continue;
$urls[$language->name] = $page->localUrl($language);
}
} else {
$urls = array('default' => $page->url());
}
// add in historical URLs
if($options['past'] && $modules->isInstalled('PagePathHistory')) {
/** @var PagePathHistory $history */
$history = $modules->get('PagePathHistory');
$rootUrl = $page->wire('config')->urls->root;
$pastPaths = $history->getPathHistory($page, array(
'language' => $options['language'],
'verbose' => true
));
foreach($pastPaths as $pathInfo) {
$key = '';
if(!empty($pathInfo['language'])) {
/** @var Language $language */
$language = $pathInfo['language'];
if($options['languages']) {
$key .= $language->name . ';';
} else {
// they asked to have multi-language excluded
if(!$language->isDefault()) continue;
}
}
$key .= wireDate('c', $pathInfo['date']);
$urls[$key] = $rootUrl . ltrim($pathInfo['path'], '/');
}
}
// update URLs for current expected slash and http settings
foreach($urls as $key => $url) {
if($url !== '/') $url = $slashUrls ? rtrim($url, '/') . '/' : rtrim($url, '/');
if($options['http']) $url = $httpHostUrl . $url;
$urls[$key] = $url;
}
return $urls;
}
/**
* Return pages that are referencing the given one by way of Page references
*
* @param Page $page
* @param string|bool $selector Optional selector to filter results by or boolean true as shortcut for `include=all`.
* @param Field|string $field Limit to follower pages using this field,
* - or specify boolean TRUE to make it return array of PageArrays indexed by field name.
* @param bool $getCount Specify true to return counts rather than PageArray(s)
* @return PageArray|array|int
* @throws WireException Highly unlikely
*
*/
public function references(Page $page, $selector = '', $field = '', $getCount = false) {
$fieldtype = $page->wire('fieldtypes')->get('FieldtypePage');
if(!$fieldtype) throw new WireException('Unable to find FieldtypePage');
if($selector === true) $selector = "include=all";
return $fieldtype->findReferences($page, $selector, $field, $getCount);
}
/**
* Return number of VISIBLE pages that are following (referencing) the given one by way of Page references
*
* Note that this excludes hidden, unpublished and otherwise non-accessible pages (access control).
* If you do not want to exclude these, use the numFollowers() function instead, OR specify "include=all" for
* the $selector argument.
*
* @param Page $page
* @param string $selector Filter count by this selector
* @param string|Field|bool $field Limit count to given Field or specify boolean true to return array of counts.
* @return int|array Returns count, or array of counts (if $field==true)
*
*/
public function hasReferences(Page $page, $selector = '', $field = '') {
return $this->references($page, $selector, $field, true);
}
/**
* Return number of ANY pages that are following (referencing) the given one by way of Page references
*
* @param Page $page
* @param string $selector Filter count by this selector
* @param string|Field|bool $field Limit count to given Field or specify boolean true to return array of counts.
* @return int|array Returns count, or array of counts (if $field==true)
*
*/
public function numReferences(Page $page, $selector = '', $field = '') {
if(stripos($selector, "include=") === false) $selector = rtrim("include=all, $selector", ', ');
return $this->hasReferences($page, $selector, $field);
}
/**
* Return pages that this page is referencing by way of Page reference fields
*
* @param Page $page
* @param bool $field Limit results to requested field, or specify boolean true to return array indexed by field names.
* @param bool $getCount Specify true to return count(s) rather than pages.
* @return PageArray|int|array
*
*/
public function referencing(Page $page, $field = false, $getCount = false) {
$fieldName = '';
$byField = null;
if(is_bool($field) || is_null($field)) {
$byField = $field ? true : false;
} else if(is_string($field)) {
$fieldName = $page->wire('sanitizer')->fieldName($field);
} else if(is_int($field)) {
$field = $page->wire('fields')->get($field);
if($field) $fieldName = $field->name;
} else if($field instanceof Field) {
$fieldName = $field->name;
}
// results
$fieldCounts = array(); // counts indexed by field name (if count mode)
$items = $page->wire('pages')->newPageArray();
$itemsByField = array();
foreach($page->template->fieldgroup as $f) {
if($fieldName && $field->name != $fieldName) continue;
if(!$f->type instanceof FieldtypePage) continue;
if($byField) $itemsByField[$f->name] = $page->wire('pages')->newPageArray();
$value = $page->get($f->name);
if($value instanceof Page && $value->id) {
$items->add($value);
if($byField) $itemsByField[$f->name]->add($value);
$fieldCounts[$f->name] = 1;
} else if($value instanceof PageArray && $value->count()) {
$items->import($value);
if($byField) $itemsByField[$f->name]->import($value);
$fieldCounts[$f->name] = $value->count();
} else {
unset($itemsByField[$f->name]);
}
}
if($getCount) return $byField ? $fieldCounts : $items->count();
if($byField) return $itemsByField;
return $items;
}
/**
* Return number of pages this one is following (referencing) by way of Page references
*
* @param Page $page
* @param bool $field Optionally limit to field, or specify boolean true to return array of counts per field.
* @return int|array
*
*/
public function numReferencing(Page $page, $field = false) {
return $this->referencing($page, $field, true);
}
/**
* Find other pages linking to the given one by way contextual links is textarea/html fields
*
* @param Page $page
* @param string $selector
* @param bool|string|Field $field
* @param array $options
* - `getIDs` (bool): Return array of page IDs rather than Page instances. (default=false)
* - `getCount` (bool): Return a total count (int) of found pages rather than Page instances. (default=false)
* - `confirm` (bool): Confirm that the links are present by looking at the actual page field data. (default=true)
* You can specify false for this option to make it perform faster, but with a potentially less accurate result.
* @return PageArray|array|int
* @throws WireException
*
*/
public function links(Page $page, $selector = '', $field = false, array $options = array()) {
/** @var FieldtypeTextarea $fieldtype */
$fieldtype = $page->wire('fieldtypes')->get('FieldtypeTextarea');
if(!$fieldtype) throw new WireException('Unable to find FieldtypeTextarea');
return $fieldtype->findLinks($page, $selector, $field, $options);
}
/**
* Return total found number of pages linking to this one with no exclusions
*
* @param Page $page
* @param bool $field
* @return int
*
*/
public function numLinks(Page $page, $field = false) {
return $this->links($page, true, $field, array('getCount' => true));
}
/**
* Return total number of pages visible to current user linking to this one
*
* @param Page $page
* @param bool $field
* @return array|int|PageArray
*
*/
public function hasLinks(Page $page, $field = false) {
return $this->links($page, '', $field, array('getCount' => true));
}
/******************************************************************************************************************
* LEGACY METHODS
@@ -621,6 +1016,7 @@ class PageTraversal {
$next = $page;
do {
/** @var Page $next */
$next = $siblings->getNext($next, false);
if(empty($selector) || !$next || $next->matches($selector)) break;
} while($next && $next->id);
@@ -664,6 +1060,7 @@ class PageTraversal {
$prev = $page;
do {
/** @var Page $prev */
$prev = $siblings->getPrev($prev, false);
if(empty($selector) || !$prev || $prev->matches($selector)) break;
} while($prev && $prev->id);
@@ -677,7 +1074,7 @@ class PageTraversal {
* @param Page $page
* @param string|array $selector Optional selector. When specified, will filter the found siblings.
* @param PageArray $siblings Optional siblings to use instead of the default.
* @return Page|NullPage Returns all matching pages after this one.
* @return PageArray Returns all matching pages after this one.
*
*/
public function nextAllSiblings(Page $page, $selector = '', PageArray $siblings = null) {
@@ -711,7 +1108,7 @@ class PageTraversal {
* @param Page $page
* @param string|array $selector Optional selector. When specified, will filter the found siblings.
* @param PageArray $siblings Optional siblings to use instead of the default.
* @return Page|NullPage Returns all matching pages before this one.
* @return PageArray
*
*/
public function prevAllSiblings(Page $page, $selector = '', PageArray $siblings = null) {
@@ -753,8 +1150,7 @@ class PageTraversal {
$siblings->prepend($page);
}
$siblings = $this->nextAll($page, '', $siblings);
$siblings = $this->nextAllSiblings($page, '', $siblings);
$all = $page->wire('pages')->newPageArray();
$stop = false;
@@ -805,8 +1201,7 @@ class PageTraversal {
$siblings->add($page);
}
$siblings = $this->prevAll($page, '', $siblings);
$siblings = $this->prevAllSiblings($page, '', $siblings);
$all = $page->wire('pages')->newPageArray();
$stop = false;

View File

@@ -12,32 +12,43 @@
* Pagefile objects are contained by a `Pagefiles` object.
* #pw-body
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* @property-read string $url URL to the file on the server.
* @property-read string $URL Same as $url property but with browser cache busting query string appended that represents the file's modification time. #pw-group-other
* @property-read string $url URL to the file on the server.
* @property-read string $httpUrl URL to the file on the server including scheme and hostname.
* @property-read string $URL Same as $url property but with browser cache busting query string appended. #pw-group-other
* @property-read string $HTTPURL Same as the cache-busting uppercase “URL” property, but includes scheme and hostname. #pw-group-other
* @property-read string $filename full disk path to the file on the server.
* @property-read string $name Returns the filename without the path, same as the "basename" property.
* @property-read string $hash Get a unique hash (for the page) representing this Pagefile.
* @property-read string $tagsArray Get file tags as an array. #pw-group-tags @since 3.0.17
* @property int $sort Sort order in database. #pw-group-other
* @property-read array $tagsArray Get file tags as an array. #pw-group-tags @since 3.0.17
* @property-read array $fieldValues Custom field values. #pw-internal @since 3.0.142
* @property string $basename Returns the filename without the path.
* @property string $description Value of the files description field (string), if enabled. Note you can also set this property directly.
* @property string $tags Value of the files tags field (string), if enabled. #pw-group-tags
* @property string $ext Files extension (i.e. last 3 or so characters)
* @property int $filesize File size (number of bytes).
* @property-read int $filesize File size (number of bytes).
* @property int $modified Unix timestamp of when Pagefile (file, description or tags) was last modified. #pw-group-date-time
* @property int $mtime Unix timestamp of when file (only) was last modified. #pw-group-date-time
* @property-read string $modifiedStr Readable date/time string of when Pagefile was last modified. #pw-group-date-time
* @property-read int $mtime Unix timestamp of when file (only) was last modified. #pw-group-date-time
* @property-read string $mtimeStr Readable date/time string when file (only) was last modified. #pw-group-date-time
* @property int $created Unix timestamp of when file was created. #pw-group-date-time
* @property-read string $createdStr Readable date/time string of when Pagefile was created #pw-group-date-time
* @property string $filesizeStr File size as a formatted string, i.e. “123 Kb”.
* @property Pagefiles $pagefiles The Pagefiles WireArray that contains this file. #pw-group-other
* @property Page $page The Page object that this file is part of. #pw-group-other
* @property Field $field The Field object that this file is part of. #pw-group-other
* @property array $filedata
* @property int $created_users_id ID of user that added/uploaded the file or 0 if not known (3.0.154+). #pw-group-other
* @property int $modified_users_id ID of user that last modified the file or 0 if not known (3.0.154+). #pw-group-other
* @property User|NullPage $createdUser User that added/uploaded the file or NullPage if not known (3.0.154)+. #pw-group-other
* @property User|NullPage $modifiedUser User that last modified the file or NullPage if not known (3.0.154)+. #pw-group-other
*
* @method void install($filename)
* @method string httpUrl()
*
* @method string noCacheURL($http = false)
*
*/
class Pagefile extends WireData {
@@ -56,6 +67,12 @@ class Pagefile extends WireData {
*/
protected $pagefiles;
/**
* @var PagefileExtra[]
*
*/
protected $extras = array();
/**
* Extra file data
*
@@ -64,6 +81,41 @@ class Pagefile extends WireData {
*/
protected $filedata = array();
/**
* Custom field values indexed by field name, loaded on request
*
* Values here have been run through wakeupValue and sanitizeValue already.
* Prior to that they are stored in $filedata (above).
*
* @var array
*
*/
protected $fieldValues = array();
/**
* Created user (populated only on rquest)
*
* @var User|null
*
*/
protected $_createdUser = null;
/**
* Modifed user (populated only on request)
*
* @var User|null
*
*/
protected $_modifiedUser = null;
/**
* Is this a brand new Pagefile rather than one loaded from DB?
*
* @var bool
*
*/
protected $_isNew = true;
/**
* Construct a new Pagefile
*
@@ -84,12 +136,31 @@ class Pagefile extends WireData {
$this->set('tags', '');
$this->set('formatted', false); // has an output formatter been run on this Pagefile?
$this->set('modified', 0);
$this->set('created', 0);
$this->set('created', 0);
$this->set('filesize', 0);
$this->set('created_users_id', 0);
$this->set('modified_users_id', 0);
}
/**
* Clone Pagefile
*
* #pw-internal
*
*/
public function __clone() {
$this->extras = array();
$this->set('filesize', 0);
$this->set('created_users_id', 0);
$this->set('modified_users_id', 0);
$this->_createdUser = null;
$this->_modifiedUser = null;
$this->isNew(true);
parent::__clone();
}
/**
* Set the filename associated with this Pagefile.
* Set the filename associated with this Pagefile
*
* No need to call this as it's already called from the constructor.
* This exists so that Pagefile/Pageimage descendents can create cloned variations, if applicable.
@@ -103,7 +174,10 @@ class Pagefile extends WireData {
$basename = basename($filename);
if(DIRECTORY_SEPARATOR != '/') $filename = str_replace('\\' . $basename, '/' . $basename, $filename); // To correct issue with XAMPP in Windows
if(DIRECTORY_SEPARATOR != '/') {
// To correct issue with XAMPP in Windows
$filename = str_replace('\\' . $basename, '/' . $basename, $filename);
}
if($basename != $filename && strpos($filename, $this->pagefiles->path()) !== 0) {
$this->install($filename);
@@ -164,6 +238,7 @@ class Pagefile extends WireData {
$this->wire('files')->chmod($destination);
$this->changed('file');
$this->isNew(true);
parent::set('basename', $basename);
}
@@ -181,20 +256,32 @@ class Pagefile extends WireData {
*/
public function set($key, $value) {
if($key == 'basename') {
if($key === 'basename') {
$value = $this->pagefiles->cleanBasename($value, false);
} else if($key == 'description') {
} else if($key === 'description') {
return $this->setDescription($value);
} else if($key == 'modified') {
} else if($key === 'modified') {
$value = ctype_digit("$value") ? (int) $value : strtotime($value);
} else if($key == 'created') {
} else if($key === 'created') {
$value = ctype_digit("$value") ? (int) $value : strtotime($value);
} else if($key == 'tags') {
} else if($key === 'created_users_id' || $key === 'createdUser') {
$this->setUser($value, 'created');
return $this;
} else if($key === 'modified_users_id' || $key === 'modifiedUser') {
$this->setUser($value, 'modified');
return $this;
} else if($key === 'tags') {
$this->tags($value);
return $this;
} else if($key == 'filedata') {
} else if($key === 'filedata') {
if(is_array($value)) $this->filedata($value);
return $this;
} else if($key === 'filesize') {
$value = (int) $value;
if(empty($this->data['filesize'])) {
$this->data['filesize'] = $value;
return $this;
}
}
if(strpos($key, 'description') === 0 && preg_match('/^description(\d+)$/', $value, $matches)) {
@@ -204,17 +291,76 @@ class Pagefile extends WireData {
$language = $languages->get((int) $matches[1]);
if($language && $language->id) return $this->setDescription($value, $language);
}
} else if($this->setFieldValue($key, $value)) {
return $this;
}
return parent::set($key, $value);
}
/**
* Set user that created or modified this file
*
* #pw-internal
*
* @param User|int|string|true $user Specify user object, name, ID, or boolean true for current user
* @param $type 'created' or 'modified'
* @since 3.0.154
*
*/
protected function setUser($user, $type) {
$id = 0;
if($user === true) $user = $this->wire('user');
if(is_object($user)) {
if($user instanceof NullPage) {
$id = 0;
} else if($user instanceof User) {
$id = $user->isGuest() ? 0 : $user->id;
}
} else if(is_int($user)) {
$id = $user;
} else if(ctype_digit($user)) {
$id = (int) $user;
} else if(is_string($user)) {
$name = $this->wire('sanitizer')->pageName($user);
$user = $name ? $this->wire('users')->get("name=$name") : null;
$id = $user && $user->id ? $user->id : 0;
}
if($id < 0) $id = 0;
if(strpos($type, 'created') === 0) {
$this->_createdUser = ($id && $user instanceof User ? $user : null);
parent::set('created_users_id', $id);
} else if(strpos($type, 'modified') === 0) {
$this->_modifiedUser = ($id && $user instanceof User ? $user : null);
parent::set('modified_users_id', $id);
}
}
/**
* Get created/modified user
*
* #pw-internal
*
* @param string $type One of 'created' or 'modified'
* @return User|NullPage
* @since 3.0.154
*
*/
protected function getUser($type) {
$type = strpos($type, 'created') === 0 ? 'created' : 'modified';
$key = $type === 'created' ? '_createdUser' : '_modifiedUser';
if(!$this->$key) {
$id = (int) parent::get($type . '_users_id');
$this->$key = $id ? $this->wire('users')->get($id) : new NullPage();
}
return $this->$key;
}
/**
* Get or set filedata
*
* Filedata is any additional data that you want to store with the files database record.
*
*
* - To get a value, specify just the $key argument. Null is returned if request value is not present.
* - To get all values, omit all arguments. An associative array will be returned.
* - To set a value, specify the $key and the $value to set.
@@ -222,7 +368,7 @@ class Pagefile extends WireData {
* - To unset, specify boolean false (or null) for $key, and the name of the property to unset as $value.
* - To unset, you can also get all values, unset it from the retuned array, and set the array back.
*
* #pw-internal
* #pw-group-manipulation
*
* @param string|array|false|null $key Specify array to set all file data, or key (string) to set or get a property,
* Or specify boolean false to remove key specified by $value argument.
@@ -382,7 +528,7 @@ class Pagefile extends WireData {
* - To SET in all languages as a JSON string: Specify boolean true, plus the JSON string $value as the 2nd argument (internal use only).
* - To SET in all languages as an array: Specify the array here, indexed by language ID or name, and omit 2nd argument.
* @param null|string $value Specify only when you are setting (single language) rather than getting a value.
* @return string
* @return string|array
*
*/
public function description($language = null, $value = null) {
@@ -499,6 +645,9 @@ class Pagefile extends WireData {
// nocache url
$value = $this->noCacheURL();
break;
case 'HTTPURL':
$value = $this->noCacheURL(true);
break;
case 'pagefiles':
$value = $this->pagefiles;
break;
@@ -512,32 +661,160 @@ class Pagefile extends WireData {
case 'created':
$value = parent::get($key);
if(empty($value)) {
$value = filemtime($this->filename());
$value = $this->filemtime();
parent::set($key, $value);
}
break;
case 'modifiedStr':
case 'createdStr':
$value = parent::get(str_replace('Str', '', $key));
$value = wireDate($this->wire('config')->dateFormat, $value);
break;
case 'created_users_id':
case 'modified_users_id':
$value = (int) parent::get($key);
break;
case 'createdUser':
case 'modifiedUser':
$value = $this->getUser($key);
break;
case 'fileData':
case 'filedata':
$value = $this->filedata();
break;
case 'mtime':
$value = filemtime($this->filename());
case 'filemtime':
$value = $this->filemtime();
break;
case 'mtimeStr':
case 'filemtimeStr':
$value = wireDate($this->wire('config')->dateFormat, $this->filemtime());
break;
case 'fieldValues':
return $this->fieldValues;
break;
default:
$value = $this->getFieldValue($key);
}
if(is_null($value)) return parent::get($key);
return $value;
}
/**
* Get a custom field value
*
* #pw-internal Most non-core cases should just use get() or direct access rather than this method
*
* @param string $name
* @param bool|null Get as formatted value? true=yes, false=no, null=use page output formatting setting (default=null)
* @return mixed|null Returns value or null if not present
* @since 3.0.142
*
*/
public function getFieldValue($name, $formatted = null) {
$field = $this->wire('fields')->get($name);
if(!$field) return null;
$template = $this->pagefiles->getFieldsTemplate();
if(!$template) return null;
$fieldgroup = $template->fieldgroup;
if(!$fieldgroup->hasField($field)) return null;
$field = $fieldgroup->getFieldContext($field); // get in context
$fieldtype = $field->type; /** @var Fieldtype $fieldtype */
$fileField = $this->pagefiles->getField(); /** @var Field $fileField */
$fileFieldtype = $fileField->type; /** @var FieldtypeFile|FieldtypeImage $fileFieldtype */
$page = $fileFieldtype->getFieldsPage($fileField);
if(array_key_exists($name, $this->fieldValues)) {
$value = $this->fieldValues[$name];
} else {
$idKey = "_$field->id";
$value = $this->filedata($idKey);
if($value !== null) {
$value = $fieldtype->wakeupValue($page, $field, $value);
$value = $fieldtype->sanitizeValue($page, $field, $value);
}
$this->fieldValues[$name] = $value;
unset($this->filedata[$idKey]); // avoid storing double copies
}
if($value === null) {
$value = $fieldtype->getBlankValue($page, $field);
$value = $fieldtype->sanitizeValue($page, $field, $value);
$this->fieldValues[$name] = $value;
}
if($formatted === null) $formatted = $this->page->of();
if($formatted) $value = $fieldtype->formatValue($page, $field, $value);
return $value;
}
/**
* Set a custom field value
*
* #pw-internal Most non-core cases should use set() instead
*
* @param string $name
* @param mixed $value
* @param bool|null $changed Specify true to force track change, false to force no change, or null to auto-detect (default=null)
* @return bool Returns true if value set, or false if not (like if theres no template defined for the purpose)
* @since 3.0.142
*
*
*/
public function setFieldValue($name, $value, $changed = null) {
$template = $this->pagefiles->getFieldsTemplate();
if(!$template) return false;
$fieldgroup = $template->fieldgroup;
if(!$fieldgroup) return false;
$field = $fieldgroup->getFieldContext($name);
if(!$field) return false;
$page = $this->pagefiles->getFieldsPage();
/** @var Fieldtype $fieldtype */
$fieldtype = $field->type;
$value = $fieldtype->sanitizeValue($page, $field, $value);
if($changed === null && $this->page->trackChanges()) {
// detect if a change has taken place
$oldValue = $this->getFieldValue($field->name, false);
if(is_object($oldValue) && $oldValue instanceof Wire && $oldValue === $value) {
// $oldValue and new $value are the same object instance, so ask it if anything has changed
$changed = $oldValue->isChanged();
if($changed) $this->trackChange($field->name);
} else if($oldValue != $value) {
// $oldValue and new $value differ, record change
$this->trackChange($field->name, $oldValue, $value);
}
} else if($changed === true) {
$this->trackChange($field->name);
}
$this->fieldValues[$field->name] = $value;
return true;
}
/**
* Hookable no-cache URL
*
* #pw-internal
*
* @param bool $http Include scheme and hostname?
* @return string
*
*/
protected function ___noCacheURL() {
return $this->url() . '?nc=' . @filemtime($this->filename());
public function ___noCacheURL($http = false) {
return ($http ? $this->httpUrl() : $this->url()) . '?nc=' . $this->filemtime();
}
/**
@@ -545,7 +822,7 @@ class Pagefile extends WireData {
*
* #pw-group-traversal
*
* @return Pagefile|null
* @return Pagefile|Wire|null
*
*/
public function getNext() {
@@ -557,7 +834,7 @@ class Pagefile extends WireData {
*
* #pw-group-traversal
*
* @return Pagefile|null
* @return Pagefile|Wire|null
*
*/
public function getPrev() {
@@ -868,14 +1145,30 @@ class Pagefile extends WireData {
return parent::get('formatted') ? true : false;
}
/**
* Get last modified time of file
*
* @param bool $reset
* @return int Unix timestamp
* @since 3.0.154
*
*/
public function filemtime($reset = false) {
if($reset) {} // @todo
return (int) @filemtime($this->filename());
}
/**
* Returns the filesize in number of bytes.
*
* @param bool $reset
* @return int
*
*/
public function filesize() {
return @filesize($this->filename());
public function filesize($reset = false) {
if($reset) {} // @todo
$filesize = (int) @filesize($this->filename());
return $filesize;
}
/**
@@ -932,8 +1225,13 @@ class Pagefile extends WireData {
*
*/
public function unlink() {
if(!strlen($this->basename) || !is_file($this->filename)) return true;
return unlink($this->filename);
/** @var WireFileTools $files */
if(!strlen($this->basename) || !is_file($this->filename)) return true;
$files = $this->wire('files');
foreach($this->extras() as $extra) {
$extra->unlink();
}
return $files->unlink($this->filename, true);
}
/**
@@ -948,10 +1246,17 @@ class Pagefile extends WireData {
*
*/
public function rename($basename) {
foreach($this->extras() as $extra) {
$extra->filename(); // init
}
$basename = $this->pagefiles->cleanBasename($basename, true);
if(rename($this->filename, $this->pagefiles->path . $basename)) {
if($this->wire('files')->rename($this->filename, $this->pagefiles->path . $basename, true)) {
$this->set('basename', $basename);
return $this->basename();
$basename = $this->basename();
foreach($this->extras() as $extra) {
$extra->rename();
}
return $basename;
}
return false;
}
@@ -966,8 +1271,13 @@ class Pagefile extends WireData {
*
*/
public function copyToPath($path) {
$result = copy($this->filename, $path . $this->basename());
if($this->config->chmodFile) chmod($path . $this->basename(), octdec($this->config->chmodFile));
/** @var WireFileTools $files */
$files = $this->wire('files');
$result = $files->copy($this->filename(), $path);
foreach($this->extras() as $extra) {
if(!$extra->exists()) continue;
$files->copy($extra->filename, $path);
}
return $result;
}
@@ -984,7 +1294,8 @@ class Pagefile extends WireData {
*
*/
public function ___changed($what, $old = null, $new = null) {
if(in_array($what, array('description', 'tags', 'file'))) {
if(in_array($what, array('description', 'tags', 'file', 'filedata'))) {
$this->setUser(true, 'modified');
$this->set('modified', time());
$this->pagefiles->trackChange('item');
}
@@ -1019,5 +1330,137 @@ class Pagefile extends WireData {
public function isTemp($set = null) {
return $this->pagefiles->isTemp($this, $set);
}
/**
* Get or set “new” status of the Pagefile
*
* This is true with a Pagefile that was created during this request and not loaded from DB.
*
* @param bool|null $set
* @return bool
*
*/
public function isNew($set = null) {
if(is_bool($set)) $this->_isNew = $set;
return $this->_isNew;
}
/**
* Get all extras, add an extra, or get an extra
*
* #pw-internal
*
* @param string $name
* @param PagefileExtra $value
* @return PagefileExtra[]|PagefileExtra|null
* @since 3.0.132
*
*/
public function extras($name = null, PagefileExtra $value = null) {
if($name === null) return $this->extras;
if($value !== null && $value instanceof PagefileExtra) {
$this->extras[$name] = $value;
}
return isset($this->extras[$name]) ? $this->extras[$name] : null;
}
/**
* Save this Pagefile independently of the Page it lives on
*
* @return bool
* @throws WireException
* @since 3.0.154
*
*/
public function save() {
/** @var FieldtypeFile $fieldtype */
$fieldtype = $this->field->type;
return $fieldtype->saveFile($this->page, $this->field, $this);
}
/**
* Replace file with another
*
* Should be followed up with a save() to ensure related properties are also committed to DB.
*
* #pw-internal
*
* @param string $filename File to replace current one with
* @param bool $move Move given $filename rather than copy? (default=true)
* @return bool
* @throws WireException
* @since 3.0.154
*
*/
public function replaceFile($filename, $move = true) {
/** @var WireFileTools $files */
$files = $this->wire('files');
if(!is_file($filename) || !is_readable($filename)) return false;
if($move && !is_writable($filename)) $move = false;
$srcFile = $filename;
$dstFile = $this->filename();
$tmpFile = dirname($dstFile) . '/.' . basename($dstFile) . '.tmp';
if(file_exists($tmpFile)) $files->unlink($tmpFile);
$files->rename($dstFile, $tmpFile);
if($move) {
$result = $files->rename($srcFile, $dstFile);
} else {
$result = $files->copy($srcFile, $dstFile);
}
if(!$result) {
$files->rename($tmpFile, $dstFile);
return false;
}
$files->unlink($tmpFile);
$this->filesize(true);
$this->filemtime(true);
return true;
}
/**
* Ensures that isset() and empty() work for dynamic class properties
*
* @param string $key
* @return bool
*
*/
public function __isset($key) {
if(parent::__isset($key)) return true;
return $this->get($key) !== null;
}
/**
* Debug info
*
* @return array
*
*/
public function __debugInfo() {
$filedata = $this->filedata();
if(empty($filedata)) $filedata = null;
$info = array(
'url' => $this->url(),
'filename' => $this->filename(),
'filesize' => $this->filesize(),
'description' => $this->description,
'tags' => $this->tags,
'created' => $this->createdStr,
'modified' => $this->modifiedStr,
'created_users_id' => $this->created_users_id,
'modified_users_id' => $this->modified_users_id,
'filemtime' => $this->mtimeStr,
'filedata' => $filedata,
);
if(empty($info['filedata'])) unset($info['filedata']);
return $info;
}
}

297
wire/core/PagefileExtra.php Normal file
View File

@@ -0,0 +1,297 @@
<?php namespace ProcessWire;
/**
* Extra extension for Pagefile or Pageimage objects
*
* Properties
* ==========
* @property string $url Local URL/path to file
* @property string $httpUrl Full HTTP URL with scheme and host
* @property string $URL No-cache version of url
* @property string $HTTPURL No-cache version of httpUrl
* @property string $filename Full disk path/file
* @property string $pathname Alias of filename
* @property string $basename Just the basename without path
* @property string $extension File extension
* @property string $ext Alias of extension
* @property bool $exists Does the file exist?
* @property int $filesize Size of file in bytes
* @property string $filesizeStr Human readable size of file
* @property Pagefile|Pageimage $pagefile Source Pageimage object
* @property int $savings Bytes saved by this extra
* @property string $savingsStr Human readable savings by this extra
* @property string $savingsPct Percent savings by this extra
*
* The following properties affect the behavior of the URL-related methods
* =======================================================================
* @property bool $useSrcUrlOnFail Use source Pagefile URL if extra image does not exist and cannot be created? (default=false)
* @property bool $useSrcUrlOnSize Use source Pagefile URL if extra file is larger than source file? (default=false)
* @property bool $useSrcExt Use longer filenames that also include the Pagefiles extension? (default=false)
*
* Hookable methods
* ================
* @method bool create()
*
*/
class PagefileExtra extends WireData {
/**
* @var Pagefile|Pageimage
*
*/
protected $pagefile;
/**
* @var string
*
*/
protected $extension = '';
/**
* Previous filename, if it changed
*
* @var string
*
*/
protected $filenamePrevious = '';
/**
* Construct
*
* @param Pagefile|Pageimage $pagefile
* @param $extension
*
*/
public function __construct(Pagefile $pagefile, $extension) {
$pagefile->wire($this);
$this->setPagefile($pagefile);
$this->setExtension($extension);
$this->useSrcUrlOnFail = true;
$this->useSrcUrlOnSize = false;
$this->useSrcExt = false;
return parent::__construct();
}
/**
* Set Pagefile instance this extra is connected to
*
* @param Pagefile $pagefile
*
*/
public function setPagefile(Pagefile $pagefile) {
$this->pagefile = $pagefile;
}
/**
* Set extension for this extra
*
* @param $extension
*
*/
public function setExtension($extension) {
$this->extension = $extension;
}
/**
* Does the extra file currently exist?
*
* @param bool $clear Clear stat cache before checking? (default=false)
* @return bool
*
*/
public function exists($clear = false) {
if($clear) clearstatcache();
return is_readable($this->filename());
}
/**
* Return the file size in bytes
*
* @return int
*
*/
public function filesize() {
return (int) @filesize($this->filename());
}
/**
* Return human readable file size string
*
* @return string
*
*/
public function filesizeStr() {
return wireBytesStr($this->filesize());
}
/**
* Return the full server disk path to the extra file, whether it exists or not
*
* @return string
*
*/
public function filename() {
$pathinfo = pathinfo($this->pagefile->filename());
$ext = '.' . $this->extension;
if($this->useSrcExt) $ext = '.' . $pathinfo['extension'] . $ext;
$filename = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $ext;
if(empty($this->filenamePrevious)) $this->filenamePrevious = $filename;
return $filename;
}
/**
* Return just the basename (no path)
*
* @return string
*
*/
public function basename() {
return basename($this->filename());
}
/**
* Return the URL to the extra file, creating it if it does not already exist
*
* @param bool $fallback Allow falling back to source Pagefile URL when appropriate?
* @return string
*
*/
public function url($fallback = true) {
if(!$this->exists()) {
$this->create();
if($fallback && !$this->exists() && $this->useSrcUrlOnFail) {
// return original pagefile URL if the extra cannot be created
return $this->pagefile->url();
}
}
if($fallback && $this->useSrcUrlOnSize && $this->filesize() > $this->pagefile->filesize()) {
$url = $this->pagefile->url();
} else {
$pathinfo = pathinfo($this->pagefile->url());
$ext = '.' . $this->extension;
if($this->useSrcExt) $ext = '.' . $pathinfo['extension'] . $ext;
$url = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $ext;
}
return $url;
}
/**
* Return the HTTP URL to the extra file
*
* @return string
*
*/
public function httpUrl() {
return str_replace($this->pagefile->url(), $this->url(), $this->pagefile->httpUrl());
}
/**
* Unlink/delete the extra file
*
* @return bool
*
*/
public function unlink() {
if(!$this->exists()) return false;
return $this->wire('files')->unlink($this->filename());
}
/**
* Rename the extra file to be consistent with Pagefile name
*
* @return bool
*
*/
public function rename() {
if(!$this->exists()) return false;
if(!$this->filenamePrevious) return false;
return $this->wire('files')->rename($this->filenamePrevious, $this->filename());
}
/**
* Create the extra file
*
* Must be implemented by a hook or by descending class
*
* @return bool Returns true on success, false on fail
*
*/
public function ___create() {
return false;
}
/**
* Get property
*
* @param string $key
* @return bool|int|mixed|null|string
*
*/
public function get($key) {
switch($key) {
case 'exists':
$value = $this->exists();
break;
case 'filesize':
$value = $this->filesize();
break;
case 'filesizeStr':
$value = $this->filesizeStr();
break;
case 'savings':
$value = $this->pagefile->filesize() - $this->filesize();
break;
case 'savingsStr':
$value = wireBytesStr($this->pagefile->filesize() - $this->filesize());
break;
case 'savingsPct':
$imageSize = $this->pagefile->filesize();
$extraSize = $this->filesize();
$value = round((($imageSize - $extraSize) / $imageSize) * 100) . '%';
break;
case 'url':
$value = $this->url();
break;
case 'httpUrl':
case 'httpURL':
$value = $this->httpUrl();
break;
case 'filename':
case 'pathname':
$value = $this->filename();
break;
case 'filenamePrevious':
$value = $this->filenamePrevious && $this->filenamePrevious != $this->filename() ? $this->filenamePrevious : '';
break;
case 'basename':
$value = $this->basename();
break;
case 'ext':
case 'extension':
$value = $this->extension;
break;
case 'URL':
case 'HTTPURL':
$value = str_replace($this->pagefile->url(), $this->url(), $this->pagefile->$key);
break;
case 'pagefile':
$value = $this->pagefile;
break;
default:
$value = parent::get($key);
if($value === null) $value = $this->pagefile->get($key);
}
return $value;
}
/**
* @return string
*
*/
public function __toString() {
return $this->basename();
}
}

Some files were not shown because too many files have changed in this diff Show More