1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-26 16:14:35 +02:00

769 Commits

Author SHA1 Message Date
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
Ryan Cramer
941ef5cb67 Update to 2018 2018-03-16 10:44:38 -04:00
Ryan Cramer
48fe0769a4 Remove console.log from ProcessProfile.js 2018-03-16 10:35:56 -04:00
Ryan Cramer
4551748659 Bump version to 3.0.96 2018-03-16 10:30:08 -04:00
Ryan Cramer
f52c3f54c9 Fix issue processwire/processwire-issues#322 where certain scenario could allow collision of language-specific page names 2018-03-16 10:24:46 -04:00
Ryan Cramer
6c4f4103d2 Fix issue processwire/processwire-issues#542 in InputfieldRepeater.module repeater item titles, where numbered HTML entities (like the one for apostrophe) were getting their "#" character removed, making it look like double encoding 2018-03-16 07:40:36 -04:00
Ryan Cramer
19a55c6d37 Enhancement for issue processwire/processwire-issues#539 show "Edit Profile" button after login when user has profile-edit permission but not page-edit permission 2018-03-15 11:02:37 -04:00
Ryan Cramer
ba21b28b4e Attempt fix for issue processwire/processwire-issues#537 update code that corrects Firefox issue where it populates autocomplete password field even when autocomplete is disabled (Firefox ignores disabled autocomplete here for some reason) 2018-03-15 09:39:03 -04:00
Ryan Cramer
bd72c59e41 Fix issue processwire/processwire-issues#535 missing null check for return value from getInputfield() in ProcessField 2018-03-15 08:55:27 -04:00
Ryan Cramer
eb95498183 Fix issue processwire/processwire-issues#534 PageList narrow mode didn't show 'New' action for home, or 'Empty' action for trash 2018-03-15 08:48:21 -04:00
Ryan Cramer
d9b30167c7 Typo fixes per processwire/processwire-issues#533 2018-03-14 11:50:50 -04:00
Ryan Cramer
bbfb4d78d2 Update for additional suggestions in processwire/processwire-issues#518 2018-03-14 10:37:24 -04:00
Ryan Cramer
27b6141a1b Fix issue processwire/processwire-issues#531 repeater within a fieldset depth changes unexpectedly when repeater item dragged up/down. 2018-03-14 10:02:15 -04:00
Ryan Cramer
1045acc057 Make PageTable buttons have better margins per processwire/processwire-issues#521 2018-03-14 08:43:56 -04:00
Ryan Cramer
11398f00bf Update for AdminThemeUikit main.js per processwire/processwire-issues#480 2018-03-14 08:13:46 -04:00
Ryan Cramer
c1ca1fde3b Fix minor JS issue where ajax-loaded inputfields didn't have the toggle arrow icon properly adjusting for open/closed state 2018-03-13 09:40:04 -04:00
Ryan Cramer
0a98458d5b Fix issue where ajax-loaded Inputfield render() didn't have before(render) hooks called when rendering the non-ajax placeholder, which was identified in processwire/processwire-issues#480 2018-03-13 09:38:32 -04:00
Ryan Cramer
3a76db94f9 Some minor optimizations in WireHooks class, plus enable before/after option for $type argument in runHooks method that was mentioned in the phpdoc but wasn't fully supported yet 2018-03-13 09:37:05 -04:00
Ryan Cramer
d82d011eca Change ksort to sort in minColumnWidth for AdminThemeUikit 2018-03-12 13:33:56 -04:00
Ryan Cramer
d99c5ec731 Fix processwire/processwire-issues#526 where part of longclick for LanguageTabs was still active (was converted to double click awhile back) 2018-03-12 10:37:29 -04:00
Ryan Cramer
ceff2e47a8 Fix issue processwire/processwire-issues#523 prevent user from uninstalling admin themes defined by $config->defaultAdminTheme 2018-03-12 10:16:11 -04:00
Ryan Cramer
afe41773d8 Fix issue processwire/processwire-issues#519 where front-edit editor w/CKEditor inline image action was not working 2018-03-12 09:45:03 -04:00
Ryan Cramer
20f02cddf9 Some AdminThemeUikit updates, mostly related to processwire/processwire-issues#480 2018-03-12 09:02:00 -04:00
Ryan Cramer
0c760171bd Bump version to 3.0.95 2018-03-09 12:26:29 -05:00
Ryan Cramer
36bceb6ed2 Fix issue in Selectize.js processwire/processwire-issues#520 where Selectize.js had bottleneck in measureString() function that caused a slowdown because of DOM manipulation (was noticed when Selectize paired with Uikit 3) 2018-03-09 09:17:04 -05:00
Ryan Cramer
5da4e17a27 Update for processwire/processwire-issues#517 and ryancramerdesign/AdminThemeUikit#46 correct issue where InputfieldImage defined buttonClass wasn't getting used in repeaters, plus update the Uikit custom button class to not be applied when InputfieldImage::renderButtons() is hooked, in order to avoid two different button styes appearing when other modules add buttons to it. 2018-03-08 10:56:32 -05:00
Ryan Cramer
bd3be5e315 Fix issue processwire/processwire-issues#518 where multi-instance + multi-site had boot error in Windows 2018-03-08 08:58:15 -05:00
Ryan Cramer
786a4b8309 Update InputfieldAsmSelect to use font-awesome based edit icon rather than a jQuery UI one 2018-03-07 13:07:38 -05:00
Ryan Cramer
3f62968392 Resolve various minor AdminThemeUikit issues and update Uikit 3 version to beta 40 2018-03-07 13:06:08 -05:00
Ryan Cramer
787c589151 Fix issue processwire/processwire-issues#516 where the WireDateTime conversion table from date to strftime incorrectly had '$' rather than '%' for some of the conversions. 2018-03-07 08:26:43 -05:00
Ryan Cramer
b1454b31a4 Add ProcessField detection for invalid Page field showIf/requiredIf dependencies per processwire/processwire-issues#509 2018-03-07 08:11:42 -05:00
Ryan Cramer
a166c22e71 Update for processwire/processwire-issues#480 2018-03-07 07:22:44 -05:00
Ryan Cramer
16f614f5a0 Fix issue processwire/processwire-issues#513 datepicker z-index when near image field 2018-03-06 11:04:15 -05:00
Ryan Cramer
0aefc2baa3 Fix issue processwire/processwire-issues#512 correct unnecessary error message when performing file field schema update 2018-03-06 10:53:54 -05:00
Ryan Cramer
aada758d14 Update AdminThemeUikit JS for Inputfield widths show/hide calculations per processwire/processwire-issues#480 2018-03-06 09:01:56 -05:00
Ryan Cramer
e20562b5c1 Add @horst-n improvements to ImageSizerEngine and InputfieldImage focus+zoom features, plus add some other adjustments and improvements in InputfieldImage.js 2018-03-05 08:45:39 -05:00
Ryan Cramer
a9ec5a640a Bump version to 3.0.94 2018-03-02 12:01:16 -05:00
Ryan Cramer
76f3dac6fd Update for processwire/processwire-issues#408 plus some phpdoc updates in ProcessModule to appease phpstorm 2018-03-01 09:30:45 -05:00
Ryan Cramer
cd0f76d32c Fix issue processwire/processwire-issues#505 add support for detection of missing closing tags in WireMarkupRegions in order to prevent timeout 2018-02-28 08:40:58 -05:00
Ryan Cramer
8acd06646f Fix issue processwire/processwire-issues#349 InputfieldOptions with InputfieldSelect required field not having blank option 2018-02-27 10:36:05 -05:00
Ryan Cramer
5674af8b99 Minor count() update per processwire/processwire-issues#408 plus some minor phpdoc updates to appease phpstorm 2018-02-27 08:33:17 -05:00
Ryan Cramer
7c0012fbd9 Additional wireCount updates per processwire/processwire-issues#408 2018-02-26 10:16:12 -05:00
Ryan Cramer
ac779c79a8 Fix issue processwire/processwire-issues#302 where markup <region> tags didn't get removed if template file produced no output 2018-02-26 09:58:29 -05:00
Ryan Cramer
9d2a986048 Add @Toutouwai request related to processwire/processwire-issues#84 for improving touch input detection in ProcessLogin via method like InputDetect module with whatinput JS. 2018-02-26 09:19:28 -05:00
Ryan Cramer
37f95199f4 Add descriptive error message when trying to use OR values with path/URL and core PathPaths module is not installed, per processwire/processwire-issues#504 2018-02-26 06:27:29 -05:00
Ryan Cramer
869040bb30 Attempt to fix issue processwire/processwire-issues#503 incorrect label index warning/notice in ProcessRecentPages 2018-02-26 06:00:52 -05:00
Ryan Cramer
bd90821d40 Minor adjustment to account for possibility that CRLF may get counted as two bytes in InputfieldTextarea::getTextLength method (for maxlength detection). 2018-02-25 07:40:03 -05:00
Ryan Cramer
f3c188254d Bump version to 3.0.93 2018-02-23 11:51:54 -05:00
Ryan Cramer
72e00189b5 Some adjustments to InputfieldCKEditor for future support of external asset pages. Not currently enabled, just getting some foundational stuff in place for future use. 2018-02-23 10:56:32 -05:00
Ryan Cramer
dd9f215247 Update LanguageSupport module config screen to recommend next steps during module installation 2018-02-23 10:36:38 -05:00
Ryan Cramer
919c475631 Various minor adjustments 2018-02-23 10:36:15 -05:00
Ryan Cramer
053ef62970 Fix issue processwire/processwire-issues#500 where trackChanges state of cloned page was off 2018-02-21 06:12:39 -05:00
Ryan Cramer
b2281d6e93 Optimize WireMail module detection, add support for replyTo() method, add $config->wireMail default settings for WireMail module(s). processwire/processwire-issues#498 2018-02-20 10:12:57 -05:00
Ryan Cramer
fc1c6e5ea1 Fix issue processwire/processwire-issues#499 as well as expand upon phpdoc in ProcessPageEditImageSelect.module 2018-02-20 06:37:35 -05:00
Ryan Cramer
8fe1eb13f4 Add support for interactive selection of zoom, combined with focus, in InputfieldImage. To enable zoom, go to your image field settings, and on the Input tab locate the "Focus point selection" field and choose "Focus point and zoom". Bump version to 3.0.92. 2018-02-16 11:28:25 -05:00
Ryan Cramer
5c6e54e24d Fix issue processwire/processwire-issues#495 update word counter to display max length even when field is blank 2018-02-14 09:49:40 -05:00
Ryan Cramer
5493d5fea6 Add @horst-n support for focus zoom setting in the ImageSizerEngines 2018-02-13 06:02:30 -05:00
Ryan Cramer
e73ec872da Fix issue processwire/processwire-issues#491 add support for automatically cloning fieldset _END field when fieldset is cloned 2018-02-12 09:20:38 -05:00
Ryan Cramer
ef6a9d56e4 Fix issue processwire/processwire-issues#492 where FieldtypeTextareaLanguage fields on multi-language install with only default language present could get caught in a loop during load of field 2018-02-12 09:12:43 -05:00
Ryan Cramer
1f6ae656dc Bump version to 3.0.91 2018-02-09 10:10:09 -05:00
Ryan Cramer
0630b3a3c3 Add support for PageFinder selectors where field name is the name of a Fieldtype module. During search, it translates to all fields having that Fieldtype. This enables you to perform a search on all text/textarea fields at once, for example, "FieldtypeTextarea%=something". You can also do "FieldtypeText.extends%=something", and that will include all fields that use or extend the given Fieldtype. Or you can do "FieldtypeText.fields%=something" and the resulting PageArray will contain a "fields" data property that contains an array of matched fields and the quantity of pages that matched each. You can also do "FieldtypeText.extends.fields%=something" and it will include both behaviors mentioned here. Finally, you can use OR expressions here too, like "FieldtypeText|FieldtypeTextarea%=something". Note that these searches are not particularly fast and may have limits on scale. The "FieldtypeName.fields" option is slower than without the "fields" option, BUT it enables it to scale further. In my testing so far, the %= operator actually performs quite a bit better here for text-based searches than the *= or ~= operators, likely because the resulting query probably really limits use of the fulltext indexes. 2018-02-09 09:58:49 -05:00
Ryan Cramer
bafe3d4a12 Fix issue processwire/processwire-issues#490 where ProcessPageEditLink didn't allow for external link rel attributes with space separated values 2018-02-08 09:22:05 -05:00
Ryan Cramer
6dbb244654 Fix issue processwire/processwire-issues#488 where UTF-8 mode urlSegments that contained only ascii would get converted to lowercase 2018-02-08 08:51:33 -05:00
Ryan Cramer
3ade2c3269 Fix issue processwire/processwire-issues#489 where file/image field with limit=1, multi-language description was not retained on when file was replaced 2018-02-08 08:07:50 -05:00
Ryan Cramer
cf292363ca Enhance wireBytesStr() function with more options per processwire/processwire-requests#133 2018-02-06 10:03:13 -05:00
Ryan Cramer
3918bba0ab Upgrade htmlpurifier version to 4.9.3 per processwire/processwire-issues#483 2018-02-06 05:44:43 -05:00
Ryan Cramer
eb80f52efe Fix issue processwire/processwire-issues#487 where svg files in $config->pageFileSecure mode needed entry in $config->fileContentTypes 2018-02-06 05:39:19 -05:00
Ryan Cramer
5c708797f9 Update for processwire/processwire-issues#408 PHP 7.2 and changed behavior of count() 2018-02-05 06:25:05 -05:00
Ryan Cramer
f89544ea04 Fix issue processwire/processwire-issues#485 where attempting to use numeric index with WireData could halt request/memory error 2018-02-05 06:09:54 -05:00
Ryan Cramer
e21c965f7b Fix issue processwire/processwire-issues#484 where PageFrontEdit with <edit field="pageID.fieldName"> tag was not working property. 2018-02-05 05:57:15 -05:00
Ryan Cramer
8e52ee8097 Bump version to 3.0.90 2018-02-02 15:00:29 -05:00
Ryan Cramer
461152a8cd This update to AdminThemeUikit fixes various minor reported issues with Inputfields and some other small details 2018-02-02 10:36:11 -05:00
Ryan Cramer
cb2683208f Various minor adjustments 2018-02-02 10:16:15 -05:00
Ryan Cramer
3eba1b58aa Fix issues processwire/processwire-issues#173 and processwire/processwire-issues#393 nested repeater labels 2018-02-02 10:10:49 -05:00
Ryan Cramer
1d82b530ff Fix issue processwire/processwire-issues#347 where field export that included roles was using IDs rather than role names. Also same issue with PageTable that was using template IDs rather than template names. 2018-02-02 10:05:40 -05:00
Ryan Cramer
f7ff7eab8c Fix issue processwire/processwire-issues#352 where deleted role could interfere with the "who can access this page" field in the page editor 2018-02-01 11:39:30 -05:00
Ryan Cramer
bbca8f5669 Add new module ImageSizerEngineAnimatedGif by @horst-n for animated GIF support in image resizes 2018-01-31 09:13:21 -05:00
Ryan Cramer
03660974ee Some additional helpful logic and messages added in ProcessRole, plus an update to resolve processwire/processwire-issues#474 2018-01-31 08:10:32 -05:00
Ryan Cramer
e917c93d76 Fix issue processwire/processwire-issues where clone of page having FieldsetPage field was not cloning the FieldsetPage portion of it 2018-01-29 09:16:03 -05:00
Ryan Cramer
90efe9b14a Bump version to 3.0.89 and some other adjustments 2018-01-26 11:43:22 -05:00
Ryan Cramer
2cebe89e57 Fix issue processwire/processwire-issues#475 adjustment to argument type hint in ProcessUser 2018-01-25 09:50:11 -05:00
Ryan Cramer
02f1eadacc Fix issue processwire/processwire-issues#474 where ProcessRole wasn't working correctly with page-view permission assignment 2018-01-25 09:43:16 -05:00
Ryan Cramer
2a946c09de Fix issue processwire/processwire-issues#473 where SelectableOptionManager.php:addOptions() method had incorrect "array" type hint in method argument 2018-01-25 06:26:52 -05:00
Ryan Cramer
b72e609cc4 Fix issue processwire/processwire-issues#468 where ProcessPageSearch wasn't working with ^= (starts with) search operator 2018-01-25 06:22:51 -05:00
Ryan Cramer
a021d3b54f Fix issue processwire/processwire-issues#467 correct typo in PaginatedArray sprintf string 2018-01-25 06:15:23 -05:00
Ryan Cramer
7b1913b6ed Add support for image focus area / focus point / focal point (or whatever the best term is) to InputfieldImage per processwire/processwire-requests#150 2018-01-24 11:48:50 -05:00
Ryan Cramer
3fb3294f5a Add support for filedata in FieldtypeFile/FieldtypeImage, which provides a way to store other general purpose data with files/images. 2018-01-24 11:31:03 -05:00
Ryan Cramer
6241fd6730 Fix issue with InputfieldSelector not recognizing template_ids property on Page reference fields that use autocomplete 2018-01-14 11:55:54 -05:00
Ryan Cramer
b3ea132d81 Fix ProcessTemplate and asmSelect issue processwire/processwire-issues#472 2018-01-11 09:51:47 -05:00
Ryan Cramer
ad6f6bd283 Fix JqueryUI modal.js issue processwire/processwire-issues#471 2018-01-11 09:39:06 -05:00
Ryan Cramer
f7b49055cd Fix PageFrontEdit issue processwire/processwire-issues#470 2018-01-11 09:36:46 -05:00
Ryan Cramer
f3749d241a Change an sanitizer entities() call to entities1() since it's getting double encoded for some reason in @adrianbj case 2018-01-05 12:37:40 -05:00
Ryan Cramer
4f7981cf75 Bump version to 3.0.88 2018-01-05 12:05:35 -05:00
Ryan Cramer
de5c241b6a Fix issue and PR #54 with similar solution though also remove predefined min/max image settings from ProcessPageEditImageSelect because they seem to be causing various issues, and the predefined settings are more about uploaded files. 2018-01-05 10:56:05 -05:00
Ryan Cramer
fb39ded94d Add support for image file actions to InputfieldImage and update ImageSizer engines to support new rotate, flip and color actions 2018-01-05 10:54:21 -05:00
Ryan Cramer
9bdad6fc86 Detect NullPage in InputfieldCKEditor::getImagesFieldName() to fix issue that appeared for someone when editing page with no images fields. 2017-12-31 11:02:42 -05:00
Ryan Cramer
a31d4a84e2 Upgrade CKEditor to 4.8.0, add support for direct image upload/paste in CKEditor. Bump version to 3.0.87. 2017-12-29 08:42:49 -05:00
Ryan Cramer
5bba89b043 Fix issue processwire/processwire-issues#462 solution for files using arabic filenames 2017-12-26 07:27:50 -05:00
Ryan Cramer
f573473066 Add support for custom configuration of what Fieldtype/Inputfield settings may be overridden for field/template context. Appears only in $config->advanced mode. You can see it when editing a field (ProcessField) on the "Overrides" tab. Related to processwire/processwire-requests#145 2017-12-22 11:11:02 -05:00
Ryan Cramer
63229872a0 Fix typo in phpdoc processwire/processwire-issues#458 2017-12-22 07:47:44 -05:00
Ryan Cramer
5b53a41c12 Fix issue processwire/processwire-issues#456 2017-12-21 09:36:17 -05:00
Ryan Cramer
87dc586c8c Add PageFinder selector support for field.owner.subfield=value where "field" is a PageTable, Page or Repeater field, and "owner" is a literal reserved word, and "subfield" is any field or field.subfield combination. It makes the selector match Repeater, PageTable or Page reference pages that have an "owner" that matches. This "owner" would be defined as the page that has a Repeater or PageTable item being matched, as one of its fields. Ping @apeisa 2017-12-20 12:21:14 -05:00
Ryan Cramer
362c0a0af5 Bump version to 3.0.86 2017-12-15 15:28:40 -05:00
Ryan Cramer
aa1f7dbd47 Fix issue processwire/processwire-issues#455 2017-12-15 09:19:18 -05:00
Ryan Cramer
ded6476832 Fix issue processwire/processwire-issues#454 for Pageimage::maxSize() 2017-12-15 06:30:36 -05:00
Ryan Cramer
77ad382bcd Attempt fix for issue processwire/processwire-issues#452 2017-12-15 06:05:52 -05:00
Ryan Cramer
04b7dd0404 Fix autoload_noget change tracking issue in Page per @apeisa 2017-12-14 06:38:08 -05:00
Ryan Cramer
9b10232b73 Add ability to specify roles that aren't allowed to login, related to processwire/processwire-requests#140 plus while I was in there, did some re-working of login related code in Session class and user management code in ProcessUser.module. 2017-12-13 10:37:39 -05:00
Ryan Cramer
bac60dc340 Add support for interlaced jpeg images per @horst-n and processwire/processwire-requests#134 2017-12-11 11:09:57 -05:00
Ryan Cramer
67cdf1d456 Add variation of @adrianbj PR#92 which adds Imagick as another method or determining SVG dimensions 2017-12-11 10:49:59 -05:00
Ryan Cramer
b4b40f19f4 Fix issue processwire/processwire-issues#449 2017-12-11 05:50:48 -05:00
Ryan Cramer
763c713f4c wireCount updates per processwire/processwire-issues#408 2017-12-11 05:42:48 -05:00
Ryan Cramer
927f66cc61 Bump version to 3.0.85 2017-12-08 11:30:53 -05:00
Ryan Cramer
7a3da093d9 Update InputfieldCheckbox to support a separately configured checkbox label, distinct from the field label (for the cases where you might want both). 2017-12-08 10:20:20 -05:00
Ryan Cramer
93a5cc20b1 Update random string generation method in Password class (randomBase64String) to support new PHP7 random_bytes function, plus add a test mode for testing all generation techniques at once. processwire/processwire-issues#447 2017-12-08 09:36:25 -05:00
Ryan Cramer
dfb5f740d0 Add usage example to FieldtypeOptions config screen 2017-12-08 09:32:46 -05:00
Ryan Cramer
1ba1323e81 count() to wireCount() update per processwire/processwire-issues#408 2017-12-08 07:55:34 -05:00
Ryan Cramer
76e956ad61 Additional updates for autojoin related issue, delay autojoin field sanitization until first access of field 2017-12-06 06:33:28 -05:00
Ryan Cramer
00a8bf03d3 Fix isSaveable() unnecessary parent load + findMany() issue per @apeisa 2017-12-06 05:58:08 -05:00
Ryan Cramer
45eadd971b Fix Page field autojoin issue per @apeisa 2017-12-06 05:45:07 -05:00
Ryan Cramer
fb21af434d Fix issue processwire/processwire-issues#443 2017-12-05 12:15:23 -05:00
Ryan Cramer
5b8761454a Fix issue processwire/processwire-issues#439 2017-12-05 10:51:39 -05:00
Ryan Cramer
3b21a3e9a8 Add @teppokoivula suggested fix for processwire/processwire-issues#435 PageFrontEnd when editing 404 page 2017-12-04 11:17:16 -05:00
Ryan Cramer
5bbd342570 Fix issue processwire/processwire-issues#433 2017-12-04 10:57:04 -05:00
Ryan Cramer
06c1a46ba5 Fix issue processwire/processwire-issues#428 2017-12-04 10:28:51 -05:00
Ryan Cramer
a865e0a053 Some cleanup in ProcessPageAdd plus add support for disabling template suggestions when adding pages per processwire/processwire-issues#424 via $config->pageAdd('noSuggestTemplates', true); or specify space-separated list of template names (string) for the "true" value. 2017-12-04 09:51:11 -05:00
Ryan Cramer
337e59663b Add support for group notifications in AdminThemeFramework (AdminThemeUikit), along with a config option in the AdminThemeUikit module. Also fix an issue in ProcessProfile that was interfering with ajax requests. 2017-11-20 09:45:16 -05:00
Ryan Cramer
32b3da7b0d Fix issue where collapsed repeater in fieldset (where repeater is only field in fieldset), when clicking to un-collapse repeater, it would jump to top of page 2017-11-19 06:33:22 -05:00
Ryan Cramer
81a07fde34 Attempt to fix issue processwire/processwire-issues#432 for disabling autocomplete and password field 2017-11-18 07:00:48 -05:00
Ryan Cramer
22c69ec599 Add latest version of AdminThemeUikit and bump version to 3.0.84 2017-11-17 10:02:49 -05:00
Ryan Cramer
2c4c9de61d Fixes to ryancramerdesign/AdminThemeUikit#55 and ryancramerdesign/AdminThemeUikit#61 (when combined with latest AdminThemeUikit version, to be added shortly) 2017-11-17 09:44:45 -05:00
Ryan Cramer
60572a4cd2 Fix issue with PageTable not having correct table styles when using AdminThemeUikit 2017-11-17 08:29:22 -05:00
Ryan Cramer
7d16590f07 Update ProcessProfile to support the ability to edit user name (if configured to do so). Also update it so that it requires you to enter your password before you can commit changes to email or user name. 2017-11-16 10:54:41 -05:00
Ryan Cramer
6c3eb6a460 Update ProcessTemplate so that it lets you specify when fields user can edit in their profile, when editing the "user" template. Meaning, it's no longer necessary to jump back and forth between ProcessProfile and the ProcessProfile module config. 2017-11-16 10:53:16 -05:00
Ryan Cramer
ac59da3427 Upate JqueryWireTabs module to support more customization of its tooltips (so admin theme can adjust) 2017-11-16 10:52:13 -05:00
Ryan Cramer
ebc150a3a7 Update PW installer to use Uikit 3 theme, plus add support for detecting and dropping existing tables (if present) during installation. 2017-11-16 10:50:36 -05:00
Ryan Cramer
58eb0c3159 Various minor adjustments 2017-11-16 10:49:26 -05:00
Ryan Cramer
221a15a653 Add AdminThemeUikit module to core and bump version to 3.0.83 2017-11-10 11:10:40 -05:00
Ryan Cramer
e0d04a4626 Upgrade InputfieldPassword to support requirement of entering your old password before it'll let you set a new one. 2017-11-10 09:13:10 -05:00
Ryan Cramer
840ab68625 Minor cosmetic adjustments 2017-11-10 09:11:00 -05:00
Ryan Cramer
e2a42381ac Some optimizations to LanguageTabs and add support for Uikit3 beta34+ 2017-11-10 09:08:30 -05:00
Ryan Cramer
7398267be1 Bump version to 3.0.82 2017-11-03 10:51:44 -04:00
Ryan Cramer
8e27f859c0 Fix issue ryancramerdesign/AdminThemeUikit#47 where AdminThemeFramework was not running the headline through the language translation function 2017-11-03 08:10:47 -04:00
Ryan Cramer
297a64f9d4 Fix issue processwire/processwire-issues#421 fixing useless redirects from manually deleted session cookies 2017-11-02 09:50:26 -04:00
Ryan Cramer
ece560daa1 Fix issue processwire/processwire-issues#418 to fix file/image drag-to-upload in IE11 using fix provided by @Toutouwai 2017-11-02 09:37:23 -04:00
Ryan Cramer
34d15dadae Fix issue processwire/processwire-issues#420 to disable autocomplete via custom attribute for InputfieldPageName using suggestion by @Toutouwai 2017-11-02 09:29:40 -04:00
Ryan Cramer
7b19df0175 Add Setup>Fields>Export/Import support for Repeater fields. processwire/processwire-issues#416 2017-11-02 06:33:46 -04:00
Ryan Cramer
ab6d158cf9 Fix issue processwire/processwire-issues#419 which corrects InputfieldImage.js sizing of list mode using fix provided by @Toutouwai 2017-11-02 06:22:50 -04:00
Ryan Cramer
7bf27cc5e5 Fix issue processwire/processwire-issues#415 where ProcessPageEditImageSelect was not showing variations for image when it was specified as one of the skipFields in module configuration. 2017-10-31 06:25:24 -04:00
Ryan Cramer
5c09f03e8b Fix issue processwire/processwire-issues#414 language tabs and language used for placeholder attribute, though not positive this one should stay...previous behavior may be preferable depending on the case. 2017-10-30 06:22:29 -04:00
Ryan Cramer
c778056991 Fix issue processwire/processwire-issues#410 by making ProcessLanguageTranslator give preference to GET var (rather than session) with language ID, by adding part of PR #93 2017-10-30 05:48:31 -04:00
Ryan Cramer
0f4cf01ec2 Update Config.php note for imageSizerOptions per processwire/processwire-issues#409 2017-10-30 05:43:20 -04:00
Ryan Cramer
066a54fb6f Fix issue processwire/processwire-issues#411 where multiple PageListSelect in same page editor weren't working properly (thanks to @Toutouwai for the fix) 2017-10-30 05:36:26 -04:00
Ryan Cramer
c90fc3f872 Add improved Roles editor that now lets you manage permissions by template 2017-10-27 11:21:13 -04:00
Ryan Cramer
e3fc776c53 Bump version to 3.0.80 2017-10-20 09:58:29 -04:00
Ryan Cramer
b2381002be Improve error reporting ability of $modules->get(), $modules->getModule(), primarily for debugging purposes. 2017-10-20 09:45:53 -04:00
Ryan Cramer
97935d156a Add wireCount() function to duplicate behavior of count() function in PHP versions prior to 7.2 2017-10-20 09:44:21 -04:00
Ryan Cramer
56e35b55ea Add support for using repeaters in user profile editor per issue processwire/processwire-issues#407 2017-10-20 09:43:08 -04:00
Ryan Cramer
b4f977c36a Fix issue processwire/processwire-issues#396 plus update ProcessCommentsManager to be compatible with AdminThemeUikit 2017-10-19 11:15:08 -04:00
Ryan Cramer
a48f4038c8 Enhancement processwire/processwire-issues#405 add support for hooks to be used in classes that extend Wire-derived classes that aren't in the ProcessWire namespace. 2017-10-17 10:00:55 -04:00
Ryan Cramer
b0f37a306e Fix issue processwire/processwire-issues#406 correction of error message in ProcessPageSort 2017-10-17 05:44:30 -04:00
Ryan Cramer
fff501cac3 Correct an issue in Repeater that interfered with some file-upload situations in draft page versions 2017-10-17 05:33:32 -04:00
Ryan Cramer
2771c353ee Fix issue processwire/processwire-issues#403 which caused problems with file uploads in some kinds of repeaters. Also bump version to 3.0.79 2017-10-13 12:59:33 -04:00
Ryan Cramer
00c183e70f Some code adjustments in ProcessPageList to support a separate renderReady() method, as well as some tweaks per @EntitySelf evernote document 2017-10-13 08:17:32 -04:00
Ryan Cramer
5dc690af9e Fix issue processwire/processwire-issues#381 where Selectors class could get confused by a quoted value with comma when contained in an OR value 2017-10-12 11:57:26 -04:00
Ryan Cramer
58bde80a81 Fix issue processwire/processwire-issues#384 update 2016 copyright date to 2017 2017-10-12 11:15:37 -04:00
Ryan Cramer
47b87d283b Per PR #91 fix issue processwire/processwire-issues#385 where ProcessPageEditImageSelect nosize option wasn't working with InputfieldCKEditor pwimage plugin 2017-10-12 11:12:42 -04:00
Ryan Cramer
f4971ae5d4 Fix issue processwire/processwire-issues#388 where repeater templates could show as selectable options in page editor template field, when they didn't need to 2017-10-12 10:52:47 -04:00
Ryan Cramer
e0a2abea07 Fix issue processwire/processwire-issues#391 remove stray console.log() in InputfieldRepeater.js 2017-10-12 10:41:54 -04:00
Ryan Cramer
7d3dc80798 Fix issue processwire/processwire-issues#395 where renaming a repeater field didn't rename the related template and fieldgroup 2017-10-12 10:36:53 -04:00
Ryan Cramer
0a790cfb43 Fix issue processwire/processwire-issues#399 where InputfieldInteger wasn't supporting defaultValue 2017-10-12 09:44:25 -04:00
Ryan Cramer
90da2e9936 Remove old CKEditor version 2017-10-12 09:43:57 -04:00
Ryan Cramer
df17b337fd Version 3.0.78 has updates primarily specific to enabling more customization from admin themes. In this case, AdminThemeUikit, but applies to any admin theme. 2017-10-06 09:33:39 -04:00
Ryan Cramer
5a39c9efc9 Just some small updates in 3.0.77, mostly related to AdminThemeUikit updates 2017-09-29 12:18:34 -04:00
Ryan Cramer
73d7e997fe Bump version to 3.0.76 2017-09-22 12:30:28 -04:00
Ryan Cramer
f822292eb3 Some updates to ProcessPageView and additions for processwire/processwire-issues#366 to better handle URLs with unrecognized characters 2017-09-21 11:37:46 -04:00
Ryan Cramer
8bcb31359f Fix issue processwire/processwire-issues#363 where repeater with min count and unpublished min item wouldn't get saved if new non-min item was added before save 2017-09-21 06:53:13 -04:00
Ryan Cramer
d765c0d589 Fix issue processwire/processwire-issues#367 where nested FieldsetPage with Repeater/Matrix combinations could cause same non-nested FieldsetPage to not get saved 2017-09-21 06:32:02 -04:00
Ryan Cramer
7943aa1064 Update for processwire/processwire-issues#371 clarify that superuser permissions are not editable (since superuser implies user with all permissions) 2017-09-20 07:50:53 -04:00
Ryan Cramer
78e07bc6cf Enhance Page::localName() method per processwire/processwire-issues#373 2017-09-20 07:15:37 -04:00
Ryan Cramer
327576ff7a Fix issue processwire/processwire-issues#374 where save+next in page editor should not proceed to next page when there were errors on editing page 2017-09-20 07:03:02 -04:00
Ryan Cramer
fffebd9214 Update CKEditor version to 4.7.3 2017-09-20 06:52:56 -04:00
Ryan Cramer
4560ed2997 Fix issue processwire/processwire-issues#377 where install of non-native site profile with 3rd party Fieldtype modules could produce error messages at install time 2017-09-20 06:43:38 -04:00
Ryan Cramer
2a3be7209c Bump version to 3.0.75 2017-09-15 10:49:48 -04:00
Ryan Cramer
064da79bae Update InputfieldFile non-ajax upload mode to warn about file uploads that exceed the max upload size. 2017-09-15 10:48:01 -04:00
Ryan Cramer
b1048297e9 Add nocache URL to image variations modal per processwire/processwire-issues#369 2017-09-11 14:17:22 -04:00
Ryan Cramer
5a9c8fea58 Attempt fix issue processwire/processwire-issues#359 where certain repeater/repeater-matrix combinations weren't initializing propertly 2017-09-11 10:44:32 -04:00
Ryan Cramer
009a2d9342 Fix issue processwire/processwire-issues#353 remove limit on max selector quantity 2017-09-11 09:05:52 -04:00
Ryan Cramer
0bdfdeeb78 Fix issue processwire/processwire-issues#354 where a.b.c='' selector was not working right 2017-09-11 08:30:08 -04:00
Ryan Cramer
8bc3e6ad2e Improve an error message on ProcessPageEditImageSelect.module 2017-09-08 10:10:27 -04:00
Ryan Cramer
41089806e1 Some minor updates for ProcessPageLister 2017-09-08 10:09:45 -04:00
Ryan Cramer
6fef531b1a Some optimizations to FieldtypeRepeater 2017-09-08 10:09:14 -04:00
Ryan Cramer
cc34ec8752 Add support to Pages/PagesLoader for populating directly to existing PageArray 2017-09-08 10:07:25 -04:00
Ryan Cramer
d427b7f563 Fix issue in ProcessPageEditLink when a FieldtypeFieldsetPage field is on the page 2017-09-03 05:58:21 -04:00
Ryan Cramer
34519c4ad3 Minor adjustment to InputfieldRepeater 2017-09-01 11:24:19 -04:00
Ryan Cramer
70289c05a8 Typo fix 2017-09-01 11:02:42 -04:00
Ryan Cramer
b544d0a50f Bump version to 3.0.74 2017-09-01 10:59:47 -04:00
Ryan Cramer
c0e8485a77 Add FieldtypeFielsetPage module to core as part of the repeaters package 2017-09-01 10:58:52 -04:00
Ryan Cramer
3cb9c46e7d Some additional updates for repeater single mode 2017-09-01 10:58:21 -04:00
Ryan Cramer
27ee1fae15 Some adjustments to FieldtypeRepeater for single page mode and update PagesExportImport for support 2017-08-31 11:19:35 -04:00
Ryan Cramer
d9fb9cd026 Updates to FieldtypeRepeater and InputfieldRepeater to support single mode, as used by FieldtypeFieldsetPage 2017-08-30 09:37:19 -04:00
Ryan Cramer
718baae573 Various small fixes and tweaks, and improvements to code documentation in several spots 2017-08-30 09:24:57 -04:00
Ryan Cramer
aa321c6b7b Bump version to 3.0.73 2017-08-25 12:23:41 -04:00
Ryan Cramer
4a26d7626f Improve extendability of FieldtypeFieldsetOpen for FieldtypeFieldsetGroup 2017-08-25 10:33:43 -04:00
Ryan Cramer
055aa6297d Remove fieldset management from ProcessTemplate since it is now native to asmSelect 2017-08-25 10:31:10 -04:00
Ryan Cramer
eaf346df12 Upgrade ProcessField with some additional hooks that can be monitored, along with a few other tweaks 2017-08-25 10:29:04 -04:00
Ryan Cramer
e68e3be9ff Upgrade asmSelect to support fieldsets natively, rather than having it bolted on separately by ProcessTemplate 2017-08-25 10:27:50 -04:00
Ryan Cramer
e96d740586 Some additional updates and fixes for pages export/import 2017-08-25 10:18:09 -04:00
Ryan Cramer
fc2b7944a2 Some minor repeater adjustments to support derived fieldtype in progress 2017-08-25 10:15:10 -04:00
Ryan Cramer
c9fca29283 Fix issue processwire/processwire-issues#344 using page name with not-equals operator in FieldtypePage selector 2017-08-22 11:22:27 -04:00
Ryan Cramer
d841a44955 Fix issue processwire/processwire-issues#293 fix missing repeater labels in nested repeater 2017-08-22 08:51:59 -04:00
Ryan Cramer
950706935e Fix issue processwire/processwire-issues#351 Fix Process.php setViewFile() method typo, and behavior improvements 2017-08-22 06:15:27 -04:00
Ryan Cramer
e38c603ce4 Continued updates to PagesExportImport, add ZIP export/import option to ProcessPagesExportImport (with page files support), and bump version to 3.0.72 2017-08-18 11:33:06 -04:00
Ryan Cramer
74a2b001cc Add support for export/import of comments fields (FieldtypeComments) 2017-08-17 09:38:06 -04:00
Ryan Cramer
b30c6a4ec8 Fix issue processwire/processwire-issues#335 error was thrown by InputfieldImage for too large file, even if client-side resize enabled 2017-08-16 10:36:17 -04:00
Ryan Cramer
56345aff26 Fix issue processwire/processwire-issues#336 where markup regions run on SVG output caused problem 2017-08-16 10:18:40 -04:00
Ryan Cramer
c496c3b859 Fix issue processwire/processwire-issues#340 - image/file tags disappear on existing files after uploading a new file 2017-08-16 09:54:12 -04:00
Ryan Cramer
74dcd51837 Additional updates per issue processwire/processwire-issues#330 - make image replacement keep the same filename (so long as files have same extension). Also upgraded it to retain description and tags during replacement. 2017-08-16 09:02:54 -04:00
Ryan Cramer
b5b2636e01 Add Repeater field support to new Page Export/Import features. 2017-08-15 12:13:25 -04:00
Ryan Cramer
e89235b757 Add $notices->move($from, $to) method for moving notices from one Wire instance to another. 2017-08-15 12:12:03 -04:00
Ryan Cramer
7873422f9e Add ProcessPagesExportImport module to core, and bump version to 3.0.71. To install this new module, go to Modules > Refresh, then Modules > Core > Process > Pages Export/Import > Install 2017-08-11 11:06:31 -04:00
Ryan Cramer
af6ab99d87 Continued major updates to the main PagesExportImport class 2017-08-11 10:45:46 -04:00
Ryan Cramer
d000117e4b Fix issue processwire/processwire-issues#338 where WireTempDir had incorrect order of arguments to explode function 2017-08-11 10:45:06 -04:00
Ryan Cramer
a216b561d1 Upgrade the multi-language text Fieldtypes to support pages export/import 2017-08-11 10:43:49 -04:00
Ryan Cramer
edd71401ed Upgrade LanguagesPageFieldValue to support population by language-name indexed array 2017-08-11 10:42:44 -04:00
Ryan Cramer
a038debd82 Upgrade FieldtypeTextarea to support conversion of file/asset URLs when exporting pages and importing elsewhere 2017-08-11 10:40:49 -04:00
Ryan Cramer
143e42722d Upgrade FieldtypeText to support importing from multi-language to non-multi-language 2017-08-11 10:39:54 -04:00
Ryan Cramer
9e76a2b770 Update FieldtypePage to implement the Fieldtype::__importValue() method for page import support 2017-08-11 10:37:57 -04:00
Ryan Cramer
7ff278a9ff Upgrade Pagefile, FieldtypeFile and FieldtypeImage for improved Page export (exportValue) support 2017-08-11 10:36:31 -04:00
Ryan Cramer
c1f7e96185 Bump version to 3.0.70 2017-08-04 12:33:52 -04:00
Ryan Cramer
3e7b2d0273 Further updates to PagesExportImport class. 2017-08-04 10:58:25 -04:00
Ryan Cramer
dc88104277 Merge branch 'horst-n-patch-1' into dev 2017-08-02 11:29:04 -04:00
Ryan Cramer
84c033a758 Merge branch 'patch-1' of https://github.com/horst-n/processwire into horst-n-patch-1 2017-08-02 11:28:28 -04:00
Ryan Cramer
cc7005192a Update InputfieldCKEditor settings screen Toolbar field to show standard toolbar names included with CKEditor 2017-08-02 11:18:33 -04:00
Ryan Cramer
502774a65f Significant refactoring of WireTempDir class, and update PagefilesManager's use of WireTempDir 2017-08-02 11:17:34 -04:00
Ryan Cramer
a07855c9f6 Minor adjustments, mostly phpdoc related 2017-08-02 11:16:32 -04:00
Ryan Cramer
fa1ff60b97 Add 2nd pass support to Markup Regions, so that a region added in 1st pass can be manipulated by commands in the 2nd pass. Also, some optimizations to region hints, and debug mode improvements. 2017-08-02 10:56:26 -04:00
Ryan Cramer
a1e1f25dff Bump version to 3.0.69 2017-07-28 15:05:27 -04:00
Ryan Cramer
629220ffc8 Add support for closing tag hints in WireMarkupRegions. These increase speed/efficiency for large documents. 2017-07-28 13:49:49 -04:00
Ryan Cramer
4a3f09d34c Fix issue processwire/processwire-issues#330 where drag-n-drop image replacement wasn't working properly 2017-07-28 11:15:00 -04:00
Ryan Cramer
abce877f02 Fix issue processwire/processwire-issues#329 Pagefile::install() method when used with URL argument that contains a query string was screwing up the file extension in the basename 2017-07-28 10:42:20 -04:00
Ryan Cramer
8169594e79 Fix issue processwire/processwire-issues#328 typo fix 2017-07-28 10:29:32 -04:00
Ryan Cramer
277674db23 Fix issue processwire/processwire-issues#325 panel.js and URL fragments fix 2017-07-28 10:10:16 -04:00
Ryan Cramer
71896f463e Fix issue processwire/processwire-issues#320 - MarkupCache, path() method, and WireArray::debugInfo() 2017-07-28 07:10:00 -04:00
Ryan Cramer
52b8389215 Fix issue processwire/processwire-issues#319 where Page::setAndSave() was only working with custom fields and not with DB native properties 2017-07-28 06:52:23 -04:00
Ryan Cramer
0c42013c45 Fix issue processwire/processwire-issues#314 - Lister bookmarks when used with parent.title and %= operator was not working correctly 2017-07-28 06:35:53 -04:00
Ryan Cramer
f5d8633590 Adjustment in Wire class and bump version to 3.0.68 2017-07-21 13:08:05 -04:00
Ryan Cramer
506f66b64a Updates to PagesExportImport class 2017-07-21 13:06:29 -04:00
Ryan Cramer
59549a01be Some optimizations in the PagesEditor class 2017-07-21 13:04:52 -04:00
Ryan Cramer
651e8bd20c CSS adjustment in AdminThemeDefault to improve appearance of language tabs in file/image fields. Also, remove an extra unnecessary margin on language-tabbed text Inputfields. 2017-07-17 06:08:47 -04:00
Ryan Cramer
b7cb72cac6 Fix issue with file/image tags where they weren't initiating properly after an AjaxUploadDone JS event 2017-07-17 05:47:43 -04:00
Ryan Cramer
faf1efc049 Adjust to WireFileTools::find() method to correct potential confusion between windows vs. unix directory separator 2017-07-16 09:57:31 -04:00
Ryan Cramer
0a06c12b82 A couple other tweaks to tags updates and bump version to 3.0.67 2017-07-14 14:09:46 -04:00
Ryan Cramer
659391a4c3 Fix issue processwire/processwire-issues#316 - legacy db API variable class and debug timers in PHP7 producing unnecessary notice 2017-07-14 10:39:54 -04:00
Ryan Cramer
0fe94521fe Fix issue processwire/processwire-issues#315 - remove some extra code that's no longer necessary for image fields in a PageTable 2017-07-14 10:15:31 -04:00
Ryan Cramer
bf351573b0 Fix issue processwire/processwire-issues#310 - update ProcessForgotPassword to use the same requirements as when editing the pass field in the page editor 2017-07-14 09:47:24 -04:00
Ryan Cramer
cab20d518d Add improved tags support for InputfieldFile and InputfieldImage. Now the UI is more tags oriented, and there are more config options for how the tags are input. This commit also adds a modified version of the Selectize js library to provide improved tag inputs. 2017-07-13 15:20:53 -04:00
Ryan Cramer
650fec9a37 Bump version to 3.0.66 2017-07-07 14:07:39 -04:00
Ryan Cramer
786995f85e Add new WireFileTools API var $files->find() method and update the include/compile methods in WireFileTools to use the TemplateFile render stack. 2017-07-07 14:06:28 -04:00
Ryan Cramer
b8d01e88d6 Add render stack support to TemplateFile so that a rendered php file can know what other file(s) are rendering it, when applicable. 2017-07-07 14:05:00 -04:00
Ryan Cramer
ae4761180f Some minor fixes to enable deleting a field that is missing its Fieldtype module. Plus addition of hasPage property to Inputfield modules. And a couple other minor things. 2017-07-07 14:03:39 -04:00
Ryan Cramer
5ec3e48de6 Fix issue processwire/processwire-issues#299 2017-06-30 10:49:31 -04:00
Ryan Cramer
bcd40cdf03 Bump version to 3.0.65 2017-06-23 15:50:18 -04:00
Ryan Cramer
5b0eb5ea81 Fix issue processwire/processwire-issues#264 2017-06-23 09:04:14 -04:00
Ryan Cramer
8107d5e90d Fix issue processwire/processwire-issues#276 (typo in translation text) 2017-06-23 07:34:17 -04:00
Ryan Cramer
651e0e8763 Fix issue processwire/processwire-issues#274 2017-06-23 07:32:03 -04:00
Ryan Cramer
0cccb763a5 Fix issue processwire/processwire-issues#281 (typo in phpdoc example) 2017-06-23 07:11:38 -04:00
Ryan Cramer
438a2944cb Upgrade CKEditor version from 4.6.2 to 4.7.0, plus fix issue processwire/processwire-issues#279 where image placement wasn't working correctly in CKE inline mode. 2017-06-23 06:51:53 -04:00
Ryan Cramer
da3c80d0ca Fix issue processwire/processwire-issues#275 - part 2, fix for exif.js 2017-06-23 06:22:10 -04:00
Ryan Cramer
7ad84bf1c3 Fix issue processwire/processwire-issues#275 2017-06-23 06:15:21 -04:00
Ryan Cramer
dbfb3e2c7e Fix issue processwire/processwire-issues#285 2017-06-22 11:29:35 -04:00
Ryan Cramer
763963ec39 Update WireMarkupRegions to remove class attribute-based action support, since our final spec uses action attributes instead. It only does this for PW installations installed today or later, just in case anyone is using some older examples on an existing site. If you want to force use of the newer version (which is more efficient) set $config->useMarkupRegions=2; The updates made in this commit might also fix processwire/processwire-issues#294 but have not yet confirmed. 2017-06-22 08:45:16 -04:00
Ryan Cramer
3348f3f13a Fix issue processwire/processwire-issues#286 2017-06-22 07:03:28 -04:00
Ryan Cramer
571266c6c1 Fix issue processwire/processwire-issues#288 2017-06-22 06:38:40 -04:00
Ryan Cramer
68a4ddee88 Fix issue processwire/processwire-issues#289 2017-06-21 10:12:09 -04:00
Ryan Cramer
b32a2f0058 Bump version to 3.0.64 2017-06-16 14:34:53 -04:00
Ryan Cramer
05b210766c Continued work on the PagesExportImport class. Not yet ready for use, but much further along and it works in testing on the API side for the basics. Wait before using it though, as there's much more to come here. 2017-06-16 12:50:07 -04:00
Ryan Cramer
0f55f41831 Correct minor issue in Lister for when both the view and edit links for individual pages are disabled 2017-06-16 12:49:19 -04:00
Ryan Cramer
e378acc7bc Correct issue with InputfieldText placeholder attribute sometimes not working for multi-language (like outside the page editor). 2017-06-16 05:56:58 -04:00
Ryan Cramer
1900675bbb Various minor fixes and bump version number to 3.0.63 2017-05-26 14:35:16 -04:00
Ryan Cramer
76fc6dada1 Fix issue processwire/processwire-issues#261 2017-05-26 09:44:14 -04:00
Ryan Cramer
1a590c586f Fix issue processwire/processwire-issues#267 2017-05-26 09:27:19 -04:00
Ryan Cramer
507555e907 Fix issue processwire/processwire-issues#268 2017-05-26 09:18:35 -04:00
Ryan Cramer
5f3827ecba Fix issue processwire/processwire-issues#272 2017-05-26 08:27:48 -04:00
Ryan Cramer
76097ea1ee Add client-side image resize support to InputfieldImage 2017-05-25 11:37:10 -04:00
Ryan Cramer
57b297fd1d Fix issue with InputfieldFile non-ajax mode not working for some cases and bump version to 3.0.62 2017-05-05 13:43:17 -04:00
Ryan Cramer
be99669203 Fix issue processwire/processwire-issues#255 2017-05-02 09:59:06 -04:00
Ryan Cramer
84a39c0667 Fix issue processwire/processwire-issues#256 where the image field modal upload function was not updating the parent window images field, thus preventing the image having the temporary status removed (and not being saved). 2017-05-02 09:50:45 -04:00
Ryan Cramer
b17c9eaed9 Fix InputfieldSelector issue identified by @renobird where checked OR-checkboxes in larger groups of same-field selectors could result in matching the OR to the wrong selector row. 2017-04-30 07:50:35 -04:00
Ryan Cramer
347240acd9 Minor adjustments to repeater and asmSelect 2017-04-28 14:54:26 -04:00
Ryan Cramer
7adf09e305 Small .htacess update for HTTPS redirect support on AWS ELB 2017-04-28 14:53:07 -04:00
Ryan Cramer
0cd8a7a276 Add WireHttp::getResponseHeaderValues() per processwire/processwire-issues#253 2017-04-28 09:46:08 -04:00
horst
64e9ca214d stripped out tab_width 2017-04-24 10:35:20 +02:00
horst
3337aff4d5 create .editorconfig in wire directory
This is very helpful for contributors who uses different editor settings, for example indentation with spaces, not tabs. Most of all common editor programms or IDEs nowadays support to preserve the defined coding style rules automatically. Read more here: http://editorconfig.org
2017-04-23 12:00:33 +02:00
horst
188d0e150d correct syntax highlighting of *.module files
on github. 
see: https://processwire.com/talk/topic/16124-github-now-support-custom-highlight-config/
2017-04-23 11:51:08 +02:00
Ryan Cramer
3fc9f69da7 Some minor adjustments and bump version to 3.0.61 2017-04-21 11:27:47 -04:00
Ryan Cramer
8d02e72320 Update font-awesome to 4.7 per processwire/processwire-requests#72 2017-04-20 05:59:28 -04:00
Ryan Cramer
1c46d0d44c Fix issue with repeaters in renderValue mode 2017-04-19 10:04:16 -04:00
Ryan Cramer
e83ed750c9 Add option for InputfieldHidden to still render as an input in renderValue mode 2017-04-19 10:03:30 -04:00
Ryan Cramer
c259ce8103 Fix minor issue with CommentForm front-end output 2017-04-19 10:02:00 -04:00
Ryan Cramer
a8febefa70 Fix issue processwire/processwire-issues#248 2017-04-19 09:33:43 -04:00
Ryan Cramer
42de2e7bda Fix issue processwire/processwire-issues#245 2017-04-19 09:29:50 -04:00
Ryan Cramer
bb12873a19 Fix issue with multi-template settings not always updating the selectable columns on columns tab 2017-04-16 06:46:31 -04:00
Ryan Cramer
93d1be8453 Bump version to 3.0.60 dev 2017-04-14 14:19:10 -04:00
Ryan Cramer
223b80d685 Update/add documentation to a few classes (like WireCache), correct issue with HTTPS detection on AWS load balancer, plus some other minor tweaks. 2017-04-14 06:32:23 -04:00
Ryan Cramer
93779e2017 Update MarkupHTMLPurifier version per processwire/processwire-issues#243 2017-04-14 06:29:11 -04:00
Ryan Cramer
fb1cc857f2 Fix issue processwire/processwire-issues#244 2017-04-14 06:12:44 -04:00
Ryan Cramer
b914586f6c Fix issue processwire/processwire-issues#227 update Sanitizer::selectorValue() allow exclamation point "!" at beginning of selector value 2017-04-13 08:54:49 -04:00
Ryan Cramer
fd2f14445c Fix issue processwire/processwire-issues#233 2017-04-13 08:36:23 -04:00
Ryan Cramer
267a368034 Fix issue processwire/processwire-issues#241 2017-04-13 06:14:45 -04:00
Ryan Cramer
ae50a0563b Attempt to fix issue processwire/processwire-issues#242 for FieldtypeComments + utf8mb4 charset combo 2017-04-13 06:06:46 -04:00
Ryan Cramer
81c8d4eb2f Fix issue processwire/processwire-issues#236 with InputfieldSelector, plus update the "None" selection option to fix another issue reported in ListerPro board 2017-04-10 08:23:45 -04:00
Ryan Cramer
168a4ffa58 Bump version to 3.0.59 plus a couple other minor updates 2017-04-07 10:14:40 -04:00
Ryan Cramer
00e7a46434 Fix issue processwire/processwire-issues#222 with InputfieldImage thumbnail logic 2017-04-07 06:08:50 -04:00
Ryan Cramer
0714279ba9 Update phpdoc for $pages->sort() per processwire/processwire-issues#225 2017-04-07 06:00:11 -04:00
Ryan Cramer
60989f97b4 Fix issue processwire/processwire-issues#229 2017-04-07 05:44:08 -04:00
Ryan Cramer
75a969bafb Various minor tweaks/updates 2017-04-07 05:11:06 -04:00
Ryan Cramer
98594eb024 Bump version to 3.0.58 2017-03-31 15:43:56 -04:00
Ryan Cramer
ef2fd54e68 Add spelling correction per @mestaritonttu PR #62 2017-03-30 07:01:51 -04:00
Ryan Cramer
2d1864c80d Add @jofalk PR #59 that fixes the issue with </edit> tags not being stripped from front-end editor. 2017-03-30 06:57:21 -04:00
Ryan Cramer
c5033e1e42 Add @derixithy PR #58 for additional sessionCookieDomain call 2017-03-30 06:48:02 -04:00
Ryan Cramer
3f67722294 Add @LostKobrakai PR #50 plus some other tweaks to FieldtypeDatetime module 2017-03-30 06:27:52 -04:00
Ryan Cramer
94876a7bde Merge branch 'Notanotherdotcom-master' into dev 2017-03-30 06:03:39 -04:00
Ryan Cramer
dd777ea9d8 Merge branch 'master' of https://github.com/Notanotherdotcom/processwire-1 into Notanotherdotcom-master 2017-03-30 06:03:16 -04:00
Ryan Cramer
2b51c75cee Various minor tweaks related to AdminThemeFramework 2017-03-30 05:53:46 -04:00
Ryan Cramer
4ca684df83 Fix 2 Lister related issues with 'parent' filters/columns identified by @somatonic in ListerPro board. 2017-03-26 06:40:35 -04:00
Ryan Cramer
ec4726b3df A few updates in support of new AdminThemeFramework and bump version to 3.0.57 2017-03-24 13:23:36 -04:00
Notanotherdotcom
04c87f1954 Branding Upgrade
Replaces the logo in the Default and Reno admin themes with the new
version for branding consistency.
2017-03-23 22:57:18 +00:00
Ryan Cramer
722b504273 Additional updates to $languages setLocale/getLocale methods per processwire/processwire-issues#215 2017-03-20 08:26:21 -04:00
Ryan Cramer
8e241f0132 Add @horst-n fix for ImageSizerEngine rounding issue processwire/processwire-issues#191 2017-03-20 05:49:34 -04:00
Ryan Cramer
a410bf8236 Some fixes to InputfieldSelector parent.subfield properties, plus fix issue where datetime field didn't always recognize when the date picker should include a time picker as well. 2017-03-19 07:07:52 -04:00
Ryan Cramer
1f23b54f45 Fix issue where non-default date format could get lost in session values of InputfieldSelector.module 2017-03-19 06:27:20 -04:00
Ryan Cramer
4414a2db2b Bump version to 3.0.56 plus some other minor tweaks 2017-03-17 13:25:29 -04:00
Ryan Cramer
9c92ce5305 Updates for issue processwire/processwire-issues#215 to better support locale settings on front-end, plus add $languages->setLocale() and $languages->getLocale() methods, and freshen up code and docs in related classes. 2017-03-16 09:49:47 -04:00
Ryan Cramer
e1928c9e3c Some cleanup in ProcessPageList.module, plus attempt compromise fix identified for ProcessPageEdit breadcrumb starting from issue #22. 2017-03-15 14:12:27 -04:00
Ryan Cramer
770c717baa Updates related to WireMarkupRegions discussion in issue processwire/processwire-issues#195 - fix behavior of boolean pw-before/pw-after attributes, and add support for <pw-region> or <region> tags. 2017-03-15 14:04:49 -04:00
Ryan Cramer
11ebcfb456 Fix issue where some non-text based FieldtypeTable subfields had selection issues in InputfieldSelector 2017-03-12 11:16:26 -04:00
Ryan Cramer
11c49f2bb1 Fix issue with page edit dropdown menu actions appearing in buttons they aren't intended to 2017-03-12 07:15:40 -04:00
Ryan Cramer
8018ecc7af Bump version to 3.0.55 and a couple other minor adjustments 2017-03-10 12:46:01 -05:00
Ryan Cramer
8b96e6b060 Attempt fix for issue processwire/processwire-issues#206 fix for multi-site config not working 2017-03-10 09:05:08 -05:00
Ryan Cramer
b899bc42e7 Fix issue processwire/processwire-issues#202 where the leave confirm box was appearing when it shouldn't, after saving after a file had been uploaded. Also added drag/drop protection so that if you accidentially drag/drop a file outside of the specified dropzone, it gets ignored, rather than loading the file in the browser. 2017-03-10 08:38:16 -05:00
Ryan Cramer
6fe703f699 Fix issue processwire/processwire-issues#203 update to make API-created users always have guest role 2017-03-10 08:13:56 -05:00
Ryan Cramer
12767e284d Fix issue processwire/processwire-issues#201 where InputfieldPassword wasn't honoring the minlength setting and was always requiring at least 8 characters 2017-03-10 07:55:18 -05:00
Ryan Cramer
9df4469314 Fix issue processwire/processwire-issues#200 where the WireMarkupRegions self-closing tags list needed <link> and a few others 2017-03-10 06:08:12 -05:00
Ryan Cramer
ed0ba504bc Fix issue processwire/processwire-issues#199 add clarification notes to InputfieldFile "Inputfield Type" option, and prevent selection of "Image" type when in a FieldtypeFile 2017-03-10 05:50:18 -05:00
Ryan Cramer
020c1beca5 Fix issue processwire/processwire-issues#197 correcting an issue with repeater item label contained string "{images.count}" 2017-03-09 15:12:26 -05:00
Ryan Cramer
97f33e02ed Fix issue processwire/processwire-issues#198 (typo misspelling in phpdoc) 2017-03-09 10:16:05 -05:00
Ryan Cramer
95f5cba2ed Fix issue processwire/processwire-issues#196 (cosmetics) 2017-03-09 10:08:54 -05:00
Ryan Cramer
a3ba477346 Fix issue processwire/processwire-issues#194 2017-03-09 09:48:32 -05:00
Ryan Cramer
e87dcd5985 Fix issue processwire/processwire-issues#192 where inserted emoji could cause text to be truncated on systems using dbEngine "utf8" (as opposed to "utf8mb4"). Because the emoji/MB4 detection and replacement has some overhead, it's not enabled by default. To enable, set $config->dbStripMB4=true; in your /site/config.php file. 2017-03-09 09:11:30 -05:00
Ryan Cramer
25bfb8a5a6 Fix issue processwire/processwire-issues#208 where JqueryWireTabs was not properly remembering current tab between requests 2017-03-09 05:44:14 -05:00
Ryan Cramer
bbb8e987c9 Add support for Pages > Tree navigation: drill down through the page tree within the top navigation dropdowns. This works in AdminThemeDefault and AdminThemeUikit but not in AdminThemeReno (which just shows the first level, per its nav setup). 2017-03-08 11:43:58 -05:00
Ryan Cramer
c4ea3c3356 Fix issue processwire/processwire-issues#189 FieldtypeOptions and "!=" operators 2017-03-07 10:31:13 -05:00
Ryan Cramer
f32593adfa Fix issue processwire/processwire-issues#188 to provide more detailed warning message when installing module that doesn't meet requirements 2017-03-07 09:03:58 -05:00
Ryan Cramer
d7d392fe95 Fix issue #187 where $page->httpUrl didn't respect Template::slashUrls==0 setting when used in non-multi-language. 2017-03-07 08:49:28 -05:00
Ryan Cramer
bc032e1ce3 Minor optimization to a few Page hook method calls in Fieldtype 2017-03-07 08:32:32 -05:00
Ryan Cramer
4d6ce48252 Update login locale detection to suggest different actions depending on whether multi-language support is installed, per processwire/processwire-issues#184 2017-03-07 08:24:04 -05:00
Ryan Cramer
c7d4e5f662 Fix issue processwire/processwire-issues#185 by adding a strict mode to the date sanitizer and updating the method notes a bit. 2017-03-07 07:54:37 -05:00
Ryan Cramer
d5971733f2 Tweak to InputfieldDatetime datepicker button option, plus adjustment to ProcessPageEdit when in modal field edit 2017-03-06 08:43:25 -05:00
Ryan Cramer
a12cb029bb Various updates supporting core admin theme customization and bump version to 3.0.54 2017-03-03 10:37:33 -05:00
Ryan Cramer
f137dd2966 Updated several modules and classes to better support admin theme customization, consistent with needs in AdminThemeUikit 2017-02-24 12:23:57 -05:00
Ryan Cramer
ad90996c0b Update CKEditor version to 4.6.2 2017-02-13 06:05:42 -05:00
Ryan Cramer
ceae45cda7 Add problematic basenaem/locale detection and warning per issue processwire/processwire-issues#157 2017-02-06 06:38:59 -05:00
Ryan Cramer
310ea9d281 Bump version to 3.0.52 2017-02-03 11:26:05 -05:00
Ryan Cramer
4fa5641256 Fix issue processwire/processwire-issues#182 2017-02-03 06:02:36 -05:00
Ryan Cramer
60d418fe12 Fix issue processwire/processwire-issues#117 2017-02-02 10:07:01 -05:00
Ryan Cramer
21f48868a1 Fix issue processwire/processwire-issues#178 2017-02-02 09:40:48 -05:00
Ryan Cramer
8305215407 Fix issue processwire/processwire-issues#175 2017-02-02 09:13:22 -05:00
Ryan Cramer
666ddc528b Fix issue processwire/processwire-issues#181 2017-02-02 05:56:44 -05:00
Ryan Cramer
e518650515 Fix issue processwire/processwire-issues#171 which updates our reserved field names list 2017-02-01 09:37:24 -05:00
Ryan Cramer
1f021206d1 Attempt to fix issue processwire/processwire-issues#168 2017-02-01 09:21:42 -05:00
Ryan Cramer
8e08e0d5ac Fix issue processwire/processwire-issues#163 2017-02-01 08:28:24 -05:00
Ryan Cramer
144bd01619 Fix issue processwire/processwire-issues#153 where selector fails when there is leading comma in quoted value 2017-02-01 06:36:40 -05:00
Ryan Cramer
81adf00b8f Some optimizations to WireMarkupRegions class, and add a hasField property to Inputfield modules when paired with a Fieldtype (similar to existing hasFieldtype property, except is a Field object rather than Fieldtype object). 2017-02-01 06:11:15 -05:00
Ryan Cramer
1886f132b5 Fix for debug mode warning in WireMarkupRegions plus other minor tweaks 2017-01-29 07:08:18 -05:00
Ryan Cramer
933d9de724 Minor admin css adjustment and bump version number to 3.0.51 2017-01-27 14:08:27 -05:00
Ryan Cramer
4a3812efe0 Add a "Publish and Add Another" option to the Submit button options in ProcessPageAdd.module (for when template contains only a title) 2017-01-27 14:05:44 -05:00
Ryan Cramer
01f4419113 Some adjustments to WireMarkupRegions 2017-01-27 14:04:31 -05:00
Ryan Cramer
2becc0cbba Add an val() method to the Inputfield class for consistency with jQuery. This method is a shortcut for getting or setting the value attribute of Inputfield objects 2017-01-27 14:03:28 -05:00
Ryan Cramer
279f65ec65 Small adjustment to InputfieldSelector to prevent access control scenario that hides selectable options for FieldtypePage/InputfieldPage fields. 2017-01-22 08:10:48 -05:00
Ryan Cramer
b50b3aa3c7 Update MarkupFieldtype class to detect FieldtypeLanguageInterface objects 2017-01-22 07:18:10 -05:00
Ryan Cramer
f01ad1fe25 Bump version to 3.0.50 2017-01-20 15:25:45 -05:00
Ryan Cramer
31efae4d16 Fix issue processwire/processwire-issues#166 2017-01-20 11:22:29 -05:00
Ryan Cramer
5a8a4af23f Updates to WireMarkupRegions class to support use of "pw-id" and "data-pw-id" as an alternative to "id", and support of "pw-[action]=id" and "data-pw-[action]=id" attributes as an alternative to the "pw-" class names introduced last week. The [action] can be: append, prepend, replace, remove, before, after. 2017-01-20 11:08:38 -05:00
Ryan Cramer
65eb8da0ff Various minor updates 2017-01-20 10:53:57 -05:00
Ryan Cramer
0d82cee465 Fix issue processwire/processwire-issues#164 2017-01-20 10:16:36 -05:00
Ryan Cramer
07dbc4e6f6 Fix issue #161 where deleted page using default file/image fallback could delete fallback file if deleting page without outputFormatting enabled. 2017-01-20 09:49:19 -05:00
Ryan Cramer
a434f6193d Bump version to 3.0.49 and adjustment to debug info generated in WireMarkupRegions.php 2017-01-13 16:52:26 -05:00
Ryan Cramer
062ebd6663 Move some code from PageRender.module to WireMarkupRegions.php and make additional updates to WireMarkupRegions, adding support for nested/recursive regions. 2017-01-13 12:23:16 -05:00
Ryan Cramer
12a45994bf Add support for markup regions, a simple new template file output strategy that bridges the gap between direct and delayed output. See comments in the PageRender.module file for more details. 2017-01-12 15:45:07 -05:00
Ryan Cramer
6bfe4e65e2 Minor corrections to phpdoc in Page, WireArray and PageArray 2017-01-12 15:09:22 -05:00
Ryan Cramer
501a097e29 Minor improvements to installer, mostly phpdoc related 2017-01-12 15:08:28 -05:00
Ryan Cramer
307d7c9e7d Adjustment to Page::isChanged() method to reduce Pages::save() overhead 2017-01-09 08:12:42 -05:00
Ryan Cramer
43986173fb Fix issue #151, minor correction to FileCompiler affecting single lines that contain both className and function from ProcessWire namespace at the same time 2017-01-08 06:52:13 -05:00
Ryan Cramer
5fbbd9e2c6 Bump version to 3.0.48 2017-01-06 16:25:29 -05:00
Ryan Cramer
cde8b0a3e3 Add some initial stuff to support pages export/import. Nothing to use here yet, just building the foundation. 2017-01-06 12:25:24 -05:00
Ryan Cramer
afd0e8bc95 Adjustment to FileCompiler to improve detection of false positive end-of-PHP blocks 2017-01-06 11:16:26 -05:00
Ryan Cramer
8d97815110 Minor correction to previous commit 2017-01-05 15:36:15 -05:00
Ryan Cramer
d4d90a53fe Add @rolandtoth PR #16 that triggers showInputfield or hideInputfield events when showing/hiding Inputfields due to dependencies. 2017-01-05 14:46:42 -05:00
Ryan Cramer
d785fd7523 Minor adjustments to comment blocks: Add @LostKobrakai PR #42, PR #44, PR #45 and add @owzim PR #43 2017-01-05 14:32:09 -05:00
Ryan Cramer
928a399b8a Add @gmclelland PR #47 that fixes "search autocomplete keyboard focus errors". This also adds a compromise of accommodates PR #48 which retains support of up-arrow-to-close only if down arrow hasn't already been pressed. See also processwire/processwire-issues#125 and processwire/processwire-issues#141 2017-01-05 14:14:29 -05:00
Ryan Cramer
9ed0e415ed Adjustment to yesterday's FileCompiler updates that corrects a case where wire() function calls are used within include() statements 2017-01-05 13:02:12 -05:00
Ryan Cramer
c0ef1aea92 Fix issue processwire/processwire-issues#98 and also make some upgrades/optimizations to FileCompiler 2017-01-04 13:06:03 -05:00
Ryan Cramer
506187d871 Merge branch 'adrianbj-fix-input-url-path' into dev 2017-01-03 05:17:04 -05:00
Ryan Cramer
34ee4e051c Adjustment to PR #46 2017-01-03 05:16:32 -05:00
Ryan Cramer
5238b00250 Merge branch 'fix-input-url-path' of https://github.com/adrianbj/processwire into adrianbj-fix-input-url-path 2017-01-03 05:14:16 -05:00
Ryan Cramer
0d8cce638c Minor adjustments 2017-01-03 05:13:21 -05:00
Ryan Cramer
51bda526eb Bump version to 3.0.47, fix issue #139, issue #123, and issue #115 2016-12-30 13:09:07 -05:00
Ryan Cramer
8cb944cf52 Some updates to the template_ids support for FieldtypePage/InputfieldPage 2016-12-30 07:22:51 -05:00
Ryan Cramer
7d57fc5784 Fix issue processwire/processwire-issues#135 2016-12-29 10:48:07 -05:00
Ryan Cramer
c2cf538317 Minor CKEditor README.md update related to last commit 2016-12-29 10:06:24 -05:00
Ryan Cramer
264c5b0b54 Fix issue processwire/processwire-issues#133 updating a few links pointing to old GitHub repo to point to new one 2016-12-29 10:03:28 -05:00
Ryan Cramer
4402932ee6 Add request issue processwire/processwire-issues#137 2016-12-29 09:55:43 -05:00
Ryan Cramer
88e143126f Fix issue processwire/processwire-issues#138 where PageFinder had a pagination issue introduced in 3.0.46 2016-12-28 13:55:52 -05:00
Ryan Cramer
e12095e622 Attempt to fix issue processwire/processwire-issues#134 2016-12-28 07:03:17 -05:00
Ryan Cramer
20b6ebb81f Fix issue #128 2016-12-26 11:10:09 -05:00
Ryan Cramer
b6ba7049a0 Fix issue #127 2016-12-26 10:35:04 -05:00
Ryan Cramer
4e2a3c1bac Fix issue that was preventing non-local property hooks from working in WireHooks class 2016-12-26 09:31:42 -05:00
Ryan Cramer
622896e028 Fix issues #130 and #131 (dup) 2016-12-25 07:44:55 -05:00
Ryan Cramer
e8b9f12943 Bump version to 3.0.46 2016-12-23 15:13:13 -05:00
Ryan Cramer
ba9688af65 Improvements to dropdown submit buttons so that the menu doesn't disappear immediately when accidentally outside of it. 2016-12-23 12:24:52 -05:00
Ryan Cramer
e14d52f722 Add new $pages API methods: findIDs(), sort(), insertAfter, insertBefore(). Add support for negative "limit" and "start" values in selectors, where negative values reference the end of the set rather than the beginning. Add support for "eq=n" (or alias "index=n") selectors (both WireArray and PageFinder) for pulling a specific n'th item, can also specify "first" or "last" for "n". Update $page->editUrl(true) method to force return of edit URL with scheme and hostname. 2016-12-23 12:18:58 -05:00
adrianbj
137cbbd186 Fix for incorrect path returned from $input->url() when called before page is available.
It used to return: 
//admin/page/edit/-id-1111/?id=1111
Instead of the correct:
/admin/page/edit/?id=1111

This appears to fix everything correctly.
2016-12-23 08:38:20 -08:00
Ryan Cramer
0844bf2e47 Fix issue processwire/processwire-issues#119 2016-12-20 06:27:26 -05:00
Ryan Cramer
381293e0c7 Add a new $pages->findIDs() method, plus some experimental options in PageFinder 2016-12-19 13:38:07 -05:00
Ryan Cramer
e34918c73a Attempt to fix processwire/processwire-issues#122 2016-12-19 13:00:20 -05:00
Ryan Cramer
2570d6c86d Fix issue processwire/processwire-issues#121 2016-12-19 12:34:59 -05:00
Ryan Cramer
315251fa04 Some optimizations to PagesLoader::find() and findShortcut() methods. 2016-12-19 07:00:25 -05:00
Ryan Cramer
aab97a0b00 Fix inadvertent debug mode memory leak issue when using $pages->findMany(), plus optimize PageTable for faster load when sortfield(s) are in use. Per @apeisa / Avoine. 2016-12-19 06:57:37 -05:00
Ryan Cramer
3f758312a6 Bump version to 3.0.45 2016-12-16 15:19:38 -05:00
Ryan Cramer
8d8d9dfe3d Update the PageArrayIterator::$chunkSize setting to be configurable via $config->lazyPageChunkSize per @apeisa 2016-12-16 11:12:23 -05:00
Ryan Cramer
ffde966920 Upgrades to configuration of FieldtypePage and InputfieldPage (as seen in field editor), Upgrades to Page and WireArray __invoke() support, Rewrite Selectors::stringHasSelector() method to make it more accurate, Upgrades to Sanitizer::minArray(), add support for load-time filtering to FieldtypePage (Page fields), plus some other small tweaks. 2016-12-16 10:45:32 -05:00
Ryan Cramer
83846d1fe2 Minor tweaks 2016-12-14 12:26:26 -05:00
Ryan Cramer
7e262fa60d Some updates to the wireRegion()/region() function to support locking prepend or append 2016-12-14 11:36:26 -05:00
Ryan Cramer
2b9a7adbcb Update to allow for Page objects to load pages (children, parent, etc.) that don't get cached when their load option specified cache=false. Also makes the 'parent' page lazy loading for Page objects, so that it doesn't load the parent Page until a $page->parent() call is requested. Updates for @apeisa / Avoine request. 2016-12-14 11:02:32 -05:00
Ryan Cramer
3821dc2be3 Add add inline script support to Inputfield elements rendered with Lister per processwire/processwire-issues#112 2016-12-14 06:00:38 -05:00
Ryan Cramer
d077dfa32b Update InputfieldRepeater to process inline scripts from Inputfields per processwire/processwire-issues#112 2016-12-14 05:47:16 -05:00
Ryan Cramer
953ca72014 Various updates including optimizations to WireHooks, support for a hookable renderReadyHook method in Inputfield, cache prevention measures for Pages::findMany() per @apeisa, and some work in progress on InputfieldSelector support for improved "custom (field=value)" searches. 2016-12-13 14:24:01 -05:00
Ryan Cramer
e2e8c35c2c Fix issue processwire/processwire-issues#110 2016-12-12 09:26:09 -05:00
Ryan Cramer
f0212dcc9c Updates to support multiple template selections for Page fields per @apeisa and @niklam PR #22, plus bump version to 3.0.44 2016-12-09 14:16:04 -05:00
Ryan Cramer
880810c6bb Additional repeater updates including addition of a "minimum items" option, and support for an accordion mode. 2016-12-09 10:08:55 -05:00
Ryan Cramer
6027e87a5e Repeater updates continued with a complete refactoring of the InputfieldRepeater.js file. This update also corrects the depth-dragging issues that were present in the last commit (issues were especially noticable dragging when using AdminThemeReno). Also adds a repeater open all/close all function per processwire/processwire-requests#33 - to use it, double click the "on/off" toggle icon that appears next to the trash icon. 2016-12-08 14:02:43 -05:00
Ryan Cramer
d92674fd4a Minor adjustment to improve repeater depth drag behavior in AdminThemeReno. Still not there yet, but this adjustment improves it a little bit. Works much better in AdminThemeDefault for some reason. 2016-12-07 15:24:42 -05:00
Ryan Cramer
c44329817c Correct issue where AdminThemeReno was missing the extra "head" output, which prevented the vex dialogs from being styled 2016-12-07 12:29:05 -05:00
Ryan Cramer
d1dcebe002 Update cachebuster version code in admin themes, just in case the previous commits updates to main.js are cached and not immediately visible 2016-12-07 12:21:47 -05:00
Ryan Cramer
e2f9597c5a Update Repeaters to add these features: 1) Support for repeater depth per @jlahijani, 2) Add new repeater item "clone" feature, 3) Add support for max items, 4) Add double-click trash icon to "delete all" feature. This commit also adds the Vex library for nicer looking Javascript alert and confirm boxes, which we use to confirm "clone" and "delete all" operations in repeaters, and will use elsewhere. 2016-12-07 11:24:49 -05:00
Ryan Cramer
423fbe6f57 Fix issue in Page.php formatFieldValue function that occurs when formatting a single image|null field. 2016-12-03 07:55:08 -05:00
Ryan Cramer
ac9487e967 Bump version to 3.0.43 2016-12-02 13:31:09 -05:00
Ryan Cramer
af5cda1d6d Minor adjustment per remaining button in issue in processwire/processwire-issues#84 2016-12-02 11:23:24 -05:00
Ryan Cramer
707cd9e735 Expand upon the Page::url() method to include an $options argument that adds many new capabilities. 2016-12-02 10:38:49 -05:00
Ryan Cramer
7eddb51bc9 Some documentation adjustments to WireArray 2016-12-01 12:36:53 -05:00
Ryan Cramer
015725191b Fix issue processwire/processwire-issues#100 where WordPress creating a fake mb_strlen() function made PW think that multibyte support was installed. 2016-12-01 12:07:25 -05:00
Ryan Cramer
9cdf68e6b8 Fix processwire/processwire-issues#99 where multi-instance URLs that had hostname specified were missing a leading slash 2016-12-01 12:00:41 -05:00
Ryan Cramer
cbdf8e9063 Fix issue processwire/processwire-issues#97 where using {images.count} in repeater label caused issue with loading images in the repeater 2016-12-01 11:22:40 -05:00
Ryan Cramer
ef42513831 Add PageFieldValueInterface as an implementation option for objects supporting page field values. Basically a helper for certain object-based values like Pagefiles/Pageimages, and likely others. 2016-12-01 10:56:04 -05:00
Ryan Cramer
30f96593bb Fix issue processwire/processwire-issues#94 for integer field not adding min attribute to HTML5 number type when 0 specified as minimum and no maximum specified. 2016-12-01 05:40:24 -05:00
Ryan Cramer
b8e51db176 Some updates to touch support per issue #84, plus a few other minor things that got bundled in 2016-11-30 13:18:14 -05:00
Ryan Cramer
6a3789d892 Merge branch 'marcus-herrmann-fix/improve-a11y-markup-pager-nav' into dev 2016-11-29 10:39:50 -05:00
Ryan Cramer
c4958461f1 Some changes to Marcus's PR, plus additional updates to MarkupPagerNav module, including expanded documentation 2016-11-29 10:38:26 -05:00
Ryan Cramer
103642ef6d Merge branch 'fix/improve-a11y-markup-pager-nav' of https://github.com/marcus-herrmann/processwire-1 into marcus-herrmann-fix/improve-a11y-markup-pager-nav 2016-11-29 06:06:06 -05:00
Ryan Cramer
76cb88815a Merge branch 'teppokoivula-feature-comments-manager-tweaks' into dev 2016-11-29 06:00:50 -05:00
Ryan Cramer
d3f9d4fd5b Merge branch 'feature-comments-manager-tweaks' of https://github.com/teppokoivula/processwire-1 into teppokoivula-feature-comments-manager-tweaks 2016-11-29 06:00:34 -05:00
Ryan Cramer
53612e9489 Update ProcessField to use more verbose field type names for additional clarity. Also a couple minor cosmetic adjustments in LanguageSupport 2016-11-28 11:04:02 -05:00
Ryan Cramer
bba6e3b00f Add option to exclude FileCompiler at include() time by specifying include(/*NoCompile*/'filename.php'); or include('filename.php'/*NoCompile*/);. The include() can also be include_once(), require(), require_once(), wireRenderFile(), wireIncludeFile() or new TemplateFile(). Relevent to PR #13 2016-11-28 10:59:33 -05:00
mhe
bf2ff863dd Update/simplify MarkupPagerNav default markup 2016-11-27 17:12:01 +01:00
mhe
016290192f Use aria-current also in built in site profiles 2016-11-27 17:10:22 +01:00
mhe
7038b787f0 Add defaults, labels, markers for accessibility to MarkupPagerNav 2016-11-27 10:47:25 +01:00
teppokoivula
a8efd4c04b Fix typo and make tab navigation always visible
This commit fixes a typo that prevented using the module and also makes
tab navigation visible even when active tab is empty.

Fixes processwire/processwire-issues#95
2016-11-26 20:42:45 +02:00
Ryan Cramer
332dbb317e Merge branch 'marcus-herrmann-fix/a11y-core-site-profiles' into dev 2016-11-25 15:04:41 -05:00
Ryan Cramer
cfb6823d49 Merge branch 'fix/a11y-core-site-profiles' of https://github.com/marcus-herrmann/processwire-1 into marcus-herrmann-fix/a11y-core-site-profiles 2016-11-25 15:03:34 -05:00
Ryan Cramer
35df716082 Additional documentation updates and bump version to 3.0.42 2016-11-25 14:58:02 -05:00
Ryan Cramer
942cac2707 Update $sanitizer->testAll() method for more methods per processwire/processwire-issues#85 2016-11-23 07:15:17 -05:00
Ryan Cramer
2a66b67174 Fix issue processwire/processwire-issues#92 where $sanitizer->markupToText() left useless trailing character when replacing br tags 2016-11-23 06:43:14 -05:00
Ryan Cramer
c1f4693ca0 Documentation updates to several core classes 2016-11-22 14:26:41 -05:00
Ryan Cramer
3647a47b86 Adjustment to ProcessPageEditLink.module to correct debug mode warnings that appear when called from inside a CKEditor field that's in a repeater 2016-11-20 06:48:52 -05:00
mhe
28d119c273 Improve accessibility in site profiles 2016-11-20 09:47:25 +01:00
Ryan Cramer
d013ef8550 Merge branch 'derixithy-dev' into dev 2016-11-18 12:29:48 -05:00
Ryan Cramer
6fbfdbab95 Merge branch 'dev' of https://github.com/derixithy/processwire into derixithy-dev 2016-11-18 12:28:39 -05:00
Derixithy
3897df4436 Added support for cookie domain 2016-11-07 20:14:52 +01:00
1344 changed files with 135457 additions and 16859 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
$ cat .gitattributes
*.module linguist-language=PHP

View File

@@ -410,7 +410,7 @@ https://processwire.com/about/license/mit/
The MIT License (MIT)
Copyright (c) 2015 Ryan Cramer <or other year/person if indicated in file>
Copyright (c) 2018 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

View File

@@ -140,5 +140,5 @@ resolved any issues.
------
Copyright 2016 by Ryan Cramer / Ryan Cramer Design, LLC
Copyright 2018 by Ryan Cramer / Ryan Cramer Design, LLC

View File

@@ -93,6 +93,10 @@ DirectoryIndex index.php index.html index.htm
# -----------------------------------------------------------------------------------------------
# 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]
# -----------------------------------------------------------------------------------------------
# 10. Set an environment variable so the installer can detect that mod_rewrite is active.
@@ -201,7 +205,10 @@ DirectoryIndex index.php index.html index.htm
# 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
# below if you don't need to use the $config->pagefileSecure option.
# below if you don't need to use the $config->pagefileSecure option. After uncommenting, test
# a URL like domain.com/site/assets/files/test.jpg to make sure you are getting a 404 and not
# your homepage. If getting your homepage, then either: do not use this option, or comment out
# section #2 above that makes ProcessWire the 404 handler.
# -----------------------------------------------------------------------------------------------
# RewriteCond %{REQUEST_FILENAME} !\.(jpg|jpeg|gif|png|ico)$ [NC]

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
</div><!--/#main-->
</main>
<!-- footer -->
<footer id='footer'>
<footer id='footer' role="contentinfo">
<p>
Powered by <a href='http://processwire.com'>ProcessWire CMS</a> &nbsp; / &nbsp;
<?php

View File

@@ -22,7 +22,7 @@ function renderNav(PageArray $items) {
if(!$items->count()) return;
echo "<ul class='nav'>";
echo "<ul class='nav' role='navigation'>";
// cycle through all the items
foreach($items as $item) {
@@ -30,7 +30,7 @@ function renderNav(PageArray $items) {
// render markup for each navigation item as an <li>
if($item->id == wire('page')->id) {
// if current item is the same as the page being viewed, add a "current" class to it
echo "<li class='current'>";
echo "<li class='current' aria-current='true'>";
} else {
// otherwise just a regular list item
echo "<li>";
@@ -67,15 +67,16 @@ function renderNavTree($items, $maxDepth = 3) {
// $out is where we store the markup we are creating in this function
// start our <ul> markup
echo "<ul class='nav nav-tree'>";
echo "<ul class='nav nav-tree' role='navigation'>";
// cycle through all the items
foreach($items as $item) {
// markup for the list item...
// if current item is the same as the page being viewed, add a "current" class to it
// if current item is the same as the page being viewed, add a "current" class and
// visually hidden text for screen readers to it
if($item->id == wire('page')->id) {
echo "<li class='current'>";
echo "<li class='current' aria-current='true'><span class='visually-hidden'>Current page: </span>";
} else {
echo "<li>";
}

View File

@@ -11,7 +11,7 @@
<body class='has-sidebar'>
<!-- top navigation -->
<ul class='topnav'><?php
<ul class='topnav' role='navigation'><?php
// top navigation consists of homepage and its visible children
$homepage = $pages->get('/');
@@ -25,7 +25,7 @@
if($child->id == $page->rootParent->id) {
// this $child page is currently being viewed (or one of it's children/descendents)
// so we highlight it as the current page in the navigation
echo "<li class='current'><a href='$child->url'>$child->title</a></li>";
echo "<li class='current' aria-current='true'><span class='visually-hidden'>Current page: </span><a href='$child->url'>$child->title</a></li>";
} else {
echo "<li><a href='$child->url'>$child->title</a></li>";
}
@@ -40,12 +40,13 @@
<!-- search form -->
<form class='search' action='<?php echo $pages->get('template=search')->url; ?>' method='get'>
<input type='text' name='q' placeholder='Search' value='' />
<button type='submit' name='submit'>Search</button>
<label for='search' class='visually-hidden'>Search:</label>
<input type='text' name='q' id='search' placeholder='Search' value='' />
<button type='submit' name='submit' class='visually-hidden'>Search</button>
</form>
<!-- breadcrumbs -->
<div class='breadcrumbs'><?php
<div class='breadcrumbs' role='navigation' aria-label='You are here:'><?php
// breadcrumbs are the current page's parents
foreach($page->parents() as $item) {
@@ -56,5 +57,5 @@
?></div>
<div id='main'>
<main id='main'>

View File

@@ -23,7 +23,7 @@ include('./_head.php'); // include header markup ?>
?></div><!-- end content -->
<div id='sidebar'><?php
<aside id='sidebar'><?php
// rootParent is the parent page closest to the homepage
// you can think of this as the "section" that the user is in
@@ -40,6 +40,6 @@ include('./_head.php'); // include header markup ?>
// output sidebar text if the page has it
echo $page->sidebar;
?></div><!-- end sidebar -->
?></aside><!-- end sidebar -->
<?php include('./_foot.php'); // include footer markup ?>

View File

@@ -6,6 +6,7 @@
* 3. Main content and sidebar
* 4. Footer
* 5. Media queries for responsive layout
* 6. Accessibility helpers
*
*/
@@ -124,9 +125,6 @@ form.search {
border: 1px solid #ccc;
width: 100%;
}
form.search button {
display: none;
}
.breadcrumbs {
clear: both;
@@ -291,3 +289,43 @@ figure figcaption {
font-size: 115%;
}
}
/*********************************************************************
* 6. Accessibility helpers
*
*/
/* Hide visually, but remain approachable for screenreader */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
white-space: nowrap;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
border: 0;
}
/* Show bypass link on hover */
.element-focusable:focus {
clip: auto;
overflow: visible;
height: auto;
}
/* Sample styling for bypass link */
.bypass-to-main:focus {
top: 0;
left: 0;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
background: #333;
color: #fff;
}

View File

@@ -6,7 +6,7 @@
if($page->numChildren) {
echo "<ul class='nav'>";
echo "<ul class='nav' role='navigation'>";
foreach($page->children as $child) {
echo "<li><p><a href='{$child->url}'>{$child->title}</a><br />";
@@ -24,7 +24,7 @@ if($page->numChildren) {
</div><!--/content-->
<div id="footer" class="footer">
<div id="footer" class="footer" role="contentinfo">
<div class="container">
<p>Powered by <a href='http://processwire.com'>ProcessWire Open Source CMS/CMF</a></p>
</div>

View File

@@ -32,6 +32,8 @@
</head>
<body>
<a href="#bodycopy" class="visually-hidden element-focusable bypass-to-main">Skip to content</a>
<p id='bgtitle'><?php
// print the section title as big faded text that appears near the top left of the page
@@ -45,11 +47,12 @@
<a href='<?php echo $config->urls->root; ?>'><p id='logo'>ProcessWire</p></a>
<ul id='topnav'><?php
<ul id='topnav' role='navigation'><?php
// Create the top navigation list by listing the children of the homepage.
// If the section we are in is the current (identified by $page->rootParent)
// then note it with <a class='on'> so we can style it differently in our CSS.
// then note it with <a class='on'> so we can style it differently in our CSS
// and add a text that is visually hidden, but available for screen readers.
// In this case we also want the homepage to be part of our top navigation,
// so we prepend it to the pages we cycle through:
@@ -58,19 +61,24 @@
$children->prepend($homepage);
foreach($children as $child) {
if ($child === $page->rootParent) {
$class = " class='on'";
$indicator = "<span class='visually-hidden'>Current page: </span>";
$ariaState = " aria-current='true' ";
}
$class = $child === $page->rootParent ? " class='on'" : '';
echo "<li><a$class href='{$child->url}'>{$child->title}</a></li>";
echo "<li><a$class$ariaState href='{$child->url}'>$indicator{$child->title}</a></li>";
}
?></ul>
<ul id='breadcrumb'><?php
<ul id='breadcrumb' role='navigation' aria-label='You are here:'><?php
// Create breadcrumb navigation by cycling through the current $page's
// parents in order, linking to each:
foreach($page->parents as $parent) {
echo "<li><a href='{$parent->url}'>{$parent->title}</a> &gt; </li>";
echo "<li><a href='{$parent->url}'>{$parent->title}</a> <span class='visually-hidden'>&gt;</span> </li>";
}
?></ul>
@@ -87,6 +95,7 @@
?></h1>
<form id='search_form' action='<?php echo $config->urls->root?>search/' method='get'>
<label for='search_query' class='visually-hidden'>Search:</label>
<input type='text' name='q' id='search_query' value='<?php echo htmlentities($input->whitelist('q'), ENT_QUOTES, 'UTF-8'); ?>' />
<button type='submit' id='search_submit'>Search</button>
</form>
@@ -111,7 +120,7 @@
<div class="container">
<div id="sidebar">
<div id="sidebar" role='complementary'>
<?php
@@ -128,11 +137,12 @@
// We have determined that we're not on the homepage
// and that this section has child pages, so make navigation:
echo "<ul id='subnav' class='nav'>";
echo "<ul id='subnav' class='nav' role='navigation'>";
foreach($page->rootParent->children as $child) {
$class = $page === $child ? " class='on'" : '';
echo "<li><a$class href='{$child->url}'>{$child->title}</a></li>";
$ariaState = $page === $child ? " aria-current='true' " : '';
echo "<li><a$class$ariaState href='{$child->url}'>{$child->title}</a></li>";
}
echo "</ul>";
@@ -156,5 +166,5 @@
</div><!--/sidebar-->
<div id="bodycopy">
<div id="bodycopy" role="main">

View File

@@ -502,3 +502,45 @@ body, input, textarea, table {
z-index: 9999;
}
/*********************************************************************
* 6. Accessibility helpers
*
*/
/* Hide visually, but remain approachable for screenreader */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
white-space: nowrap;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
border: 0;
}
/* Show bypass link on hover */
.element-focusable:focus {
clip: auto;
overflow: visible;
height: auto;
}
/* Sample styling for bypass link */
.bypass-to-main:focus {
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
background: #333;
color: #fff;
}

View File

@@ -27,7 +27,7 @@ class Helloworld extends WireData implements Module {
return array(
// The module'ss title, typically a little more descriptive than the class name
// The module's title, typically a little more descriptive than the class name
'title' => 'Hello World',
// version number

View File

@@ -69,9 +69,6 @@ learning module development:
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
--------------------

View File

@@ -41,12 +41,14 @@
</head>
<body class="<?php if($sidebar) echo "has-sidebar "; ?>">
<a href="#main" class="visually-hidden element-focusable bypass-to-main">Skip to content</a>
<!-- top navigation -->
<ul class='topnav'><?php
<ul class='topnav' role='navigation'><?php
// top navigation consists of homepage and its visible children
foreach($homepage->and($homepage->children) as $item) {
if($item->id == $page->rootParent->id) {
echo "<li class='current'>";
echo "<li class='current' aria-current='true'><span class='visually-hidden'>Current page: </span>";
} else {
echo "<li>";
}
@@ -59,12 +61,13 @@
<!-- search form-->
<form class='search' action='<?php echo $pages->get('template=search')->url; ?>' method='get'>
<input type='text' name='q' placeholder='Search' value='<?php echo $sanitizer->entities($input->whitelist('q')); ?>' />
<button type='submit' name='submit'>Search</button>
<label for='search' class='visually-hidden'>Search:</label>
<input type='text' name='q' placeholder='Search' id='search' value='<?php echo $sanitizer->entities($input->whitelist('q')); ?>' />
<button type='submit' name='submit' class='visually-hidden'>Search</button>
</form>
<!-- breadcrumbs -->
<div class='breadcrumbs'><?php
<div class='breadcrumbs' role='navigation' aria-label='You are here:'><?php
// breadcrumbs are the current page's parents
foreach($page->parents() as $item) {
echo "<span><a href='$item->url'>$item->title</a></span> ";
@@ -83,9 +86,9 @@
<!-- sidebar content -->
<?php if($sidebar): ?>
<div id='sidebar'>
<aside id='sidebar'>
<?php echo $sidebar; ?>
</div>
</aside>
<?php endif; ?>
</div>

View File

@@ -6,6 +6,7 @@
* 3. Main content and sidebar
* 4. Footer
* 5. Media queries for responsive layout
* 6. Accessibility helpers
*
*/
@@ -124,9 +125,6 @@ form.search {
border: 1px solid #ccc;
width: 100%;
}
form.search button {
display: none;
}
.breadcrumbs {
clear: both;
@@ -297,3 +295,42 @@ figure figcaption {
font-size: 115%;
}
}
/*********************************************************************
* 6. Accessibility helpers
*
*/
/* Hide visually, but remain approachable for screenreader */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
white-space: nowrap;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
border: 0;
}
/* Show bypass link on hover */
.element-focusable:focus {
clip: auto;
overflow: visible;
height: auto;
}
/* Sample styling for bypass link */
.bypass-to-main:focus {
top: 0;
left: 0;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
background: #333;
color: #fff;
}

View File

@@ -144,10 +144,10 @@
"text": "Pfade sollten relativ zum Root-Verzeichnis ihrer ProcessWire Installation sein (d.h. wenn die Seite in einem Unterverzeichnis l\u00e4uft, diesen Teil weglassen)."
},
"9728a8f280cf7e8b0b89b52749f7e800": {
"text": "Bitte lesen Sie die [Anweisungen](https:\/\/github.com\/ryancramerdesign\/ProcessWire\/blob\/dev\/wire\/modules\/Inputfield\/InputfieldCKEditor\/README.md#custom-editor-css-file) zur Benutzung."
"text": "Bitte lesen Sie die [Anweisungen](https:\/\/github.com\/processwire\/processwire\/blob\/master\/wire\/modules\/Inputfield\/InputfieldCKEditor\/README.md#custom-editor-css-file) zur Benutzung."
},
"63ba0561c6027fed354e6c5af252a77b": {
"text": "Bitte lesen Sie die [Anweisungen](https:\/\/github.com\/ryancramerdesign\/ProcessWire\/blob\/dev\/wire\/modules\/Inputfield\/InputfieldCKEditor\/README.md#custom-editor-js-styles-set) zur Benutzung."
"text": "Bitte lesen Sie die [Anweisungen](https:\/\/github.com\/processwire\/processwire\/blob\/master\/wire\/modules\/Inputfield\/InputfieldCKEditor\/README.md#custom-editor-js-styles-set) zur Benutzung."
},
"8cd140c060d298234e0079cb77ee8190": {
"text": "Folgende Plugins wurden gefunden. Markieren Sie die Box neben jedem Plugin welches geladen werden soll."

View File

@@ -60,8 +60,10 @@
</head>
<body class="<?php if($sidebar) echo "has-sidebar"; ?>">
<a href="#main" class="visually-hidden element-focusable bypass-to-main"><?php echo _x('Skip to content', 'bypass'); ?></a>
<!-- language switcher / navigation -->
<ul class='languages'><?php
<ul class='languages' role='navigation'><?php
foreach($languages as $language) {
if(!$page->viewable($language)) continue; // is page viewable in this language?
if($language->id == $user->language->id) {
@@ -76,11 +78,11 @@
?></ul>
<!-- top navigation -->
<ul class='topnav'><?php
<ul class='topnav' role='navigation'><?php
// top navigation consists of homepage and its visible children
foreach($homepage->and($homepage->children) as $item) {
if($item->id == $page->rootParent->id) {
echo "<li class='current'>";
echo "<li class='current' aria-current='true'><span class='visually-hidden'>" . _x('Current page:', 'navigation') . " </span>";
} else {
echo "<li>";
}
@@ -92,7 +94,7 @@
?></ul>
<!-- breadcrumbs -->
<div class='breadcrumbs'><?php
<div class='breadcrumbs' role='navigation' aria-label='<?php echo _x('You are here:', 'breadcrumbs'); ?>'><?php
// breadcrumbs are the current page's parents
foreach($page->parents() as $item) {
echo "<span><a href='$item->url'>$item->title</a></span> ";
@@ -103,12 +105,13 @@
<!-- search engine -->
<form class='search' action='<?php echo $pages->get('template=search')->url; ?>' method='get'>
<input type='text' name='q' placeholder='<?php echo _x('Search', 'placeholder'); ?>' />
<button type='submit' name='submit'><?php echo _x('Search', 'button'); ?></button>
<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'); ?>' />
<button type='submit' name='submit' class='visually-hidden'><?php echo _x('Search', 'button'); ?></button>
</form>
<div id='main'>
<main id='main'>
<!-- main content -->
<div id='content'>
@@ -121,15 +124,15 @@
<!-- sidebar content -->
<?php if($sidebar): ?>
<div id='sidebar'>
<aside id='sidebar'>
<?php echo $sidebar; ?>
</div>
</aside>
<?php endif; ?>
</div>
</main>
<!-- footer -->
<footer id='footer'>

View File

@@ -6,6 +6,7 @@
* 3. Main content and sidebar
* 4. Footer
* 5. Media queries for responsive layout
* 6. Accessibility helpers
*
*/
@@ -156,9 +157,6 @@ form.search {
border: 1px solid #ccc;
width: 100%;
}
form.search button {
display: none;
}
.breadcrumbs {
font-size: 80%;
@@ -351,3 +349,43 @@ figure figcaption {
font-size: 115%;
}
}
/*********************************************************************
* 6. Accessibility helpers
*
*/
/* Hide visually, but remain approachable for screenreader */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
white-space: nowrap;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
border: 0;
}
/* Show bypass link on hover */
.element-focusable:focus {
clip: auto;
overflow: visible;
height: auto;
}
/* Sample styling for bypass link */
.bypass-to-main:focus {
top: 0;
left: 0;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
background: #333;
color: #fff;
}

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/.

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

@@ -0,0 +1,44 @@
<?php
/**
* ProcessWire Configuration File
*
* Site-specific configuration for ProcessWire.
* This config.php file was generated by the ProcessExportProfile module.
*
* 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) 2018 by Ryan Cramer
*
* https://processwire.com
*
*/
if(!defined("PROCESSWIRE")) die();
/*** SITE 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 = true;
$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/

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

@@ -0,0 +1,45 @@
<?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.
*
*/
/** @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-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 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-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;
}
}

28
wire/.editorconfig Normal file
View File

@@ -0,0 +1,28 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
[*.{php,inc,module}]
indent_style = tab
trim_trailing_whitespace = false
insert_final_newline = true
[*.js]
indent_style = tab
trim_trailing_whitespace = true
insert_final_newline = true
[*.{css,less,scss}]
indent_style = tab
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -46,6 +46,9 @@ if(!defined("PROCESSWIRE")) die();
* always have this disabled for live/production sites since it reveals more information
* than is advisible for security.
*
* You may also set this to the constant `Config::debugVerbose` to enable verbose debug mode,
* which uses more memory and time.
*
* #notes This enables debug mode for ALL requests. See the debugIf option for an alternative.
*
* @var bool
@@ -127,6 +130,37 @@ $config->demo = false;
*/
$config->useFunctionsAPI = false;
/**
* Enable use of front-end markup regions?
*
* When enabled, HTML elements with an "id" attribute that are output before the opening
* `<!doctype>` or `<html>` tag can replace elements in the document that have the same id.
* Also supports append, prepend, replace, remove, before and after options.
*
* @var bool
*
*/
$config->useMarkupRegions = 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 *************************************************************************/
@@ -253,6 +287,15 @@ $config->sessionChallenge = true;
* 12: Fingerprint the forwarded/client IP and useragent
* 14: Fingerprint the remote IP, forwarded/client IP and useragent (all).
*
* 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.
*
* 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.
*
* @var int
*
*/
@@ -274,6 +317,17 @@ $config->sessionFingerprint = 1;
*/
$config->sessionCookieSecure = 1;
/**
* Cookie domain for sessions
*
* Enables a session to traverse multiple subdomains.
* Specify a string having “.domain.com” (with leading period) or NULL to disable (default/recommended).
*
* @var string|null
*
*/
$config->sessionCookieDomain = null;
/**
* Number of session history entries to record.
*
@@ -296,6 +350,24 @@ $config->sessionHistory = 0;
*/
$config->userAuthHashType = 'sha1';
/**
* Names (string) or IDs (int) of roles that are not allowed to login
*
* Note that you must create these roles yourself in the admin. When a user has
* one of these named roles, $session->login() will not accept a login from them.
* This affects the admin login form and any other login forms that use ProcessWires
* session system.
*
* The default value specifies a role name of "login-disabled", meaning if you create
* a role with that name, and assign it to a user, that user will no longer be able
* to login.
*
* @var array
*
*/
$config->loginDisabledRoles = array(
'login-disabled'
);
/*** 4. TEMPLATE FILES **************************************************************************/
@@ -509,6 +581,7 @@ $config->fileContentTypes = array(
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/x-png',
'svg' => 'image/svg+xml'
);
@@ -519,7 +592,8 @@ $config->fileContentTypes = array(
*
* #property bool upscaling Upscale if necessary to reach target size? (1=true, 0=false)
* #property bool cropping Crop if necessary to reach target size? (1=true, 0=false)
* #property bool autoRotation Automatically correct orientation?
* #property bool autoRotation Automatically correct orientation? (1=true, 0=false)
* #property bool interlace Use interlaced JPEGs by default? Recommended. (1=true, 0=false)
* #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)
@@ -531,6 +605,7 @@ $config->imageSizerOptions = array(
'upscaling' => true, // upscale if necessary to reach target size?
'cropping' => true, // crop if necessary to reach target size?
'autoRotation' => true, // automatically correct orientation?
'interlace' => false, // use interlaced JPEGs by default? (recommended)
'sharpening' => 'soft', // sharpening: none | soft | medium | strong
'quality' => 90, // quality: 1-100 where higher is better but bigger
'hidpiQuality' => 60, // Same as above quality setting, but specific to hidpi images
@@ -590,8 +665,8 @@ $config->fileCompilerOptions = array(
'siteOnly' => false, // only allow compilation of files in /site/ directory
'showNotices' => true, // show notices about compiled files to superuser when logged in
'logNotices' => true, // log notices about compiled files and maintenance to file-compiler.txt log.
'chmodFile' => $config->chmodFile, // mode to use for created files, i.e. "0644"
'chmodDir' => $config->chmodDir, // mode to use for created directories, i.e. "0755"
'chmodFile' => '', // mode to use for created files, i.e. "0644"
'chmodDir' => '', // mode to use for created directories, i.e. "0755"
'exclusions' => array(), // exclude filenames or paths that start with any of these
'extensions' => array('php', 'module', 'inc'), // file extensions we compile
'cachePath' => $config->paths->cache . 'FileCompiler/', // path where compiled files are stored
@@ -663,6 +738,14 @@ $config->protectCSRF = true;
*/
$config->maxUrlSegments = 4;
/**
* Maximum length for any individual URL segment (default=128)
*
* @var int
*
*/
$config->maxUrlSegmentLength = 128;
/**
* Maximum URL/path slashes (depth) for request URLs
*
@@ -723,6 +806,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
*
@@ -748,11 +841,55 @@ $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;
/*** 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
*
@@ -767,9 +904,7 @@ $config->dbCharset = 'utf8';
/**
* Database engine
*
* MyISAM is the recommended value, but you may also use InnoDB (experimental).
*
* Note that use of 'InnoDB' is currently experimental. Avoid changing this after install.
* May be 'InnoDB' or 'MyISAM'. Avoid changing this after install.
*
*/
$config->dbEngine = 'MyISAM';
@@ -809,30 +944,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)
*
@@ -864,8 +975,18 @@ $config->dbSqlModes = array(
"5.7.0" => "remove:STRICT_TRANS_TABLES,ONLY_FULL_GROUP_BY"
);
/**
* A key=>value array of any additional driver-specific connection options.
*
* @var array
*
*/
$config->dbOptions = array();
/**
* Optional DB socket config for sites that need it (for most you should exclude this)
*
* @var string
*
*/
$config->dbSocket = '';
@@ -878,6 +999,17 @@ $config->dbSocket = '';
*/
$config->dbQueryLogMax = 500;
/**
* Remove 4-byte characters (like emoji) when dbEngine is not utf8mb4?
*
* When charset is not “utf8mb4” and this value is true, 4-byte UTF-8 characters are stripped
* out of inserted values when possible. Note that this can add some overhead to INSERTs.
*
* @var bool
*
*/
$config->dbStripMB4 = false;
/*** 8. MODULES *********************************************************************************/
@@ -928,6 +1060,29 @@ $config->substituteModules = array(
'InputfieldTinyMCE' => 'InputfieldCKEditor'
);
/**
* WireMail module(s) default settings
*
* 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.
*
* #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=[])
*
* @var array
*
*/
$config->wireMail = array(
'module' => '',
'from' => '',
'fromName' => '',
'headers' => array(),
);
/**
* PageList default settings
*
@@ -958,6 +1113,7 @@ $config->pageList = array(
* #property bool confirm Notify user if they attempt to navigate away from unsaved changes?
* #property bool ajaxChildren Whether to load the 'children' tab via ajax
* #property bool ajaxParent Whether to load the 'parent' field via ajax
* #property bool editCrumbs Whether or not breadcrumbs load page editor (false=load page list).
*
* @var array
*
@@ -967,8 +1123,19 @@ $config->pageEdit = array(
'confirm' => true,
'ajaxChildren' => true,
'ajaxParent' => true,
'editCrumbs' => false,
);
/**
* PageAdd default settings
*
* #property string noSuggestTemplates Disable suggestions for new pages (1=disable all, or specify template names separated by space)
*
*/
$config->pageAdd = array(
'noSuggestTemplates' => '',
);
/*** 9. MISC ************************************************************************************/
@@ -988,12 +1155,20 @@ $config->logs = array(
'exceptions',
);
/**
* Include IP address in logs, when applicable?
*
* @var bool
*
*/
$config->logIP = false;
/**
* Default admin theme
*
* Module name of default admin theme for guest and users that haven't already selected one
*
* Core options include: **AdminThemeDefault** or **AdminThemeReno**.
* Core options include: **AdminThemeDefault** or **AdminThemeReno** or **AdminThemeUikit**.
* Additional options will depend on what other 3rd party AdminTheme modules you have installed.
*
* @var string
@@ -1107,6 +1282,14 @@ $config->allowExceptions = false;
*/
$config->usePoweredBy = true;
/**
* Chunk size for lazy-loaded pages used by $pages->findMany()
*
* @var int
*
*/
$config->lazyPageChunkSize = 250;
/**
* Settings specific to InputfieldWrapper class
*
@@ -1123,6 +1306,7 @@ $config->usePoweredBy = true;
*
*/
/*** 10. RUNTIME ********************************************************************************
*
* The following are runtime-only settings and cannot be changed from /site/config.php
@@ -1141,6 +1325,12 @@ $config->https = null;
*/
$config->ajax = false;
/**
* modal: This is automatically set to TRUE when request is in a modal window.
*
*/
$config->modal = false;
/**
* external: This is automatically set to TRUE when PW is externally bootstrapped.
*
@@ -1172,9 +1362,17 @@ $config->versionName = '';
* Value is null, 0, or 1 or higher. This should be kept at null in this file.
*
*/
$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

@@ -15,6 +15,7 @@ namespace PHPSTORM_META {
\wire('') => [
'' == '@',
'config' instanceof \ProcessWire\Config,
'cache' instanceof \ProcessWire\WireCache,
'wire' instanceof \ProcessWire\ProcessWire,
'log' instanceof \ProcessWire\WireLog,
'notices' instanceof \ProcessWire\Notices,
@@ -46,6 +47,7 @@ namespace PHPSTORM_META {
// this one does not appear to work, leaving in case someone knows how to make it work
'' == '@',
'config' instanceof \ProcessWire\Config,
'cache' instanceof \ProcessWire\WireCache,
'wire' instanceof \ProcessWire\ProcessWire,
'log' instanceof \ProcessWire\WireLog,
'notices' instanceof \ProcessWire\Notices,

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,30 @@ abstract class AdminTheme extends WireData implements Module {
*/
protected $bodyClasses = array();
/**
* General purpose classes indexed by name
*
* @var array
*
*/
protected $classes = array();
/**
* Extra markup regions
*
* @var array
*
*/
protected $extraMarkup = array(
'head' => '',
'notices' => '',
'body' => '',
'masthead' => '',
'content' => '',
'footer' => '',
'sidebar' => '', // sidebar not used in all admin themes
);
/**
* URLs to place in link prerender tags
*
@@ -79,9 +103,11 @@ abstract class AdminTheme extends WireData implements Module {
}
/**
* Initialize the admin theme systme and determine which admin theme should be used
* Initialize the admin theme system and determine which admin theme should be used
*
* All admin themes must call this init() method to register themselves.
*
* Note: this should be called after API ready.
*
*/
public function init() {
@@ -100,40 +126,38 @@ abstract class AdminTheme extends WireData implements Module {
// if admin theme has already been set, then no need to continue
if($this->wire('adminTheme')) return;
$isCurrent = false;
/** @var Config $config */
$config = $this->wire('config');
/** @var Session $session */
$session = $this->wire('session');
/** @var string $adminTheme */
$adminTheme = $this->wire('user')->admin_theme;
if($adminTheme) {
// there is user specified admin theme
// check if this is the one that should be used
if($adminTheme == $this->className()) $isCurrent = true;
if($adminTheme == $this->className()) $this->setCurrent();
} else if($this->wire('config')->defaultAdminTheme == $this->className()) {
// there is no user specified admin theme, so use this one
$isCurrent = true;
}
// set as an API variable and populate configuration variables
if($isCurrent) {
$this->wire('adminTheme', $this);
$this->config->paths->set('adminTemplates', $this->config->paths->get($this->className()));
$this->config->urls->set('adminTemplates', $this->config->urls->get($this->className()));
$this->setCurrent();
}
// adjust $config adminThumbOptions[scale] for auto detect when requested
$o = $this->wire('config')->adminThumbOptions;
$o = $config->adminThumbOptions;
if($o && isset($o['scale']) && $o['scale'] === 1) {
$o['scale'] = $this->wire('session')->hidpi ? 0.5 : 1.0;
$this->wire('config')->adminThumbOptions = $o;
$o['scale'] = $session->hidpi ? 0.5 : 1.0;
$config->adminThumbOptions = $o;
}
$this->config->js('modals', $this->config->modals);
$config->js('modals', $config->modals);
if($session->hidpi) $this->addBodyClass('hidpi-device');
if($session->touch) $this->addBodyClass('touch-device');
if($this->wire('session')->hidpi) $this->addBodyClass('hidpi-device');
if($this->wire('session')->touch) $this->addBodyClass('touch-device');
$this->addBodyClass($this->className());
}
public function get($key) {
if($key == 'version') return $this->version;
return parent::get($key);
@@ -147,6 +171,19 @@ abstract class AdminTheme extends WireData implements Module {
return $this->wire('adminTheme') === $this;
}
/**
* Set this admin theme as the current one
*
*/
protected function setCurrent() {
$config = $this->wire('config');
$name = $this->className();
$config->paths->set('adminTemplates', $config->paths->get($name));
$config->urls->set('adminTemplates', $config->urls->get($name));
$config->set('defaultAdminTheme', $name);
$this->wire('adminTheme', $this);
}
/**
* Enables hooks to append extra markup to various sections of the admin page
*
@@ -161,15 +198,7 @@ abstract class AdminTheme extends WireData implements Module {
*
*/
public function ___getExtraMarkup() {
$parts = array(
'head' => '',
'notices' => '',
'body' => '',
'masthead' => '',
'content' => '',
'footer' => '',
'sidebar' => '', // sidebar not used in all admin themes
);
$parts = $this->extraMarkup;
$isLoggedin = $this->wire('user')->isLoggedin();
if($isLoggedin && $this->wire('modules')->isInstalled('InputfieldCKEditor')
&& $this->wire('process') instanceof WirePageEditor) {
@@ -178,21 +207,118 @@ abstract class AdminTheme extends WireData implements Module {
"window.CKEDITOR_BASEPATH='" . $this->wire('config')->urls->InputfieldCKEditor .
'ckeditor-' . InputfieldCKEditor::CKEDITOR_VERSION . "/';</script>";
}
/*
if($isLoggedin && $this->wire('config')->advanced) {
$parts['footer'] = "<p class='AdvancedMode'><i class='fa fa-flask'></i> " . $this->_('Advanced Mode') . "</p>";
}
*/
foreach($this->preRenderURLs as $url) {
$parts['head'] .= "<link rel='prerender' href='$url'>";
}
return $parts;
}
public function addBodyClass($className) {
$this->bodyClasses[$className] = $className;
/**
* Add extra markup to a region in the admin theme
*
* @param string $name
* @param string $value
*
*/
public function addExtraMarkup($name, $value) {
if(!empty($this->extraMarkup[$name])) {
$this->extraMarkup[$name] .= "\n$value";
} else {
$this->extraMarkup[$name] = $value;
}
}
/**
* Add a <body> class to the admin theme
*
* @param string $className
*
*/
public function addBodyClass($className) {
$this->addClass('body', $className);
}
/**
* Get the body[class] attribute string
*
* @return string
*
*/
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);
}
}
}
/**
@@ -214,36 +340,44 @@ abstract class AdminTheme extends WireData implements Module {
// we already have this field installed, no need to continue
if($field) {
$this->message($toUseNote);
return;
} else {
// this will be the 2nd admin theme installed, so add a field that lets them select admin theme
$field = $this->wire(new Field());
$field->name = 'admin_theme';
$field->type = $this->wire('modules')->get('FieldtypeModule');
$field->set('moduleTypes', array('AdminTheme'));
$field->set('labelField', 'title');
$field->set('inputfieldClass', 'InputfieldRadios');
$field->label = 'Admin Theme';
$field->flags = Field::flagSystem;
try {
$field->save();
} catch(\Exception $e) {
// $this->error("Error creating 'admin_theme' field: " . $e->getMessage());
}
}
// this will be the 2nd admin theme installed, so add a field that lets them select admin theme
$field = $this->wire(new Field());
$field->name = 'admin_theme';
$field->type = $this->wire('modules')->get('FieldtypeModule');
$field->set('moduleTypes', array('AdminTheme'));
$field->set('labelField', 'title');
$field->set('inputfieldClass', 'InputfieldRadios');
$field->label = 'Admin Theme';
$field->flags = Field::flagSystem;
$field->save();
$fieldgroup = $this->wire('fieldgroups')->get('user');
$fieldgroup->add($field);
$fieldgroup->save();
// make this field one that the user is allowed to configure in their profile
$data = $this->wire('modules')->getModuleConfigData('ProcessProfile');
$data['profileFields'][] = 'admin_theme';
$this->wire('modules')->saveModuleConfigData('ProcessProfile', $data);
$this->message($this->_('Installed field "admin_theme" and added to user profile settings.'));
$this->message($toUseNote);
if($field && $field->id) {
/** @var Fieldgroup $fieldgroup */
$fieldgroup = $this->wire('fieldgroups')->get('user');
if(!$fieldgroup->hasField($field)) {
$fieldgroup->add($field);
$fieldgroup->save();
$this->message($this->_('Installed field "admin_theme" and added to user profile settings.'));
$this->message($toUseNote);
}
// make this field one that the user is allowed to configure in their profile
$data = $this->wire('modules')->getModuleConfigData('ProcessProfile');
$data['profileFields'][] = 'admin_theme';
$this->wire('modules')->saveModuleConfigData('ProcessProfile', $data);
}
}
/**
* Set a pre-render URL or get currently pre-render URL(s)
*
* #pw-internal
*
* @param string $url
* @return array
*
@@ -254,6 +388,14 @@ abstract class AdminTheme extends WireData implements Module {
}
public function ___uninstall() {
$defaultAdminTheme = $this->wire('config')->defaultAdminTheme;
if($defaultAdminTheme == $this->className()) {
throw new WireException(
"Cannot uninstall this admin theme because \$config->defaultAdminTheme = '$defaultAdminTheme'; " .
"Please add this setting with a different value in /site/config.php"
);
}
/*
if(self::$numAdminThemes > 1) return;

View File

@@ -0,0 +1,810 @@
<?php namespace ProcessWire;
/**
* AdminTheme Framework
*
* The methods in this class may eventually be merged to AdminTheme.php,
* but are isolated to this class during development.
*
* @property bool $isSuperuser
* @property bool $isEditor
* @property bool $isLoggedIn
* @property bool|string $isModal
* @property bool|int $useAsLogin
* @method array getUserNavArray()
*
*/
abstract class AdminThemeFramework extends AdminTheme {
/**
* Is there currently a logged in user?
*
* @var bool
*
*/
protected $isLoggedIn = false;
/**
* Is user logged in with page-edit permission?
*
* @var bool
*
*/
protected $isEditor = false;
/**
* Is current user a superuser?
*
* @var bool
*
*/
protected $isSuperuser = false;
/**
* Is the current request a modal request?
*
* @var bool|string Either false, true, or "inline"
*
*/
protected $isModal = false;
/**
* @var Sanitizer
*
*/
protected $sanitizer;
/**
* Construct
*
*/
public function __construct() {
parent::__construct();
$this->set('useAsLogin', false);
$this->sanitizer = $this->wire('sanitizer');
}
/**
* Override get() method from WireData to support additional properties
*
* @param string $key
* @return bool|int|mixed|null|string
*
*/
public function get($key) {
switch($key) {
case 'isSuperuser': $value = $this->isSuperuser; break;
case 'isEditor': $value = $this->isEditor; break;
case 'isLoggedIn': $value = $this->isLoggedIn; break;
case 'isModal': $value = $this->isModal; break;
default: $value = parent::get($key);
}
return $value;
}
/**
* Initialize and attach hooks
*
* Note: descending classes should call this after API ready
*
*/
public function init() {
$user = $this->wire('user');
if(!$user->isLoggedin() && $this->useAsLogin) $this->setCurrent();
parent::init();
// if this is not the current admin theme, exit now so no hooks are attached
if(!$this->isCurrent()) return;
$this->isLoggedIn = $user->isLoggedin();
$this->isSuperuser = $this->isLoggedIn && $user->isSuperuser();
$this->isEditor = $this->isLoggedIn && ($this->isSuperuser || $user->hasPermission('page-edit'));
$this->includeInitFile();
$modal = $this->wire('input')->get('modal');
if($modal) $this->isModal = $modal == 'inline' ? 'inline' : true;
// test notices when requested
if($this->wire('input')->get('test_notices') && $this->isLoggedIn) $this->testNotices();
}
/**
* Include the admin theme init file
*
*/
public function includeInitFile() {
$config = $this->wire('config');
$initFile = $config->paths->adminTemplates . 'init.php';
if(file_exists($initFile)) {
if(strpos($initFile, $config->paths->site) === 0) {
// admin themes in /site/modules/ may be compiled
$initFile = $this->wire('files')->compile($initFile);
}
/** @noinspection PhpIncludeInspection */
include_once($initFile);
}
}
/**
* Perform a translation, based on text from shared admin file: /wire/templates-admin/default.php
*
* @param string $text
* @return string
*
*/
public function _($text) {
static $translate = null;
if(is_null($translate)) $translate = $this->wire('languages') !== null;
if($translate === false) return $text;
$value = __($text, $this->wire('config')->paths->root . 'wire/templates-admin/default.php');
if($value === $text) $value = parent::_($text);
return $value;
}
/**
* Get the current page headline
*
* @return string
*
*/
public function getHeadline() {
$headline = $this->wire('processHeadline');
if(!$headline) $headline = $this->wire('page')->get('title|name');
if($this->wire('languages')) $headline = $this->_($headline);
return $this->sanitizer->entities1($headline);
}
/**
* Get navigation title for the given page, return blank if page should not be shown
*
* @param Page $p
* @return string
*
*/
public function getPageTitle(Page $p) {
if($p->name == 'add' && $p->parent->name == 'page') {
$title = $this->getAddNewLabel();
} else {
$title = $this->_($p->title);
}
$title = $this->sanitizer->entities1($title);
return $title;
}
/**
* Get icon used by the given page
*
* @param Page $p
* @return mixed|null|string
*
*/
public function getPageIcon(Page $p) {
$icon = '';
if($p->template == 'admin') {
$info = $this->wire('modules')->getModuleInfo($p->process);
if(!empty($info['icon'])) $icon = $info['icon'];
}
// allow for option of an admin field overriding the module icon
$pageIcon = $p->get('page_icon');
if($pageIcon) $icon = $pageIcon;
if(!$icon) switch($p->id) {
case 22: $icon = 'gears'; break; // Setup
case 21: $icon = 'plug'; break; // Modules
case 28: $icon = 'key'; break; // Access
}
if(!$icon && $p->parent->id != $this->wire('config')->adminRootPageID) {
$icon = 'file-o ui-priority-secondary';
}
return $icon;
}
/**
* Get “Add New” button actions
*
* - Returns array of arrays, each with 'url', 'label' and 'icon' properties.
* - Returns empty array if Add New button should not be displayed.
*
* @return array
*
*/
public function getAddNewActions() {
$page = $this->wire('page');
$process = $this->wire('process');
$input = $this->wire('input');
if(!$this->isEditor) return array();
if($page->name != 'page' || $this->wire('input')->urlSegment1) return array();
if($input->urlSegment1 || $input->get('modal')) return array();
if(strpos($process, 'ProcessPageList') !== 0) return array();
/** @var ProcessPageAdd $module */
$module = $this->wire('modules')->getModule('ProcessPageAdd', array('noInit' => true));
$data = $module->executeNavJSON(array('getArray' => true));
$actions = array();
foreach($data['list'] as $item) {
$item['url'] = $data['url'] . $item['url'];
$actions[] = $item;
}
return $actions;
}
/**
* Get the translated “Add New” label thats used in a couple spots
*
* @return string
*
*/
public function getAddNewLabel() {
return $this->_('Add New');
}
/**
* Get the classes that will be used in the <body class=''> tag
*
* @return string
*
*/
public function getBodyClass() {
$page = $this->wire('page');
$process = $this->wire('process');
$classes = array(
"id-{$page->id}",
"template-{$page->template->name}",
"pw-init",
parent::getBodyClass(),
);
if($this->isModal) $classes[] = 'modal';
if($this->isModal === 'inline') $classes[] = 'modal-inline';
if($this->wire('input')->urlSegment1) $classes[] = 'hasUrlSegments';
if($process) $classes[] = $process->className();
return implode(' ', $classes);
}
/**
* Get Javascript that must be present in the document <head>
*
* @return string
*
*/
public function getHeadJS() {
/** @var Config $config */
$config = $this->wire('config');
/** @var Paths $urls */
$urls = $config->urls;
/** @var array $jsConfig */
$jsConfig = $config->js();
$jsConfig['debug'] = $config->debug;
$jsConfig['urls'] = array(
'root' => $urls->root,
'admin' => $urls->admin,
'modules' => $urls->modules,
'core' => $urls->core,
'files' => $urls->files,
'templates' => $urls->templates,
'adminTemplates' => $urls->adminTemplates,
);
$out =
"var ProcessWire = { config: " . wireEncodeJSON($jsConfig, true, $config->debug) . " }; " .
"var config = ProcessWire.config;\n"; // legacy support
return $out;
}
/**
* Allow the given Page to appear in admin theme navigation?
*
* @param Page $p Page to test
* @param PageArray|array $children Children of page, if applicable (optional)
* @param string|null $permission Specify required permission (optional)
* @return bool
*
*/
public function allowPageInNav(Page $p, $children = array(), $permission = null) {
if($this->isSuperuser) return true;
$pageViewable = $p->viewable();
if(!$pageViewable) return false;
$allow = false;
$numChildren = count($children);
if($p->process == 'ProcessPageAdd') {
// ProcessPageAdd: avoid showing this menu item if there are no predefined family settings to use
$numAddable = $this->wire('session')->getFor('ProcessPageAdd', 'numAddable');
if($numAddable === null) {
/** @var ProcessPageAdd $processPageAdd */
$processPageAdd = $this->wire('modules')->getModule('ProcessPageAdd', array('noInit' => true));
if($processPageAdd) {
$addData = $processPageAdd->executeNavJSON(array('getArray' => true));
$numAddable = $addData['list'];
$this->wire('session')->setFor('ProcessPageAdd', 'numAddable', $numAddable);
}
}
// no addable options, so do not show the "Add New" item
if(!$numAddable) return false;
} else if(empty($permission)) {
// no permission specified
if(!$p->process) {
// no process module present, so we delegate to just the page viewable state if no children to check
if($pageViewable && !$numChildren) return true;
} else if($p->process == 'ProcessList') {
// page just serves as a list for children
} else {
// determine permission from Process module, if present
$moduleInfo = $this->wire('modules')->getModuleInfo($p->process);
if(!empty($moduleInfo['permission'])) $permission = $moduleInfo['permission'];
}
}
if($permission) {
// specific permission required to determine view access
$allow = $this->wire('user')->hasPermission($permission);
} else if($pageViewable && $p->parent_id == $this->wire('config')->adminRootPageID) {
// primary nav page requires that at least one child is viewable
foreach($children as $child) {
if($this->allowPageInNav($child)) {
$allow = true;
break;
}
}
}
return $allow;
}
/**
* Return nav array of primary navigation
*
* @return array
*
*/
public function getPrimaryNavArray() {
$items = array();
$config = $this->wire('config');
$admin = $this->wire('pages')->get($config->adminRootPageID);
foreach($admin->children("check_access=0") as $p) {
$item = $this->pageToNavArray($p);
if($item) $items[] = $item;
}
return $items;
}
/**
* Get navigation array from a Process module
*
* @param array|Module|string $module Module info array or Module object or string
* @param Page $p Page upon which the Process module is contained
* @return array
*
*/
public function moduleToNavArray($module, Page $p) {
$config = $this->wire('config');
$modules = $this->wire('modules');
$textdomain = str_replace($config->paths->root, '/', $modules->getModuleFile($p->process));
$user = $this->wire('user');
$navArray = array();
if(is_array($module)) {
$moduleInfo = $module;
} else {
$moduleInfo = $modules->getModuleInfo($module);
}
foreach($moduleInfo['nav'] as $navItem) {
$permission = empty($navItem['permission']) ? '' : $navItem['permission'];
if($permission && !$user->hasPermission($permission)) continue;
$navArray[] = array(
'id' => 0,
'parent_id' => $p->id,
'title' => $this->sanitizer->entities1(__($navItem['label'], $textdomain)), // translate from context of Process module
'name' => '',
'url' => $p->url . $navItem['url'],
'icon' => empty($navItem['icon']) ? '' : $navItem['icon'],
'children' => array(),
'navJSON' => empty($navItem['navJSON']) ? '' : $p->url . $navItem['navJSON'],
);
}
return $navArray;
}
/**
* Get a navigation array the given Page, or null if page not allowed in nav
*
* @param Page $p
* @return array|null
*
*/
public function pageToNavArray(Page $p) {
$children = $p->numChildren ? $p->children("check_access=0") : array();
if(!$this->allowPageInNav($p, $children)) return null;
$navArray = array(
'id' => $p->id,
'parent_id' => $p->parent_id,
'url' => $p->url,
'name' => $p->name,
'title' => $this->getPageTitle($p),
'icon' => $this->getPageIcon($p),
'children' => array(),
'navJSON' => '',
);
if(!count($children)) {
// no children available
if($p->template == 'admin' && $p->process) {
// see if process module defines its own navigation
$moduleInfo = $this->wire('modules')->getModuleInfo($p->process);
if(!empty($moduleInfo['nav'])) {
$navArray['children'] = $this->moduleToNavArray($moduleInfo, $p);
}
} else {
// The /page/ and /page/list/ are the same process, so just keep them on /page/ instead.
if(strpos($navArray['url'], '/page/list/') !== false) {
$navArray['url'] = str_replace('/page/list/', '/page/', $navArray['url']);
}
}
return $navArray;
}
// if we reach this point, then we have a PageArray of children
$modules = $this->wire('modules');
foreach($children as $c) {
if(!$c->process) continue;
$moduleInfo = $modules->getModuleInfo($c->process);
$permission = empty($moduleInfo['permission']) ? '' : $moduleInfo['permission'];
if(!$this->allowPageInNav($c, array(), $permission)) continue;
$childItem = array(
'id' => $c->id,
'parent_id' => $c->parent_id,
'title' => $this->getPageTitle($c),
'name' => $c->name,
'url' => $c->url,
'icon' => $this->getPageIcon($c),
'children' => array(),
'navJSON' => empty($moduleInfo['useNavJSON']) ? '' : $c->url . 'navJSON/',
);
if(!empty($moduleInfo['nav'])) {
$childItem['children'] = $this->moduleToNavArray($moduleInfo, $c);
}
$navArray['children'][] = $childItem;
} // foreach
return $navArray;
}
/**
* Get navigation items for the “user” menu
*
* This is hookable so that something else could add stuff to it.
* See the method body for details on format used.
*
* @return array
*
*/
public function ___getUserNavArray() {
$urls = $this->wire('urls');
$navArray = array();
$navArray[] = array(
'url' => $urls->root,
'title' => $this->_('View site'),
'target' => '_top',
'icon' => 'eye',
);
if($this->wire('user')->hasPermission('profile-edit')) $navArray[] = array(
'url' => $urls->admin . 'profile/',
'title' => $this->_('Profile'),
'icon' => 'user',
'permission' => 'profile-edit',
);
$navArray[] = array(
'url' => $urls->admin . 'login/logout/',
'title' => $this->_('Logout'),
'target' => '_top',
'icon' => 'power-off',
);
return $navArray;
}
/**
* Get the browser <title>
*
* @return string
*
*/
public function getBrowserTitle() {
$browserTitle = $this->wire('processBrowserTitle');
$modal = $this->wire('input')->get('modal');
if(!$browserTitle) {
if($modal) return $this->wire('processHeadline');
$browserTitle = $this->_(strip_tags($this->wire('page')->get('title|name'))) . ' • ProcessWire';
}
if(!$modal) {
$httpHost = $this->wire('config')->httpHost;
if(strpos($httpHost, 'www.') === 0) $httpHost = substr($httpHost, 4); // remove www
if(strpos($httpHost, ':')) $httpHost = preg_replace('/:\d+/', '', $httpHost); // remove port
$browserTitle .= "$httpHost";
}
return $this->sanitizer->entities1($browserTitle);
}
/**
* Test all notice types
*
* @return bool
*
*/
public function testNotices() {
if(!$this->wire('user')->isLoggedin()) return false;
$this->message('Message test');
$this->message('Message test debug', Notice::debug);
$this->message('Message test markup <a href="#">example</a>', Notice::allowMarkup);
$this->warning('Warning test');
$this->warning('Warning test debug', Notice::debug);
$this->warning('Warning test markup <a href="#">example</a>', Notice::allowMarkup);
$this->error('Error test');
$this->error('Error test debug', Notice::debug);
$this->error('Error test markup <a href="#">example</a>', Notice::allowMarkup);
return true;
}
/**
* Render runtime notices div#notices
*
* @param Notices|bool $notices Notices object or specify boolean true to return array of all available $options
* @param array $options See defaults in method
* @return string|array Returns string unless you specify true for $notices argument, then it returns an array.
*
*/
public function renderNotices($notices, array $options = array()) {
$defaults = array(
'messageClass' => 'NoticeMessage', // class for messages
'messageIcon' => 'check-square', // default icon to show with notices
'warningClass' => 'NoticeWarning', // class for warnings
'warningIcon' => 'exclamation-circle', // icon for warnings
'errorClass' => 'NoticeError', // class for errors
'errorIcon' => 'exclamation-triangle', // icon for errors
'debugClass' => 'NoticeDebug', // class for debug items (appended)
'debugIcon' => 'bug', // icon for debug notices
'closeClass' => 'pw-notice-remove notice-remove', // class for close notices link <a>
'closeIcon' => 'times', // icon for close notices link
'listMarkup' => "<ul class='pw-notices' id='notices'>{out}</ul><!--/notices-->",
'itemMarkup' => "<li class='{class}'>{remove}{icon}{text}</li>",
// the following apply only when groupByType==true
'groupByType' => true, // Group notices by type
'groupParentClass' => 'pw-notice-group-parent', // class for parent notices
'groupChildClass' => 'pw-notice-group-child', // class for children (of parent notices)
'groupToggleMarkup' => "<a class='pw-notice-group-toggle' href='#'>{label}" .
"<i class='fa fa-fw fa-bell-o' data-toggle='fa-bell-o fa-bell'></i>" .
"<i class='fa fa-fw fa-angle-right' data-toggle='fa-angle-right fa-angle-down'></i></a>",
'groupToggleLabel' => $this->_("+{n-1}"),
);
$options = array_merge($defaults, $options);
if($notices === true) return $options;
$config = $this->wire('config');
$noticesArray = array();
$out = '';
$removeIcon = $this->renderIcon($options['closeIcon']);
$removeLabel = $this->_('Close all');
$removeLink = "<a class='$options[closeClass]' href='#' title='$removeLabel'>$removeIcon</a>";
if($this->isLoggedIn && $this->wire('modules')->isInstalled('SystemNotifications')) {
$defaults['groupByType'] = false;
//$systemNotifications = $this->wire('modules')->get('SystemNotifications');
//if(!$systemNotifications->placement) return '';
}
foreach($notices as $n => $notice) {
$text = $notice->text;
$allowMarkup = $notice->flags & Notice::allowMarkup;
if($allowMarkup) {
// leave $text alone
} else {
// 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) {
$class = $options['errorClass'];
$icon = $options['errorIcon'];
$noticeType = 'errors';
} else if($notice instanceof NoticeWarning) {
$class = $options['warningClass'];
$icon = $options['warningIcon'];
$noticeType = 'warnings';
} else {
$class = $options['messageClass'];
$icon = $options['messageIcon'];
$noticeType = 'messages';
}
if($notice->flags & Notice::debug) {
$class .= " " . $options['debugClass'];
$icon = $options['debugIcon'];
// ensure non-debug version is set as well
if(!isset($noticesArray[$noticeType])) $noticesArray[$noticeType] = array();
$noticeType .= "-debug";
}
// indicate which class the notice originated from in debug mode
if($notice->class && $config->debug) $text = "{$notice->class}: $text";
$replacements = array(
'{class}' => $class,
'{remove}' => '',
'{icon}' => $this->renderNavIcon($notice->icon ? $notice->icon : $icon),
'{text}' => $text,
);
if($options['groupByType']) {
if(!isset($noticesArray[$noticeType])) $noticesArray[$noticeType] = array();
$noticesArray[$noticeType][] = $replacements;
} else {
if($n === 0) $replacements['{remove}'] = $removeLink;
$out .= str_replace(array_keys($replacements), array_values($replacements), $options['itemMarkup']);
}
}
if($options['groupByType']) {
$cnt = 0;
foreach($noticesArray as $noticeType => $noticeReplacements) {
if(strpos($noticeType, '-debug')) continue;
if(isset($noticesArray["$noticeType-debug"])) {
$noticeReplacements = array_merge($noticeReplacements, $noticesArray["$noticeType-debug"]);
}
$n = count($noticeReplacements);
if($n > 1) {
$notice =& $noticeReplacements[0];
$label = str_replace(array('{n}', '{n-1}'), array($n, $n-1), $options['groupToggleLabel']);
$notice['{text}'] .= ' ' . str_replace(array('{label}'), array($label), $options['groupToggleMarkup']);
$notice['{class}'] .= ' ' . $options['groupParentClass'];
$childClass = $options['groupChildClass'];
} else {
$childClass = '';
}
foreach($noticeReplacements as $i => $replacements) {
if(!$cnt) $replacements['{remove}'] = $removeLink;
if($childClass && $i > 0) $replacements['{class}'] .= ' ' . $childClass;
$out .= str_replace(array_keys($replacements), array_values($replacements), $options['itemMarkup']);
$cnt++;
}
}
}
$out = str_replace('{out}', $out, $options['listMarkup']);
$out .= $this->renderExtraMarkup('notices');
return $out;
}
/**
* Render markup for a font-awesome icon
*
* @param string $icon Name of icon to render, excluding the “fa-” prefix
* @param bool $fw Specify true to make fixed width (default=false).
* @return string
*
*/
public function renderIcon($icon, $fw = false) {
if($fw) $icon .= ' fa-fw';
return "<i class='fa fa-$icon'></i>";
}
/**
* Render markup for a font-awesome icon that precedes a navigation label
*
* This is the same as renderIcon() except that fixed-width is assumed and a "nav-nav-icon"
* class is added to it.
*
* @param string $icon Name of icon to render, excluding the “fa-” prefix
* @return string
*
*/
public function renderNavIcon($icon) {
return $this->renderIcon("$icon pw-nav-icon", true);
}
/**
* Render an extra markup region
*
* @param string $for
* @return mixed|string
*
*/
public function renderExtraMarkup($for) {
static $extras = array();
if(empty($extras)) $extras = $this->getExtraMarkup();
return isset($extras[$for]) ? $extras[$for] : '';
}
/**
* Module Configuration
*
* @param InputfieldWrapper $inputfields
*
*/
public function getModuleConfigInputfields(InputfieldWrapper $inputfields) {
/** @var InputfieldCheckbox $f */
$f = $this->modules->get('InputfieldCheckbox');
$f->name = 'useAsLogin';
$f->label = $this->_('Use this admin theme for login screen?');
$f->description = $this->_('When checked, this admin theme will be used on the user login screen.');
$f->icon = 'sign-in';
$f->collapsed = Inputfield::collapsedBlank;
if($this->get('useAsLogin')) $f->attr('checked', 'checked');
$inputfields->add($f);
if($f->attr('checked') && $this->input->requestMethod('GET')) {
$class = $this->className();
foreach($this->modules->findByPrefix('AdminTheme') as $name) {
if($name == $class) continue;
$cfg = $this->modules->getConfig($name);
if(!empty($cfg['useAsLogin'])) {
unset($cfg['useAsLogin']);
$this->modules->saveConfig($name, $cfg);
$this->message("Removed 'useAsLogin' setting from $name", Notice::debug);
}
}
}
}
}

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

@@ -12,11 +12,16 @@
* https://processwire.com
*
* #pw-summary Holds ProcessWire configuration settings as defined in /wire/config.php and /site/config.php.
* #pw-body =
* For more detailed descriptions of these $config properties, including default values, see the
* [/wire/config.php](https://github.com/processwire/processwire/blob/master/wire/config.php) file.
* #pw-body
*
*
* @see /wire/config.php for more detailed descriptions of all config properties.
*
* @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 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
@@ -34,7 +39,7 @@
*
* @property bool $protectCSRF Enables CSRF (cross site request forgery) protection on all PW forms, recommended for security. #pw-group-HTTP-and-input
*
* @property array $imageSizerOptions Default value is array('upscaling' => true, 'cropping' => true, 'quality' => 90) #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 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
@@ -52,17 +57,19 @@
* @property string $sessionName Default session name to use (default='wire') #pw-group-session
* @property string $sessionNameSecure Session name when on HTTPS. Used when the sessionCookieSecure option is enabled (default). When blank (default), it will assume sessionName + 's'. #pw-group-session
* @property bool|int $sessionCookieSecure Use secure cookies when on HTTPS? When enabled, separate sessions will be maintained for HTTP vs. HTTPS. Good for security but tradeoff is login session may be lost when switching (default=1 or true). #pw-group-session
* @property null|string $sessionCookieDomain Domain to use for sessions, which enables a session to work across subdomains, or NULL to disable (default/recommended). #pw-group-session
* @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? May conflict with dynamic IPs. #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 $sessionHistory Number of session entries to keep (default=0, which means off). #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. @deprecated #pw-group-deprecated
* @property string $uploadUnzipCommand Shell command to unzip archives, used by WireUpload class. #pw-group-deprecated
* @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
*
@@ -70,15 +77,18 @@
*
* @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 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
* @property bool $debug Special mode for use when debugging or developing a site. Recommended TRUE when site is in development and FALSE when not. #pw-group-system
* @property bool|int $debug Special mode for use when debugging or developing a site. Recommended TRUE when site is in development and FALSE when not. Or set to Config::debugVerbose for verbose debug mode. #pw-group-system
* @property string $debugIf Enable debug mode if condition is met #pw-group-system
* @property array $debugTools Tools, and their order, to show in debug mode (admin) #pw-group-system
*
@@ -87,30 +97,36 @@
* @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
* @property string $dbUser Database user #pw-group-database
* @property string $dbPass Database password #pw-group-database
* @property string $dbPort Database port (default=3306) #pw-group-database
* @property string $dbCharset Default is 'utf8' #pw-group-database
* @property string $dbCharset Default is 'utf8' but 'utf8mb4' is also supported. #pw-group-database
* @property string $dbEngine Database engine (MyISAM or InnoDB) #pw-group-database
* @property string $dbSocket Optional DB socket config for sites that need it. #pw-group-database
* @property bool $dbCache Whether to allow MySQL query caching. #pw-group-database
* @property bool $dbLowercaseTables Force any created field_* tables to be lowercase. #pw-group-database
* @property string $dbEngine Database engine (MyISAM or InnoDB) #pw-group-database
* @property string $dbPath MySQL database exec path (Path to mysqldump) #pw-group-database
* @property array $dbOptions Any additional driver options to pass as $options argument to "new PDO(...)". #pw-group-database
* @property array $dbSqlModes Set or adjust SQL mode per MySQL version, where array keys are MySQL version and values are SQL mode command(s). #pw-group-database
* @property int $dbQueryLogMax Maximum number of queries WireDatabasePDO will log in memory, when debug mode is enabled (default=1000). #pw-group-database
* @property string $dbInitCommand Database init command, for PDO::MYSQL_ATTR_INIT_COMMAND. Note placeholder {charset} gets replaced with $config->dbCharset. #pw-group-database
* $property array $dbSqlModes Set, add or remove SQL mode based on MySQL version. See default in /wire/config.php for details. #pw-group-database
* @property bool $dbStripMB4 When dbEngine is not utf8mb4 and this is true, we will attempt to remove 4-byte characters (like emoji) from inserts when possible. Note that this adds some overhead. #pw-group-database
*
* @property array $pageList Settings specific to Page lists. #pw-group-modules
* @property array $pageEdit Settings specific to Page editors. #pw-group-modules
* @property array $pageAdd Settings specific to Page adding. #pw-group-modules
* @property string $moduleServiceURL URL where the modules web service can be accessed #pw-group-modules
* @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 $substituteModules Associative array with names of substitutute modules for when requested module doesn't exist #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 array $modals Settings for modal windows #pw-group-admin
@@ -118,6 +134,8 @@
* @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 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
* @property string $userAuthHashType Default is 'sha1' - used only if Blowfish is not supported by the system. #pw-group-session
@@ -128,31 +146,38 @@
* @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 bool $debugMarkupQA Set to true to make the MarkupQA class report verbose debugging messages (to superusers). #pw-internal
* @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 int $rootPageID ID of homepage (usually 1) #pw-group-system-IDs
* @property int $adminRootPageID ID of admin root page #pw-group-system-IDs
* @property int $trashPageID #pw-group-system-IDs
* @property int $loginPageID #pw-group-system-IDs
* @property int $http404PageID #pw-group-system-IDs
* @property int $usersPageID #pw-group-system-IDs
* @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
* @property int $trashPageID Page ID of the trash page. #pw-group-system-IDs
* @property int $loginPageID Page ID of the admin login page. #pw-group-system-IDs
* @property int $http404PageID Page ID of the 404 “page not found” page. #pw-group-system-IDs
* @property int $usersPageID Page ID of the page having users as children. #pw-group-system-IDs
* @property array $usersPageIDs Populated if multiple possible users page IDs (parent for users pages) #pw-group-system-IDs
* @property int $rolesPageID #pw-group-system-IDs
* @property int $permissionsPageID #pw-group-system-IDs
* @property int $guestUserPageID #pw-group-system-IDs
* @property int $superUserPageID #pw-group-system-IDs
* @property int $guestUserRolePageID #pw-group-system-IDs
* @property int $superUserRolePageID #pw-group-system-IDs
* @property int $userTemplateID #pw-group-system-IDs
* @property int $rolesPageID Page ID of the page having roles as children. #pw-group-system-IDs
* @property int $permissionsPageID Page ID of the page having permissions as children. #pw-group-system-IDs
* @property int $guestUserPageID Page ID of the guest (default/not-logged-in) user. #pw-group-system-IDs
* @property int $superUserPageID Page ID of the original superuser (created during installation). #pw-group-system-IDs
* @property int $guestUserRolePageID Page ID of the guest user role (inherited by all users, not just guest). #pw-group-system-IDs
* @property int $superUserRolePageID Page ID of the superuser role. #pw-group-system-IDs
* @property int $userTemplateID Template ID of the user template. #pw-group-system-IDs
* @property array $userTemplateIDs Array of template IDs when multiple allowed for users. #pw-group-system-IDs
* @property int $roleTemplateID #pw-group-system-IDs
* @property int $permissionTemplateID #pw-group-system-IDs
* @property int $externalPageID ID of page assigned to $page API variable when externally bootstrapped #pw-group-system-IDs
* @property array $preloadPageIDs IDs of pages that will be preloaded at beginning of request #pw-group-system-IDs
* @property int $installed Timestamp of when this PW was installed, set automatically for compatibility detection. #pw-group-system
* @property int $roleTemplateID Template ID of the role template. #pw-group-system-IDs
* @property int $permissionTemplateID Template ID of the permission template. #pw-group-system-IDs
* @property int $externalPageID Page ID of page assigned to $page API variable when externally bootstrapped #pw-group-system-IDs
* @property array $preloadPageIDs Page IDs of pages that will always be preloaded at beginning of request #pw-group-system-IDs
* @property int $installed Timestamp of when this PW was installed, set automatically by the installer for future compatibility detection. #pw-group-system
*
*/
class Config extends WireData {
/**
* Constant for verbose debug mode (uses more memory)
*
*/
const debugVerbose = 2;
/**
* Get URL for requested resource or module
*
@@ -166,7 +191,7 @@ class Config extends WireData {
* $url = $config->urls->admin;
* ~~~~~
*
* @param string $for Predefined ProcessWire URLs property or module name
* @param string|Wire $for Predefined ProcessWire URLs property or module name
* @return string|null
*
*/
@@ -179,7 +204,7 @@ class Config extends WireData {
*
* #pw-internal
*
* @param string $for Predefined ProcessWire URLs property or module name
* @param string|Wire $for Predefined ProcessWire URLs property or module name
* @return null|string
*
*/
@@ -328,6 +353,9 @@ class Config extends WireData {
* 'siteOnly' => true,
* 'cachePath' => $config->paths->root . '.my-cache/'
* ]);
*
* // To unset a property specify null for first argument and property to unset as second argument
* $config->fileCompilerOptions(null, 'siteOnly');
* ~~~~~
*
* #pw-internal
@@ -362,7 +390,14 @@ class Config extends WireData {
}
} else {
// property and value provided
$value[$property] = $arguments[1];
if($property === null && is_string($arguments[1])) {
// unset property
$property = $arguments[1];
unset($value[$property]);
} else {
// set property with value
$value[$property] = $arguments[1];
}
parent::set($method, $value);
}
} else if($numArgs === 1) {
@@ -372,5 +407,45 @@ 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
* }
* ~~~~~
*
* @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 versino
*
* If no version argument is given, it simply returns the current ProcessWire version.
*
* ~~~~~
* if($config->version('3.0.100')) {
* // ProcessWire version is 3.0.100 or newer
* }
* ~~~~~
*
* @param string $minVersion Specify version string if you want to compare against current version
* @return bool|string Returns current version if no argument given, 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
*
*/
public function version($minVersion = '') {
return version_compare($this->version, $minVersion) >= 0;
}
}

View File

@@ -104,7 +104,7 @@ class Database extends \mysqli implements WireDatabase {
if($this->debug) {
$timerKey = Debug::timer();
if(!$timerFirstStartTime) $timerFirstStartTime = $timerKey;
if(!$timerFirstStartTime) $timerFirstStartTime = (float) $timerKey;
} else $timerKey = null;
$result = @parent::query($sql, $resultmode);
@@ -113,9 +113,9 @@ class Database extends \mysqli implements WireDatabase {
if($this->debug) {
if(isset($result->num_rows)) $sql .= " [" . $result->num_rows . " rows]";
if(!is_null($timerKey)) {
$elapsed = Debug::timer($timerKey);
$elapsed = (float) Debug::timer($timerKey);
$timerTotalQueryTime += $elapsed;
$timerTotalSinceStart = Debug::timer() - $timerFirstStartTime;
$timerTotalSinceStart = ((float) Debug::timer()) - $timerFirstStartTime;
$sql .= " [{$elapsed}s, {$timerTotalQueryTime}s, {$timerTotalSinceStart}s]";
}
$this->queryLog($sql);

View File

@@ -18,7 +18,7 @@
*
* @property array $where
* @property array $bindValues
* @properety array $bindIndex
* @property array $bindIndex
*
*/
abstract class DatabaseQuery extends WireData {
@@ -216,6 +216,9 @@ abstract class DatabaseQuery extends WireData {
/**
* Execute the query with the current database handle
*
* @return \PDOStatement
* @throws WireException|\Exception|\PDOException
*
*/
public function execute() {

View File

@@ -66,7 +66,7 @@ class Debug {
$startTime = -microtime(true);
if(!$key) {
$key = (string) $startTime;
while(isset(self::$timers[$key])) $key .= ".";
while(isset(self::$timers[$key])) $key .= "0";
}
self::$timers[(string) $key] = $startTime;
$value = $key;

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 2018 by Ryan Cramer
* https://processwire.com
*
*/
@@ -20,21 +20,51 @@
class WireException extends \Exception {}
/**
* 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 {}
/**
* 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 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

@@ -7,10 +7,12 @@
* and is managed by the 'Fields' class.
*
* #pw-summary Field represents a custom field that is used on a Page.
* #pw-var $field
* #pw-instantiate $field = $fields->get('field_name');
* #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 2018 by Ryan Cramer
* https://processwire.com
*
* @property int $id Numeric ID of field in the database #pw-group-properties
@@ -24,17 +26,20 @@
* @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
*
* Common Inputfield properties that Field objects store:
* @property int|bool|null $required
* @property string|null $requiredIf
* @property string|null $showIf
* @property int|null $columnWidth
* @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
*
* @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
@@ -200,6 +205,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
*
@@ -216,7 +229,7 @@ class Field extends WireData implements Saveable, Exportable {
*
* @param string $key Property name to set
* @param mixed $value
* @return $this
* @return Field|WireData
*
*/
public function set($key, $value) {
@@ -338,17 +351,26 @@ 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 == '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 == 'editRoles') return $this->editRoles;
else if($key == 'table') return $this->getTable();
else if($key == 'prevTable') return $this->prevTable;
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 == 'tagList') return $this->getTags();
else if($key == 'tags') return $this->getTags(true);
$value = parent::get($key);
if($key === 'allowContexts' && !is_array($value)) $value = array();
if(is_array($this->trackGets)) $this->trackGets($key);
return $value;
}
@@ -444,6 +466,20 @@ class Field extends WireData implements Saveable, Exportable {
if(strpos($key, '_') === 0) unset($data[$key]);
}
// convert access roles from IDs to names
if($this->useRoles) {
foreach(array('viewRoles', 'editRoles') as $roleType) {
if(!is_array($data[$roleType])) $data[$roleType] = array();
$roleNames = array();
foreach($data[$roleType] as $key => $roleID) {
$role = $this->wire('roles')->get($roleID);
if(!$role || !$role->id) continue;
$roleNames[] = $role->name;
}
$data[$roleType] = $roleNames;
}
}
return $data;
}
@@ -493,7 +529,11 @@ class Field extends WireData implements Saveable, Exportable {
$this->type = $this->wire('fieldtypes')->get($data['type']);
}
if(!$this->type) $this->type = $this->wire('fieldtypes')->get('FieldtypeText');
if(!$this->type) {
if(!empty($data['type'])) $this->error("Unable to locate field type: $data[type]");
$this->type = $this->wire('fieldtypes')->get('FieldtypeText');
}
$data = $this->type->importConfigData($this, $data);
// populate import data
@@ -628,6 +668,15 @@ class Field extends WireData implements Saveable, Exportable {
$ids[] = (int) $role;
} else if($role instanceof Role) {
$ids[] = (int) $role->id;
} else if(is_string($role) && strlen($role)) {
$rolePage = $this->wire('roles')->get($role);
if($rolePage && $rolePage->id) {
$ids[] = $rolePage->id;
} else {
$this->error("Unknown role '$role'");
}
} else {
// invalid
}
}
if($type == 'view') {
@@ -808,8 +857,10 @@ class Field extends WireData implements Saveable, Exportable {
$inputfield->attr('name', $this->name . $contextStr);
$inputfield->set('label', $this->label);
// just in case an Inputfield needs to know it's Fieldtype context, or lack of it
$inputfield->set('hasFieldtype', $this->type);
// just in case an Inputfield needs to know its Fieldtype/Field context, or lack of it
$inputfield->set('hasFieldtype', $this->type);
$inputfield->set('hasField', $this);
$inputfield->set('hasPage', $page);
// custom field settings
foreach($this->data as $key => $value) {
@@ -912,6 +963,7 @@ class Field extends WireData implements Saveable, Exportable {
if($fieldgroupContext) {
$allowContext = $this->type->getConfigAllowContext($this);
if(!is_array($allowContext)) $allowContext = array();
$allowContext = array_merge($allowContext, $this->allowContexts);
} else {
$allowContext = array();
}
@@ -922,6 +974,8 @@ class Field extends WireData implements Saveable, Exportable {
if(!$fieldgroupContext) $inputfields->head = $this->_('Field type details');
$inputfields->attr('title', $this->_('Details'));
$inputfields->attr('id+name', 'fieldtypeConfig');
$remainingNames = array();
foreach($allowContext as $name) $remainingNames[$name] = $name;
try {
$fieldtypeInputfields = $this->type->getConfigInputfields($this);
@@ -936,7 +990,19 @@ class Field extends WireData implements Saveable, Exportable {
foreach($fieldtypeInputfields as $inputfield) {
if($fieldgroupContext && !in_array($inputfield->name, $allowContext)) continue;
$inputfields->append($inputfield);
unset($remainingNames[$inputfield->name]);
}
// now capture those that may have been stuck in a fieldset
if($fieldgroupContext) {
foreach($remainingNames as $name) {
if($inputfields->getChildByName($name)) continue;
$inputfield = $fieldtypeInputfields->getChildByName($name);
if(!$inputfield) continue;
$inputfields->append($inputfield);
unset($remainingNames[$inputfield->name]);
}
}
} catch(\Exception $e) {
$this->trackException($e, false, true);
}
@@ -951,13 +1017,18 @@ class Field extends WireData implements Saveable, Exportable {
if($inputfield) {
if($fieldgroupContext) {
$allowContext = array('visibility', 'collapsed', 'columnWidth', 'required', 'requiredIf', 'showIf');
$allowContext = array_merge($allowContext, $inputfield->getConfigAllowContext($this));
$allowContext = array_merge($allowContext, $this->allowContexts, $inputfield->getConfigAllowContext($this));
} else {
$allowContext = array();
$inputfields->head = $this->_('Input field settings');
}
$remainingNames = array();
foreach($allowContext as $name) {
$remainingNames[$name] = $name;
}
$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();
@@ -970,6 +1041,16 @@ class Field extends WireData implements Saveable, Exportable {
foreach($inputfieldInputfields as $i) {
if($fieldgroupContext && !in_array($i->name, $allowContext)) continue;
$inputfields->append($i);
unset($remainingNames[$i->name]);
}
if($fieldgroupContext) {
foreach($remainingNames as $name) {
if($inputfields->getChildByName($name)) continue;
$inputfield = $inputfieldInputfields->getChildByName($name);
if(!$inputfield) continue;
$inputfields->append($inputfield);
unset($remainingNames[$inputfield->name]);
}
}
}
@@ -1003,6 +1084,8 @@ class Field extends WireData implements Saveable, Exportable {
/**
* Set an override table name, or omit (or null) to restore default table name
*
* #pw-group-advanced
*
* @param null|string $table
*
*/
@@ -1185,6 +1268,104 @@ 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 $tagList Array of tags to add
* @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);
}
/**
* debugInfo PHP 5.6+ magic method
*

View File

@@ -133,7 +133,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
/**
* Remove a field from this fieldgroup
*
* Note that this must be followed up with a `$field->save()` before it does anything destructive.
* Note that this must be followed up with a `$fieldgroup->save()` before it does anything destructive.
* This method does nothing more than queue the removal.
*
* _Technical Details_
@@ -177,7 +177,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
* #pw-internal
*
* @param Field $field
* @return bool
* @return Fieldgroup|WireArray $this
*
*/
public function finishRemove(Field $field) {
@@ -194,7 +194,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
* #pw-group-manipulation
*
* @param Field|string|int $field Field object, name or id.
* @return bool
* @return bool|Fieldgroup|WireArray
*
*/
public function softRemove($field) {
@@ -379,7 +379,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
*
* @param string $key Name of property to set
* @param string|int|object $value Value of property
* @return Fieldgroup $this
* @return Fieldgroup|WireArray $this
* @throws WireException if passed invalid data
*
*/
@@ -448,7 +448,9 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
*
*/
public function getExportData() {
return $this->wire('fieldgroups')->getExportData($this);
/** @var Fieldgroups $fieldgroups */
$fieldgroups = $this->wire('fieldgroups');
return $fieldgroups->getExportData($this);
}
/**
@@ -469,7 +471,9 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
*
*/
public function setImportData(array $data) {
return $this->wire('fieldgroups')->setImportData($this, $data);
/** @var Fieldgroups $fieldgroups */
$fieldgroups = $this->wire('fieldgroups');
return $fieldgroups->setImportData($this, $data);
}
/**
@@ -612,7 +616,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
if(!$inputfield) continue;
if($inputfield->collapsed == Inputfield::collapsedHidden) continue;
$inputfield->value = $page->get($field->name);
$inputfield->setAttribute('value', $page->get($field->name));
if($multiMode) {
$fieldInputfields[$field->id] = $inputfield;
@@ -640,7 +644,9 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
*
*/
public function getTemplates() {
return $this->wire('fieldgroups')->getTemplates($this);
/** @var Fieldgroups $fieldgroups */
$fieldgroups = $this->wire('fieldgroups');
return $fieldgroups->getTemplates($this);
}
/**
@@ -686,7 +692,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
return $this->fieldContexts[$field_id][$namespace];
}
return array();
} else {
} else if(isset($this->fieldContexts[$field_id])) {
return $this->fieldContexts[$field_id];
}
}
@@ -699,8 +705,8 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
* #pw-internal
*
* @param int $field_id Field ID
* @param string $namespace Optional namespace
* @param array $data
* @param string $namespace Optional namespace
*
*/
public function setFieldContextArray($field_id, $data, $namespace = '') {

View File

@@ -147,21 +147,12 @@ class Fieldgroups extends WireSaveableItemsLookup {
if($item->id && $item->removedFields) {
foreach($this->wire('templates') as $template) {
if($template->fieldgroup->id !== $item->id) continue;
foreach($item->removedFields as $field) {
// make sure the field is valid to delete from this template
if(($field->flags & Field::flagGlobal) && !$template->noGlobal) {
throw new WireException("Field '$field' may not be removed from fieldgroup '{$item->name}' because it is globally required (Field::flagGlobal)");
}
if($field->flags & Field::flagPermanent) {
throw new WireException("Field '$field' may not be removed from fieldgroup '{$item->name}' because it is permanent.");
}
$field->type->deleteTemplateField($template, $field);
$error = $this->isFieldNotRemoveable($field, $item, $template);
if($error !== false) throw new WireException("$error Save of fieldgroup changes aborted.");
if($field->type) $field->type->deleteTemplateField($template, $field);
$item->finishRemove($field);
}
}
@@ -206,7 +197,7 @@ class Fieldgroups extends WireSaveableItemsLookup {
* Also deletes the references in fieldgroups_fields table
*
* @param Saveable|Fieldgroup $item
* @return Fieldgroups $this
* @return bool
* @throws WireException
*
*/
@@ -218,7 +209,10 @@ class Fieldgroups extends WireSaveableItemsLookup {
}
if(count($templates)) {
throw new WireException("Can't delete fieldgroup '{$item->name}' because it is in use by template(s): " . implode(', ', $templates));
throw new WireException(
"Can't delete fieldgroup '{$item->name}' because it is in use by template(s): " .
implode(', ', $templates)
);
}
return parent::___delete($item);
@@ -272,7 +266,8 @@ class Fieldgroups extends WireSaveableItemsLookup {
$contexts = $fieldgroup->getFieldContextArray();
$numSaved = 0;
foreach($contexts as $fieldID => $context) {
$field = $fieldgroup->getFieldContext($fieldID);
$field = $fieldgroup->getFieldContext((int) $fieldID);
if(!$field) continue;
if($this->wire('fields')->saveFieldgroupContext($field, $fieldgroup)) $numSaved++;
}
return $numSaved;
@@ -454,5 +449,35 @@ class Fieldgroups extends WireSaveableItemsLookup {
return $return;
}
/**
* Is the given Field not allowed to be removed from given Template?
*
* #pw-internal
*
* @param Field $field
* @param Template $template
* @param Fieldgroup $fieldgroup
* @return bool|string Returns error message string if not removeable or boolean false if it is removeable
*
*/
public function isFieldNotRemoveable(Field $field, Fieldgroup $fieldgroup, Template $template = null) {
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($field->flags & Field::flagPermanent) {
return
"Field '$field' may not be removed from fieldgroup '{$this->name}' " .
"because it is permanent (Field::flagPermanent).";
}
return false;
}
}

View File

@@ -5,7 +5,7 @@
*
* 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
@@ -16,6 +16,7 @@
* @method bool deleteFieldDataByTemplate(Field $field, Template $template) #pw-hooker
* @method void changedType(Saveable $item, Fieldtype $fromType, Fieldtype $toType) #pw-hooker
* @method void changeTypeReady(Saveable $item, Fieldtype $fromType, Fieldtype $toType) #pw-hooker
* @method bool|Field clone(Field $item, $name = '') Clone a field and return it or return false on fail.
*
*/
@@ -34,60 +35,81 @@ class Fields extends WireSaveableItems {
*
*/
static protected $nativeNamesSystem = array(
'id',
'parent_id',
'parent', // alias
'parents',
'templates_id',
'template', // alias
'name',
'status',
'child',
'children',
'count',
'check_access',
'created_users_id',
'created',
'createdUser',
'createdUser',
'createdUserID',
'createdUsersID',
'created_users_id',
'data',
'description',
'editUrl',
'end',
'fieldgroup',
'fields',
'find',
'flags',
'get',
'has_parent',
'hasParent',
'httpUrl',
'id',
'include',
'modified',
'modifiedUser',
'isNew',
'limit',
'modified_users_id',
'modified',
'modifiedUser',
'modifiedUserID',
'modifiedUsersID',
'modified_users_id',
'published',
'name',
'num_children',
'numChildren',
'sort',
'sortfield',
'flags',
'find',
'get',
'child',
'children',
'siblings',
//'roles',
'url',
'path',
'templatePrevious',
'rootParent',
'fieldgroup',
'fields',
'description',
'data',
'isNew',
);
'numChildren',
'parent_id',
'parent',
'parents',
'path',
'published',
'rootParent',
'siblings',
'sort',
'sortfield',
'start',
'status',
'template',
'templatePrevious',
'templates_id',
'url',
'_custom',
);
/**
* 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;
/**
* Construct
*
*/
public function __construct() {
$this->fieldsArray = new FieldsArray();
// 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);
}
/**
@@ -205,6 +227,8 @@ class Fields extends WireSaveableItems {
}
}
}
$this->getTags('reset');
return true;
}
@@ -281,7 +305,7 @@ class Fields extends WireSaveableItems {
/**
* Create and return a cloned copy of the given Field
*
*
* @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
@@ -622,14 +646,14 @@ class Fields extends WireSaveableItems {
if($success) {
$this->message(
sprintf($this->_('Deleted field "%1$s" data in %2$d row(s) from %3$d page(s).'),
$field->name, $numRows, $numPages) . " [$deleteType]",
sprintf($this->_('Deleted field "%1$s" data in %2$d row(s) from %3$d page(s) using template "%4$s".'),
$field->name, $numRows, $numPages, $template->name) . " [$deleteType]",
Notice::log
);
} else {
$this->error(
sprintf($this->_('Error deleting field "%1$s" data, %2$d row(s), %3$d page(s).'),
$field->name, $numRows, $numPages) . " [$deleteType]",
sprintf($this->_('Error deleting field "%1$s" data, %2$d row(s), %3$d page(s) using template "%4$s".'),
$field->name, $numRows, $numPages, $template->name) . " [$deleteType]",
Notice::log
);
}
@@ -676,6 +700,8 @@ class Fields extends WireSaveableItems {
'countPages' => false,
'getPageIDs' => false,
);
if(!$field->type) return 0;
$options = array_merge($defaults, $options);
$database = $this->wire('database');
@@ -802,8 +828,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;
}
@@ -816,7 +842,76 @@ 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;
}
/**

View File

@@ -33,6 +33,7 @@
* @method mixed wakeupValue(Page $page, Field $field, $value)
* @method string|int|array sleepValue(Page $page, Field $field, $value)
* @method string|float|int|array exportValue(Page $page, Field $field, $value, array $options = array())
* @method string|float|int|array|object importValue(Page $page, Field $field, $value, array $options = array())
* @method bool createField(Field $field)
* @method array getSelectorInfo(Field $field, array $data = array())
* @method mixed|null loadPageField(Page $page, Field $field)
@@ -51,6 +52,7 @@
* @property bool $_exportMode True when Fieldtype is exporting config data, false otherwise. #pw-internal
* @property string $name Name of Fieldtype module. #pw-group-other
* @property string $shortName Short name of Fieldtype, which excludes the "Fieldtype" prefix. #pw-group-other
* @property string $longName Long name of Fieldtype, which is typically the module title. #pw-group-other
*
*/
abstract class Fieldtype extends WireData implements Module {
@@ -75,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
*
@@ -89,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
*
@@ -520,6 +554,7 @@ abstract class Fieldtype extends WireData implements Module {
* @param Field $field
* @param string|int|float|array|object $value
* @return string|int|float|array
* @see Fieldtype::wakeupValue()
*
*/
public function ___sleepValue(Page $page, Field $field, $value) {
@@ -530,24 +565,69 @@ abstract class Fieldtype extends WireData implements Module {
/**
* Given a value originally generated by exportValue() convert it to a live/runtime value.
*
* This is intended for importing from PW-driven web services.
* This is intended for importing from PW-driven web services. If not overridden, it does
* the same thing as the `Fieldtype::wakeupValue()` method.
*
* #pw-internal
*
* @param Page $page
* @param Field $field
* @param string|int|array $value
* @param string|int|float|array|null $value
* @param array $options Additional options if needed/applicable
* @return string|int|array|object $value
* @see Fieldtype::exportValue()
*
public function ___importValue(Page $page, Field $field, $value) {
*/
public function ___importValue(Page $page, Field $field, $value, array $options = array()) {
if($options) {}
$value = $this->wakeupValue($page, $field, $value);
return $value;
}
/**
* Get associative array of options and info (name => value) that Fieldtype supports for importValue
*
* Current recognized options include the following:
*
* - `importable` (bool): Is the field importable (and exportable)? (default=auto-detect)
*
* - `test` (bool): Indicates Fieldtype supports testing import before committing & populates notices to
* returned Wire object. (default=false)
*
* - `returnsPageValue` (bool): True if it returns the value that should set back to Page? False if return
* value should not be set to Page. When false, it indicates the Fieldtype::importValue() handles the
* actual commit to DB of import data. (default=true)
*
* - `requiresExportValue` (bool): Indicates Fieldtype::importValue() requires an 'exportValue' of the
* current value from Page in $options. (default=false)
*
* - `restoreOnException` (bool): Restore previous value if Exception thrown during import (default=false).
*
* #pw-internal
*
* @param array Field $field
* @return array
*
*/
public function getImportValueOptions(Field $field) {
$schema = $this->getDatabaseSchema($field);
$options = array(
'importable' => (!isset($schema['xtra']['all']) || $schema['xtra']['all'] !== true) ? false : true,
'test' => false,
'returnsPageValue' => true,
'requiresExportValue' => false,
'restoreOnException' => false,
);
return $options;
}
/**
* Given a value, return an portable version of it as either a string, int, float or array
*
* If an array is returned, it should only contain: strings, ints, floats or more arrays of those types.
* This is intended for web service exports.
*
* When applicable, this method should map things like internal IDs to named equivalents (name, path, etc.).
*
* If not overridden, this takes on the same behavior as `Fieldtype::sleepValue()`. However, if overridden,
* it is intended to be more verbose than wakeupValue, where applicable.
@@ -556,8 +636,10 @@ abstract class Fieldtype extends WireData implements Module {
*
* @param Page $page
* @param Field $field
* @param string|int|array|object $value
* @param array $options Optional settings to shape the exported value, if needed.
* @param string|int|float|array|object|null $value
* @param array $options Optional settings to shape the exported value, if needed:
* - `system` (boolean): Indicates value is being used for a system export via $pages->export() call (default=false).
* - `human` (boolean): When true, Fieldtype may optionally emphasize human readability over importability (default=false).
* @return string|float|int|array
*
*/
@@ -1257,7 +1339,12 @@ abstract class Fieldtype extends WireData implements Module {
*/
public function get($key) {
if($key == 'name') return $this->className();
if($key == 'shortName') return str_replace('Fieldtype', '', $this->className());
if($key == 'shortName') {
return str_replace('Fieldtype', '', $this->className());
} else if($key == 'longName' && method_exists($this, 'getModuleInfo')) {
$info = $this->getModuleInfo($this);
return $info['title'];
}
return parent::get($key);
}

View File

@@ -798,11 +798,16 @@ abstract class FieldtypeMulti extends Fieldtype {
}
// only allow matches using templates with the requested field
$sql = 'pages.templates_id IN(';
foreach($field->getTemplates() as $template) {
$sql .= ((int) $template->id) . ',';
$templates = $field->getTemplates();
if(count($templates)) {
$sql = 'pages.templates_id IN(';
foreach($templates as $template) {
$sql .= ((int) $template->id) . ',';
}
$sql = rtrim($sql, ',') . ')';
} else {
$sql = 'pages.templates_id=0';
}
$sql = rtrim($sql, ',') . ')';
$query->where($sql); // QA
} else {

View File

@@ -99,6 +99,22 @@ class FileCompiler extends Wire {
*/
protected $ns = '';
/**
* String with raw PHP blocks only, and with any quoted values removed.
*
* @var string
*
*/
protected $rawPHP = '';
/**
* Same as raw PHP but with all quoted values converted to literal "string"
*
* @var string
*
*/
protected $rawDequotedPHP = '';
/**
* Construct
*
@@ -214,6 +230,52 @@ class FileCompiler extends Wire {
}
}
/**
* Populate the $this->rawPHP data which contains only raw php without quoted values
*
* @param string $data
*
*/
protected function initRawPHP(&$data) {
$this->rawPHP = '';
$this->rawDequotedPHP = '';
$phpOpen = '<' . '?';
$phpClose = '?' . '>';
$phpBlocks = explode($phpOpen, $data);
foreach($phpBlocks as $key => $phpBlock) {
$pos = strpos($phpBlock, $phpClose);
if($pos !== false) {
$closeBlock = substr($phpBlock, strlen($phpClose) + 2);
if(strrpos($closeBlock, '{') && strrpos($closeBlock, '}') && strrpos($closeBlock, '=')
&& strrpos($closeBlock, '(') && strrpos($closeBlock, ')')
&& preg_match('/\sif\s*\(/', $closeBlock)
&& preg_match('/\$[_a-zA-Z][_a-zA-Z0-9]+/', $closeBlock)) {
// closeBlock still looks a lot like PHP, leave $phpBlock as-is
// happens when for example a phpClose is within a PHP string
} else {
$phpBlock = substr($phpBlock, 0, $pos);
}
}
$this->rawPHP .= $phpOpen . $phpBlock . $phpClose . "\n";
}
// remove docblocks/comments
// $this->rawPHP = preg_replace('!/\*.+?\*/!s', '', $this->rawPHP);
// remove escaped quotes
$this->rawDequotedPHP = str_replace(array('\\"', "\\'"), '', $this->rawPHP);
// remove double quoted blocks
$this->rawDequotedPHP = preg_replace('/([\s(.=,])"[^"]*"/s', '$1"string"', $this->rawDequotedPHP);
// remove single quoted blocks
$this->rawDequotedPHP = preg_replace('/([\s(.=,])\'[^\']*\'/s', '$1\'string\'', $this->rawDequotedPHP);
}
/**
* Allow the given filename to be compiled?
*
@@ -326,7 +388,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(
@@ -384,9 +446,13 @@ class FileCompiler extends Wire {
// file already declares a namespace and options indicate we shouldn't compile
return $data;
}
$this->initRawPHP($data);
if($this->options['includes']) {
$dataHash = md5($data);
$this->compileIncludes($data, $sourceFile);
if(md5($data) != $dataHash) $this->initRawPHP($data);
}
if($this->options['namespace']) {
@@ -424,11 +490,16 @@ class FileCompiler extends Wire {
}
if(!strlen(__NAMESPACE__)) {
if(strpos($data, "ProcessWire\\")) {
if(strpos($this->rawPHP, "ProcessWire\\")) {
$data = str_replace(array("\\ProcessWire\\", "ProcessWire\\"), "\\", $data);
}
}
if(stripos($data, "FileCompiler=?") !== false) {
// Allow for a token that gets replaced so a file can detect if it's compiled
$data = str_replace("FileCompiler=?", "FileCompiler=Yes", $data);
}
return $data;
}
@@ -507,11 +578,14 @@ class FileCompiler extends Wire {
protected function compileIncludes(&$data, $sourceFile) {
// other related to includes
if(strpos($data, '__DIR__') !== false) {
$rawPHP = $this->rawPHP;
if(strpos($rawPHP, '__DIR__') !== false) {
$data = str_replace('__DIR__', "'" . dirname($sourceFile) . "'", $data);
$rawPHP = str_replace('__DIR__', "'" . dirname($sourceFile) . "'", $rawPHP);
}
if(strpos($data, '__FILE__') !== false) {
if(strpos($rawPHP, '__FILE__') !== false) {
$data = str_replace('__FILE__', "'" . $sourceFile . "'", $data);
$rawPHP = str_replace('__FILE__', "'" . $sourceFile . "'", $rawPHP);
}
$optionsStr = $this->optionsToString($this->options);
@@ -535,10 +609,15 @@ class FileCompiler extends Wire {
'([;\r\n])' . // 5:close, whatever the last character is on the line
'/im';
if(!preg_match_all($re, $data, $matches)) return;
if(!preg_match_all($re, $rawPHP, $matches)) return;
foreach($matches[0] as $key => $fullMatch) {
// if the include statement looks like one of these below then skip compilation for included file
// include(/*NoCompile*/__DIR__ . '/file.php');
// include(__DIR__ . '/file.php'/*NoCompile*/);
if(strpos($fullMatch, 'NoCompile') !== false) continue;
$open = $matches[1][$key];
$funcMatch = $matches[2][$key];
$argOpen = trim($matches[3][$key]);
@@ -550,45 +629,10 @@ class FileCompiler extends Wire {
// only include, include_once, require, require_once can be used without opening parenthesis
continue;
}
if(strpos($fileMatch, '$') === 0) {
// fileMatch stars with a var name
} else if(strpos($fileMatch, '"') !== strrpos($fileMatch, '"')) {
// fileMatch has both open and close double quotes
} else if(strpos($fileMatch, "'") !== strrpos($fileMatch, "'")) {
// fileMatch has both open and close single quotes
} else if(strpos($fileMatch, '(') !== false && strpos($fileMatch, ')') !== false) {
// likely a function call
} else {
// likely NOT a valid file match, as it doesn't have any of the expected characters
continue;
}
if(strlen($open)) {
$skipMatch = false;
$test = $open;
foreach(array('"', "'") as $quote) {
// skip when words like "require" are in a string
if(strpos($test, $quote) === false) continue;
$test = str_replace('\\' . $quote, '', $test); // ignore quotes that are escaped
if(strpos($test, $quote) === false) continue;
if(substr_count($test, $quote) % 2 > 0) {
// there are an uneven number of quotes, indicating that
// our $funcMatch is likely part of a quoted string
$skipMatch = true;
break;
}
if($quote == '"' && strpos($test, "'") !== false) {
// remove quoted apostrophes so they don't confuse the next iteration
$test = preg_replace('/"[^"\']*\'[^"]*"/', '', $test);
}
}
if($skipMatch) continue;
if(preg_match('/^[$_a-zA-Z0-9]+$/', substr($open, -1))) {
// skip things like: something_include(... and $include
continue;
}
}
$fileMatchType = $this->compileIncludesFileMatchType($fileMatch, $funcMatch);
if(!$fileMatchType) continue;
if(!$this->compileIncludesValidLineOpen($open)) continue;
if(strpos($fileMatch, '?' . '>')) {
// move closing PHP tag out of the fileMatch and into the close
@@ -641,14 +685,6 @@ class FileCompiler extends Wire {
// replace absolute root path references with runtime generated versions
$rootPath = $this->wire('config')->paths->root;
if(strpos($data, $rootPath)) {
/*
$data = preg_replace('%([\'"])' . preg_quote($rootPath) . '([^\'"\s\r\n]*[\'"])%',
'(isset($this) && $this instanceof \\ProcessWire\\Wire ? ' .
'$this->wire("config")->paths->root : ' .
'\\ProcessWire\\wire("config")->paths->root' .
') . $1$2',
$data);
*/
$ns = __NAMESPACE__ ? "\\ProcessWire" : "";
$data = preg_replace('%([\'"])' . preg_quote($rootPath) . '([^\'"\s\r\n]*[\'"])%',
$ns . '\\wire("config")->paths->root . $1$2',
@@ -657,6 +693,98 @@ class FileCompiler extends Wire {
}
/**
* Test the given line $open preceding an include statement for validity
*
* @param string $open
* @return bool Returns true if valid, false if not
*
*/
protected function compileIncludesValidLineOpen($open) {
if(!strlen($open)) return true;
$skipMatch = false;
$test = $open;
foreach(array('"', "'") as $quote) {
// skip when words like "require" are in a string
if(strpos($test, $quote) === false) continue;
$test = str_replace('\\' . $quote, '', $test); // ignore quotes that are escaped
if(strpos($test, $quote) === false) continue;
if(substr_count($test, $quote) % 2 > 0) {
// there are an uneven number of quotes, indicating that
// our $funcMatch is likely part of a quoted string
$skipMatch = true;
break;
}
if($quote == '"' && strpos($test, "'") !== false) {
// remove quoted apostrophes so they don't confuse the next iteration
$test = preg_replace('/"[^"\']*\'[^"]*"/', '', $test);
}
}
if(!$skipMatch && preg_match('/^[$_a-zA-Z0-9]+$/', substr($open, -1))) {
// skip things like: something_include(... and $include
$skipMatch = true;
}
return $skipMatch ? false : true;
}
/**
* Returns fileMatch type of 'var', 'file', 'func' or boolean false if not valid
*
* @param string $fileMatch The $fileMatch var from compileIncludes() method
* @param string $funcMatch include function name
* @return string|bool
*
*/
protected function compileIncludesFileMatchType($fileMatch, $funcMatch) {
$fileMatch = trim($fileMatch);
$isValid = false;
$phpVarSign = strpos($fileMatch, '$');
$doubleQuote1 = strpos($fileMatch, '"');
$doubleQuote2 = strrpos($fileMatch, '"');
$singleQuote1 = strpos($fileMatch, "'");
$singleQuote2 = strrpos($fileMatch, "'");
$parenthesis1 = strpos($fileMatch, '(');
$parenthesis2 = strrpos($fileMatch, ')');
$testFile = '';
if($phpVarSign === 0) {
// fileMatch starts with a var name, make sure it at least starts in PHP var format
if(preg_match('/^\$[_a-zA-Z]/', $fileMatch)) $isValid = 'var';
} else if($doubleQuote1 !== false && $doubleQuote2 > $doubleQuote1) {
// fileMatch has both open and close double quotes with possibly a filename, so validate extension
$testFile = substr($fileMatch, $doubleQuote1 + 1, $doubleQuote2 - $doubleQuote1 - 1);
} else if($singleQuote1 !== false && $singleQuote2 > $singleQuote1) {
// fileMatch has both open and close single quotes with possibly a filename, so validate extension
$testFile = substr($fileMatch, $singleQuote1 + 1, $singleQuote2 - $singleQuote1 - 1);
} else if($parenthesis1 > 0 && $parenthesis2 > $parenthesis1) {
// likely a function call, make sure open parenthesis is preceded by PHP name format
if(preg_match('/[_a-zA-Z][_a-zA-Z0-9]+\(/', $fileMatch)) $isValid = 'func';
} else {
// likely NOT a valid file match, as it doesn't have any of the expected characters
$isValid = false;
}
if($testFile) {
if(strrpos($testFile, '.')) {
// test contains a filename that needs extension validated
$parts = explode('.', $testFile);
$testExt = array_pop($parts);
if($testExt && in_array(strtolower($testExt), $this->extensions)) $isValid = 'file';
} else if($funcMatch == 'wireRenderFile' || $funcMatch == 'wireIncludeFile') {
// these methods don't require a file extension
$isValid = 'file';
}
}
return $isValid;
}
/**
* Compile global class/interface/function references to namespaced versions
*
@@ -677,7 +805,6 @@ class FileCompiler extends Wire {
}
}
*/
$classes = get_declared_classes();
$classes = array_merge($classes, get_declared_interfaces());
@@ -703,6 +830,9 @@ class FileCompiler extends Wire {
$classes = array_merge($classes, $files);
if(!__NAMESPACE__) $classes = array_merge($classes, array_keys($this->wire('modules')->getInstallable()));
$rawPHP = $this->rawPHP;
$rawDequotedPHP = $this->rawDequotedPHP;
// update classes and interfaces
foreach($classes as $class) {
@@ -714,7 +844,7 @@ class FileCompiler extends Wire {
$ns = '';
}
if($ns) {}
if(stripos($data, $class) === false) continue; // quick exit if class name not referenced in data
if(stripos($rawDequotedPHP, $class) === false) continue; // quick exit if class name not referenced in data
$patterns = array(
// 1=open 2=close
@@ -727,26 +857,28 @@ class FileCompiler extends Wire {
"instanceof" => '(\sinstanceof\s+)' . $class . '([^_a-zA-Z0-9]|$)', // 'instanceof Page'
"$class " => '(\(\s*|,\s*)' . $class . '(\s+\$)', // type hinted '(Page $something' or '($foo, Page $something'
);
foreach($patterns as $check => $regex) {
if(stripos($data, $check) === false) continue;
if(!preg_match_all('/' . $regex . '/im', $data, $matches)) continue;
//echo "<pre>" . print_r($matches, true) . "</pre>";
if(stripos($rawDequotedPHP, $check) === false) continue;
if(!preg_match_all('/' . $regex . '/im', $rawDequotedPHP, $matches)) continue;
foreach($matches[0] as $key => $fullMatch) {
$open = $matches[1][$key];
$close = $matches[2][$key];
if(substr($open, -1) == '\\') continue; // if last character in open is '\' then skip the replacement
$className = __NAMESPACE__ ? '\\' . __NAMESPACE__ . '\\' . $class : '\\' . $class;
$data = str_replace($fullMatch, $open . $className . $close, $data);
$repl = $open . $className . $close;
$data = str_replace($fullMatch, $repl, $data);
$rawPHP = str_replace($fullMatch, $repl, $rawPHP);
$rawDequotedPHP = str_replace($fullMatch, $repl, $rawDequotedPHP);
}
}
}
// update PW procedural function calls
$functions = get_defined_functions();
$hasFunctionExists = strpos($data, 'function_exists') !== false;
$hasFunctionExists = strpos($rawDequotedPHP, 'function_exists') !== false;
foreach($functions['user'] as $function) {
@@ -761,14 +893,16 @@ class FileCompiler extends Wire {
}
if($ns) {}
/** @noinspection PhpUnusedLocalVariableInspection */
if(stripos($data, $function) === false) continue; // if function name not mentioned in data, quick exit
if(stripos($rawDequotedPHP, $function) === false) continue; // if function name not mentioned in data, quick exit
$n = 0;
while(preg_match_all('/^(.*?[()!;,@\[=\s.])' . $function . '\s*\(/im', $data, $matches)) {
while(preg_match_all('/^(.*?[()!;,@\[=\s.])' . $function . '\s*\(/im', $rawPHP, $matches)) {
foreach($matches[0] as $key => $fullMatch) {
$open = $matches[1][$key];
if(strpos($open, 'function') !== false) continue; // skip function defined with same name
$data = str_replace($fullMatch, $open . $functionName . '(', $data);
$repl = $open . $functionName . '(';
$data = str_replace($fullMatch, $repl, $data);
$rawPHP = str_replace($fullMatch, $repl, $rawPHP);
}
if(++$n > 5) break;
}
@@ -782,10 +916,10 @@ class FileCompiler extends Wire {
// update other function calls
$ns = __NAMESPACE__ ? "\\ProcessWire" : "";
if(strpos($data, 'class_parents(') !== false) {
if(strpos($rawDequotedPHP, 'class_parents(') !== false) {
$data = preg_replace('/\bclass_parents\(/', $ns . '\\wireClassParents(', $data);
}
if(strpos($data, 'class_implements(') !== false) {
if(strpos($rawDequotedPHP, 'class_implements(') !== false) {
$data = preg_replace('/\bclass_implements\(/', $ns . '\\wireClassImplements(', $data);
}
@@ -843,7 +977,7 @@ class FileCompiler extends Wire {
copy($sourceFile, $targetFile);
$this->chmod($targetFile);
touch($targetFile, filemtime($sourceFile));
$this->touch($targetFile, filemtime($sourceFile));
$numCopied++;
}
@@ -926,7 +1060,7 @@ class FileCompiler extends Wire {
// maintenance already run today
return false;
}
touch($lastRunFile);
$this->touch($lastRunFile);
$this->chmod($lastRunFile);
clearstatcache();
@@ -976,14 +1110,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");
}
}
@@ -1028,5 +1162,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

@@ -451,11 +451,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 +477,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 +491,7 @@ class FileLog extends Wire {
*
*/
public function delete() {
return @unlink($this->logFilename);
return $this->wire('files')->unlink($this->logFilename, true);
}
public function __toString() {

View File

@@ -77,6 +77,7 @@ endif;
* - Specify false to exclude all empty values (this is the default if not specified).
* - Specify true to allow all empty values to be retained.
* - Specify an array of keys (from data) that should be retained if you want some retained and not others.
* - Specify array of literal empty value types to retain, i.e. [ 0, '0', array(), false, null ].
* - Specify the digit 0 to retain values that are 0, but not other types of empty values.
* @param bool $beautify Beautify the encoded data when possible for better human readability? (requires PHP 5.4+)
* @return string String of JSON data
@@ -500,7 +501,7 @@ function wireRenderFile($filename, array $vars = array(), array $options = array
* - It will assume a ".php" extension if filename has no extension.
*
* Note this function produced direct output. To retrieve output as a return value, use the
* wireTemplateFile function instead.
* `wireRenderFile()` function instead.
*
* @param $filename
* @param array $vars Optional variables you want to hand to the include (associative array)
@@ -626,14 +627,96 @@ function wireIconMarkupFile($filename, $class = '') {
/**
* Given a quantity of bytes, return a more readable size string
*
* @param int $size
* @param int $bytes Quantity in bytes
* @param bool|int|array $small Make returned string as small as possible (default=false),
* …or specify integer 1 for $small option but with space between number and unit label.
* …or optionally specify $options argument here.
* @param array|int $options Options to modify default behavior, or if an integer then `decimals` option is assumed:
* - `decimals` (int): Number of decimals to use in returned value (default=0).
* - `decimal_point` (string|null): Decimal point character, or null to detect from locale (default=null).
* - `thousands_sep` (string|null): Thousands separator, or null to detect from locale (default=null).
* - `small` (bool): If no $small argument was specified, you can optionally specify it in this $options array.
* @return string
*
*/
function wireBytesStr($size) {
if($size < 1024) return number_format($size) . ' ' . __('bytes', __FILE__);
$kb = round($size / 1024);
return number_format($kb) . " " . __('kB', __FILE__); // kilobytes
function wireBytesStr($bytes, $small = false, $options = array()) {
$defaults = array(
'decimals' => 0,
'decimal_point' => null,
'thousands_sep' => null,
);
if(is_array($small)) {
$options = $small;
$small = isset($options['small']) ? $options['small'] : false;
}
if(!is_array($options)) $options = array('decimals' => (int) $options);
if(!is_int($bytes)) $bytes = (int) $bytes;
$options = array_merge($defaults, $options);
$locale = array();
// determine size value and units label
if($bytes < 1024) {
$val = $bytes;
if($small) {
$label = $val > 0 ? __('B', __FILE__) : ''; // bytes
} else {
$label = __('bytes', __FILE__);
}
} else if($bytes < 1000000) {
$val = $bytes / 1024;
$label = __('kB', __FILE__); // kilobytes
} else if($bytes < 1073741824) {
$val = $bytes / 1024 / 1024;
$label = __('MB', __FILE__); // megabytes
} else {
$val = $bytes / 1024 / 1024 / 1024;
$label = __('GB', __FILE__); // gigabytes
}
// determine decimal point if not specified in $options
if($options['decimal_point'] === null) {
if($options['decimals'] > 0) {
// determine decimal point from locale
if(empty($locale)) $locale = localeconv();
$options['decimal_point'] = empty($locale['decimal_point']) ? '.' : $locale['decimal_point'];
} else {
// no decimal point needed (not used)
$options['decimal_point'] = '.';
}
}
// determine thousands separator if not specified in $options
if($options['thousands_sep'] === null) {
if($small || $val < 1000) {
// no thousands separator needed
$options['thousands_sep'] = '';
} else {
// get thousands separator from current locale
if(empty($locale)) $locale = localeconv();
$options['thousands_sep'] = empty($locale['thousands_sep']) ? '' : $locale['thousands_sep'];
}
}
// format number to string
$str = number_format($val, $options['decimals'], $options['decimal_point'], $options['thousands_sep']);
// in small mode remove numbers with decimals that consist only of zeros "0"
if($small && $options['decimals'] > 0) {
$test = substr($str, -1 * $options['decimals']);
if(((int) $test) === 0) {
$str = substr($str, 0, strlen($str) - ($options['decimals'] + 1)); // i.e. 123.00 => 123
} else {
$str = rtrim($str, '0'); // i.e. 123.10 => 123.1
}
}
// append units label to number
$str .= ($small === true ? '' : ' ') . $label;
return $str;
}
/**
@@ -757,6 +840,82 @@ function wireClassParents($className, $autoload = true) {
return $a;
}
/**
* Does given instance (or class) represent an instance of the given className (or class names)?
*
* Since version 3.0.108 the $className argument may also represent an interface,
* array of interfaces, or mixed array of interfaces and class names. Previous versions did
* not support interfaces unless the $instance argument was an object.
*
* @param object|string $instance Object instance to test (or string of its class name).
* @param string|array $className Class/interface name or array of class/interface names to test against.
* @param bool $autoload
* @return bool|string Returns one of the following:
* - boolean false if not an instance (whether $className argument is string or array).
* - boolean true if given a single $className (string) and $instance is an instance of it.
* - string of first matching class/interface name if $className was an array of classes to test.
*
*/
function wireInstanceOf($instance, $className, $autoload = true) {
if(is_array($className)) {
$returnClass = true;
$classNames = $className;
} else {
$returnClass = false;
$classNames = array($className);
}
$matchClass = null;
$instanceIsObject = is_object($instance);
$instanceParents = null;
$instanceInterfaces = null;
$instanceClass = null;
if($instanceIsObject) {
// instance is an object
} else if(is_string($instance)) {
// instance is a class name, make sure it has namespace
$instanceClass = wireClassName($instance, true);
if($instanceClass === null) $instanceClass = $instance; // if above failed
$instance = $instanceClass;
} else {
// unrecognized instance value
return false;
}
foreach($classNames as $className) {
$className = wireClassName($className, true); // with namespace
if($instanceIsObject && (class_exists($className, $autoload) || interface_exists($className, $autoload))) {
if($instance instanceof $className) {
$matchClass = $className;
}
} else {
if($instanceClass === null) {
$instanceClass = wireClassName($instance, true);
if($instanceClass === null) break;
}
if($instanceParents === null) {
$instanceParents = wireClassParents($instance, $autoload);
$instanceParents[$instanceClass] = 1;
}
if(isset($instanceParents[$className])) {
$matchClass = $className;
} else {
if($instanceInterfaces === null) {
$instanceInterfaces = wireClassImplements($instance, $autoload);
}
if(isset($instanceInterfaces[$className])) {
$matchClass = $className;
}
}
}
if($matchClass !== null) break;
}
return $returnClass ? $matchClass : ($matchClass !== null);
}
/**
* ProcessWire namespace aware version of PHP's is_callable() function
*
@@ -771,6 +930,25 @@ function wireIsCallable($var, $syntaxOnly = false, &$callableName = '') {
return is_callable($var, $syntaxOnly, $callableName);
}
/**
* Return the count of item(s) present in the given value
*
* Duplicates behavior of PHP count() function prior to PHP 7.2, which states:
* Returns the number of elements in $value. When the parameter is neither an array nor an
* object with implemented Countable interface, 1 will be returned. There is one exception,
* if $value is NULL, 0 will be returned.
*
* @param mixed $value
* @return int
*
*/
function wireCount($value) {
if($value === null) return 0;
if(is_array($value)) return count($value);
if(is_object($value) && $value instanceof \Countable) return count($value);
return 1;
}
/**
* Get or set an output region (primarily for front-end output usage)
*
@@ -801,13 +979,16 @@ function wireIsCallable($var, $syntaxOnly = false, &$callableName = '') {
* - 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.
*
*/
function wireRegion($key, $value = null) {
static $regions = array();
static $locked = array();
if(empty($key) || $key === '*') {
// all regions
@@ -822,17 +1003,24 @@ function wireRegion($key, $value = null) {
} else {
// set region
$pos = strpos($key, '+');
if($pos !== false) $key = trim($key, '+');
if($pos !== false) {
$lock = strpos($key, '++') !== false;
$key = trim($key, '+');
if($lock !== false && !isset($locked[$key])) {
$locked[$key] = $lock === 0 ? '^' : '$'; // prepend : append
}
}
$lock = isset($locked[$key]) ? $locked[$key] : '';
if(!isset($regions[$key])) $regions[$key] = '';
if($pos === 0) {
if($pos === 0 || ($pos === false && $lock == '^')) {
// prepend
$regions[$key] = $value . $regions[$key];
} else if($pos) {
} else if($pos || ($pos === false && $lock == '$')) {
// append
$regions[$key] .= $value;
} else if($value === '') {
// clear region
unset($regions[$key]);
if(!$lock) unset($regions[$key]);
} else {
// insert/replace
$regions[$key] = $value;
@@ -843,3 +1031,26 @@ function wireRegion($key, $value = null) {
return $result;
}
/**
* Create new WireArray, add given $items to it, and return it
*
* @param array|WireArray $items
* @return WireArray
*
*/
function WireArray($items = array()) {
return WireArray::newInstance($items);
}
/**
* Create new PageArray, add given $items (pages) to it, and return it
*
* @param array|PageArray $items
* @return WireArray
*
*/
function PageArray($items = array()) {
return PageArray::newInstance($items);
}

View File

@@ -430,3 +430,47 @@ 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.
*
* ~~~~~
* // 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 setting($name = '', $value = null) {
static $settings = [];
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;
}

View File

@@ -9,6 +9,7 @@
* Instances of HookEvent are passed to Hook handlers when their requested method has been called.
*
* #pw-summary HookEvent is a type provided to hook functions with information about the event.
* #pw-var $event
* #pw-body =
* ~~~~~~
* // Example
@@ -42,17 +43,23 @@ class HookEvent extends WireData {
/**
* Construct the HookEvent and establish default values
*
* @param array $eventData Optional event data to start with
*
*/
public function __construct() {
$this->set('object', null);
$this->set('method', '');
$this->set('arguments', array());
$this->set('return', null);
$this->set('replace', false);
$this->set('options', array());
$this->set('id', '');
$this->set('cancelHooks', false);
public function __construct(array $eventData = array()) {
$data = array(
'object' => null,
'method' => '',
'arguments' => array(),
'return' => null,
'replace' => false,
'options' => array(),
'id' => '',
'cancelHooks' => false
);
if(!empty($eventData)) $data = array_merge($data, $eventData);
$this->data = $data;
}
/**
@@ -201,7 +208,7 @@ class HookEvent extends WireData {
* ~~~~~
*
* @param string|null $hookId
* @return $this
* @return HookEvent|WireData $this
*
*/
public function removeHook($hookId) {
@@ -226,6 +233,5 @@ class HookEvent extends WireData {
return $s;
}
}

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(null)));
}
$this->info['appmarker'] = $appmarker;
if(isset($additionalInfo['APP13'])) {
@@ -232,7 +232,7 @@ class ImageInspector extends WireData {
$i['trans'] = isset($gi->m_bTrans) ? $gi->m_bTrans : false;
$i['transcolor'] = isset($gi->m_nTrans) ? $gi->m_nTrans : '';
$i['bgcolor'] = $gfh->m_nBgColor;
$i['numcolors'] = $gfh->m_colorTable->m_nColors;
$i['numcolors'] = isset($gfh->m_colorTable->m_nColors) ? $gfh->m_colorTable->m_nColors : 0;
$i['interlace'] = $gih->m_bInterlace;
$this->info = $i;
unset($gif, $gih, $gfh, $gi, $i);

View File

@@ -88,20 +88,8 @@ class ImageSizer extends Wire {
*
*/
public function __construct($filename = '', $options = array()) {
if(isset($options['forceEngine'])) {
$this->forceEngineName = $options['forceEngine'];
unset($options['forceEngine']);
}
$this->filename = $filename;
$this->initialOptions = $options;
if(strlen($filename)) {
$imageInspector = new ImageInspector($filename);
$this->inspectionResult = $imageInspector->inspect($filename, true);
$this->engine = $this->newImageSizerEngine($filename, $options, $this->inspectionResult);
}
if(!empty($options)) $this->setOptions($options);
if(!empty($filename)) $this->setFilename($filename);
}
/**
@@ -158,6 +146,7 @@ class ImageSizer extends Wire {
if(empty($inspectionResult) && $filename && is_readable($filename)) {
$imageInspector = new ImageInspector($filename);
$this->wire($imageInspector);
$inspectionResult = $imageInspector->inspect($filename, true);
$this->inspectionResult = $inspectionResult;
}
@@ -226,17 +215,8 @@ class ImageSizer extends Wire {
*/
public function ___resize($targetWidth, $targetHeight = 0) {
if(empty($this->filename)) throw new WireException('No file to resize: please call setFilename($file) before resize()');
if(empty($this->engine)) {
// set the engine, and check if the engine is ready to use
$this->engine = $this->newImageSizerEngine();
if(!$this->engine) {
throw new WireException('There seems to be no support for the GD image library on your host?');
}
}
$success = $this->engine->resize($targetWidth, $targetHeight);
$engine = $this->getEngine();
$success = $engine->resize($targetWidth, $targetHeight);
if(!$success) {
// fallback to GD
@@ -297,8 +277,12 @@ class ImageSizer extends Wire {
*
*/
public function setOptions(array $options) {
if(isset($options['forceEngine'])) {
$this->setForceEngine($options['forceEngine']);
unset($options['forceEngine']);
}
$this->initialOptions = array_merge($this->initialOptions, $options);
if($this->engine) $this->engine->setOptions($options);
if($this->engine) $this->engine->setOptions($this->initialOptions);
return $this;
}
@@ -329,16 +313,48 @@ class ImageSizer extends Wire {
public function setUpscaling($value = true) { return $this->setOptions(array('upscaling', $value)); }
public function setUseUSM($value = true) { return $this->setOptions(array('useUSM', $value)); }
// getters (@todo phpdocs)
public function getWidth() { return $this->engine->image['width']; }
public function getHeight() { return $this->engine->image['height']; }
public function getFilename() { return $this->engine->filename; }
public function getExtension() { return $this->engine->extension; }
public function getImageType() { return $this->engine->imageType; }
public function isModified() { return $this->engine->modified; }
public function getOptions() { return $this->engine->getOptions(); }
public function getEngine() { return $this->engine; }
public function __get($key) { return $this->engine->__get($key); }
public function getWidth() {
$image = $this->getEngine()->get('image');
return $image['width'];
}
public function getHeight() {
$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 getOptions() { return $this->getEngine()->getOptions(); }
/**
* Get the current ImageSizerEngine
*
* @return ImageSizerEngine
* @throws WireException
*
*/
public function getEngine() {
if($this->engine) return $this->engine;
if(empty($this->filename)) {
throw new WireException('No file to process: please call setFilename($file) before calling other methods');
}
$imageInspector = new ImageInspector($this->filename);
$this->inspectionResult = $imageInspector->inspect($this->filename, true);
$this->engine = $this->newImageSizerEngine($this->filename, $this->initialOptions, $this->inspectionResult);
// set the engine, and check if the engine is ready to use
if(!$this->engine) {
throw new WireException('There seems to be no support for the GD image library on your host?');
}
return $this->engine;
}
public function __get($key) { return $this->getEngine()->__get($key); }
/**
* ImageInformation from Image Inspector in short form or full RawInfoData
@@ -348,7 +364,8 @@ class ImageSizer extends Wire {
*
*/
public function getImageInfo($rawData = false) {
$this->getEngine();
if($rawData) return $this->inspectionResult;
$imageType = $this->inspectionResult['info']['imageType'];
$type = '';
@@ -507,7 +524,9 @@ class ImageSizer extends Wire {
*
*/
static public function imageResetIPTC($image) {
$wire = null;
if($image instanceof Pageimage) {
$wire = $image;
$filename = $image->filename;
} else if(is_readable($image)) {
$filename = $image;
@@ -515,8 +534,72 @@ class ImageSizer extends Wire {
return null;
}
$sizer = new ImageSizerEngineGD($filename);
if($wire) $wire->wire($sizer);
$result = false !== $sizer->writeBackIPTC($filename) ? true : false;
return $result;
}
/**
* Rotate image by given degrees
*
* @param int $degrees
* @return bool
*
*/
public function rotate($degrees) {
return $this->getEngine()->rotate($degrees);
}
/**
* Flip image vertically
*
* @return bool
*
*/
public function flipVertical() {
return $this->getEngine()->flipVertical();
}
/**
* Flip image horizontally
*
* @return bool
*
*/
public function flipHorizontal() {
return $this->getEngine()->flipHorizontal();
}
/**
* Flip both vertically and horizontally
*
* @return bool
*
*/
public function flipBoth() {
return $this->getEngine()->flipBoth();
}
/**
* Convert image to greyscale (black and white)
*
* @return bool
*
*/
public function convertToGreyscale() {
return $this->getEngine()->convertToGreyscale();
}
/**
* Convert image to sepia tone
*
* @param int $sepia Sepia amount
* @return bool
*
*/
public function convertToSepia($sepia = 55) {
return $this->getEngine()->convertToSepia('', $sepia);
}
}

View File

@@ -8,6 +8,7 @@
*
* @property bool $autoRotation
* @property bool $upscaling
* @property bool $interlace
* @property array|string|bool $cropping
* @property int $quality
* @property string $sharpening
@@ -59,6 +60,14 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*/
protected $quality = 90;
/**
* Image interlace setting, false or true
*
* @var bool
*
*/
protected $interlace = false;
/**
* Information about the image (width/height)
*
@@ -100,9 +109,11 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
* Possible values: northwest, north, northeast, west, center, east, southwest, south, southeast
* or TRUE to crop to center, or FALSE to disable cropping.
* Or array where index 0 is % or px from left, and index 1 is % or px from top. Percent is assumed if
* values are number strings that end with %. Pixels are assumed of values are just integers.
* Default is: TRUE
*
* @var bool
* @var bool|array
*
*/
protected $cropping = true;
@@ -206,6 +217,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
'autoRotation',
'upscaling',
'cropping',
'interlace',
'quality',
'sharpening',
'defaultGamma',
@@ -388,6 +400,36 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*/
abstract protected function processResize($srcFilename, $dstFilename, $fullWidth, $fullHeight, $finalWidth, $finalHeight);
/**
* Process rotate of an image
*
* @param string $srcFilename
* @param string $dstFilename
* @param int $degrees Clockwise degrees, i.e. 90, 180, 270, -90, -180, -270
* @return bool
*
*/
protected function processRotate($srcFilename, $dstFilename, $degrees) {
if($srcFilename && $dstFilename && $degrees) {}
$this->error('rotate not implemented for ' . $this->className());
return false;
}
/**
* Process vertical or horizontal flip of an image
*
* @param string $srcFilename
* @param string $dstFilename
* @param bool $flipVertical True if flip is vertical, false if flip is horizontal
* @return bool
*
*/
protected function processFlip($srcFilename, $dstFilename, $flipVertical) {
if($srcFilename && $dstFilename && $flipVertical) {}
$this->error('flip not implemented for ' . $this->className());
return false;
}
/**
* Get array of image file extensions this ImageSizerModule can process
*
@@ -510,9 +552,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
@@ -623,28 +665,32 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
$pWidth = $this->getProportionalWidth($targetHeight);
}
if(!$this->upscaling) {
// we are going to shoot for something smaller than the target
// rounding issue fix via @horst-n for #191
if($targetWidth == $originalTargetWidth && 1 + $targetWidth == $pWidth) $pWidth = $pWidth - 1;
if($targetHeight == $originalTargetHeight && 1 + $targetHeight == $pHeight) $pHeight = $pHeight - 1;
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';
}
}
@@ -699,10 +745,29 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
$cropping = strtolower($cropping);
if(strpos($cropping, ',')) {
$cropping = explode(',', $cropping);
if(strpos($cropping[0], '%') !== false) $cropping[0] = round(min(100, max(0, $cropping[0]))) . '%';
else $cropping[0] = (int) $cropping[0];
if(strpos($cropping[1], '%') !== false) $cropping[1] = round(min(100, max(0, $cropping[1]))) . '%';
else $cropping[1] = (int) $cropping[1];
} else if(strpos($cropping, 'x') && preg_match('/^([pd])(\d+)x(\d+)(z\d+)?/', $cropping, $matches)) {
$cropping = array(0 => $matches[1], 1 => $matches[2]);
if(isset($matches[3])) $cropping[2] = (int) $matches[3];
if($matches[1] == 'p') {
$cropping[0] .= '%';
$cropping[0] .= '%';
}
}
}
if(is_array($cropping)) {
if(strpos($cropping[0], '%') !== false) {
$cropping[0] = round(min(100, max(0, $cropping[0]))) . '%';
} else {
$cropping[0] = (int) $cropping[0];
}
if(strpos($cropping[1], '%') !== false) {
$cropping[1] = round(min(100, max(0, $cropping[1]))) . '%';
} else {
$cropping[1] = (int) $cropping[1];
}
if(isset($cropping[2])) { // zoom
$cropping[2] = (int) $cropping[2];
if($cropping[2] < 2 || $cropping[2] > 99) unset($cropping[2]);
}
}
@@ -732,8 +797,12 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
// crop name if custom center point is specified
if(is_array($cropping)) {
// p = percent, d = pixel dimension
$cropping = (strpos($cropping[0], '%') !== false ? 'p' : 'd') . ((int) $cropping[0]) . 'x' . ((int) $cropping[1]);
// p = percent, d = pixel dimension, z = zoom
$zoom = isset($cropping[2]) ? (int) $cropping[2] : 0;
$cropping =
(strpos($cropping[0], '%') !== false ? 'p' : 'd') .
((int) $cropping[0]) . 'x' . ((int) $cropping[1]);
if($zoom > 1 && $zoom < 100) $cropping .= "z$zoom";
}
// if crop is TRUE or FALSE, we don't reflect that in the filename, so make it blank
@@ -790,11 +859,13 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
if(isset($value[$v])) $$v = $value[$v];
}
}
foreach(array('x', 'y', 'w', 'h') as $k) {
$v = isset($$k) ? $$k : -1;
if(!is_int($v) || $v < 0) throw new WireException("Missing or wrong param $k for ImageSizer-cropExtra!");
if(('w' == $k || 'h' == $k) && 0 == $v) throw new WireException("Wrong param $k for ImageSizer-cropExtra!");
$v = (int) (isset($$k) ? $$k : -1);
if(!$v && $k == 'w' && $h > 0) $v = $this->getProportionalWidth((int) $h);
if(!$v && $k == 'h' && $w > 0) $v = $this->getProportionalHeight((int) $w);
if($v < 0) throw new WireException("Missing or wrong param $k=$v for ImageSizer-cropExtra! " . print_r($value, true));
if(('w' == $k || 'h' == $k) && 0 == $v) throw new WireException("Wrong param $k=$v for ImageSizer-cropExtra! " . print_r($value, true));
}
$this->cropExtra = array($x, $y, $w, $h);
@@ -906,6 +977,19 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
$this->upscaling = $this->getBooleanValue($value);
return $this;
}
/**
* Turn on/off interlace
*
* @param bool $value Whether to upscale or not (default = true)
*
* @return $this
*
*/
public function setInterlace($value = true) {
$this->interlace = $this->getBooleanValue($value);
return $this;
}
/**
* Set default gamma value: 0.5 - 4.0 | -1
@@ -1055,6 +1139,9 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
case 'upscaling':
$this->setUpscaling($value);
break;
case 'interlace':
$this->setInterlace($value);
break;
case 'sharpening':
$this->setSharpening($value);
break;
@@ -1083,7 +1170,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
$this->setFlip($value);
break;
case 'useUSM':
$this->setUseUsm($value);
$this->setUseUSM($value);
break;
default:
@@ -1122,6 +1209,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
'quality' => $this->quality,
'cropping' => $this->cropping,
'upscaling' => $this->upscaling,
'interlace' => $this->interlace,
'autoRotation' => $this->autoRotation,
'sharpening' => $this->sharpening,
'defaultGamma' => $this->defaultGamma,
@@ -1323,7 +1411,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
protected function getCropDimensions(&$w1, &$h1, $gdWidth, $targetWidth, $gdHeight, $targetHeight) {
if(is_string($this->cropping)) {
// calculate from 8 named cropping points
switch($this->cropping) {
case 'nw':
$w1 = 0;
@@ -1357,20 +1445,45 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
}
} else if(is_array($this->cropping)) {
// calculate from specific percent or pixels from left and top
// $this->cropping is an array with the following:
// index 0 represents % or pixels from left
// index 1 represents % or pixels from top
// @interrobang + @u-nikos
if(strpos($this->cropping[0], '%') === false) $pointX = (int) $this->cropping[0];
else $pointX = $gdWidth * ((int) $this->cropping[0] / 100);
if(strpos($this->cropping[0], '%') === false) {
$pointX = (int) $this->cropping[0];
} else {
$pointX = $gdWidth * ((int) $this->cropping[0] / 100);
}
if(strpos($this->cropping[1], '%') === false) $pointY = (int) $this->cropping[1];
else $pointY = $gdHeight * ((int) $this->cropping[1] / 100);
if(strpos($this->cropping[1], '%') === false) {
$pointY = (int) $this->cropping[1];
} else {
$pointY = $gdHeight * ((int) $this->cropping[1] / 100);
}
/*
if(isset($this->cropping[2]) && $this->cropping[2] > 1) {
// zoom percent (2-100)
$zoom = (int) $this->cropping[2];
}
*/
if($pointX < $targetWidth / 2) $w1 = 0;
else if($pointX > ($gdWidth - $targetWidth / 2)) $w1 = $gdWidth - $targetWidth;
else $w1 = $pointX - $targetWidth / 2;
if($pointX < $targetWidth / 2) {
$w1 = 0;
} else if($pointX > ($gdWidth - $targetWidth / 2)) {
$w1 = $gdWidth - $targetWidth;
} else {
$w1 = $pointX - $targetWidth / 2;
}
if($pointY < $targetHeight / 2) $h1 = 0;
else if($pointY > ($gdHeight - $targetHeight / 2)) $h1 = $gdHeight - $targetHeight;
else $h1 = $pointY - $targetHeight / 2;
if($pointY < $targetHeight / 2) {
$h1 = 0;
} else if($pointY > ($gdHeight - $targetHeight / 2)) {
$h1 = $gdHeight - $targetHeight;
} else {
$h1 = $pointY - $targetHeight / 2;
}
}
}
@@ -1406,8 +1519,6 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
$this->fullHeight = $this->image['height'];
if(0 == $this->finalWidth && 0 == $this->finalHeight) return false;
if(0 == $this->finalWidth) $this->finalWidth = ceil(($this->finalHeight / $this->fullHeight) * $this->fullWidth);
if(0 == $this->finalHeight) $this->finalHeight = ceil(($this->finalWidth / $this->fullWidth) * $this->fullHeight);
if($this->scale !== 1.0) { // adjust for hidpi
if($this->finalWidth) $this->finalWidth = ceil($this->finalWidth * $this->scale);
@@ -1429,8 +1540,8 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
// 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);
$this->wire('files')->chmod($this->filename);
$this->wire('files')->unlink($this->tmpFile);
// post processing: IPTC, setModified and reload ImageInfo
$this->writeBackIPTC($this->filename, false);
@@ -1440,6 +1551,118 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
return true;
}
/**
* Just rotate image by number of degrees
*
* @param int $degrees
* @param string $dstFilename Optional destination filename. If not present, source will be overwritten.
* @return bool True on success, false on fail
*
*/
public function rotate($degrees, $dstFilename = '') {
$degrees = (int) $degrees;
$srcFilename = $this->filename;
if(empty($dstFilename)) $dstFilename = $srcFilename;
if($degrees > 360) $degrees = 360 - $degrees;
if($degrees < -360) $degrees = $degrees - 360;
if($degrees == 0 || $degrees == 360 || $degrees == -360) {
if($dstFilename != $this->filename) wireCopy($this->filename, $dstFilename);
return true;
}
if($srcFilename == $dstFilename) {
// src and dest are the same, so use a temporary file
$n = 1;
do {
$tmpFilename = dirname($dstFilename) . "/.ise$n-" . basename($dstFilename);
} while(file_exists($tmpFilename) && $n++);
} else {
// src and dest are different files
$tmpFilename = $dstFilename;
}
$result = $this->processRotate($srcFilename, $tmpFilename, $degrees);
if($result) {
// success
if($tmpFilename != $dstFilename) {
if(is_file($dstFilename)) $this->wire('files')->unlink($dstFilename);
$this->wire('files')->rename($tmpFilename, $dstFilename);
}
$this->wire('files')->chmod($dstFilename);
} else {
// fail
if(is_file($tmpFilename)) $this->wire('files')->unlink($tmpFilename);
}
return $result;
}
/**
* Flip vertically
*
* @param string $dstFilename
* @return bool
*
*/
public function flipVertical($dstFilename = '') {
if(empty($dstFilename)) $dstFilename = $this->filename;
return $this->processFlip($this->filename, $dstFilename, 'vertical');
}
/**
* Flip horizontally
*
* @param string $dstFilename
* @return bool
*
*/
public function flipHorizontal($dstFilename = '') {
if(empty($dstFilename)) $dstFilename = $this->filename;
return $this->processFlip($this->filename, $dstFilename, 'horizontal');
}
/**
* Flip both vertically and horizontally
*
* @param string $dstFilename
* @return bool
*
*/
public function flipBoth($dstFilename = '') {
if(empty($dstFilename)) $dstFilename = $this->filename;
return $this->processFlip($this->filename, $dstFilename, 'both');
}
/**
* Convert image to greyscale
*
* @param string $dstFilename If different from source file
* @return bool
*
*/
public function convertToGreyscale($dstFilename = '') {
if($dstFilename) {}
return false;
}
/**
* Convert image to sepia
*
* @param string $dstFilename If different from source file
* @param float|int $sepia Sepia value
* @return bool
*
*/
public function convertToSepia($dstFilename = '', $sepia = 55) {
if($dstFilename && $sepia) {}
return false;
}
/**
* Get an integer representing the resize method to use
*
@@ -1473,6 +1696,121 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
return 4;
}
/**
* Helper function to perform a cropExtra / cropBefore cropping
*
* Intended for use by the getFocusZoomCropDimensions() method
*
* @param string $focus (focus point in percent, like: 54.7%)
* @param int $sourceDimension (source image width or height)
* @param int $cropDimension (target crop-image width or height)
* @param int $zoom
*
* @return int $position (crop position x or y in pixel)
*
*/
protected function getFocusZoomPosition($focus, $sourceDimension, $cropDimension, $zoom) {
$focus = intval($focus); // string with float value and percent char, (needs to be converted to integer)
$scale = 1 + (($zoom / 100) * 2);
$focusPX = ($sourceDimension / 100 * $focus);
$posMinPX = $cropDimension / 2 / $scale;
$posMaxPX = $sourceDimension - ($cropDimension / 2);
// calculate the position in pixel !
if($focusPX >= $posMaxPX) {
$posPX = $sourceDimension - $cropDimension;
} else if($focusPX <= $posMinPX) {
$posPX = 0;
} else {
$posPX = $focusPX - ($cropDimension / 2);
if(0 > $posPX) $posPX = 0;
}
return $posPX;
}
/**
* Get an array of the 4 dimensions necessary to perform a cropExtra / cropBefore cropping
*
* Intended for use by the resize() method
*
* @param int $zoom
* @param int $fullWidth
* @param int $fullHeight
* @param int $finalWidth
* @param int $finalHeight
* @return array
*
*/
protected function getFocusZoomCropDimensions($zoom, $fullWidth, $fullHeight, $finalWidth, $finalHeight) {
// validate & calculate / prepare params
$zoom = $zoom <= 70 ? $zoom : 70; // validate / correct the zoom value, it needs to be between 2 and 70
$zoom = $zoom >= 2 ? $zoom : 2;
// calculate the max crop dimensions
$ratioFinal = $finalWidth / $finalHeight; // get the ratio of the requested crop
$percentW = $finalWidth / $fullWidth * 100; // calculate percentage of the crop width in regard of the original width
$percentH = $finalHeight / $fullHeight * 100; // calculate percentage of the crop height in regard of the original height
if($percentW >= $percentH) { // check wich one is greater
$maxW = $fullWidth; // if percentW is greater, maxW becomes the original Width
$maxH = $fullWidth / $ratioFinal; // ... and maxH gets calculated via the ratio
} else {
$maxH = $fullHeight; // if percentH is greater, maxH becomes the original Height
$maxW = $fullHeight * $ratioFinal; // ... and maxW gets calculated via the ratio
}
// calculate the zoomed dimensions
$cropW = $maxW - ($maxW * $zoom / 100); // to get the final crop Width and Height, the amount for zoom-in
$cropH = $maxH - ($maxH * $zoom / 100); // needs to get stripped out
// validate against the minimal dimensions
if(!$this->upscaling) { // if upscaling isn't allowed, we decrease the zoom, so that we get a crop with the min-Dimensions
if($cropW < $finalWidth) {
$cropW = $finalWidth;
$cropH = $finalWidth / $ratioFinal;
}
if($cropH < $finalHeight) {
$cropH = $finalHeight;
$cropW = $finalHeight * $ratioFinal;
}
}
// calculate the crop positions
$posX = $this->getFocusZoomPosition($this->cropping[0], $fullWidth, $cropW, $zoom); // calculate the x-position
$posY = $this->getFocusZoomPosition($this->cropping[1], $fullHeight, $cropH, $zoom); // calculate the y-position
return array(
0 => (int) $posX,
1 => (int) $posY,
2 => (int) $cropW,
3 => (int) $cropH
);
}
/**
* Get current zoom percentage setting or 0 if not set
*
* Value is determined from the $this->cropping array index 2 and is used only if index 0 and
* index 1 are percentages (and indicated as such with a percent sign).
*
* @return int
*
*/
protected function getFocusZoomPercent() {
// check if we have to proceed a zoomed focal point cropping,
// therefore we need index 0 and 1 to be strings with '%' sign included
// and index 2 to be an integer between 2 and 70
$a = $this->cropping;
if(is_array($a) && isset($a[2]) && strpos($a[0], '%') !== false && strpos($a[1], '%') !== false) {
$zoom = (int) $a[2];
if($zoom < 2) $zoom = 0;
if($zoom > 70) $zoom = 70;
} else {
$zoom = 0;
}
return $zoom;
}
/**
* Module info: not-autoload
*

View File

@@ -102,11 +102,12 @@ class ImageSizerEngineGD extends ImageSizerEngine {
protected function processResize($srcFilename, $dstFilename, $fullWidth, $fullHeight, $finalWidth, $finalHeight) {
$this->modified = false;
$isModified = false;
if(isset($this->info['bits'])) $this->imageDepth = $this->info['bits'];
$this->imageFormat = strtoupper(str_replace('image/', '', $this->info['mime']));
if(!in_array($this->imageFormat, $this->validSourceImageFormats())) {
throw new WireException(sprintf($this->_("loaded file '%s' is not in the list of valid images", basename($dstFilename))));
throw new WireException(sprintf($this->_("loaded file '%s' is not in the list of valid images"), basename($dstFilename)));
}
$image = null;
@@ -141,6 +142,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
if($this->rotate || $needRotation) { // @horst
$degrees = $this->rotate ? $this->rotate : $orientations[0];
$image = $this->imRotate($image, $degrees);
$isModified = true;
if(abs($degrees) == 90 || abs($degrees) == 270) {
// we have to swap width & height now!
$tmp = array($this->getWidth(), $this->getHeight());
@@ -155,7 +157,17 @@ class ImageSizerEngineGD extends ImageSizerEngine {
} else if($orientations[1] > 0) {
$vertical = $orientations[1] == 2;
}
if(!is_null($vertical)) $image = $this->imFlip($image, $vertical);
if(!is_null($vertical)) {
$image = $this->imFlip($image, $vertical);
$isModified = true;
}
}
$zoom = $this->getFocusZoomPercent();
if($zoom > 1) {
// we need to configure a cropExtra call to respect the zoom factor
$this->cropExtra = $this->getFocusZoomCropDimensions($zoom, $fullWidth, $fullHeight, $finalWidth, $finalHeight);
$this->cropping = false;
}
// if there is requested to crop _before_ resize, we do it here @horst
@@ -182,9 +194,11 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$this->prepareImageLayer($image, $imageTemp);
imagecopy($image, $imageTemp, 0, 0, $x, $y, $w, $h);
unset($x, $y, $w, $h);
$isModified = true;
// now release the intermediate image and update settings
imagedestroy($imageTemp);
$imageTemp = null;
$this->setImageInfo(imagesx($image), imagesy($image));
// $this->cropping = false; // ?? set this to prevent overhead with the following manipulation ??
}
@@ -202,12 +216,20 @@ class ImageSizerEngineGD extends ImageSizerEngine {
// this is the case if the original size is requested or a greater size but upscaling is set to false
// the current version is allready the desired result, we only may have to apply compression where possible
$this->sharpening = 'none'; // we set sharpening to none
// 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)) {
$result = @copy($srcFilename, $dstFilename);
if(isset($image) && is_resource($image)) @imagedestroy($image); // clean up
if(isset($image)) $image = null;
return $result; // early return !
}
// 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");
}
$this->sharpening = 'none'; // we set sharpening to none, as the image only gets compressed, but not resized
$thumb = imagecreatetruecolor(imagesx($image), imagesy($image)); // create the final memory image
$this->prepareImageLayer($thumb, $image);
imagecopy($thumb, $image, 0, 0, 0, 0, imagesx($image), imagesy($image)); // copy our intermediate image into the final one
@@ -224,17 +246,33 @@ 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_
if(self::checkMemoryForImage(array($bgWidth, $bgHeight, 3)) === false) {
throw new WireException(basename($srcFilename) . " - not enough memory to resize to the intermediate image");
}
$sourceX = 0;
$sourceY = 0;
$sourceWidth = $this->image['width'];
$sourceHeight = $this->image['height'];
$thumb2 = imagecreatetruecolor($bgWidth, $bgHeight);
$this->prepareImageLayer($thumb2, $image);
imagecopyresampled($thumb2, $image, 0, 0, 0, 0, $bgWidth, $bgHeight, $this->image['width'], $this->image['height']);
imagecopyresampled(
$thumb2, // destination image
$image, // source image
0, // destination X
0, // destination Y
$sourceX, // source X
$sourceY, // source Y
$bgWidth, // destination width
$bgHeight, // destination height
$sourceWidth, // source width
$sourceHeight // source height
);
if(self::checkMemoryForImage(array($finalWidth, $finalHeight, 3)) === false) {
throw new WireException(basename($srcFilename) . " - not enough memory to crop to the final image");
@@ -242,7 +280,18 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$thumb = imagecreatetruecolor($finalWidth, $finalHeight);
$this->prepareImageLayer($thumb, $image);
imagecopyresampled($thumb, $thumb2, 0, 0, $bgX, $bgY, $finalWidth, $finalHeight, $finalWidth, $finalHeight);
imagecopyresampled(
$thumb, // destination image
$thumb2, // source image
0, // destination X
0, // destination Y
$bgX, // source X
$bgY, // source Y
$finalWidth, // destination width
$finalHeight, // destination height
$finalWidth, // source width
$finalHeight // source height
);
imagedestroy($thumb2);
}
@@ -273,6 +322,15 @@ 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;
switch($this->imageType) {
@@ -299,7 +357,6 @@ class ImageSizerEngineGD extends ImageSizerEngine {
return $result;
}
/**
* Rotate image (@horst)
*
@@ -313,7 +370,8 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$degree = (is_float($degree) || is_int($degree)) && $degree > -361 && $degree < 361 ? $degree : false;
if($degree === false) return $im;
if(in_array($degree, array(-360, 0, 360))) return $im;
return @imagerotate($im, $degree, imagecolorallocate($im, 0, 0, 0));
$angle = 360 - $degree; // because imagerotate() expects counterclockwise angle rather than degrees
return @imagerotate($im, $angle, imagecolorallocate($im, 0, 0, 0));
}
/**
@@ -397,7 +455,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$amount = intval($amount / 100 * $this->usmValue);
// apply unsharp mask filter
return $this->UnsharpMask($im, $amount, $radius, $threshold);
return $this->unsharpMask($im, $amount, $radius, $threshold);
}
// if we do not use USM, we use our default sharpening method,
@@ -541,12 +599,12 @@ class ImageSizerEngineGD extends ImageSizerEngine {
for($x = 0; $x < $w - 1; $x++) { // each row
for($y = 0; $y < $h; $y++) { // each pixel
$rgbOrig = ImageColorAt($img, $x, $y);
$rgbOrig = imagecolorat($img, $x, $y);
$rOrig = (($rgbOrig >> 16) & 0xFF);
$gOrig = (($rgbOrig >> 8) & 0xFF);
$bOrig = ($rgbOrig & 0xFF);
$rgbBlur = ImageColorAt($imgBlur, $x, $y);
$rgbBlur = imagecolorat($imgBlur, $x, $y);
$rBlur = (($rgbBlur >> 16) & 0xFF);
$gBlur = (($rgbBlur >> 8) & 0xFF);
@@ -565,20 +623,20 @@ class ImageSizerEngineGD extends ImageSizerEngine {
: $bOrig;
if(($rOrig != $rNew) || ($gOrig != $gNew) || ($bOrig != $bNew)) {
$pixCol = ImageColorAllocate($img, $rNew, $gNew, $bNew);
ImageSetPixel($img, $x, $y, $pixCol);
$pixCol = imagecolorallocate($img, $rNew, $gNew, $bNew);
imagesetpixel($img, $x, $y, $pixCol);
}
}
}
} else {
for($x = 0; $x < $w; $x++) { // each row
for($y = 0; $y < $h; $y++) { // each pixel
$rgbOrig = ImageColorAt($img, $x, $y);
$rgbOrig = imagecolorat($img, $x, $y);
$rOrig = (($rgbOrig >> 16) & 0xFF);
$gOrig = (($rgbOrig >> 8) & 0xFF);
$bOrig = ($rgbOrig & 0xFF);
$rgbBlur = ImageColorAt($imgBlur, $x, $y);
$rgbBlur = imagecolorat($imgBlur, $x, $y);
$rBlur = (($rgbBlur >> 16) & 0xFF);
$gBlur = (($rgbBlur >> 8) & 0xFF);
@@ -603,7 +661,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$bNew = 0;
}
$rgbNew = ($rNew << 16) + ($gNew << 8) + $bNew;
ImageSetPixel($img, $x, $y, $rgbNew);
imagesetpixel($img, $x, $y, $rgbNew);
}
}
}
@@ -721,11 +779,12 @@ class ImageSizerEngineGD extends ImageSizerEngine {
* @param array $sourceDimensions - array with three values: width, height, number of channels
* @param array|bool $targetDimensions - optional - mixed: bool true | false or array with three values:
* width, height, number of channels
* @param int|float Multiply needed memory by this factor
*
* @return bool|null if a calculation was possible (true|false), or null if the calculation could not be done
*
*/
static public function checkMemoryForImage($sourceDimensions, $targetDimensions = false) {
static public function checkMemoryForImage($sourceDimensions, $targetDimensions = false, $factor = 1) {
// with this static we only once need to read from php.ini and calculate phpMaxMem,
// regardless how often this function is called in a request
@@ -755,10 +814,8 @@ class ImageSizerEngineGD extends ImageSizerEngine {
}
// calculate $sourceDimensions
if(!isset($sourceDimensions[0]) || !isset($sourceDimensions[1])
|| !isset($sourceDimensions[2]) || !is_int($sourceDimensions[0])
|| !is_int($sourceDimensions[1]) || !is_int($sourceDimensions[2])
) {
if(!isset($sourceDimensions[0]) || !isset($sourceDimensions[1]) || !isset($sourceDimensions[2]) ||
!is_int($sourceDimensions[0]) || !is_int($sourceDimensions[1]) || !is_int($sourceDimensions[2])) {
return null;
}
@@ -771,10 +828,8 @@ class ImageSizerEngineGD extends ImageSizerEngine {
} else if(is_array($targetDimensions)) {
// we have to add ram for a targetimage
if(!isset($targetDimensions[0]) || !isset($targetDimensions[1])
|| !isset($targetDimensions[2]) || !is_int($targetDimensions[0])
|| !is_int($targetDimensions[1]) || !is_int($targetDimensions[2])
) {
if(!isset($targetDimensions[0]) || !isset($targetDimensions[1]) || !isset($targetDimensions[2]) ||
!is_int($targetDimensions[0]) || !is_int($targetDimensions[1]) || !is_int($targetDimensions[2])) {
return null;
}
@@ -785,7 +840,239 @@ class ImageSizerEngineGD extends ImageSizerEngine {
$curMem = memory_get_usage(true); // memory_get_usage() is always available with PHP since 5.2.1
// check if there is enough RAM loading the image(s), plus 3 MB for GD to use for calculations/transforms
return ($phpMaxMem - $curMem >= $imgMem + (3 * 1048576)) ? true : false;
$extraMem = 3 * 1048576;
$availableMem = $phpMaxMem - $curMem;
$neededMem = ($imgMem + $extraMem) * $factor;
return $availableMem >= $neededMem;
}
/**
* Additional functionality on top of existing checkMemoryForImage function for the flip/rotate actions
*
* @param string $filename Filename to check. Default is whatever was set to this ImageSizer.
* @param bool $double Need enough for both src and dst files loaded at same time? (default=true)
* @param int|float $factor Tweak factor (multiply needed memory by this factor), i.e. 2 for rotate actions. (default=1)
* @param string $action Name of action (if something other than "action")
* @param bool $throwIfNot Throw WireException if not enough memory? (default=false)
* @return bool
* @throws WireException
*
*/
protected function hasEnoughMemory($filename = '', $double = true, $factor = 1, $action = 'action', $throwIfNot = false) {
$error = '';
if(empty($filename)) $filename = $this->filename;
if($filename) {
if($filename != $this->filename || empty($this->info['width'])) {
$this->prepare($filename); // to populate $this->info
}
} else {
$error = 'No filename to check memory for';
}
if(!$error) {
$hasEnough = self::checkMemoryForImage(array(
$this->info['width'],
$this->info['height'],
$this->info['channels']
), $double, $factor);
if($hasEnough === false) {
$error = sprintf($this->_('Not enough memory for “%1$s” on image file: %2$s'), $action, basename($filename));
}
}
if($error) {
if($throwIfNot) {
throw new WireException($error);
} else {
$this->error($error);
return false;
}
}
return true;
}
/**
* Process a rotate or flip action
*
* @param string $srcFilename
* @param string $dstFilename
* @param string $action One of 'rotate' or 'flip'
* @param int|string $value If rotate, specify int of degrees. If flip, specify one of 'vertical', 'horizontal' or 'both'.
* @return bool
* @throws WireException
*
*/
private function processAction($srcFilename, $dstFilename, $action, $value) {
$action = strtolower($action);
$ext = strtolower(pathinfo($srcFilename, PATHINFO_EXTENSION));
$useTransparency = true;
$memFactor = 1;
$img = null;
if(empty($dstFilename)) $dstFilename = $srcFilename;
if($action == 'rotate') $memFactor *= 2;
if(!$this->hasEnoughMemory($srcFilename, true, $memFactor, $action, false)) return false;
if($ext == 'jpg' || $ext == 'jpeg') {
$img = imagecreatefromjpeg($srcFilename);
$useTransparency = false;
} else if($ext == 'png') {
$img = imagecreatefrompng($srcFilename);
} else if($ext == 'gif') {
$img = imagecreatefromgif($srcFilename);
}
if(!$img) {
$this->error("imagecreatefrom$ext failed", Notice::debug);
return false;
}
if($useTransparency) {
imagealphablending($img, true);
imagesavealpha($img, true);
}
$success = true;
$method = '_processAction' . ucfirst($action);
$imgNew = $this->$method($img, $value);
if($imgNew === false) {
// action fail
$success = false;
$this->error($this->className() . ".$method(img, $value) returned fail", Notice::debug);
} else if($imgNew !== $img) {
// a new img object was created
imagedestroy($img);
$img = $imgNew;
if($useTransparency) {
imagealphablending($img, true);
imagesavealpha($img, true);
}
} else {
// existing img object was updated
$img = $imgNew;
}
if($success) {
if($ext == 'png') {
$success = imagepng($img, $dstFilename, 9);
} else if($ext == 'gif') {
$success = imagegif($img, $dstFilename);
} else {
$success = imagejpeg($img, $dstFilename, $this->quality);
}
if(!$success) $this->error("image{$ext}() failed", Notice::debug);
}
imagedestroy($img);
return $success;
}
/**
* Process flip action (internal)
*
* @param resource $img
* @param string $flipType vertical, horizontal or both
* @return bool|resource
*
*/
private function _processActionFlip(&$img, $flipType) {
if(!function_exists('imageflip')) {
$this->error("Image flip requires PHP 5.5 or newer");
return false;
}
if(!in_array($flipType, array('vertical', 'horizontal', 'both'))) {
$this->error("Image flip type must be one of: 'vertical', 'horizontal', 'both'");
return false;
}
$constantName = 'IMG_FLIP_' . strtoupper($flipType);
$flipType = constant($constantName);
if($flipType === null) {
$this->error("Unknown constant for image flip: $constantName");
return false;
}
$success = imageflip($img, $flipType);
return $success ? $img : false;
}
/**
* Process rotate action (internal)
*
* @param resource $img
* @param $degrees
* @return bool|resource
*
*/
private function _processActionRotate(&$img, $degrees) {
$degrees = (int) $degrees;
$angle = 360 - $degrees; // imagerotate is anti-clockwise
$imgNew = imagerotate($img, $angle, 0);
return $imgNew ? $imgNew : false;
}
private function _processActionGreyscale(&$img, $unused) {
if($unused) {}
imagefilter($img, IMG_FILTER_GRAYSCALE);
return $img;
}
private function _processActionSepia(&$img, $sepia = 55) {
imagefilter($img, IMG_FILTER_GRAYSCALE);
imagefilter($img, IMG_FILTER_BRIGHTNESS, -30);
imagefilter($img, IMG_FILTER_COLORIZE, 90, (int) $sepia, 30);
return $img;
}
/**
* Process rotate of an image
*
* @param string $srcFilename
* @param string $dstFilename
* @param int $degrees Clockwise degrees, i.e. 90, 180, 270, -90, -180, -270
* @return bool
*
*/
protected function processRotate($srcFilename, $dstFilename, $degrees) {
return $this->processAction($srcFilename, $dstFilename, 'rotate', $degrees);
}
/**
* Process vertical or horizontal flip of an image
*
* @param string $srcFilename
* @param string $dstFilename
* @param string $flipType Specify vertical, horizontal, or both
* @return bool
*
*/
protected function processFlip($srcFilename, $dstFilename, $flipType) {
return $this->processAction($srcFilename, $dstFilename, 'flip', $flipType);
}
/**
* Convert image to greyscale
*
* @param string $dstFilename If different from source file
* @return bool
*
*/
public function convertToGreyscale($dstFilename = '') {
return $this->processAction($this->filename, $dstFilename, 'greyscale', null);
}
/**
* Convert image to sepia
*
* @param string $dstFilename If different from source file
* @param float|int $sepia Sepia value
* @return bool
*
*/
public function convertToSepia($dstFilename = '', $sepia = 55) {
return $this->processAction($this->filename, $dstFilename, 'sepia', $sepia);
}
}

View File

@@ -45,16 +45,30 @@
* @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 $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,24 +76,41 @@
* @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
*
*
* 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 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
* @property null|Page $hasPage The Page object associated with this Inputfield, or null when not applicable or not known. #pw-group-other
* @property bool|null $useLanguages When multi-language support active, can be set to true to make it provide inputs for each language, where supported (default=false). #pw-group-behavior
* @property null|bool $entityEncodeLabel Set to boolean false to specifically disable entity encoding of field header/label (default=true). #pw-group-output
* @property null|bool|int $entityEncodeLabel Set to boolean false to specifically disable entity encoding of field header/label (default=true). #pw-group-output
* @property null|bool $entityEncodeText Set to boolean false to specifically disable entity encoding for other text: description, notes, etc. (default=true). #pw-group-output
* @property int $renderValueFlags Options that can be applied to renderValue mode, see "renderValue" constants (default=0). #pw-group-output
* @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
*
* @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
* ================
* @method string render()
* @method string renderValue()
* @method void renderReadyHook(Inputfield $parent, $renderValueMode)
* @method Inputfield processInput(WireInputData $input)
* @method InputfieldWrapper getConfigInputfields()
* @method array getConfigArray()
@@ -258,7 +289,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();
@@ -301,7 +332,7 @@ abstract class Inputfield extends WireData implements Module {
self::$numInstances++;
$this->set('label', ''); // primary clikable 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
@@ -317,6 +348,8 @@ abstract class Inputfield extends WireData implements Module {
$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', '');
$this->set('appendMarkup', '');
// default ID attribute if no 'id' attribute set
$this->defaultID = $this->className() . self::$numInstances;
@@ -385,7 +418,7 @@ abstract class Inputfield extends WireData implements Module {
*
* @param string $key Name of property to set
* @param mixed $value Value of property
* @return $this
* @return Inputfield|WireData
*
*/
public function set($key, $value) {
@@ -463,6 +496,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.
*
@@ -493,6 +538,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
*
@@ -642,7 +761,7 @@ abstract class Inputfield extends WireData implements Module {
* - 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.
* @return mixed|$this If setting an attribute, it returns this instance. If getting an attribute, the attribute is returned.
* @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()
*
*/
@@ -658,6 +777,58 @@ abstract class Inputfield extends WireData implements Module {
}
return $this->setAttribute($key, $value);
}
/**
* Shortcut for getting or setting “value” attribute
*
* When setting a value, it returns $this (for fluent interface).
*
* ~~~~~
* $value = $inputfield->val(); * // Getting
* $inputfield->val('foo'); * // Setting
* ~~~~~
*
* @param string|null $value
* @return string|int|float|array|object|Wire|WireData|WireArray|Inputfield
*
*/
public function val($value = null) {
if($value === null) return $this->getAttribute('value');
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
@@ -702,7 +873,7 @@ abstract class Inputfield extends WireData implements Module {
* - Omit if getting an attribute.
* - Value to set for $key of setting.
* - Boolean false to remove the attribute specified for $key.
* @return string|array|$this Returns one of the following:
* @return Inputfield|string|array|null Returns one of the following:
* - If getting, returns attribute value of NULL if not present.
* - If setting, returns $this.
* @see Inputfield::attr(), Inputfield::addClass()
@@ -788,12 +959,13 @@ abstract class Inputfield extends WireData implements Module {
foreach($addClasses as $addClass) {
$addClass = trim($addClass);
if(!strlen($addClass)) continue;
if(in_array($addClass, $classes)) continue; // if already present, don't add it
$classes[] = $addClass;
}
$classes = array_unique($classes);
// convert back to string
$value = implode(' ', $classes);
$value = trim(implode(' ', $classes));
// set back to Inputfield
if($property == 'class') {
@@ -1030,9 +1202,24 @@ abstract class Inputfield extends WireData implements Module {
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
if($parent) {}
if($renderValueMode) {}
return $this->wire('modules')->loadModuleFileAssets($this) > 0;
$result = $this->wire('modules')->loadModuleFileAssets($this) > 0;
if($this->wire('hooks')->isMethodHooked($this, 'renderReadyHook')) {
$this->renderReadyHook($parent, $renderValueMode);
}
return $result;
}
/**
* Hookable version of renderReady(), not called unless 'renderReadyHook' is hooked
*
* Hook this method instead if you want to hook renderReady().
*
* @param Inputfield $parent
* @param bool $renderValueMode
*
*/
public function ___renderReadyHook(Inputfield $parent = null, $renderValueMode = false) { }
/**
* This hook was replaced by renderReady
*
@@ -1237,7 +1424,7 @@ abstract class Inputfield extends WireData implements Module {
$field = $this->modules->get('InputfieldInteger');
$value = (int) $this->getSetting('columnWidth');
if($value < 10 || $value >= 100) $value = 100;
$field->label = sprintf($this->_("Column Width (%d%%)"), $value);
$field->label = sprintf($this->_('Column width (%d%%)'), $value);
$field->icon = 'arrows-h';
$field->attr('id+name', 'columnWidth');
$field->attr('type', 'text');
@@ -1419,7 +1606,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);
}
@@ -1502,7 +1691,7 @@ abstract class Inputfield extends WireData implements Module {
* @param string $what Name of property that changed
* @param mixed $old Previous value before change
* @param mixed $new New value
* @return $this
* @return Inputfield|WireData $this
*
*/
public function trackChange($what, $old = null, $new = null) {
@@ -1530,9 +1719,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-z]+);/', $str)) {
$str = html_entity_decode($str, ENT_QUOTES, "UTF-8");
if(strpos($str, '&') !== false && preg_match('/&(#\d+|[a-zA-Z]+);/', $str)) {
$str = $sanitizer->unentities($str);
}
if($markdown && $markdown !== self::textFormatNone) {
@@ -1544,17 +1736,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
*
*/
@@ -101,6 +102,14 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
*/
protected $requiredLabel = '';
/**
* Whether or not column width is handled internally
*
* @var bool
*
*/
protected $useColumnWidth = true;
/**
* Construct the Inputfield, setting defaults for all properties
*
@@ -119,6 +128,8 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
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);
}
@@ -175,23 +186,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
*
@@ -341,6 +416,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
array_push($wrappers, $inputfield);
}
$inputfield->unsetParent();
$wrapper->add($inputfield);
}
@@ -368,10 +444,10 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$_classes = array_merge(self::$defaultClasses, self::$classes);
$markup = array();
$classes = array();
$useColumnWidth = true;
$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;
}
@@ -424,8 +500,12 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
foreach(array('error', 'description', 'head', 'notes') as $property) {
$text = $property == 'error' ? $errorsOut : $inputfield->getSetting($property);
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 = '';
}
@@ -470,7 +550,7 @@ 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) ||
$collapsed === Inputfield::collapsedYes ||
@@ -504,7 +584,13 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
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
if($inputfield->getSetting('entityEncodeLabel') !== false) $label = $inputfield->entityEncode($label);
$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'];
@@ -528,20 +614,36 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
} else if(strpos($label, '{class}') !== false) {
$label = str_replace('{class}', '', $label);
}
} else {
// no header
// $inputfield->addClass('InputfieldNoHeader', 'wrapClass');
}
if($useColumnWidth) {
$columnWidth = (int) $inputfield->getSetting('columnWidth');
$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'];
$ffAttrs['style'] = "width: $columnWidthAdjusted%;";
$columnWidthTotal += $columnWidth;
//if($columnWidthTotal >= 100 && !$requiredIf) $columnWidthTotal = 0; // requiredIf meant to be a showIf?
if($columnWidthTotal >= 100) $columnWidthTotal = 0;
} else {
$columnWidthTotal = 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']);
@@ -688,23 +790,8 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
return $this->renderInputfield($in, $renderValueMode);
} else {
// do not render ajax inputfield
$url = $this->wire('input')->url();
$queryString = $this->wire('input')->queryString();
if(strpos($queryString, 'renderInputfieldAjax=') !== false) {
// in case nested ajax request
$queryString = preg_replace('/&?renderInputfieldAjax=[^&]+/', '', $queryString);
}
$url .= $queryString ? "?$queryString&" : "?";
$url .= "renderInputfieldAjax=$inputfieldID";
$out = "<div class='renderInputfieldAjax'><input type='hidden' value='$url' /></div>";
if($inputfield instanceof InputfieldWrapper) {
// load assets they will need
foreach($inputfield->getAll() as $in) {
$in->renderReady($inputfield, $renderValueMode);
}
}
return $out;
// do not render ajax inputfield now, instead render placeholder
return $this->renderInputfieldAjaxPlaceholder($inputfield, $renderValueMode);
}
}
@@ -721,6 +808,45 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
return $out;
}
/**
* Render a placeholder for an ajax-loaded Inputfield
*
* @param Inputfield $inputfield
* @param bool $renderValueMode
* @return string
*
*/
protected function renderInputfieldAjaxPlaceholder(Inputfield $inputfield, $renderValueMode) {
$inputfieldID = $inputfield->attr('id');
$url = $this->wire('input')->url();
$queryString = $this->wire('input')->queryString();
if(strpos($queryString, 'renderInputfieldAjax=') !== false) {
// in case nested ajax request
$queryString = preg_replace('/&?renderInputfieldAjax=[^&]+/', '', $queryString);
}
$url .= $queryString ? "?$queryString&" : "?";
$url .= "renderInputfieldAjax=$inputfieldID";
$out = "<div class='renderInputfieldAjax'><input type='hidden' value='$url' /></div>";
if($inputfield instanceof InputfieldWrapper) {
// load assets they will need
foreach($inputfield->getAll() as $in) {
$in->renderReady($inputfield, $renderValueMode);
}
}
// ensure that Inputfield::render() hooks are still called
if($inputfield->hasHook('render()')) {
$inputfield->runHooks('render', array(), 'before');
}
return $out;
}
/**
* Process input for all children
*
@@ -750,7 +876,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);
}
}
@@ -838,6 +966,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.
*
@@ -879,6 +1035,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)
*
@@ -1010,7 +1221,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
* #pw-internal
*
* @param bool $trackChanges
* @return $this
* @return Inputfield|InputfieldWrapper
*
*/
public function setTrackChanges($trackChanges = true) {
@@ -1024,7 +1235,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

@@ -443,6 +443,68 @@ interface LanguagesValueInterface {
}
/**
* Interface used to indicate that the Fieldtype supports multiple languages
*
*/
interface FieldtypeLanguageInterface {
/*
* This interface is symbolic only and doesn't require any additional methods,
* however you do need to add an 'implements FieldtypeLanguageInterface' when defining your class.
*
*/
}
/**
* Interface for objects that carry a Field value for a Page
*
* Optional, but enables Page to do some of the work rather than the Fieldtype
*
*/
interface PageFieldValueInterface {
/**
* Get or set formatted state
*
* @param bool|null $set Specify bool to set formatted state or omit to retrieve formatted state
* @return bool
*
*/
public function formatted($set = null);
/**
* Set the Page
*
* @param Page $page
*
*/
public function setPage(Page $page);
/**
* Set the Field
*
* @param Field $field
*
*/
public function setField(Field $field);
/**
* Get the page or null if not set
*
* @return Page|null
*
*/
public function getPage();
/**
* Get the field or null if not set
*
* @return Field|null
*
*/
public function getField();
}
/**
* Interface for tracking runtime events
*
@@ -486,3 +548,10 @@ interface WireProfilerInterface {
*/
interface InputfieldHasArrayValue { }
/**
* Inputfield that has a sortable value (usually in addition to InputfieldHasArrayValue)
*
*/
interface InputfieldHasSortableValue { }

View File

@@ -5,7 +5,7 @@
*
* Provide GetText like language translation functions to ProcessWire
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
*/
@@ -13,6 +13,12 @@
/**
* Perform a language translation
*
* ~~~~~~
* echo __('This is translatable text');
* echo __('Translatable with current file as textdomain', __FILE__);
* echo __('Translatable with other file as textdomain', '/site/templates/_init.php');
* ~~~~~~
*
* @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.
@@ -20,14 +26,23 @@
*
*/
function __($text, $textdomain = null, $context = '') {
static $useLimit = null;
if(!wire('languages')) return $text;
if(!$language = wire('user')->language) return $text;
/** @var Language $language */
if(!$language->id) return $text;
if($useLimit === null) {
$useLimit = version_compare(PHP_VERSION, '5.4.0') >= 0;
}
if(is_null($textdomain)) {
if(defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
if($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__) {
@@ -36,13 +51,18 @@ function __($text, $textdomain = null, $context = '') {
$textdomain = $traces[1]['file'];
}
if(is_null($textdomain)) $textdomain = 'site';
} else if($textdomain === 'common') {
// common translation
$textdomain = 'wire/modules/LanguageSupport/LanguageTranslator.php';
}
$value = htmlspecialchars($language->translator()->getTranslation($textdomain, $text, $context), ENT_QUOTES, 'UTF-8');
$value = $language->translator()->getTranslation($textdomain, $text, $context);
if($value === "=") {
$value = $text;
} else if($value === "+") {
$v = $language->translator()->commonTranslation($text);
$value = empty($v) ? $text : $v;
} else {
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
}
return $value;
}
@@ -52,6 +72,11 @@ function __($text, $textdomain = null, $context = '') {
*
* Used when to text strings might be the same in English, but different in other languages.
*
* ~~~~~
* echo _x('Click for more', 'button');
* echo _x('Click for more', 'text-link');
* ~~~~~
*
* @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.
@@ -65,6 +90,13 @@ 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);
* ~~~~~
*
* @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.

View File

@@ -102,7 +102,17 @@ 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);
$a[] = $field->type->markupValue($page, $field, $v);
}
return $this->valueToString($a);
} else {
$value = $value->explode($property, array('getMethod' => 'getFormatted'));
}
$valid = true;
} else if($value instanceof WireArray) {
@@ -115,8 +125,24 @@ class MarkupFieldtype extends WireData implements Module {
$page = $value;
$value = $page->getFormatted($property);
$field = $this->wire('fields')->get($property);
if($field && $field->type) return $field->type->markupValue($page, $field, $value);
if(is_object($field) && $field->type) return $field->type->markupValue($page, $field, $value);
$valid = true;
} else if($value instanceof LanguagesValueInterface) {
/** @var LanguagesValueInterface $value */
/** @var Languages $languages */
$languages = $this->wire('languages');
if($property) {
if($property === 'data') {
$languageID = $languages->getDefault()->id;
} else if(is_string($property) && preg_match('/^data(\d+)$/', $property, $matches)) {
$languageID = (int) $matches[1];
} else {
$languageID = 0;
}
$value = $languageID ? $value->getLanguageValue($languageID) : (string) $value;
} else {
$value = (string) $value;
}
} else if($value instanceof WireData) {
// WireData object

View File

@@ -73,7 +73,10 @@ class MarkupQA extends Wire {
if($page) $this->setPage($page);
if($field) $this->setField($field);
$this->assetsURL = $this->wire('config')->urls->assets;
$this->debug = (bool) $this->wire('config')->debugMarkupQA && $this->wire('user')->isSuperuser();
if($this->wire('config')->debugMarkupQA) {
$user = $this->wire('user');
if($user) $this->debug = $user->isSuperuser();
}
}
/**
@@ -411,7 +414,8 @@ class MarkupQA extends Wire {
$pageID = $pwid;
$languageID = 0;
}
$pageID = (int) $pageID;
$full = $matches[0][$key];
$start = $matches[1][$key];
$href = $matches[3][$key];
@@ -424,7 +428,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,6 +440,10 @@ class MarkupQA extends Wire {
$langName = $this->debug && $language ? $language->name : '';
if($livePath) {
if($path && substr($path, -1) != '/') {
// no trailing slash, retain the editors wishes here
$livePath = rtrim($livePath, '/');
}
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') . ')');
@@ -462,6 +470,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
*
@@ -547,7 +657,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;

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.
@@ -199,7 +199,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 +214,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
@@ -488,3 +496,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 implement also implement this method as hookable, i.e. ___search(), but not 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

@@ -14,6 +14,17 @@
* https://processwire.com
*
* #pw-summary Loads and manages all modules in ProcessWire.
* #pw-body =
* The `$modules` API variable is most commonly used for getting individual modules to use their API.
* ~~~~~
* // Getting a module by name
* $m = $modules->get('MarkupPagerNav');
*
* // Getting a module by name (alternate)
* $m = $modules->MarkupPagerNav;
* ~~~~~
*
* #pw-body
*
* @todo Move all module information methods to a ModulesInfo class
* @todo Move all module loading methods to a ModulesLoad class
@@ -23,7 +34,7 @@
* @method bool|int delete($class)
* @method bool uninstall($class)
* @method bool saveModuleConfigData($className, array $configData) Alias of saveConfig() method #pw-internal
* @method bool saveConfig($class, array $data)
* @method bool saveConfig($class, $data, $value = null)
* @method InputfieldWrapper|null getModuleConfigInputfields($moduleName, InputfieldWrapper $form = null) #pw-internal
* @method void moduleVersionChanged(Module $module, $fromVersion, $toVersion) #pw-internal
*
@@ -239,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
*
@@ -253,6 +280,7 @@ class Modules extends WireArray {
'core',
'versionStr',
'permissions',
'searchable',
'page',
);
@@ -556,14 +584,19 @@ class Modules extends WireArray {
* Initialize a single module
*
* @param Module $module
* @param bool $clearSettings If true, module settings will be cleared when appropriate to save space.
* @param array $options
* - `clearSettings` (bool): When true, module settings will be cleared when appropriate to save space. (default=true)
* - `throw` (bool): When true, exceptions will be allowed to pass through. (default=false)
* @return bool True on success, false on fail
* @throws \Exception Only if the `throw` option is true.
*
*/
protected function initModule(Module $module, $clearSettings = true) {
protected function initModule(Module $module, array $options = array()) {
$result = true;
$debugKey = null;
$clearSettings = isset($options['clearSettings']) ? (bool) $options['clearSettings'] : true;
$throw = isset($options['throw']) ? (bool) $options['throw'] : false;
if($this->debug) {
static $n = 0;
@@ -590,6 +623,7 @@ class Modules extends WireArray {
try {
$module->init();
} catch(\Exception $e) {
if($throw) throw($e);
$this->error(sprintf($this->_('Failed to init module: %s'), $moduleName) . " - " . $e->getMessage());
$result = false;
}
@@ -731,10 +765,9 @@ class Modules extends WireArray {
/**
* Retrieve the installed module info as stored in the database
*
* @return array Indexed by module class name => array of module info
*
*/
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
@@ -749,7 +782,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
@@ -767,6 +801,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;
}
@@ -782,12 +824,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) {
@@ -803,7 +846,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)) {
@@ -982,7 +1025,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);
}
}
}
@@ -1014,6 +1058,7 @@ class Modules extends WireArray {
static $startPath;
static $callNum = 0;
static $prependFiles = array();
$callNum++;
$config = $this->wire('config');
@@ -1037,6 +1082,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);
@@ -1054,7 +1104,6 @@ class Modules extends WireArray {
if(DIRECTORY_SEPARATOR != '/') {
$pathname = str_replace(DIRECTORY_SEPARATOR, '/', $pathname);
$filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename);
}
if(strpos($pathname, '/.') !== false) {
@@ -1068,16 +1117,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::expireNever);
}
}
return $files;
@@ -1156,13 +1219,15 @@ class Modules extends WireArray {
* Get the requested Module (with options)
*
* This is the same as `$modules->get()` except that you can specify additional options to modify default behavior.
* These are the options you can speicfy in the `$options` array argument:
* These are the options you can specify in the `$options` array argument:
*
* - `noPermissionCheck` (bool): Specify true to disable module permission checks (and resulting exception).
* - `noInstall` (bool): Specify true to prevent a non-installed module from installing from this request.
* - `noInit` (bool): Specify true to prevent the module from being initialized.
* - `noSubstitute` (bool): Specify true to prevent inclusion of a substitute module.
* - `noCache` (bool): Specify true to prevent module instance from being cached for later getModule() calls.
* - `noPermissionCheck` (bool): Specify true to disable module permission checks (and resulting exception). (default=false)
* - `noInstall` (bool): Specify true to prevent a non-installed module from installing from this request. (default=false)
* - `noInit` (bool): Specify true to prevent the module from being initialized. (default=false)
* - `noSubstitute` (bool): Specify true to prevent inclusion of a substitute module. (default=false)
* - `noCache` (bool): Specify true to prevent module instance from being cached for later getModule() calls. (default=false)
* - `noThrow` (bool): Specify true to prevent exceptions from being thrown on permission or fatal error. (default=false)
* - `returnError` (bool): Return an error message (string) on error, rather than null. (default=false)
*
* If the module is not installed, but is installable, it will be installed, instantiated, and initialized.
* If you don't want that behavior, call `$modules->isInstalled('ModuleName')` as a condition first, OR specify
@@ -1170,69 +1235,110 @@ class Modules extends WireArray {
*
* @param string|int $key Module name or database ID.
* @param array $options Optional settings to change load behavior, see method description for details.
* @return Module|_Module|null Returns ready-to-use module or NULL if not found.
* @throws WirePermissionException If module requires a particular permission the user does not have
* @return Module|_Module|null|string Returns ready-to-use module or NULL|string if not found (string if `returnError` option used).
* @throws WirePermissionException|\Exception If module requires a particular permission the user does not have
* @see Modules::get()
*
*/
public function getModule($key, array $options = array()) {
if(empty($key)) return null;
$module = null;
$needsInit = false;
$error = '';
if(empty($key)) {
return empty($options['returnError']) ? null : "No module specified";
}
// check for optional module ID and convert to classname if found
if(ctype_digit("$key")) {
if(!$key = array_search($key, $this->moduleIDs)) return null;
$moduleID = (int) $key;
if(!$key = array_search($key, $this->moduleIDs)) {
return empty($options['returnError']) ? null : "Unable to find module ID $moduleID";
}
} else {
$key = wireClassName($key, false);
}
$module = parent::get($key);
if(!$module && empty($options['noSubstitute'])) {
if($this->isInstallable($key) && empty($options['noInstall'])) {
// module is on file system and may be installed, no need to substitute
if(!$module) {
if(empty($options['noSubstitute'])) {
if($this->isInstallable($key) && empty($options['noInstall'])) {
// module is on file system and may be installed, no need to substitute
} else {
$module = $this->getSubstituteModule($key, $options);
if($module) return $module; // returned module is ready to use
}
} else {
$module = $this->getSubstituteModule($key, $options);
if($module) return $module; // returned module is ready to use
$error = "Module '$key' not found and substitute not allowed (noSubstitute=true)";
}
}
if($module) {
// check if it's a placeholder, and if it is then include/instantiate/init the real module
// OR check if it's non-singular, so that a new instance is created
if($module instanceof ModulePlaceholder || !$this->isSingular($module)) {
$placeholder = $module;
$class = $this->getModuleClass($placeholder);
if($module instanceof ModulePlaceholder) $this->includeModule($module);
$module = $this->newModule($class);
try {
if($module instanceof ModulePlaceholder) $this->includeModule($module);
$module = $this->newModule($class);
} catch(\Exception $e) {
if(empty($options['noThrow'])) throw $e;
return empty($options['returnError']) ? null : "Module '$key' - " . $e->getMessage();
}
// if singular, save the instance so it can be used in later calls
if($module && $this->isSingular($module) && empty($options['noCache'])) $this->set($key, $module);
$needsInit = true;
}
} else if(empty($options['noInstall']) && array_key_exists($key, $this->getInstallable())) {
// check if the request is for an uninstalled module
// if so, install it and return it
$module = $this->install($key);
$needsInit = true;
} else if(empty($options['noInstall'])) {
// module was not available to get, see if we can install it
if(array_key_exists($key, $this->getInstallable())) {
// check if the request is for an uninstalled module
// if so, install it and return it
try {
$module = $this->install($key);
} catch(\Exception $e) {
if(empty($options['noThrow'])) throw $e;
if(!empty($options['returnError'])) return "Module '$key' install failed: " . $e->getMessage();
}
$needsInit = true;
if(!$module) $error = "Module '$key' not installed and install failed";
} else {
$error = "Module '$key' is not present or listed as installable";
}
} else {
$error = "Module '$key' is not present and not installable (noInstall=true)";
}
if($module && empty($options['noPermissionCheck'])) {
if(!$module) {
if(!$error) $error = "Unable to get module '$key'";
return empty($options['returnError']) ? null : $error;
}
if(empty($options['noPermissionCheck'])) {
// check that user has permission required to use module
if(!$this->hasPermission($module, $this->wire('user'), $this->wire('page'))) {
throw new WirePermissionException($this->_('You do not have permission to execute this module') . ' - ' . wireClassName($module));
$error = $this->_('You do not have permission to execute this module') . ' - ' . wireClassName($module);
if(empty($options['noThrow'])) throw new WirePermissionException($error);
return empty($options['returnError']) ? null : $error;
}
}
// skip autoload modules because they have already been initialized in the load() method
// unless they were just installed, in which case we need do init now
if($module && $needsInit) {
if($needsInit && empty($options['noInit'])) {
// if the module is configurable, then load it's config data
// and set values for each before initializing the module
if(empty($options['noInit'])) {
if(!$this->initModule($module, false)) $module = null;
try {
if(!$this->initModule($module, array('clearSettings' => false, 'throw' => true))) {
return empty($options['returnError']) ? null : "Module '$module' failed init";
}
} catch(\Exception $e) {
if(empty($options['noThrow'])) throw $e;
return empty($options['returnError']) ? null : "Module '$module' throw Exception on init - " . $e->getMessage();
}
}
@@ -1449,7 +1555,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) {
@@ -1479,17 +1585,25 @@ 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:
* - 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 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);
if($load === 1) {
$results[$className] = $this->getModuleInfo($className);
} else if($load === 2) {
$results[$className] = $this->getModuleInfoVerbose($className);
} else if($load === true) {
$results[$className] = $this->getModule($className);
} else {
$results[$className] = $className;
}
@@ -1497,6 +1611,110 @@ class Modules extends WireArray {
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.
*
@@ -1850,6 +2068,7 @@ 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);
@@ -1943,7 +2162,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);
@@ -2361,13 +2580,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
@@ -2418,19 +2638,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.
@@ -2438,10 +2659,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
@@ -2456,12 +2678,17 @@ 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()
@@ -2469,15 +2696,28 @@ class Modules extends WireArray {
*
*/
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
@@ -2517,7 +2757,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
@@ -2531,8 +2773,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
@@ -2545,28 +2809,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;
@@ -2607,10 +2855,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();
@@ -2624,7 +2873,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
@@ -2633,12 +2882,16 @@ 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) $info['requires'] = explode(',', $info['requires']);
else $info['requires'] = array($info['requires']);
if(strpos($info['requires'], ',') !== false) {
$info['requires'] = explode(',', $info['requires']);
} else {
$info['requires'] = array($info['requires']);
}
}
// populate requiresVersions
@@ -2656,14 +2909,18 @@ 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) $info['installs'] = explode(',', $info['installs']);
else $info['installs'] = array($info['installs']);
if(strpos($info['installs'], ',') !== false) {
$info['installs'] = explode(',', $info['installs']);
} else {
$info['installs'] = array($info['installs']);
}
}
// 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?
@@ -2719,6 +2976,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"));
@@ -2729,7 +2998,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.
@@ -2755,6 +3024,29 @@ 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
*
*/
public function getModuleInfoProperty($class, $property, array $options = array()) {
if(isset($this->moduleInfoVerboseKeys[$property])) {
$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
*
@@ -2926,6 +3218,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
@@ -3550,6 +3863,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);
@@ -3839,10 +4153,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;
}
/**
@@ -4628,7 +4945,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) {
@@ -4706,7 +5023,7 @@ class Modules extends WireArray {
*
* @param array|Wire|string $text
* @param int $flags
* @return $this
* @return Modules|WireArray
*
*/
public function error($text, $flags = 0) {
@@ -4717,6 +5034,8 @@ class Modules extends WireArray {
/**
* Compile and return the given file for module, if allowed to do so
*
* #pw-internal
*
* @param Module|string $moduleName
* @param string $file Optionally specify the module filename as an optimization
* @param string|null $namespace Optionally specify namespace as an optimization

View File

@@ -3,6 +3,10 @@
/**
* ProcessWire Notices
*
* #pw-summary Manages notifications in the ProcessWire admin, primarily for internal use.
* #pw-use-constants
* #pw-use-constructor
*
* Base class that holds a message, source class, and timestamp.
* Contains notices/messages used by the application to the user.
*
@@ -28,6 +32,8 @@ abstract class Notice extends WireData {
/**
* Flag indicates the notice is a warning
*
* #pw-internal
*
* @deprecated use NoticeWarning instead.
*
*/
@@ -46,7 +52,7 @@ abstract class Notice extends WireData {
const logOnly = 16;
/**
* Flag indicates the notice is allowed to contain markup and won't be automatically entity encoded
* Flag indicates the notice is allowed to contain markup and wont be automatically entity encoded
*
* Note: entity encoding is done by the admin theme at output time, which should detect this flag.
*
@@ -56,8 +62,8 @@ abstract class Notice extends WireData {
/**
* Create the Notice
*
* @param string $text
* @param int $flags
* @param string $text Notification text
* @param int $flags Flags
*
*/
public function __construct($text, $flags = 0) {
@@ -69,6 +75,8 @@ abstract class Notice extends WireData {
}
/**
* Get the notice log
*
* @return string Name of log (basename)
*
*/
@@ -111,21 +119,88 @@ class NoticeWarning extends Notice {
/**
* A class to contain multiple Notice instances, whether messages or errors
* ProcessWire Notices
*
* #pw-summary A class to contain multiple Notice instances, whether messages, warnings or errors
* #pw-body =
* This class manages notices that have been sent by `Wire::message()`, `Wire::warning()` and `Wire::error()` calls.
* The message(), warning() and error() methods are available on every `Wire` derived object. This class is primarily
* for internal use in the admin. However, it may also be useful in some front-end contexts.
* ~~~~~
* // Adding a NoticeMessage using object syntax
* $notices->add(new NoticeMessage("Hello World"));
*
* // Adding a NoticeMessage using regular syntax
* $notices->message("Hello World");
*
* // Adding a NoticeWarning, and allow markup in it
* $notices->message("Hello <strong>World</strong>", Notice::allowMarkup);
*
* // Adding a NoticeError that only appears if debug mode is on
* $notices->error("Hello World", Notice::debug);
* ~~~~~
* Iterating and outputting Notices:
* ~~~~~
* foreach($notices as $notice) {
* // skip over debug notices, if debug mode isn't active
* if($notice->flags & Notice::debug && !$config->debug) continue;
* // entity encode notices unless the allowMarkup flag is set
* if($notice->flags & Notice::allowMarkup) {
* $text = $notice->text;
* } else {
* $text = $sanitizer->entities($notice->text);
* }
* // output either an error, warning or message notice
* if($notice instanceof NoticeError) {
* echo "<p class='error'>$text</p>";
* } else if($notice instanceof NoticeWarning) {
* echo "<p class='warning'>$text</p>";
* } else {
* echo "<p class='message'>$text</p>";
* }
* }
* ~~~~~
*
* #pw-body
*
*
*/
class Notices extends WireArray {
const logAllNotices = false; // for debugging/dev purposes
/**
* #pw-internal
*
* @param mixed $item
* @return bool
*
*/
public function isValidItem($item) {
return $item instanceof Notice;
}
}
/**
* #pw-internal
*
* @return Notice
*
*/
public function makeBlankItem() {
return $this->wire(new NoticeMessage(''));
}
/**
* Add a Notice object
*
* ~~~~
* $notices->add(new NoticeError("An error occurred!"));
* ~~~~
*
* @param Notice $item
* @return $this
*
*/
public function add($item) {
if($item->flags & Notice::debug) {
@@ -178,6 +253,12 @@ class Notices extends WireArray {
$this->wire('log')->save($item->getName(), $text);
}
/**
* Are there NoticeError items present?
*
* @return bool
*
*/
public function hasErrors() {
$numErrors = 0;
foreach($this as $notice) {
@@ -185,7 +266,13 @@ class Notices extends WireArray {
}
return $numErrors > 0;
}
/**
* Are there NoticeWarning items present?
*
* @return bool
*
*/
public function hasWarnings() {
$numWarnings = 0;
foreach($this as $notice) {
@@ -199,6 +286,8 @@ class Notices extends WireArray {
*
* This enables us to safely print_r the string for debugging purposes
*
* #pw-internal
*
* @param array $a
* @return array
*
@@ -218,4 +307,32 @@ class Notices extends WireArray {
}
return $b;
}
/**
* Move notices from one Wire instance to another
*
* @param Wire $from
* @param Wire $to
* @param array $options Additional options:
* - `types` (array): Types to move (default=['messages','warnings','errors'])
* - `prefix` (string): Optional prefix to add to moved notices text (default='')
* - `suffix` (string): Optional suffix to add to moved notices text (default='')
* @return int Number of notices moved
*
*/
public function move(Wire $from, Wire $to, array $options = array()) {
$n = 0;
$types = isset($options['types']) ? $options['types'] : array('errors', 'warnings', 'messages');
foreach($types as $type) {
$method = rtrim($type, 's');
foreach($from->$type('clear') as $notice) {
$text = $notice->text;
if(isset($options['prefix'])) $text = "$options[prefix]$text";
if(isset($options['suffix'])) $text = "$text$options[suffix]";
$to->$method($text, $notice->flags);
$n++;
}
}
return $n;
}
}

View File

@@ -44,10 +44,11 @@ class NullPage extends Page {
/**
* #pw-internal
*
* @param array $options
* @return string
*
*/
public function url() { return ''; }
public function url($options = array()) { return ''; }
/**
* #pw-internal

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,13 @@
*/
class PageAccess {
/**
* @var ProcessWire
*
*/
protected $wire;
/**
* Allowed types for page access
*
@@ -43,7 +49,7 @@ class PageAccess {
$permission = $this->wire('permissions')->get($name);
$name = $permission ? $permission->name : 'edit';
} else if($name instanceof Permission) {
$name = $permission->name;
$name = $name->name;
}
if(strpos($name, 'page-') === 0) $name = str_replace('page-', '', $name);
@@ -56,12 +62,13 @@ class PageAccess {
* Returns the parent page that has the template from which we get our role/access settings from
*
* @param Page $page
* @param string Type, one of 'view', 'edit', 'create' or 'add' (default='view')
* @param string $type Type, one of 'view', 'edit', 'create' or 'add' (default='view')
* @param int $level Recursion level for internal use only
* @return Page|NullPage Returns NullPage if none found
*
*/
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) {
// found an access parent
@@ -80,7 +87,7 @@ class PageAccess {
* Returns the template from which we get our role/access settings from
*
* @param Page $page
* @param string Type, one of 'view', 'edit', 'create' or 'add' (default='view')
* @param string $type Type, one of 'view', 'edit', 'create' or 'add' (default='view')
* @return Template|null Returns null if none
*
*/
@@ -114,7 +121,7 @@ class PageAccess {
*
* @param Page $page
* @param string|int|Role $role
* @param string Default is 'view', but you may specify 'create' or 'add' as well
* @param string $type Default is 'view', but you may specify 'create' or 'add' as well
* @return bool
*
*/
@@ -125,4 +132,43 @@ class PageAccess {
if(is_int($role)) return $roles->has("id=$role");
return false;
}
/**
* Get or inject a ProcessWire API variable or fuel a new object instance
*
* See Wire::wire() for explanation of all options.
*
* @param string|WireFuelable $name Name of API variable to retrieve, set, or omit to retrieve entire Fuel object.
* @param null|mixed $value Value to set if using this as a setter, otherwise omit.
* @param bool $lock When using as a setter, specify true if you want to lock the value from future changes (default=false)
* @return mixed|Fuel
* @throws WireException
*
*/
public function wire($name = '', $value = null, $lock = false) {
if(!is_null($value)) return $this->wire->wire($name, $value, $lock);
else if($name instanceof WireFuelable && $this->wire) $name->setWire($this->wire);
else if($name) return $this->wire->wire($name);
return $this->wire;
}
/**
* Set the ProcessWire instance
*
* @param ProcessWire $wire
*
*/
public function setWire(ProcessWire $wire) {
$this->wire = $wire;
}
/**
* Get the ProcessWire instance
*
* @return ProcessWire
*
*/
public function getWire() {
return $this->wire;
}
}

View File

@@ -14,13 +14,19 @@
*
* 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.
*
* The recommended way to create a new PageArray is to use the `$pages->newPageArray()` method:
* ~~~~~
* $pageArray = $pages->newPageArray();
* ~~~~~
* #pw-body
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* https://processwire.com
*
* @method string getMarkup($key = null) Render a simple/default markup value for each item #pw-internal
*
* @property Page|null $first First item
* @property Page|null $last Last item
* @property Page[] $data #pw-internal
*
*/
@@ -102,6 +108,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;
@@ -278,7 +285,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* #pw-internal
*
* @param int|Page $key
* @return bool true if removed, false if not
* @return $this This PageArray instance
*
*/
public function remove($key) {
@@ -345,7 +352,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* #pw-internal
*
* @param int $num Number of items to return
* @return PageArray
* @return PageArray|WireArray New PageArray instance
*
*/
public function findRandom($num) {
@@ -362,7 +369,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
*
* @param int $start Starting index.
* @param int $limit Number of items to include. If omitted, includes the rest of the array.
* @return PageArray
* @return PageArray|WireArray New PageArray instance
*
*/
public function slice($start, $limit = 0) {
@@ -377,7 +384,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* #pw-internal
*
* @param int $num Return the nth item in this WireArray. Specify a negative number to count from the end rather than the start.
* @return Page|null
* @return Page|Wire|null Returns Page object or null if not present
*
*/
public function eq($num) {
@@ -447,7 +454,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
*
* @param string|Selectors|array $selectors AttributeSelector string to use as the filter.
* @param bool $not Make this a "not" filter? (default is false)
* @return PageArray reference to current [filtered] instance
* @return PageArray|WireArray reference to current [filtered] PageArray
*
*/
protected function filterData($selectors, $not = false) {
@@ -461,7 +468,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* #pw-internal
*
* @param string $selector AttributeSelector string to use as the filter.
* @return PageArray reference to current instance.
* @return PageArray|PaginatedArray|WireArray reference to current PageArray instance.
*
*/
public function filter($selector) {
@@ -474,7 +481,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* #pw-internal
*
* @param string $selector AttributeSelector string to use as the filter.
* @return PageArray reference to current instance.
* @return PageArray|PaginatedArray|WireArray reference to current PageArray instance.
*
*/
public function not($selector) {
@@ -489,7 +496,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
* #pw-internal
*
* @param string $selector AttributeSelector string.
* @return PageArray
* @return PageArray|WireArray New PageArray instance
*
*/
public function find($selector) {
@@ -561,7 +568,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
*
* #pw-internal
*
* @return Page[]|\ArrayObject
* @return Page[]|\ArrayObject|PageArrayIterator
*
*/
public function getIterator() {
@@ -622,7 +629,7 @@ class PageArray extends PaginatedArray implements WirePaginatable {
public function __debugInfo() {
$info = parent::__debugInfo();
$info['selectors'] = (string) $this->selectors;
if(!count($info['selectors'])) unset($info['selectors']);
if(!wireCount($info['selectors'])) unset($info['selectors']);
return $info;
}

View File

@@ -73,7 +73,7 @@ class PageArrayIterator extends Wire implements \Iterator {
* @var int
*
*/
protected $chunkSize = 1000;
protected $chunkSize = 250;
/**
* Construct
@@ -92,7 +92,7 @@ class PageArrayIterator extends Wire implements \Iterator {
*
*/
protected function loadChunk() {
$this->chunkSize = (int) $this->wire('config')->lazyPageChunkSize;
$this->pagesPosition = 0;
$start = $this->currentChunk++ * $this->chunkSize;
@@ -121,10 +121,11 @@ class PageArrayIterator extends Wire implements \Iterator {
$ids[] = $page->id;
}
$debug = $this->wire('pages');
if($debug) $this->wire('pages')->debug(false);
$this->pages = $this->wire('pages')->getById($ids, $options);
if($debug) $this->wire('pages')->debug(true);
$pages = $this->wire('pages');
$debug = $pages->debug();
if($debug) $pages->debug(false);
$this->pages = $pages->getById($ids, $options);
if($debug) $pages->debug(true);
}
$this->pagesCount = count($this->pages);

View File

@@ -1,163 +0,0 @@
<?php namespace ProcessWire;
/**
* Class PageExport
*
* PLEASE NOTE: this class is not yet functional and here as a work in progress, not currently used by the core.
*
* @todo make this module use a 'guid', adding it if not there already
*
*/
class PageExport extends Wire {
/**
* Export the page's data to an array that can be later imported
*
* @param Page $page
* @return array
*
*/
public function export(Page $page) {
$of = $page->of();
$page->of(false);
// todo: make user definable guid, except for ID part
$guid = $this->wire('config')->httpHost . $this->wire('config')->urls->root . $page->id;
$data = array(
'id' => $page->id,
'guid' => $guid,
'parent_id' => $page->parent_id,
'parent' => $page->parent->path,
'templates_id' => $page->templates_id,
'template' => $page->template->name,
'name' => $page->name,
'status' => $page->status,
'sort' => $page->sort,
'sortfield' => $page->sortfield,
'num_children' => $page->numChildren(),
'created' => $page->created,
'created_users_id' => $page->created_users_id,
'created_user' => $page->createdUser->name,
'modified' => $page->modified,
'modified_users_id' => $page->modified_users_id,
'modified_user' => $page->modifiedUser->name,
'published' => $page->published,
'core_version' => $this->wire('config')->version,
'export_time' => time(),
'data' => array(),
'types' => array(),
);
foreach($page->template->fieldgroup as $field) {
if($field->type instanceof FieldtypeFieldsetOpen) continue;
$data['data'][$field->name] = $this->exportValue($page, $field, $page->get($field->name));
$data['types'][$field->name] = $field->type->className();
}
$page->of($of);
return $data;
}
public function ___import($page, $data = null) {
if(is_null($data)) {
$data = $page;
$page = $this->wire('pages')->newPage();
}
if(empty($data['core_version'])) throw new WireException("Invalid import data");
$page->of(false);
$page->resetTrackChanges(true);
if(!is_array($data)) throw new WireException("Data passed to import() must be an array");
if(!$page->parent_id) {
$parent = $this->wire('pages')->get($data['parent']);
if(!$parent->id) throw new WireException("Unknown parent: $data[parent]");
$page->parent = $parent;
}
if(!$page->templates_id) {
$template = $this->wire('templates')->get($data['template']);
if(!$template) throw new WireException("Unknown template: $data[template]");
$page->template = $template;
}
$page->name = $data['name'];
$page->sort = $data['sort'];
$page->sortfield = $data['sortfield'];
$page->status = $data['status'];
$page->guid = $data['id'];
if(!$page->id) $page->save();
foreach($data['data'] as $name => $value) {
$field = $this->wire('fields')->get($name);
if(!$field) {
$this->error("Unknown field: $name");
continue;
}
if($data['types'][$name] != $field->type->className()) {
$this->error("Import data for field '$field->name' has different fieldtype '" . $data['types'][$name] . "' != '" . $field->type->className() . "', skipping...");
continue;
}
$newStr = var_export($value, true);
$oldStr = var_export($this->exportValue($page, $field, $page->get($field->name)), true);
if($newStr === $oldStr) continue; // value has not changed, so abort
$value = $this->importValue($page, $field, $value);
$page->set($field->name, $value);
}
return $page;
}
protected function exportValue($page, $field, $value) {
return $field->type->exportValue($page, $field, $value);
/*
$sleepValue = $field->type->sleepValue($page, $field, $value);
if($field->type instanceof FieldtypePage) {
foreach($sleepValue as $key => $id) {
$p = $this->wire('pages')->get($id);
$info = array('id' => $p->id, 'path' => $p->path);
$sleepValue[$key] = $info;
}
}
return $sleepValue;
*/
}
protected function importValue($page, $field, $value) {
return $field->type->importValue($page, $field, $value);
/*
if($field->type instanceof FieldtypePage) {
foreach($value as $key => $info) {
// convert $value[$key] from array to page ID
$p = $this->wire('pages')->get((int) $info['id']);
if(!$p->id || $p->path != $info['path']) {
// if page ID wasn't found or path doesn't match, then we try to retrieve it by path instead
// since path may be a more reliable indicator
$p2 = $this->wire('pages')->get($info['path']);
if($p2->id) $p = $p2;
}
$value[$key] = $p->id;
}
}
$value = $field->type->wakeupValue($page, $field, $value);
return $value;
*/
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,52 +22,98 @@ 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->wire('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->wire('pages')->count("parent_id=$page->id, include=unpublished");
return $page->wire('pages')->count("parent_id=$page->id, include=hidden");
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");
}
} 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->wire('pages')->count($selector);
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
*
* @param Page $page
* @param string|array $selector Selector to use, or blank to return all children
* @param array $options
* @return PageArray
* @return PageArray|array
*
*/
public function children(Page $page, $selector = '', $options = array()) {
if(!$page->numChildren) return $page->wire('pages')->newPageArray();
if(!$page->numChildren) return $page->_pages()->newPageArray();
$defaults = array('caller' => 'page.children');
$options = array_merge($defaults, $options);
$sortfield = $page->sortfield();
@@ -81,7 +127,7 @@ class PageTraversal {
$selector = trim("parent_id=$page->id, $selector", ", ");
if(strpos($selector, 'sort=') === false) $selector .= ", sort=$sortfield";
}
return $page->wire('pages')->find($selector, $options);
return $page->_pages('find', $selector, $options);
}
/**
@@ -96,7 +142,7 @@ class PageTraversal {
*
*/
public function child(Page $page, $selector = '', $options = array()) {
if(!$page->numChildren) return $page->wire('pages')->newNullPage();
if(!$page->numChildren) return $page->_pages()->newNullPage();
$defaults = array('getTotal' => false, 'caller' => 'page.child');
$options = array_merge($defaults, $options);
if(is_array($selector)) {
@@ -107,7 +153,7 @@ class PageTraversal {
if(strpos($selector, 'start=') === false) $selector .= ", start=0"; // prevent pagination
}
$children = $this->children($page, $selector, $options);
return count($children) ? $children->first() : $page->wire('pages')->newNullPage();
return count($children) ? $children->first() : $page->_pages()->newNullPage();
}
/**
@@ -128,6 +174,28 @@ class PageTraversal {
return 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;
}
/**
* Return all parent from current till the one matched by $selector
*
@@ -211,14 +279,29 @@ class PageTraversal {
$selector = trim($selector, ", ");
}
$options = array('caller' => 'page.siblings');
return $page->wire('pages')->find($selector, $options);
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
*
@@ -230,7 +313,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;
@@ -247,18 +339,18 @@ class PageTraversal {
} else if(strpos($until, '/') === 0) {
// page path
$stopPage = $pages->get($until);
$stopPage = $page->_pages('get', $until);
} else if(is_array($selector) || is_array($options['until'])) {
// either selector or until is an array
$s = new Selectors($options['until']);
foreach(new Selectors($selector) as $item) $s->add($item);
$s->add(new SelectorEqual('limit', 1));
$stopPage = $page->wire('pages')->find($s)->first();
$stopPage = $page->_pages('find', $s)->first();
} else {
// selector string
$stopPage = $page->wire('pages')->find("$selector, limit=1, $until")->first();
$stopPage = $page->_pages('find', "$selector, limit=1, $until")->first();
}
if($stopPage && $stopPage->id) {
@@ -278,7 +370,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).
@@ -303,12 +395,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();
@@ -324,6 +423,7 @@ class PageTraversal {
} else if($options['all']) {
$result = $pages->getById($rows, array(
'parent_id' => $parent->id,
'cache' => $page->loaderCache
));
if($options['all'] && $options['prev']) $result = $result->reverse();
@@ -332,7 +432,8 @@ class PageTraversal {
$result = $pages->getById(array($row['id']), array(
'template' => $page->wire('templates')->get($row['templates_id']),
'parent_id' => $row['parent_id'],
'getOne' => true
'getOne' => true,
'cache' => $page->loaderCache
));
}
@@ -342,12 +443,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;
}
@@ -355,7 +470,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.
*
*/
@@ -367,7 +482,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.
*
*/
@@ -380,9 +495,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()) {
@@ -395,9 +510,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()) {
@@ -413,7 +528,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
@@ -447,6 +562,388 @@ class PageTraversal {
$options = array_merge($options, $defaults);
return $this->_next($page, $filter, $options);
}
/**
* Returns the URL to the page with $options
*
* 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).
* - `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.
*
* You can also specify any of the following for `$options` as shortcuts:
*
* - If you specify an `int` for options it is assumed to be the `pageNum` option.
* - If you specify `+` or `-` for options it is assumed to be the `pageNum` “next/previous pagination” option.
* - If you specify any other `string` for options it is assumed to be the `urlSegmentStr` option.
* - If you specify a `boolean` (true) for options it is assumed to be the `http` option.
*
* Please also note regarding `$options`:
*
* - This method honors template slash settings for page, URL segments and page numbers.
* - Any passed in URL segments are automatically sanitized with `Sanitizer::pageNameUTF8()`.
* - If using the `pageNum` or URL segment options please also make sure these are enabled on the pages template.
* - The query string generated by any `data` variables is entity encoded when output formatting is on.
* - The `language` option requires that the `LanguageSupportPageNames` module is installed.
* - The prefix for page numbers honors `$config->pageNumUrlPrefix` and multi-language prefixes as well.
*
* @param Page $page
* @param array|int|string|bool|Language $options Optionally specify options to modify default behavior (see method description).
* @return string Returns page URL, for example: `/my-site/about/contact/`
* @see Page::path(), Page::httpUrl(), Page::editUrl(), Page::localUrl()
*
*/
public function urlOptions(Page $page, $options = array()) {
$config = $page->wire('config');
$template = $page->template;
$defaults = array(
'http' => is_bool($options) ? $options : false,
'pageNum' => is_int($options) || (is_string($options) && in_array($options, array('+', '-'))) ? $options : 1,
'data' => array(),
'urlSegmentStr' => is_string($options) ? $options : '',
'urlSegments' => array(),
'language' => is_object($options) && $options instanceof Page && $options->className() === 'Language' ? $options : null,
);
if(empty($options)) {
$url = rtrim($config->urls->root, '/') . $page->path();
if($template->slashUrls === 0 && $page->id > 1) $url = rtrim($url, '/');
return $url;
}
$options = is_array($options) ? array_merge($defaults, $options) : $defaults;
$sanitizer = $page->wire('sanitizer');
$language = null;
$url = null;
if(count($options['urlSegments'])) {
$options['urlSegmentStr'] = implode('/', $options['urlSegments']);
}
if($options['language'] && $page->wire('modules')->isInstalled('LanguageSupportPageNames')) {
if(!is_object($options['language'])) {
$options['language'] = null;
} else if(!$options['language'] instanceof Page) {
$options['language'] = null;
} else if(strpos($options['language']->className(), 'Language') === false) {
$options['language'] = null;
}
if($options['language']) {
/** @var Language $language */
$language = $options['language'];
// localUrl method provided as hook by LanguageSupportPageNames
$url = $page->localUrl($language);
}
}
if(is_null($url)) {
$url = rtrim($config->urls->root, '/') . $page->path();
if($template->slashUrls === 0 && $page->id > 1) $url = rtrim($url, '/');
}
if(is_string($options['urlSegmentStr']) && strlen($options['urlSegmentStr'])) {
$url = rtrim($url, '/') . '/' . $sanitizer->pagePathNameUTF8(trim($options['urlSegmentStr'], '/'));
if($template->slashUrlSegments === '' || $template->slashUrlSegments) $url .= '/';
}
if($options['pageNum']) {
if($options['pageNum'] === '+') {
$options['pageNum'] = $page->wire('input')->pageNum + 1;
} else if($options['pageNum'] === '-' || $options['pageNum'] === -1) {
$options['pageNum'] = $page->wire('input')->pageNum - 1;
}
if((int) $options['pageNum'] > 1) {
$prefix = '';
if($language) {
$lsp = $page->wire('modules')->get('LanguageSupportPageNames');
$prefix = $lsp ? $lsp->get("pageNumUrlPrefix$language") : '';
}
if(!strlen($prefix)) $prefix = $config->pageNumUrlPrefix;
$url = rtrim($url, '/') . '/' . $prefix . ((int) $options['pageNum']);
if($template->slashPageNum) $url .= '/';
}
}
if(count($options['data'])) {
$query = http_build_query($options['data']);
if($page->of()) $query = $sanitizer->entities($query);
$url .= '?' . $query;
}
if($options['http']) {
switch($template->https) {
case -1: $scheme = 'http'; break;
case 1: $scheme = 'https'; break;
default: $scheme = $config->https ? 'https' : 'http';
}
$url = "$scheme://" . $page->wire('config')->httpHost . $url;
}
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
@@ -491,6 +988,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);
@@ -534,6 +1032,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);
@@ -547,7 +1046,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) {
@@ -581,7 +1080,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) {
@@ -623,8 +1122,7 @@ class PageTraversal {
$siblings->prepend($page);
}
$siblings = $this->nextAll($page, '', $siblings);
$siblings = $this->nextAllSiblings($page, '', $siblings);
$all = $page->wire('pages')->newPageArray();
$stop = false;
@@ -675,8 +1173,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

@@ -7,28 +7,34 @@
* #pw-summary-traversal For the most part youll want to traverse from the parent `Pagefiles` object than these methods.
* #pw-summary-manipulation Remember to follow up any manipulations with a `$pages->save()` call.
* #pw-summary-tags Be sure to see the `Pagefiles::getTag()` and `Pagesfiles::findTag()` methods, which enable you retrieve files by tag.
* #pw-use-constructor
* #pw-body =
* Pagefile objects are contained by a `Pagefiles` object.
* #pw-body
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 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-read int $sort Sort order in database. #pw-group-other
* @property int $sort Sort order in database. #pw-group-other
* @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
@@ -36,7 +42,8 @@
*
* @method void install($filename)
* @method string httpUrl()
*
* @method string noCacheURL($http = false)
*
*/
class Pagefile extends WireData {
@@ -49,9 +56,19 @@ class Pagefile extends WireData {
/**
* Reference to the owning collection of Pagefiles
*
* @var Pagefiles
*
*/
protected $pagefiles;
protected $pagefiles;
/**
* Extra file data
*
* @var array
*
*/
protected $filedata = array();
/**
* Construct a new Pagefile
@@ -115,8 +132,17 @@ class Pagefile extends WireData {
*
*/
protected function ___install($filename) {
$basename = $filename;
if(strpos($basename, '?') !== false) {
list($basename, $queryString) = explode('?', $basename);
if($queryString) {} // do not use in basename
}
if(empty($basename)) throw new WireException("Empty filename");
$basename = $this->pagefiles->cleanBasename($filename, true, false, true);
$basename = $this->pagefiles->cleanBasename($basename, true, false, true);
$pathInfo = pathinfo($basename);
$basename = basename($basename, ".$pathInfo[extension]");
@@ -156,19 +182,25 @@ class Pagefile extends WireData {
*
* @param string $key
* @param mixed $value
* @return $this
* @return Pagefile|WireData
*
*/
public function set($key, $value) {
if($key == 'basename') $value = $this->pagefiles->cleanBasename($value, false);
if($key == 'description') return $this->setDescription($value);
if($key == 'modified') $value = ctype_digit("$value") ? (int) $value : strtotime($value);
if($key == 'created') $value = ctype_digit("$value") ? (int) $value : strtotime($value);
if($key == 'tags') {
if($key == 'basename') {
$value = $this->pagefiles->cleanBasename($value, false);
} else if($key == 'description') {
return $this->setDescription($value);
} else if($key == 'modified') {
$value = ctype_digit("$value") ? (int) $value : strtotime($value);
} else if($key == 'created') {
$value = ctype_digit("$value") ? (int) $value : strtotime($value);
} else if($key == 'tags') {
$this->tags($value);
return $this;
} else if($key == 'filedata') {
if(is_array($value)) $this->filedata($value);
return $this;
}
if(strpos($key, 'description') === 0 && preg_match('/^description(\d+)$/', $value, $matches)) {
@@ -183,16 +215,75 @@ class Pagefile extends WireData {
return parent::set($key, $value);
}
/**
* 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.
* - To set all values at once, specify an associative array for the $key argument.
* - 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
*
* @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.
* @param null|string|array|int|float $value Specify a value to set for given property
* @return Pagefile|Pageimage|array|string|int|float|bool|null
*
*/
public function filedata($key = '', $value = null) {
$filedata = $this->filedata;
$changed = false;
if($key === false || $key === null) {
// unset property named in $value
if(!empty($value) && isset($filedata[$value])) {
unset($this->filedata[$value]);
$changed = true;
}
} else if(empty($key)) {
// return all
return $filedata;
} else if(is_array($key)) {
// set all
if($key != $filedata) {
$this->filedata = $key;
$changed = true;
}
} else if($value === null) {
// return value for key
return isset($this->filedata[$key]) ? $this->filedata[$key] : null;
} else {
// set value for key
if(!isset($filedata[$key]) || $filedata[$key] != $value) {
$this->filedata[$key] = $value;
$changed = true;
}
}
if($changed) {
$this->trackChange('filedata', $filedata, $this->filedata);
if($this->page && $this->field) $this->page->trackChange($this->field->name);
}
return $this;
}
/**
* Set a description, optionally parsing JSON language-specific descriptions to separate properties
*
* @param string $value
* @param string|array $value
* @param Page|Language Langage to set it for. Omit to determine automatically.
* @return $this
*
*/
protected function setDescription($value, Page $language = null) {
/** @var Languages $languages */
$languages = $this->wire('languages');
/** @var Language|null $language */
$field = $this->field;
@@ -204,27 +295,46 @@ class Pagefile extends WireData {
$name .= $language->id;
}
parent::set($name, $value);
if($name != 'description' && $this->isChanged($name)) $this->trackChange('description');
return $this;
}
// check if it contains JSON?
$first = substr($value, 0, 1);
$last = substr($value, -1);
if(($first == '{' && $last == '}') || ($first == '[' && $last == ']')) {
$values = json_decode($value, true);
if(is_array($value)) {
$values = $value;
} else {
$values = array();
// check if it contains JSON?
$first = substr($value, 0, 1);
$last = substr($value, -1);
if(($first == '{' && $last == '}') || ($first == '[' && $last == ']')) {
$values = json_decode($value, true);
} else {
$values = array();
}
}
$numChanges = 0;
if($values && count($values)) {
$n = 0;
foreach($values as $id => $v) {
// first item is always default language. this ensures description will still
// work even if language support is later uninstalled.
if($noLang && $n > 0) continue;
$name = $n > 0 ? "description$id" : "description";
parent::set($name, $v);
$name = 'description';
if($noLang && $n > 0) break;
$n++;
if(ctype_digit("$id")) {
$id = (int) $id;
if(!$id) $id = '';
$name = $n > 0 ? "description$id" : "description";
} else if($id === 'default') {
$name = 'description';
} else if($languages) {
$language = $languages->get($id); // i.e. "default" or "es"
if(!$language->id) continue;
$name = $language->isDefault() ? "description" : "description$language->id";
}
parent::set($name, $v);
if($this->isChanged($name)) $numChanges++;
}
} else {
// no JSON values so assume regular language description
@@ -232,11 +342,15 @@ class Pagefile extends WireData {
$language = $languages ? $this->wire('user')->language : null;
if($languages && $language && !$noLang && !$language->isDefault()) {
parent::set("description$language", $value);
$name = "description$language->id";
} else {
parent::set("description", $value);
$name = "description";
}
parent::set($name, $value);
if($this->isChanged($name)) $numChanges++;
}
if($numChanges && !$this->isChanged('description')) $this->trackChange('description');
return $this;
}
@@ -265,17 +379,31 @@ class Pagefile extends WireData {
* #pw-group-common
* #pw-group-manipulation
*
* @param null|bool|Language
* @param null|bool|Language|array
* - To GET in current user language: Omit arguments or specify null.
* - To GET in another language: Specify a Language name, id or object.
* - To GET in all languages as a JSON string: Specify boolean true (if LanguageSupport not installed, regular string returned).
* - To GET in all languages as an array indexed by language name: Specify boolean true for both arguments.
* - To SET for a language: Specify a language name, id or object, plus the $value as the 2nd argument.
* - 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
*
*/
public function description($language = null, $value = null) {
if($language === true && $value === true) {
// return all in array indexed by language name
/** @var Languages $languages */
$languages = $this->wire('languages');
if(!$languages) return array('default' => parent::get('description'));
$value = array();
foreach($languages as $language) {
$value[$language->name] = (string) parent::get("description" . ($language->isDefault() ? '' : $language->id));
}
return $value;
}
if(!is_null($value)) {
// set description mode
@@ -288,6 +416,13 @@ class Pagefile extends WireData {
}
return $value;
}
if(is_array($language)) {
// set all from array, then return description in current language
$this->setDescription($language);
$language = null;
$value = null;
}
if((is_string($language) || is_int($language)) && $this->wire('languages')) {
// convert named or ID'd languages to Language object
@@ -368,7 +503,10 @@ class Pagefile extends WireData {
break;
case 'URL':
// nocache url
$value = $this->url() . '?nc=' . @filemtime($this->filename());
$value = $this->noCacheURL();
break;
case 'HTTPURL':
$value = $this->noCacheURL(true);
break;
case 'pagefiles':
$value = $this->pagefiles;
@@ -387,14 +525,40 @@ class Pagefile extends WireData {
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 'fileData':
case 'filedata':
$value = $this->filedata();
break;
case 'mtime':
case 'mtimeStr':
case 'filemtime':
case 'filemtimeStr':
$value = filemtime($this->filename());
if(strpos($key, 'Str')) $value = wireDate($this->wire('config')->dateFormat, $value);
break;
}
if(is_null($value)) return parent::get($key);
return $value;
}
/**
* Hookable no-cache URL
*
* #pw-internal
*
* @param bool $http Include scheme and hostname?
* @return string
*
*/
public function ___noCacheURL($http = false) {
return ($http ? $this->httpUrl() : $this->url()) . '?nc=' . @filemtime($this->filename());
}
/**
* Return the next sibling Pagefile in the parent Pagefiles, or NULL if at the end.
*
@@ -760,7 +924,7 @@ class Pagefile extends WireData {
*
*/
public function __toString() {
return $this->basename;
return (string) $this->basename;
}
/**
@@ -788,7 +952,7 @@ class Pagefile extends WireData {
*/
public function unlink() {
if(!strlen($this->basename) || !is_file($this->filename)) return true;
return unlink($this->filename);
return $this->wire('files')->unlink($this->filename, true);
}
/**
@@ -804,7 +968,7 @@ class Pagefile extends WireData {
*/
public function rename($basename) {
$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();
}
@@ -817,7 +981,7 @@ class Pagefile extends WireData {
* #pw-internal
*
* @param string $path Path (not including basename)
* @return mixed result of copy() function
* @return bool result of copy() function
*
*/
public function copyToPath($path) {
@@ -874,5 +1038,29 @@ class Pagefile extends WireData {
public function isTemp($set = null) {
return $this->pagefiles->isTemp($this, $set);
}
/**
* 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,
'filemtime' => $this->mtimeStr,
'filedata' => $filedata,
);
if(empty($info['filedata'])) unset($info['filedata']);
return $info;
}
}

View File

@@ -38,7 +38,7 @@
* Typically a Pagefiles object will be associated with a specific field attached to a Page.
* There may be multiple instances of Pagefiles attached to a given Page (depending on what fields are in it's fieldgroup).
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
*
@@ -47,10 +47,11 @@
* @property Page $page Returns the Page that contains this set of files, same as the getPage() method. #pw-group-other
* @property Field $field Returns the Field that contains this set of files, same as the getField() method. #pw-group-other
* @method Pagefiles delete() delete(Pagefile $file) Removes the file and deletes from disk when page is saved. #pw-group-manipulation
* @method Pagefile|bool clone(Pagefile $item, array $options = array()) Duplicate a file and return it. #pw-group-manipulation
*
*/
class Pagefiles extends WireArray {
class Pagefiles extends WireArray implements PageFieldValueInterface {
/**
* The Page object associated with these Pagefiles
@@ -75,6 +76,22 @@ class Pagefiles extends WireArray {
*
*/
protected $unlinkQueue = array();
/**
* Items to be renamed when Page is saved (oldName => newName)
*
* @var array
*
*/
protected $renameQueue = array();
/**
* Items to be made non-temp upon page save (like duplicated files)
*
* @var array
*
*/
protected $unTempQueue = array();
/**
* IDs of any hooks added in this instance, used by the destructor
@@ -83,6 +100,14 @@ class Pagefiles extends WireArray {
*
*/
protected $hookIDs = array();
/**
* Whether or not this is a formatted value
*
* @var bool
*
*/
protected $formatted = false;
/**
* Construct a Pagefiles object
@@ -161,7 +186,7 @@ class Pagefiles extends WireArray {
*
* #pw-internal
*
* @return WireArray
* @return Pagefiles|WireArray
*
*/
public function makeNew() {
@@ -310,10 +335,25 @@ class Pagefiles extends WireArray {
public function add($item) {
if(is_string($item)) {
/** @var Pagefile $item */
$item = $this->wire(new Pagefile($this, $item));
} else if($item instanceof Pagefile) {
$page = $this->get('page');
if($page && "$page" !== "$item->page") {
$newItem = clone $item;
$newItem->setPagefilesParent($this);
$newItem->install($item->filename);
$newItem->isTemp(true);
$this->unTempQueue($newItem);
$this->message("Copied $item->url to $newItem->url", Notice::debug);
$item = $newItem;
}
}
return parent::add($item);
/** @var Pagefiles $result */
$result = parent::add($item);
return $result;
}
/**
@@ -323,14 +363,36 @@ class Pagefiles extends WireArray {
*
*/
public function hookPageSave() {
if($this->page && $this->field && !$this->page->isChanged($this->field->name)) return $this;
foreach($this->unTempQueue as $item) {
$item->isTemp(false);
}
foreach($this->unlinkQueue as $item) {
$item->unlink();
}
foreach($this->renameQueue as $item) {
$name = $item->get('_rename');
if(!$name) continue;
$item->rename($name);
}
$this->unTempQueue = array();
$this->unlinkQueue = array();
$this->renameQueue = array();
$this->removeHooks();
return $this;
}
protected function addSaveHook() {
if(!count($this->unlinkQueue) && !count($this->renameQueue) && !count($this->unTempQueue)) {
$this->hookIDs[] = $this->page->filesManager->addHookBefore('save', $this, 'hookPageSave');
}
}
/**
* Delete a pagefile item
@@ -364,9 +426,7 @@ class Pagefiles extends WireArray {
public function remove($item) {
if(is_string($item)) $item = $this->get($item);
if(!$this->isValidItem($item)) throw new WireException("Invalid type to {$this->className}::remove(item)");
if(!count($this->unlinkQueue)) {
$this->hookIDs[] = $this->page->filesManager->addHookBefore('save', $this, 'hookPageSave');
}
$this->addSaveHook();
$this->unlinkQueue[] = $item;
parent::remove($item);
return $this;
@@ -390,6 +450,87 @@ class Pagefiles extends WireArray {
return $this;
}
/**
* Queue a rename of a Pagefile
*
* This only queues a rename. Rename actually occurs when page is saved.
* Note this differs from the behavior of `Pagefile::rename()`.
*
* #pw-group-manipulation
*
* @param Pagefile $item
* @param string $name
* @return Pagefiles
* @see Pagefile::rename()
*
*/
public function rename(Pagefile $item, $name) {
$item->set('_rename', $name);
$this->renameQueue[] = $item;
$this->trackChange('renameQueue', $item->name, $name);
$this->addSaveHook();
return $this;
}
/**
* Duplicate the Pagefile and add to this Pagefiles instance
*
* After duplicating a file, you must follow up with a save of the page containing it.
* Otherwise the file is marked for deletion.
*
* @param Pagefile $item Pagefile item to duplicate
* @param array $options Options to modify default behavior:
* - `action` (string): Specify "append", "prepend", "after", "before" or blank to only return Pagefile. (default="after")
* - `pagefiles` (Pagefiles): Pagefiles instance file should be duplicated to. (default=$this)
* @return Pagefile|bool Returns new Pagefile or boolean false on fail
*
*/
public function ___clone(Pagefile $item, array $options = array()) {
$defaults = array(
'action' => 'after',
'pagefiles' => $this,
);
$options = array_merge($defaults, $options);
/** @var Pagefiles $pagefiles */
$pagefiles = $options['pagefiles'];
$itemCopy = false;
$path = $pagefiles->path();
$parts = explode('.', $item->basename(), 2);
$n = $path === $this->path() ? 1 : 0;
if($n && preg_match('/^(.+?)-(\d+)$/', $parts[0], $matches)) {
$parts[0] = $matches[1];
$n = (int) $matches[2];
if(!$n) $n = 1;
}
do {
$pathname = $n ? ($path . $parts[0] . "-$n." . $parts[1]) : ($path . $item->basename);
} while(file_exists($pathname) && $n++);
if(copy($item->filename(), $pathname)) {
$this->wire('files')->chmod($pathname);
$itemCopy = clone $item;
$itemCopy->setPagefilesParent($pagefiles);
$itemCopy->setFilename($pathname);
$itemCopy->isTemp(true);
switch($options['action']) {
case 'append': $pagefiles->append($itemCopy); break;
case 'prepend': $pagefiles->prepend($itemCopy); break;
case 'before': $pagefiles->insertBefore($itemCopy, $item); break;
case 'after': $pagefiles->insertAfter($itemCopy, $item); break;
}
$pagefiles->unTempQueue($itemCopy);
}
return $itemCopy;
}
/**
* Return the full disk path where files are stored
*
@@ -510,6 +651,59 @@ class Pagefiles extends WireArray {
}
return $item;
}
/**
* Get list of tags for all files in this Pagefiles array, or return files matching given tag(s)
*
* This method can either return a list of all tags available, or return all files
* matching the given tag or tags (an alias of findTag method).
*
* ~~~~~
* // Get string of all tags
* $tagsString = $page->files->tags();
*
* // Get array of all tags
* $tagsArray = $page->files->tags(true);
*
* // Find all files matching given tag
* $pagefiles = $page->files->tags('foobar');
* ~~~~~
*
* #pw-group-tags
*
* @param bool|string|array $value Specify one of the following:
* - Omit to return all tags as a string.
* - Boolean true if you want to return tags as an array (rather than string).
* - Boolean false to return tags as an array, with lowercase enforced.
* - String if you want to return files matching tags (See `Pagefiles::findTag()` method for usage)
* - Array if you want to return files matching tags (See `Pagefiles::findTag()` method for usage)
* @return string|array|Pagefiles Returns all tags as a string or an array, or Pagefiles matching given tag(s).
* When a tags array is returned, it is an associative array where the key and value are both the tag (keys are always lowercase).
* @see Pagefiles::findTag(), Pagefile::tags()
*
*/
public function tags($value = null) {
if($value === null) {
$returnString = true;
$value = true;
} else {
$returnString = false;
}
if(is_bool($value)) {
// return array of tags
$tags = array();
foreach($this as $pagefile) {
$tags = array_merge($tags, $pagefile->tags($value));
}
if($returnString) $tags = implode(' ', $tags);
return $tags;
}
// fallback to behavior of findTag
return $this->findTag($value);
}
/**
* Track a change
@@ -522,10 +716,11 @@ class Pagefiles extends WireArray {
* @return $this
*
*/
public function trackChange($what, $old = null, $new = null) {
if($this->field && $this->page) $this->page->trackChange($this->field->name);
return parent::trackChange($what, $old, $new);
/** @var Pagefiles $result */
$result = parent::trackChange($what, $old, $new);
return $result;
}
/**
@@ -653,6 +848,19 @@ class Pagefiles extends WireArray {
return count($removed);
}
/**
* Add Pagefile as item to have temporary status removed when Page is saved
*
* #pw-internal
*
* @param Pagefile $pagefile
*
*/
public function unTempQueue(Pagefile $pagefile) {
$this->addSaveHook();
$this->unTempQueue[] = $pagefile;
}
/**
* Is the given Pagefiles identical to this one?
*
@@ -677,13 +885,14 @@ class Pagefiles extends WireArray {
* @return $this
*
*/
public function resetTrackChanges($trackChanges = true) {
$this->unlinkQueue = array();
if($this->page && $this->page->id && $this->field) {
$this->page->untrackChange($this->field->name);
}
return parent::resetTrackChanges($trackChanges);
/** @var Pagefiles $result */
$result = parent::resetTrackChanges($trackChanges);
return $result;
}
/**
@@ -696,6 +905,41 @@ class Pagefiles extends WireArray {
//$this->page = null;
}
/**
* Get or set formatted state
*
* @param bool|null $set
* @return bool
*
*/
public function formatted($set = null) {
if(is_bool($set)) $this->formatted = $set;
return $this->formatted;
}
/**
* Debug info
*
* @return array
*
*/
public function __debugInfo() {
$info = array(
'count' => $this->count(),
'page' => $this->page ? $this->page->path() : '?',
'field' => $this->field ? $this->field->name : '?',
'url' => $this->url(),
'path' => $this->path(),
'items' => array(),
);
foreach($this as $key => $pagefile) {
/** @var Pagefile $pagefile */
$info['items'][$key] = $pagefile->__debugInfo();
}
return $info;
}
}

View File

@@ -230,6 +230,40 @@ class PagefilesManager extends Wire {
return $this->_copyFiles($this->path(), $toPath);
}
/**
* Copy/import files from given path into the pages files directory
*
* #pw-group-manipulation
*
* @param string $fromPath Path to copy/import files from.
* @param bool $move Move files into directory rather than copy?
* @return int Number of files/directories copied.
* @since 3.0.114
*
*/
public function importFiles($fromPath, $move = false) {
return $this->_copyFiles($fromPath, $this->path(), $move);
}
/**
* Replace all pages files with those from given path
*
* #pw-group-manipulation
*
* @param string $fromPath
* @param bool $move Move files to destination rather than copy? (default=false)
* @return int Number of files/directories copied.
* @throws WireException if given a path that does not exist.
* @since 3.0.114
*
*
*/
public function replaceFiles($fromPath, $move = false) {
if(!is_dir($fromPath)) throw new WireException("Path does not exist: $fromPath");
$this->emptyPath();
return $this->_copyFiles($fromPath, $this->path(), $move);
}
/**
* Recursively move all files managed by this PagefilesManager into a new path.
*
@@ -284,16 +318,16 @@ class PagefilesManager extends Wire {
$errors = 0;
if($recursive) {
// clear out path and everything below it
if(!wireRmdir($path, true)) $errors++;
if(!$this->wire('files')->rmdir($path, true, true)) $errors++;
if(!$rmdir) $this->_createPath($path);
} else {
// only clear out files in path
foreach(new \DirectoryIterator($path) as $file) {
if($file->isDot() || $file->isDir()) continue;
if(!unlink($file->getPathname())) $errors++;
if(!$this->wire('files')->unlink($file->getPathname(), true)) $errors++;
}
if($rmdir) {
@rmdir($path); // will not be successful if other dirs within it
$this->wire('files')->rmdir($path, false, true); // will not be successful if other dirs within it
}
}
return $errors === 0;
@@ -572,15 +606,23 @@ class PagefilesManager extends Wire {
}
/**
* Return a path where temporary files can be stored.
* Return a path where temporary files can be stored unique to this ProcessWire instance
*
* @return string
*
*/
public function getTempPath() {
static $wtd = null;
if(is_null($wtd)) $wtd = $this->wire(new WireTempDir($this->className() . $this->page->id));
if(is_null($wtd)) {
$wtd = new WireTempDir();
$this->wire($wtd);
$wtd->setMaxAge(3600);
$name = $wtd->createName('PFM');
$wtd->create($name);
}
return $wtd->get();
// if(is_null($wtd)) $wtd = $this->wire(new WireTempDir($this->className() . $this->page->id));
// return $wtd->get();
}
}

View File

@@ -6,6 +6,7 @@
* #pw-summary Represents an image item attached to a page, typically via an Image Fieldtype.
* #pw-summary-variations A variation refers to an image that is based upon another (like a resized or cropped version for example).
* #pw-order-groups common,resize-and-crop,variations,other
* #pw-use-constructor
* #pw-body =
* Pageimage objects are usually contained by a `Pageimages` object, which is a type of `Pagefiles` and `WireArray`.
* In addition to the methods and properties below, you'll also want to look at `Pagefile` which this class inherits
@@ -23,20 +24,25 @@
* ~~~~~
* #pw-body
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
* @property int $width Width of image, in pixels.
* @property int $height Height of image, in pixels.
* @property int $hidpiWidth HiDPI width of image, in pixels. #pw-internal
* @property int $hidpiHeight HiDPI heigh of image, in pixels. #pw-internal
* @property string $error Last image resizing error message, when applicable. #pw-group-resize-and-crop
* @property Pageimage $original Reference to original $image, if this is a resized version. #pw-group-variations
* @property string $url
* @property string $basename
* @property string $filename
* @property-read int $width Width of image, in pixels.
* @property-read int $height Height of image, in pixels.
* @property-read int $hidpiWidth HiDPI width of image, in pixels. #pw-internal
* @property-read int $hidpiHeight HiDPI heigh of image, in pixels. #pw-internal
* @property-read string $error Last image resizing error message, when applicable. #pw-group-resize-and-crop
* @property-read Pageimage $original Reference to original $image, if this is a resized version. #pw-group-variations
* @property-read string $url
* @property-read string $basename
* @property-read string $filename
* @property-read array $focus Focus array contains 'top' (float), 'left' (float), 'zoom' (int), and 'default' (bool) properties.
* @property-read string $focusStr Readable string containing focus information.
* @property-read bool $hasFocus Does this image have custom focus settings? (i.e. $focus['default'] == true)
* @property-read array $suffix Array containing file suffix(es).
* @property-read string $suffixStr String of file suffix(es) separated by comma.
*
* @method bool|array isVariation($basename, $allowSelf = false)
* @method bool|array isVariation($basename, $options = array())
* @method Pageimage crop($x, $y, $width, $height, $options = array())
* @method array rebuildVariations($mode = 0, array $suffix = array(), array $options = array())
* @method install($filename)
@@ -93,9 +99,14 @@ class Pageimage extends Pagefile {
protected $error = '';
/**
* Construct a new Pagefile
* Construct a new Pageimage
*
* ~~~~~
* // Construct a new Pageimage, assumes that $page->images is a FieldtypeImage Field
* $pageimage = new Pageimage($page->images, '/path/to/file.png');
* ~~~~~
*
* @param Pagefiles $pagefiles
* @param Pageimages|Pagefiles $pagefiles
* @param string $filename Full path and filename to this pagefile
* @throws WireException
*
@@ -171,6 +182,133 @@ class Pageimage extends Pagefile {
}
}
/**
* Get or set focus area for crops to use
*
* These settings are used by $this->size() calls that specify BOTH width AND height. Focus helps to
* ensure that the important subject of the photo is not cropped out when the requested size proportion
* differs from the original image proportion. For example, not chopping off someones head in a photo.
*
* Default behavior is to return an array containing "top" and "left" indexes, representing percentages
* from top and left. When arguments are specified, you are either setting the top/left percentages, or
* unsetting focus, or getting focus in different ways, described in arguments below.
*
* A zoom argument/property is also present here for future use, but not currently supported.
*
* #pw-group-other
*
* @param null|float|int|array|false $top Omit to get focus array, or specify one of the following:
* - GET: Omit all arguments to get focus array (default behavior).
* - GET: Specify boolean TRUE to return TRUE if focus data is present or FALSE if not.
* - GET: Specify integer 1 to make this method return pixel dimensions rather than percentages.
* - SET: Specify both $top and $left arguments to set (values assumed to be percentages).
* - SET: Specify array containing "top" and "left" indexes to set (percentages).
* - SET: Specify array where index 0 is top and index 1 is left (percentages).
* - SET: Specify string in the format "top left", i.e. "25 70" or "top left zoom", i.e. "25 70 30" (percentages).
* - SET: Specify CSV key=value string in the format "top=25%, left=70%, zoom=30%" in any order
* - UNSET: Specify boolean false to remove any focus values.
* @param null|float|int $left Set left value (when $top value is float|int)
* - This argument is only used when setting focus and should be omitted otherwise.
* @param null|int $zoom Zoom percent (not currently supported)
* @return array|bool|Pageimage Returns one of the following:
* - When getting returns array containing top, left and default properties.
* - When TRUE was specified for the $top argument, it returns either TRUE (has focus) or FALSE (does not have).
* - When setting or unsetting returns $this.
*
*/
public function focus($top = null, $left = null, $zoom = null) {
if(is_string($top) && $left === null) {
if(strpos($top, '=')) {
// SET string "top=25%, left=70%, zoom=0%"
$a = array('top' => 50, 'left' => 50, 'zoom' => 0);
$parts = explode(',', str_replace(array(' ', '%'), '', $top));
foreach($parts as $part) {
if(!strpos($part, '=')) continue;
list($name, $pct) = explode('=', $part);
$a[$name] = strpos($pct, '.') !== false ? (float) $pct : (int) $pct;
}
$top = $a; // for later setting by array
unset($a);
} else if(strpos($top, ' ')) {
// SET string like "25 70 0" (representing "top left zoom")
if(strpos($top, ' ') != strrpos($top, ' ')) {
// with zoom
list($top, $left, $zoom) = explode(' ', $top, 3);
} else {
// without zoom
list($top, $left) = explode(' ', $top, 2);
$zoom = 0;
}
}
}
if($top === null || $top === true || ($top === 1 && $left === null)) {
// GET
$focus = $this->filedata('focus');
if(!is_array($focus) || empty($focus)) {
// use default
if($top === true) return false;
$focus = array(
'top' => 50,
'left' => 50,
'zoom' => 0,
'default' => true,
'str' => '50 50 0',
);
} else {
// use custom
if($top === true) return true;
if(!isset($focus['zoom'])) $focus['zoom'] = 0;
$focus['default'] = false;
$focus['str'] = "$focus[top] $focus[left] $focus[zoom]";
}
if($top === 1) {
// return pixel dimensions rather than percentages
$centerX = ($focus['left'] / 100) * $this->width(); // i.e. (50 / 100) * 500 = 250;
$centerY = ($focus['top'] / 100) * $this->height();
$focus['left'] = $centerX;
$focus['top'] = $centerY;
}
return $focus;
} else if($top === false) {
// UNSET
$this->filedata(false, 'focus');
} else if($top !== null && $left !== null) {
// SET
if(is_array($top)) {
if(isset($top['left'])) {
$left = $top['left'];
$top = $top['top'];
$zoom = isset($top['zoom']) ? $top['zoom'] : 0;
} else {
$top = $top[0];
$left = $top[1];
$zoom = isset($top[2]) ? $top[2] : 0;
}
}
$top = (float) $top;
$left = (float) $left;
$zoom = (int) $zoom;
if(((int) $top) == 50 && ((int) $left) == 50 && ($zoom < 2)) {
// if matches defaults, then no reason to store in filedata
$this->filedata(false, 'focus');
} else {
$this->filedata('focus', array(
'top' => round($top, 1),
'left' => round($left, 1),
'zoom' => $zoom
));
}
}
return $this;
}
/**
* Get a property from this Pageimage
*
@@ -200,6 +338,22 @@ class Pageimage extends Pagefile {
case 'error':
$value = $this->error;
break;
case 'focus':
$value = $this->focus();
break;
case 'focusStr':
$focus = $this->focus();
$value = "top=$focus[top]%,left=$focus[left]%,zoom=$focus[zoom]%" . ($focus['default'] ? " (default)" : "");
break;
case 'hasFocus':
$value = $this->focus(true);
break;
case 'suffix':
$value = $this->suffix();
break;
case 'suffixStr':
$value = implode(',', $this->suffix());
break;
default:
$value = parent::get($key);
}
@@ -211,7 +365,8 @@ class Pageimage extends Pagefile {
*
* #pw-internal
*
* @param bool $reset
* @param bool|string $reset Specify true to retrieve info fresh, or filename to check and return info for.
* When specifying a filename, the info is only returned (not populated with this object).
* @return array
*
*/
@@ -220,25 +375,73 @@ class Pageimage extends Pagefile {
if($reset) $checkImage = true;
else if($this->imageInfo['width']) $checkImage = false;
else $checkImage = true;
$imageInfo = $this->imageInfo;
$filename = is_string($reset) && file_exists($reset) ? $reset : '';
if($checkImage) {
if($checkImage || $filename) {
if($this->ext == 'svg') {
$xml = @file_get_contents($this->filename);
if($xml) {
$a = @simplexml_load_string($xml)->attributes();
$this->imageInfo['width'] = (int) $a->width > 0 ? (int) $a->width : '100%';
$this->imageInfo['height'] = (int) $a->height > 0 ? (int) $a->height : '100%';
}
$info = $this->getImageInfoSVG($filename);
$imageInfo['width'] = $info['width'];
$imageInfo['height'] = $info['height'];
} else {
$info = @getimagesize($this->filename);
if($filename) {
$info = @getimagesize($filename);
} else {
$info = @getimagesize($this->filename);
}
if($info) {
$this->imageInfo['width'] = $info[0];
$this->imageInfo['height'] = $info[1];
$imageInfo['width'] = $info[0];
$imageInfo['height'] = $info[1];
}
}
if(!$filename) $this->imageInfo = $imageInfo;
}
return $this->imageInfo;
return $imageInfo;
}
/**
* Gets the image info/size of an SVG
*
* Returned width and height values may be integers OR percentage strings.
*
* #pw-internal
*
* @param string $filename Optional filename to check
* @return array of width and height
*
*/
protected function getImageInfoSVG($filename = '') {
$width = 0;
$height = 0;
if(!$filename) $filename = $this->filename;
$xml = @file_get_contents($filename);
if($xml) {
$a = @simplexml_load_string($xml)->attributes();
if((int) $a->width > 0) $width = (int) $a->width;
if((int) $a->height > 0) $height = (int) $a->height;
}
if((!$width || !$height) && (extension_loaded('imagick') || class_exists('\IMagick'))) {
try {
$imagick = new \Imagick();
$imagick->readImage($filename);
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
} catch(\Exception $e) {
// fallback to 100%
}
}
if($width < 1) $width = '100%';
if($height < 1) $height = '100%';
return array(
'width' => $width,
'height' => $height
);
}
/**
@@ -285,7 +488,7 @@ class Pageimage extends Pagefile {
*
* - `quality` (int): Quality setting 1-100 (default=90, or as specified in /site/config.php).
* - `upscaling` (bool): Allow image to be upscaled? (default=true).
* - `cropping` (string|bool): Cropping mode, see possible values in "cropping" section below (default=center).
* - `cropping` (string|bool|array): Cropping mode, see possible values in "cropping" section below (default=center).
* - `suffix` (string|array): Suffix word to identify the new image, or use array of words for multiple (default=none).
* - `forceNew` (bool): Force re-creation of the image even if it already exists? (default=false).
* - `sharpening` (string): Sharpening mode: "none", "soft", "medium", or "strong" (default=soft).
@@ -295,10 +498,17 @@ class Pageimage extends Pagefile {
* - `hidpi` (bool): Use HiDPI/retina pixel doubling? (default=false).
* - `hidpiQuality` (bool): Quality setting for HiDPI (default=40, typically lower than regular quality setting).
* - `cleanFilename` (bool): Clean filename of historical resize information for shorter filenames? (default=false).
* - `nameWidth` (int): Width to use for filename (default is to use specified $width argument).
* - `nameHeight` (int): Height to use for filename (default is to use specified $height argument).
* - `focus` (bool): Should resizes that result in crop use focus area if available? (default=true).
* In order for focus to be applicable, resize must include both width and height.
* - `allowOriginal` (bool): Return original if already at width/height? May not be combined with other options. (default=false)
*
* **Possible values for "cropping" option**
*
* - `center` (string): to crop to center of image, default behavior.
* - `true` (bool): Auto detect and allow use of focus (default).
* - `false` (bool): Disallow cropping.
* - `center` (string): to crop to center of image.
* - `x111y222` (string): to crop by pixels, 111px from left and 222px from top (replacing 111 and 222 with your values).
* - `north` (string): Crop North (top), may also be just "n".
* - `northwest` (string): Crop from Northwest (top left), may also be just "nw".
@@ -309,7 +519,9 @@ class Pageimage extends Pagefile {
* - `west` (string): Crop West (left), may also be just "w".
* - `east` (string): Crop East (right), may alos be just "e".
* - `blank` (string): Specify a blank string to disallow cropping during resize.
*
* - `array(111,222)` (array): Array of integers index 0 is left pixels and index 1 is top pixels.
* - `array('11%','22%')` (array): Array of '%' appended strings where index 0 is left percent and index 1 is top percent.
*
* **Note about "quality" and "upscaling" options**
*
* ProcessWire doesn't keep separate copies of images with different "quality" or "upscaling" values.
@@ -356,16 +568,6 @@ class Pageimage extends Pagefile {
*/
protected function ___size($width, $height, $options) {
// I was getting unnecessarily resized images without this code below,
// but this may be better solved in ImageSizer?
/*
$w = $this->width();
$h = $this->height();
if($w == $width && $h == $height) return $this;
if(!$height && $w == $width) return $this;
if(!$width && $h == $height) return $this;
*/
if($this->ext == 'svg') return $this;
if(!is_array($options)) {
@@ -392,6 +594,8 @@ class Pageimage extends Pagefile {
$defaultOptions = array(
'upscaling' => true,
'cropping' => true,
'interlace' => false,
'sharpening' => 'soft',
'quality' => 90,
'hidpiQuality' => 40,
'suffix' => array(), // can be array of suffixes or string of 1 suffix
@@ -400,6 +604,11 @@ class Pageimage extends Pagefile {
'cleanFilename' => false, // clean filename of historial resize information
'rotate' => 0,
'flip' => '',
'nameWidth' => null, // override width to use for filename, int when populated
'nameHeight' => null, // override height to use for filename, int when populated
'focus' => true, // allow single dimension resizes to use focus area?
'zoom' => null, // zoom override, used only if focus is applicable, int when populated
'allowOriginal' => false, // Return original image if already at requested dimensions? (must be only specified option)
);
$this->error = '';
@@ -407,16 +616,32 @@ class Pageimage extends Pagefile {
$configOptions = $this->wire('config')->imageSizerOptions;
if(!is_array($configOptions)) $configOptions = array();
$options = array_merge($defaultOptions, $configOptions, $options);
if($options['cropping'] === 1) $options['cropping'] = true;
$width = (int) $width;
$height = (int) $height;
if(is_string($options['cropping'])
if($options['allowOriginal'] && count($requestOptions) === 1) {
if((!$width || $this->width() == $width) && (!$height || $this->height() == $height)) {
// return original image if already at requested width/height
return $this;
}
}
if($options['cropping'] === true && empty($options['cropExtra']) && $options['focus'] && $this->hasFocus && $width && $height) {
// crop to focus area
$focus = $this->focus();
if(is_int($options['zoom'])) $focus['zoom'] = $options['zoom']; // override
$options['cropping'] = array("$focus[left]%", "$focus[top]%", "$focus[zoom]");
$crop = ''; // do not add suffix
} else if(is_string($options['cropping'])
&& strpos($options['cropping'], 'x') === 0
&& preg_match('/^x(\d+)[yx](\d+)/', $options['cropping'], $matches)) {
$options['cropping'] = true;
$options['cropExtra'] = array((int) $matches[1], (int) $matches[2], $width, $height);
$crop = '';
} else {
$crop = ImageSizer::croppingValueStr($options['cropping']);
}
@@ -426,9 +651,15 @@ class Pageimage extends Pagefile {
$options['suffix'] = empty($options['suffix']) ? array() : explode(' ', $options['suffix']);
}
if($options['rotate'] && !in_array(abs((int) $options['rotate']), array(90, 180, 270))) $options['rotate'] = 0;
if($options['rotate']) $options['suffix'][] = ($options['rotate'] > 0 ? "rot" : "tor") . abs($options['rotate']);
if($options['flip']) $options['suffix'][] = strtolower(substr($options['flip'], 0, 1)) == 'v' ? 'flipv' : 'fliph';
if($options['rotate'] && !in_array(abs((int) $options['rotate']), array(90, 180, 270))) {
$options['rotate'] = 0;
}
if($options['rotate']) {
$options['suffix'][] = ($options['rotate'] > 0 ? "rot" : "tor") . abs($options['rotate']);
}
if($options['flip']) {
$options['suffix'][] = strtolower(substr($options['flip'], 0, 1)) == 'v' ? 'flipv' : 'fliph';
}
$suffixStr = '';
if(!empty($options['suffix'])) {
@@ -447,23 +678,31 @@ class Pageimage extends Pagefile {
if($options['hidpiQuality']) $options['quality'] = $options['hidpiQuality'];
}
//$basename = $this->pagefiles->cleanBasename($this->basename(), false, false, false);
// cleanBasename($basename, $originalize = false, $allowDots = true, $translate = false)
$originalName = $this->basename();
$basename = basename($originalName, "." . $this->ext()); // i.e. myfile
// determine basename without extension, i.e. myfile
$basename = basename($originalName, "." . $this->ext());
$originalSize = $debug ? @filesize($this->filename) : 0;
if($options['cleanFilename'] && strpos($basename, '.') !== false) {
$basename = substr($basename, 0, strpos($basename, '.'));
}
$basename .= '.' . $width . 'x' . $height . $crop . $suffixStr . "." . $this->ext(); // i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg
// filename uses requested width/height unless another specified via nameWidth or nameHeight options
$nameWidth = is_int($options['nameWidth']) ? $options['nameWidth'] : $width;
$nameHeight = is_int($options['nameHeight']) ? $options['nameHeight'] : $height;
// i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg
$basename .= '.' . $nameWidth . 'x' . $nameHeight . $crop . $suffixStr . "." . $this->ext();
$filenameFinal = $this->pagefiles->path() . $basename;
$filenameUnvalidated = '';
$exists = file_exists($filenameFinal);
// create a new resize if it doesn't already exist or forceNew option is set
if(!$exists || $options['forceNew']) {
// filenameUnvalidated is temporary filename used for resize
$filenameUnvalidated = $this->pagefiles->page->filesManager()->getTempPath() . $basename;
if($exists && $options['forceNew']) @unlink($filenameFinal);
if(file_exists($filenameUnvalidated)) @unlink($filenameUnvalidated);
if($exists && $options['forceNew']) $this->wire('files')->unlink($filenameFinal, true);
if(file_exists($filenameUnvalidated)) $this->wire('files')->unlink($filenameUnvalidated, true);
if(@copy($this->filename(), $filenameUnvalidated)) {
try {
@@ -521,8 +760,8 @@ class Pageimage extends Pagefile {
// if an error occurred, that error property will be populated with details
if($this->error) {
// error condition: unlink copied file
if(is_file($filenameFinal)) @unlink($filenameFinal);
if($filenameUnvalidated && is_file($filenameUnvalidated)) @unlink($filenameUnvalidated);
if(is_file($filenameFinal)) $this->wire('files')->unlink($filenameFinal, true);
if($filenameUnvalidated && is_file($filenameUnvalidated)) $this->wire('files')->unlink($filenameUnvalidated);
// write an invalid image so it's clear something failed
// todo: maybe return a 1-pixel blank image instead?
@@ -781,18 +1020,43 @@ class Pageimage extends Pagefile {
*
* @param int $width Max allowed width
* @param int $height Max allowed height
* @param array $options See `Pageimage::size()` method for options
* @param array $options See `Pageimage::size()` method for options, or these additional options:
* - `allowOriginal` (bool): Allow original image to be returned if already within max requested dimensions? (default=false)
* @return Pageimage
*
*/
public function maxSize($width, $height, $options = array()) {
$w = $this->width();
$h = $this->height();
if($w >= $h) {
return $this->maxWidth($width, $options);
} else {
return $this->maxHeight($height, $options);
$defaults = array(
'allowOriginal' => false,
'upscaling' => false,
'cropping' => false
);
$options = array_merge($defaults, $options);
$adjustedWidth = $width < 1 || $this->width() <= $width ? 0 : $width;
$adjustedHeight = $height < 1 || $this->height() <= $height ? 0 : $height;
// if already within maxSize dimensions then do nothing
if(!$adjustedWidth && !$adjustedHeight) {
if($options['allowOriginal']) return $this; // image already within target
$adjustedWidth = $width;
$options['nameHeight'] = $height;
} else if(!$adjustedWidth) {
$options['nameWidth'] = $width;
} else if(!$adjustedHeight) {
$options['nameHeight'] = $height;
}
if($this->wire('config')->installed > 1513336849) {
// New installations from 2017-12-15 forward use an "ms" suffix for images from maxSize() method
$suffix = isset($options['suffix']) ? $options['suffix'] : array();
if(!is_array($suffix)) $suffix = array();
$suffix[] = 'ms';
$options['suffix'] = $suffix;
}
return $this->size($adjustedWidth, $adjustedHeight, $options);
}
/**
@@ -804,7 +1068,10 @@ class Pageimage extends Pagefile {
* #pw-group-variations
*
* @param array $options Optional, one or more options in an associative array of the following:
* - `info` (bool): when true, method returns variation info arrays rather than Pageimage objects
* - `info` (bool): when true, method returns variation info arrays rather than Pageimage objects (default=false).
* - `verbose` (bool): Return verbose array of info. If false, returns only filenames (default=true).
* This option does nothing unless the `info` option is true. Also note that if verbose is false, then all options
* following this one no longer apply (since it is no longer returning width/height info).
* - `width` (int): only variations with given width will be returned
* - `height` (int): only variations with given height will be returned
* - `width>=` (int): only variations with width greater than or equal to given will be returned
@@ -812,24 +1079,56 @@ class Pageimage extends Pagefile {
* - `width<=` (int): only variations with width less than or equal to given will be returned
* - `height<=` (int): only variations with height less than or equal to given will be returned
* - `suffix` (string): only variations having the given suffix will be returned
* - `suffixes` (array): only variations having one of the given suffixes will be returned
* - `noSuffix` (string): exclude variations having this suffix
* - `noSuffixes` (array): exclude variations having any of these suffixes
* - `name` (string): only variations containing this text in filename will be returned (case insensitive)
* - `noName` (string): only variations NOT containing this text in filename will be returned (case insensitive)
* - `regexName` (string): only variations that match this PCRE regex will be returned
* @return Pageimages|array Returns Pageimages array of Pageimage instances.
* Only returns regular array if provided `$options['info']` is true.
*
*/
public function getVariations(array $options = array()) {
if(!is_null($this->variations)) return $this->variations;
$variations = $this->wire(new Pageimages($this->pagefiles->page));
if(!is_null($this->variations) && empty($options)) return $this->variations;
$defaults = array(
'info' => false,
'verbose' => true,
);
$options = array_merge($defaults, $options);
if(!$options['verbose'] && !$options['info']) $options['verbose'] = true; // non-verbose only allowed if info==true
$variations = $options['info'] ? null : $this->wire(new Pageimages($this->pagefiles->page));
$dir = new \DirectoryIterator($this->pagefiles->path);
$infos = array();
// if suffix or noSuffix option contains space, convert it to suffixes or noSuffixes array option
foreach(array('suffix', 'noSuffix') as $key) {
if(!isset($options[$key])) continue;
if(strpos(trim($options['suffix']), ' ') === false) continue;
$keyPlural = $key . 'es';
$value = isset($options[$keyPlural]) ? $options[$keyPlural] : array();
$options[$keyPlural] = array_merge($value, explode(' ', trim($options[$key])));
unset($options[$key]);
}
foreach($dir as $file) {
if($file->isDir() || $file->isDot()) continue;
$info = $this->isVariation($file->getFilename());
$info = $this->isVariation($file->getFilename(), array('verbose' => $options['verbose']));
if(!$info) continue;
if($options['info'] && !$options['verbose']) {
$infos[] = $info;
continue;
}
$allow = true;
if(count($options)) foreach($options as $option => $value) {
foreach($options as $option => $value) {
switch($option) {
case 'width': $allow = $info['width'] == $value; break;
case 'width>=': $allow = $info['width'] >= $value; break;
@@ -837,10 +1136,34 @@ class Pageimage extends Pagefile {
case 'height': $allow = $info['height'] == $value; break;
case 'height>=': $allow = $info['height'] >= $value; break;
case 'height<=': $allow = $info['height'] <= $value; break;
case 'name': $allow = stripos($file->getBasename(), $value) !== false; break;
case 'noName': $allow = stripos($file->getBasename(), $value) === false; break;
case 'regexName': $allow = preg_match($value, $file->getBasename()); break;
case 'suffix': $allow = in_array($value, $info['suffix']); break;
case 'noSuffix': $allow = !in_array($value, $info['suffix']); break;
case 'suffixes':
// any one of given suffixes will allow the variation
$allow = false;
foreach($value as $suffix) {
$allow = in_array($suffix, $info['suffix']);
if($allow) break;
}
break;
case 'noSuffixes':
// any one of the given suffixes will disallow the variation
$allow = true;
foreach($value as $noSuffix) {
if(!in_array($noSuffix, $info['suffix'])) continue;
$allow = false;
break;
}
break;
}
if(!$allow) break;
}
if(!$allow) continue;
if(!empty($options['info'])) {
$infos[$file->getBasename()] = $info;
} else {
@@ -853,12 +1176,11 @@ class Pageimage extends Pagefile {
}
}
if(!empty($options['info'])) {
return $infos;
} else {
$this->variations = $variations;
return $variations;
}
if(!empty($options['info'])) return $infos;
if(empty($options)) $this->variations = $variations;
return $variations;
}
/**
@@ -872,6 +1194,7 @@ class Pageimage extends Pagefile {
* - `1` (int): Rebuild all non-suffix variations, and those w/suffix specifed in $suffix argument. ($suffix is INCLUSION list)
* - `2` (int): Rebuild all variations, except those with suffix specified in $suffix argument. ($suffix is EXCLUSION list)
* - `3` (int): Rebuild only variations specified in the $suffix argument. ($suffix is ONLY-INCLUSION list)
* - `4` (int): Rebuild only non-proportional, non-crop variations (variations that specify both width and height)
*
* Mode 0 is the only truly safe mode, as in any other mode there are possibilities that the resulting
* rebuild of the variation may not be exactly what was intended. The issues with other modes primarily
@@ -899,7 +1222,7 @@ class Pageimage extends Pagefile {
$options['forceNew'] = true;
foreach($this->getVariations(array('info' => true)) as $info) {
$o = $options;
unset($o['cropping']);
$skip = false;
@@ -943,6 +1266,11 @@ class Pageimage extends Pagefile {
}
}
if($mode == 4 && ($info['width'] == 0 || $info['height'] == 0)) {
// skip images that don't specify both width and height
$skip = true;
}
if($skip) {
$skipped[] = $name;
continue;
@@ -951,14 +1279,36 @@ class Pageimage extends Pagefile {
// rebuild the variation
$o['forceNew'] = true;
$o['suffix'] = $info['suffix'];
if(is_file($info['path'])) unlink($info['path']);
if(is_file($info['path'])) $this->wire('files')->unlink($info['path'], true);
/*
if(!$info['width'] && $info['actualWidth']) {
$info['width'] = $info['actualWidth'];
$options['nameWidth'] = 0;
}
if(!$info['height'] && $info['actualHeight']) {
$info['height'] = $info['actualHeight'];
$options['nameHeight'] = 0;
}
*/
if($info['crop'] && preg_match('/^x(\d+)y(\d+)$/', $info['crop'], $matches)) {
// dimensional cropping info contained in filename
$cropX = (int) $matches[1];
$cropY = (int) $matches[2];
$variation = $this->crop($cropX, $cropY, $info['width'], $info['height'], $options);
$variation = $this->crop($cropX, $cropY, $info['width'], $info['height'], $options);
} else if($info['crop']) {
// direct cropping info contained in filename
$options['cropping'] = $info['crop'];
$variation = $this->size($info['width'], $info['height'], $options);
} else if($this->hasFocus) {
// crop to focus area, which the size() method will determine on its own
$variation = $this->size($info['width'], $info['height'], $options);
} else {
if($info['crop']) $options['cropping'] = $info['crop'];
// no crop, no focus, just resize
$variation = $this->size($info['width'], $info['height'], $options);
}
@@ -986,8 +1336,10 @@ class Pageimage extends Pagefile {
* - `original` (string): Original basename
* - `url` (string): URL to image
* - `path` (string): Full path + filename to image
* - `width` (int): Specified width
* - `height` (int): Specified height
* - `width` (int): Specified width in filename
* - `height` (int): Specified height in filename
* - `actualWidth` (int): Actual width when checked manually
* - `actualHeight` (int): Acual height when checked manually
* - `crop` (string): Cropping info string or blank if none
* - `suffix` (array): Array of suffixes
*
@@ -1000,15 +1352,26 @@ class Pageimage extends Pagefile {
* #pw-group-variations
*
* @param string $basename Filename to check (basename, which excludes path)
* @param bool $allowSelf When true, it will return variation info even if same as current Pageimage.
* @return bool|array Returns false if not a variation, or array of info if it is.
* @param array|bool $options Array of options to modify behavior, or boolean to only specify `allowSelf` option.
* - `allowSelf` (bool): When true, it will return variation info even if same as current Pageimage. (default=false)
* - `verbose` (bool): Return verbose array of info? If false, just returns basename (string) or false. (default=true)
* @return bool|string|array Returns false if not a variation, or array (verbose) or string (non-verbose) of info if it is.
*
*/
public function ___isVariation($basename, $allowSelf = false) {
public function ___isVariation($basename, $options = array()) {
$defaults = array(
'allowSelf' => false,
'verbose' => true,
);
if(!is_array($options)) $options = array('allowSelf' => (bool) $options);
$options = array_merge($defaults, $options);
static $level = 0;
$variationName = basename($basename);
$originalName = $this->basename;
$info = array();
// that that everything from the beginning up to the first period is exactly the same
// otherwise, they are different source files
@@ -1026,7 +1389,7 @@ class Pageimage extends Pagefile {
}
// if file is the same as the original, then it's not a variation
if(!$allowSelf && $variationName == $this->basename) return false;
if(!$options['allowSelf'] && $variationName == $this->basename) return false;
// if file doesn't start with the original name then it's not a variation
if(strpos($variationName, $originalName) !== 0) return false;
@@ -1049,16 +1412,14 @@ class Pageimage extends Pagefile {
// identify parent and any parent suffixes
$suffixAll = array();
while(($pos = strrpos($base, '.')) !== false) {
$part = substr($base, $pos+1);
// if(is_null($parent)) {
// $parent = substr($base, 0, $pos) . $ext;
//$parent = $originalName . "." . $part . $ext;
// }
$base = substr($base, 0, $pos);
while(($rpos = strrpos($part, '-')) !== false) {
$suffixAll[] = substr($part, $rpos+1);
$part = substr($part, 0, $rpos);
if($options['verbose']) {
while(($pos = strrpos($base, '.')) !== false) {
$part = substr($base, $pos + 1);
$base = substr($base, 0, $pos);
while(($rpos = strrpos($part, '-')) !== false) {
$suffixAll[] = substr($part, $rpos + 1);
$part = substr($part, 0, $rpos);
}
}
}
@@ -1083,7 +1444,7 @@ class Pageimage extends Pagefile {
// if regex does not match, return false
if(preg_match($re1, $meat, $matches)) {
// this is a variation with dimensions, return array of info
$info = array(
if($options['verbose']) $info = array(
'name' => $basename,
'url' => $this->pagefiles->url . $basename,
'path' => $this->pagefiles->path . $basename,
@@ -1097,7 +1458,7 @@ class Pageimage extends Pagefile {
} else if(preg_match($re2, $meat, $matches)) {
// this is a variation only with suffix
$info = array(
if($options['verbose']) $info = array(
'name' => $basename,
'url' => $this->pagefiles->url . $basename,
'path' => $this->pagefiles->path . $basename,
@@ -1111,9 +1472,15 @@ class Pageimage extends Pagefile {
} else {
return false;
}
$info['hidpiWidth'] = $this->hidpiWidth(0, $info['width']);
$info['hidpiHeight'] = $this->hidpiWidth(0, $info['height']);
// if not in verbose mode, just return variation basename
if(!$options['verbose']) return $variationName;
$actualInfo = $this->getImageInfo($info['path']);
$info['actualWidth'] = $actualInfo['width'];
$info['actualHeight'] = $actualInfo['height'];
$info['hidpiWidth'] = $this->hidpiWidth(0, $info['actualWidth']);
$info['hidpiHeight'] = $this->hidpiWidth(0, $info['actualHeight']);
if(empty($info['crop'])) {
// attempt to extract crop info from suffix
@@ -1146,19 +1513,41 @@ class Pageimage extends Pagefile {
*
* #pw-group-variations
*
* @return $this
* @param array $options See options for getVariations() method to limit what variations are removed, plus these:
* - `dryRun` (bool): Do not remove now and instead only return the filenames of variations that would be deleted (default=false).
* - `getFiles` (bool): Return deleted filenames? Also assumed if the test option is used (default=false).
* @return $this|array Returns $this by default, or array of deleted filenames if the `returnFiles` option is specified
*
*/
public function removeVariations() {
public function removeVariations(array $options = array()) {
$defaults = array(
'dryRun' => false,
'getFiles' => false
);
$variations = $this->getVariations();
$variations = $this->getVariations($options);
if(!empty($options['dryrun'])) $defaults['dryRun'] = $options['dryrun']; // case insurance
$options = array_merge($defaults, $options); // placement after getVariations() intended
$deletedFiles = array();
/** @var WireFileTools $files */
$files = $this->wire('files');
foreach($variations as $variation) {
if(is_file($variation->filename)) unlink($variation->filename);
$filename = $variation->filename;
if(!is_file($filename)) continue;
if($options['dryRun']) {
$success = true;
} else {
$success = $files->unlink($filename, true);
}
if($success) $deletedFiles[] = $filename;
}
$this->variations = null;
return $this;
if(!$options['dryRun']) $this->variations = null;
return ($options['dryRun'] || $options['getFiles'] ? $deletedFiles : $this);
}
/**
@@ -1244,5 +1633,35 @@ class Pageimage extends Pagefile {
}
}
/**
* Debug info
*
* @return array
*
*/
public function __debugInfo() {
static $depth = 0;
$depth++;
$info = parent::__debugInfo();
$info['width'] = $this->width();
$info['height'] = $this->height();
$info['suffix'] = $this->suffixStr;
if($this->hasFocus) $info['focus'] = $this->focusStr;
if(isset($info['filedata']) && isset($info['filedata']['focus'])) unset($info['filedata']['focus']);
if(empty($info['filedata'])) unset($info['filedata']);
$original = $this->original;
if($original && $original !== $this) $info['original'] = $original->basename;
if($depth < 2) {
$info['variations'] = array();
$variations = $this->getVariations(array('info' => true, 'verbose' => false));
foreach($variations as $name) {
$info['variations'][] = $name;
}
if(empty($info['variations'])) unset($info['variations']);
}
$depth--;
return $info;
}
}

View File

@@ -24,17 +24,20 @@
*
* HOOKABLE METHODS
* ================
* @method PageArray find() find($selectorString, array $options = array()) Find and return all pages matching the given selector string. Returns a PageArray. #pw-group-retrieval
* @method bool save() save(Page $page) Save any changes made to the given $page. Same as : $page->save() Returns true on success. #pw-group-manipulation
* @method bool saveField() saveField(Page $page, $field) Save just the named field from $page. Same as: $page->save('field') #pw-group-manipulation
* @method bool trash() trash(Page $page, $save = true) Move a page to the trash. If you have already set the parent to somewhere in the trash, then this method won't attempt to set it again. #pw-group-manipulation
* @method PageArray find($selectorString, array $options = array()) Find and return all pages matching the given selector string. Returns a PageArray. #pw-group-retrieval
* @method bool save(Page $page, $options = array()) Save any changes made to the given $page. Same as : $page->save() Returns true on success. #pw-group-manipulation
* @method bool saveField(Page $page, $field, array $options = array()) Save just the named field from $page. Same as: $page->save('field') #pw-group-manipulation
* @method bool trash(Page $page, $save = true) Move a page to the trash. If you have already set the parent to somewhere in the trash, then this method won't attempt to set it again. #pw-group-manipulation
* @method bool restore(Page $page, $save = true) Restore a trashed page to its original location. #pw-group-manipulation
* @method int emptyTrash() Empty the trash and return number of pages deleted. #pw-group-manipulation
* @method bool delete() delete(Page $page, $recursive = false) Permanently delete a page and it's fields. Unlike trash(), pages deleted here are not restorable. If you attempt to delete a page with children, and don't specifically set the $recursive param to True, then this method will throw an exception. If a recursive delete fails for any reason, an exception will be thrown. #pw-group-manipulation
* @method int|array emptyTrash(array $options = array()) Empty the trash and return number of pages deleted. #pw-group-manipulation
* @method bool delete(Page $page, $recursive = false, array $options = array()) Permanently delete a page and it's fields. Unlike trash(), pages deleted here are not restorable. If you attempt to delete a page with children, and don't specifically set the $recursive param to True, then this method will throw an exception. If a recursive delete fails for any reason, an exception will be thrown. #pw-group-manipulation
* @method Page|NullPage clone(Page $page, Page $parent = null, $recursive = true, $options = array()) Clone an entire page, it's assets and children and return it. #pw-group-manipulation
* @method Page|NullPage add($template, $parent, $name = '', array $values = array()) #pw-group-manipulation
* @method int sort(Page $page, $value = false) Set the “sort” value for given $page while adjusting siblings, or re-build sort for its children. #pw-group-manipulation
* @method setupNew(Page $page) Setup new page that does not yet exist by populating some fields to it. #pw-internal
* @method string setupPageName(Page $page, array $options = []) Determine and populate a name for the given page. #pw-internal
* @method string setupPageName(Page $page, array $options = array()) Determine and populate a name for the given page. #pw-internal
* @method void insertBefore(Page $page, Page $beforePage) Insert one page as a sibling before another. #pw-advanced
* @method void insertAfter(Page $page, Page $afterPage) Insert one page as a sibling after another. #pw-advanced
*
* METHODS PURELY FOR HOOKS
* ========================
@@ -53,6 +56,7 @@
* @method cloneReady(Page $page, Page $copy) Hook called just before a page is cloned.
* @method cloned(Page $page, Page $copy) Hook called after a page has been successfully cloned.
* @method renamed(Page $page) Hook called after a page has been successfully renamed.
* @method sorted(Page $page, $children = false, $total = 0) Hook called after $page has been sorted.
* @method statusChangeReady(Page $page) Hook called when a page's status has changed and is about to be saved.
* @method statusChanged(Page $page) Hook called after a page status has been changed and saved.
* @method publishReady(Page $page) Hook called just before an unpublished page is published.
@@ -118,6 +122,12 @@ class Pages extends Wire {
*/
protected $editor;
/**
* @var PagesNames
*
*/
protected $names;
/**
* @var PagesLoaderCache
*
@@ -138,12 +148,12 @@ class Pages extends Wire {
*/
public function __construct(ProcessWire $wire) {
$this->setWire($wire);
$this->debug = $wire->config->debug === Config::debugVerbose ? true : false;
$this->sortfields = $this->wire(new PagesSortfields());
$this->loader = $this->wire(new PagesLoader($this));
$this->cacher = $this->wire(new PagesLoaderCache($this));
$this->trasher = null;
$this->editor = null;
$this->debug = $wire->config->debug;
}
/**
@@ -199,21 +209,24 @@ class Pages extends Wire {
*
* @param string|int|array|Selectors $selector Specify selector (standard usage), but can also accept page ID or array of page IDs.
* @param array|string $options One or more options that can modify certain behaviors. May be associative array or "key=value" selector string.
* - `findOne` (boolean): Apply optimizations for finding a single page.
* - `findAll` (boolean): Find all pages with no exculsions (same as include=all option).
* - `getTotal` (boolean): Whether to set returning PageArray's "total" property (default: true except when findOne=true).
* - `loadPages` (boolean): Whether to populate the returned PageArray with found pages (default: true).
* - `findOne` (boolean): Apply optimizations for finding a single page (default=false).
* - `findAll` (boolean): Find all pages with no exclusions, same as "include=all" option (default=false).
* - `findIDs` (boolean|int): Specify 1 to return array of only page IDs, or true to return verbose array (default=false).
* - `getTotal` (boolean): Whether to set returning PageArray's "total" property (default=true, except when findOne=true).
* - `loadPages` (boolean): Whether to populate the returned PageArray with found pages (default=true).
* The only reason why you'd want to change this to false would be if you only needed the count details from
* the PageArray: getTotal(), getStart(), getLimit, etc. This is intended as an optimization for $pages->count().
* Does not apply if $selector argument is an array.
* - `caller` (string): Optional name of calling function, for debugging purposes, i.e. pages.count
* - `include` (string): Optional inclusion mode of 'hidden', 'unpublished' or 'all'. Default=none. Typically you would specify this
* - `cache` (boolean): Allow caching of selectors and loaded pages? (default=true). Also sets loadOptions[cache].
* - `allowCustom` (boolean): Allow use of _custom="another selector" in given $selector? For specific uses. (default=false)
* - `caller` (string): Optional name of calling function, for debugging purposes, i.e. "pages.count" (default=blank).
* - `include` (string): Optional inclusion mode of 'hidden', 'unpublished' or 'all'. (default=none). Typically you would specify this
* directly in the selector string, so the option is mainly useful if your first argument is not a string.
* - `stopBeforeID` (int): Stop loading pages once page matching this ID is found (default=0).
* - `startAfterID` (int): Start loading pages once page matching this ID is found (default=0).
* - `lazy` (bool): Specify true to force lazy loading. This is the same as using the Pages::findMany() method (default=false).
* - `loadOptions` (array): Optional assoc array of options to pass to getById() load options.
* @return PageArray Pages that matched the given selector.
* - `loadOptions` (array): Optional associative array of options to pass to getById() load options.
* @return PageArray|array PageArray of that matched the given selector, or array of page IDs (if using findIDs option).
*
* Non-visible pages are excluded unless an "include=x" mode is specified in the selector
* (where "x" is "hidden", "unpublished" or "all"). If "all" is specified, then non-accessible
@@ -252,7 +265,7 @@ class Pages extends Wire {
}
/**
* Like find(), but with “lazy loading” to support giant result sets without running of memory.
* Like find(), but with “lazy loading” to support giant result sets without running out of memory.
*
* When using this method, you can retrieve tens of thousands, or hundreds of thousands of pages
* or more, without needing a pagination "limit" in your selector. Individual pages are loaded
@@ -289,11 +302,56 @@ class Pages extends Wire {
$debug = $this->debug;
if($debug) $this->debug(false);
$options['lazy'] = true;
$matches = $this->loader->find($selector, $options);
$options['caller'] = 'pages.findMany';
if(!isset($options['cache'])) $options['cache'] = false;
$matches = $this->find($selector, $options);
if($debug) $this->debug($debug);
return $matches;
}
/**
* Like $pages->find() except returns array of IDs rather than Page objects.
*
* - This is a faster method to use when you only need to know the matching page IDs.
* - The default behavior is to simply return a regular PHP array of matching page IDs in order.
* - The alternate behavior (verbose) returns more information for each match, as outlined below.
*
* **Verbose option:**
* When specifying boolean true for the `$options` argument (or using the `verbose` option),
* the return value is an array of associative arrays, with each of those associative arrays
* containing `id`, `parent_id` and `templates_id` keys for each page.
*
* ~~~~~
* // returns array of page IDs (integers) like [ 1234, 1235, 1236 ]
* $a = $pages->findIDs("foo=bar");
*
* // verbose option: returns array of associative arrays, each with id, parent_id and templates_id
* $a = $pages->findIDs("foo=bar", true);
* ~~~~~
*
* #pw-group-retrieval
*
* @param string|array|Selectors $selector Selector to find page IDs.
* @param array|bool $options Options to modify behavior.
* - `verbose` (bool): Specify true to make return value array of associative arrays, each with verbose info.
* - The verbose option above can also be specified by providing boolean true as the $options argument.
* - See `Pages::find()` $options argument for additional options.
* @return array Array of page IDs, or in verbose mode: array of arrays, each with id, parent_id and templates_id keys.
* @since 3.0.46
*
*/
public function findIDs($selector, $options = array()) {
$verbose = false;
if($options === true) $verbose = true;
if(!is_array($options)) $options = array();
if(isset($options['verbose'])) {
$verbose = $options['verbose'];
unset($options['verbose']);
}
$options['findIDs'] = $verbose ? true : 1;
return $this->find($selector, $options);
}
/**
* Returns the first page matching the given selector with no exclusions
*
@@ -313,12 +371,13 @@ class Pages extends Wire {
* #pw-group-retrieval
*
* @param string|array|Selectors|int $selector Selector string, array or Selectors object. May also be page path or ID.
* @param array $options See `Pages::find()` for extra options that may be specified.
* @return Page|NullPage Always returns a Page object, but will return NullPage (with id=0) when no match found.
* @see Pages::findOne(), Pages::find()
*
*/
public function get($selector) {
return $this->loader->get($selector);
public function get($selector, $options = array()) {
return $this->loader->get($selector, $options);
}
/**
@@ -341,13 +400,14 @@ class Pages extends Wire {
*
* @param Page $page Page object to save
* @param array $options Optional array to modify default behavior, with one or more of the following:
* - `uncacheAll` (boolean): Whether the memory cache should be cleared (default=true)
* - `resetTrackChanges` (boolean): Whether the page's change tracking should be reset (default=true)
* - `quiet` (boolean): When true, modified date and modified_users_id won't be updated (default=false)
* - `adjustName` (boolean): Adjust page name to ensure it is unique within its parent (default=false)
* - `forceID` (integer): Use this ID instead of an auto-assigned one (new page) or current ID (existing page)
* - `ignoreFamily` (boolean): Bypass check of allowed family/parent settings when saving (default=false)
* - `uncacheAll` (boolean): Whether the memory cache should be cleared (default=true).
* - `resetTrackChanges` (boolean): Whether the page's change tracking should be reset (default=true).
* - `quiet` (boolean): When true, modified date and modified_users_id won't be updated (default=false).
* - `adjustName` (boolean): Adjust page name to ensure it is unique within its parent (default=false).
* - `forceID` (integer): Use this ID instead of an auto-assigned one (new page) or current ID (existing page).
* - `ignoreFamily` (boolean): Bypass check of allowed family/parent settings when saving (default=false).
* - `noHooks` (boolean): Prevent before/after save hooks (default=false), please also use $pages->___save() for call.
* - `noFields` (boolean): Bypass saving of custom fields, leaving only native properties to be saved (default=false).
* @return bool True on success, false on failure
* @throws WireException
* @see Page::save(), Pages::saveField()
@@ -559,13 +619,14 @@ class Pages extends Wire {
*
* #pw-group-manipulation
*
* @return int Returns total number of pages deleted from trash.
* @param array $options See PagesTrash::emptyTrash() for advanced options
* @return int|array Returns total number of pages deleted from trash, or array if verbose option specified.
* This number is negative or 0 if not all pages could be deleted and error notices may be present.
* @see Pages::trash(), Pages::restore()
*
*/
public function ___emptyTrash() {
return $this->trasher()->emptyTrash();
public function ___emptyTrash(array $options = array()) {
return $this->trasher()->emptyTrash($options);
}
/**
@@ -591,6 +652,7 @@ class Pages extends Wire {
* - `findTemplates` (boolean): Determine which templates will be used (when no template specified) for more specific autojoins. (default=true)
* - `pageClass` (string): Class to instantiate Page objects with. Leave blank to determine from template. (default=auto-detect)
* - `pageArrayClass` (string): PageArray-derived class to store pages in (when 'getOne' is false). (default=PageArray)
* - `pageArray` (PageArray|null): Populate this existing PageArray rather than creating a new one. (default=null)
* - `page` (Page|null): Existing Page object to populate (also requires the getOne option to be true). (default=null)
*
* **Use the `$options` array for potential speed optimizations:**
@@ -785,6 +847,79 @@ class Pages extends Wire {
return $this->editor()->touch($pages, $modified);
}
/**
* Set the “sort” value for given $page while adjusting siblings, or re-build sort for its children
*
* *This method is primarily applicable to manually sorted pages. If pages are automatically
* sorted by some other field, this method isnt useful unless using the “re-build children” option,
* which may be helpful if converting a pages children from auto-sort to manual sort.*
*
* The default behavior of this method is to set the “sort” value for the given $page, and adjust the
* sort value of sibling pages having the same or greater sort value, to ensure all are unique and in
* order without gaps.
*
* The alternate behavior of this method is to re-build the sort values of all children of the given $page.
* This is done by specifying boolean true for the $value argument. When used, duplicate sort values and
* gaps are removed from all children.
*
* **Do you need this method?**
* If you are wondering whether you need to use this method for something, chances are that you do not.
* This method is mostly applicable for internal core use, as ProcessWire manages Page sort values on its own
* internally for the most part.
*
* ~~~~~
* // set $page to have sort=5, moving any 5+ sort pages ahead
* $pages->sort($page, 5);
*
* // re-build sort values for children of $page, removing duplicates and gaps
* $pages->sort($page, true);
* ~~~~~
*
* #pw-advanced
*
* @param Page $page Page to sort (or parent of pages to sort, if using $value=true option)
* @param int|bool $value Specify one of the following:
* - Omit to set and use sort value from given $page.
* - Specify sort value (integer) to save that value.
* - Specify boolean true to instead rebuild sort for all of $page children.
* @return int Number of pages that had sort values adjusted
* @throws WireException
*
*/
public function ___sort(Page $page, $value = false) {
if($value === false) $value = $page->sort;
if($value === true) return $this->editor()->sortRebuild($page);
return $this->editor()->sortPage($page, $value);
}
/**
* Sort/move one page above another (for manually sorted pages)
*
* #pw-advanced
*
* @param Page $page Page you want to move/sort
* @param Page $beforePage Page you want to insert before
* @throws WireException
*
*/
public function ___insertBefore(Page $page, Page $beforePage) {
$this->editor()->insertBefore($page, $beforePage);
}
/**
* Sort/move one page after another (for manually sorted pages)
*
* #pw-advanced
*
* @param Page $page Page you want to move/sort
* @param Page $afterPage Page you want to insert after
* @throws WireException
*
*/
public function ___insertAfter(Page $page, Page $afterPage) {
$this->editor()->insertBefore($page, $afterPage, true);
}
/**
* Is the given page in a state where it can be saved from the API?
*
@@ -843,10 +978,11 @@ class Pages extends Wire {
* #pw-internal
*
* @param Page $page
* @return void
*
*/
public function cache(Page $page) {
return $this->cacher->cache($page);
$this->cacher->cache($page);
}
/**
@@ -1068,11 +1204,17 @@ class Pages extends Wire {
*
* #pw-internal
*
* @param array $options Optionally specify array('pageArrayClass' => 'YourPageArrayClass')
* @param array $options Optionally specify ONE of the following:
* - `pageArrayClass` (string): Name of PageArray class to use (if not “PageArray”).
* - `pageArray` (PageArray): Wire and return this given PageArray, rather than instantiating a new one.
* @return PageArray
*
*/
public function newPageArray(array $options = array()) {
if(!empty($options['pageArray']) && $options['pageArray'] instanceof PageArray) {
$this->wire($options['pageArray']);
return $options['pageArray'];
}
$class = 'PageArray';
if(!empty($options['pageArrayClass'])) $class = $options['pageArrayClass'];
$class = wireClassName($class, true);
@@ -1089,6 +1231,7 @@ class Pages extends Wire {
* @param array $options Optionally specify array of any of the following:
* - `pageClass` (string): Class to use for Page object (default='Page').
* - `template` (Template|id|string): Template to use.
* - Plus any other Page properties or fields you want to set at this time
* @return Page
*
*/
@@ -1107,9 +1250,16 @@ class Pages extends Wire {
} else {
$template = null;
}
$class = wireClassName($class, true);
$page = $this->wire(new $class($template));
if(!$page instanceof Page) $page = $this->wire(new Page($template));
unset($options['pageClass'], $options['template']);
foreach($options as $name => $value) {
$page->set($name, $value);
}
return $page;
}
@@ -1141,7 +1291,7 @@ class Pages extends Wire {
*
*/
public function executeQuery(\PDOStatement $query, $throw = true, $maxTries = 3) {
$this->wire('database')->execute($query, $throw, $maxTries);
return $this->wire('database')->execute($query, $throw, $maxTries);
}
/**
@@ -1152,7 +1302,7 @@ class Pages extends Wire {
* When given an array, it calls $pages->getById($key);
*
* @param string|int|array $key
* @return Page|PageArray
* @return Page|Pages|PageArray
*
*/
public function __invoke($key) {
@@ -1200,6 +1350,17 @@ class Pages extends Wire {
if(!$this->editor) $this->editor = $this->wire(new PagesEditor($this));
return $this->editor;
}
/**
* @return PagesNames
*
* #pw-internal
*
*/
public function names() {
if(!$this->names) $this->names = $this->wire(new PagesNames($this));
return $this->names;
}
/**
* @return PagesLoaderCache
@@ -1262,7 +1423,9 @@ class Pages extends Wire {
$str = "Saved page";
if(count($changes)) $str .= " (Changes: " . implode(', ', $changes) . ")";
$this->log($str, $page);
$this->wire('cache')->maintenance($page);
/** @var WireCache $cache */
$cache = $this->wire('cache');
$cache->maintenance($page);
if($page->className() != 'Page') {
$manager = $page->getPagesManager();
if($manager instanceof PagesType) $manager->saved($page, $changes, $values);
@@ -1395,7 +1558,9 @@ class Pages extends Wire {
*/
public function ___deleted(Page $page) {
$this->log("Deleted page", $page);
$this->wire('cache')->maintenance($page);
/** @var WireCache $cache */
$cache = $this->wire('cache');
$cache->maintenance($page);
if($page->className() != 'Page') {
$manager = $page->getPagesManager();
if($manager instanceof PagesType) $manager->deleted($page);
@@ -1427,7 +1592,7 @@ class Pages extends Wire {
}
/**
* Hook called when a page has been renamed (i.e. had it's name field change)
* Hook called when a page has been renamed (i.e. had its name field change)
*
* The previous name can be accessed at `$page->namePrevious`.
* The new name can be accessed at `$page->name`.
@@ -1451,6 +1616,20 @@ class Pages extends Wire {
}
}
/**
* Hook called after a page has been sorted, or had its children re-sorted
*
* #pw-hooker
*
* @param Page $page Page given to have sort adjusted
* @param bool $children If true, children of $page have been all been re-sorted
* @param int $total Total number of pages that had sort adjusted as a result
*
*/
public function ___sorted(Page $page, $children = false, $total = 0) {
if($page && $children && $total) {}
}
/**
* Hook called when a page status has been changed and saved
*

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