Leaderboard
Popular Content
Showing content with the highest reputation on 02/11/2019 in all areas
-
I've created a small module which lets you define a timestamp after which a page should be accessible. In addition you can define a timestamp when the release should end and the page should not be accessable any more. ProcessWire-Module: http://modules.processwire.com/modules/page-access-releasetime/ Github: https://github.com/Sebiworld/PageAccessReleasetime Usage PageAccessReleasetime can be installed like every other module in ProcessWire. Check the following guide for detailed information: How-To Install or Uninstall Modules After that, you will find checkboxes for activating the releasetime-fields at the settings-tab of each page. You don't need to add the fields to your templates manually. Check e.g. the checkbox "Activate Releasetime from?" and fill in a date in the future. The page will not be accessable for your users until the given date is reached. If you have $config->pagefileSecure = true, the module will protect files of unreleased pages as well. How it works This module hooks into Page::viewable to prevent users to access unreleased pages: public function hookPageViewable($event) { $page = $event->object; $viewable = $event->return; if($viewable){ // If the page would be viewable, additionally check Releasetime and User-Permission $viewable = $this->canUserSee($page); } $event->return = $viewable; } To prevent access to the files of unreleased pages, we hook into Page::isPublic and ProcessPageView::sendFile. public function hookPageIsPublic($e) { $page = $e->object; if($e->return && $this->isReleaseTimeSet($page)) { $e->return = false; } } The site/assets/files/ directory of pages, which isPublic() returns false, will get a '-' as prefix. This indicates ProcessWire (with activated $config->pagefileSecure) to check the file's permissions via PHP before delivering it to the client. The check wether a not-public file should be accessable happens in ProcessPageView::sendFile. We throw an 404 Exception if the current user must not see the file. public function hookProcessPageViewSendFile($e) { $page = $e->arguments[0]; if(!$this->canUserSee($page)) { throw new Wire404Exception('File not found'); } } Additionally we hook into ProcessPageEdit::buildForm to add the PageAccessReleasetime fields to each page and move them to the settings tab. Limitations In the current version, releasetime-protected pages will appear in wire('pages')->find() queries. If you want to display a list of pages, where pages could be releasetime-protected, you should double-check with $page->viewable() wether the page can be accessed. $page->viewable() returns false, if the page is not released yet. If you have an idea how unreleased pages can be filtered out of ProcessWire selector queries, feel free to write an issue, comment or make a pull request!5 points
-
Dont know if that's the best option, but you could use two selectors: $cities = $pages->findIDs("template=city, parent=yourcity"); $photos = $pages->find([ 'template' => 'photo', 'city' => $cities, ]); Another option would be a saveReady hook that populates the country on each photo save. And I'm quite sure there are even better solutions ?2 points
-
ProCache can do less compilation. This is what Ryan uses to compile UIKit on the new PW site (he seems to be using sass version, but less is said to be supported as well). Consider buying it to support ProcessWire development.2 points
-
2 points
-
2 points
-
ProcessWire InputfieldRepeaterMatrixDuplicate Thanks to the great ProModule "RepeaterMatrix" I have the possibility to create complex repeater items. With it I have created a quite powerful page builder. Many different content modules, with many more possible design options. The RepeaterMatrix module supports the cloning of items, but only within the same page. Now I often have the case that very design-intensive pages and items are created. If you want to use a content module on a different page (e.g. in the same design), you have to rebuild each item manually every time. This module extends the commercial ProModule "RepeaterMatrix" by the function to duplicate repeater items from one page to another page. The condition is that the target field is the same matrix field from which the item is duplicated. This module is currently understood as proof of concept. There are a few limitations that need to be considered. The intention of the module is that this functionality is integrated into the core of RepeaterMatrix and does not require an extra module. Check out the screencast What the module can do Duplicate multible repeater items from one page to another No matter how complex the item is Full support for file and image fields Multilingual support Support of Min and Max settings Live synchronization of clipboard between multiple browser tabs. Copy an item and simply switch the browser tab to the target page and you will immediately see the past button Support of multiple RepeaterMatrix fields on one page Configurable which roles and fields are excluded Configurable dialogs for copy and paste Duplicated items are automatically pasted to the end of the target field and set to hidden status so that changes are not directly published Automatic clipboard update when other items are picked Automatically removes old clipboard data if it is not pasted within 6 hours Delete clipboard itself by clicking the selected item again Benefit: unbelievably fast workflow and content replication What the module can't do Before an item can be duplicated in its current version, the source page must be saved. This means that if you make changes to an item and copy this, the old saved state will be duplicated Dynamic loading is currently not possible. Means no AJAX. When pasting, the target page is saved completely No support for nested repeater items. Currently only first level items can be duplicated. Means a repeater field in a repeater field cannot be duplicated. Workaround: simply duplicate the parent item Dynamic reloading and adding of repeater items cannot be registered. Several interfaces and events from the core are missing. The initialization occurs only once after the page load event Attention, please note! Nested repeaters cannot be supported technically. Therefore a check is made to prevent this. However, a nested repeater can only be detected if the field name ends for example with "_repeater1234". For example, if your MatrixRepeater field is named like this: "content_repeater" or "content_repeater123", this field is identified as nested and the module does not load. In version 2.0.1 the identification has been changed so that a field ending with the name repeater is only detected as nested if at least a two-digit number sequence follows. But to avoid this problem completely, make sure that your repeater matrix field does NOT end with the name "repeater". Changelog 2.0.1 Bug fix: Thanks to @ngrmm I could discover a bug which causes that the module cannot be loaded if the MatrixRepeater field ends with the name "repeater". The code was adjusted and information about the problem was provided 2.0.0 Feature: Copy multiple items at once! The fundament for copying multiple items was created by @Autofahrn - THX! Feature: Optionally you can disable the copy and/or paste dialog Bug fix: A fix suggestion when additional and normal repeater fields are present was contributed by @joshua - THX! 1.0.4 Bug fix: Various bug fixes and improvements in live synchronization Bug fix: Items are no longer inserted when the normal save button is clicked. Only when the past button is explicitly clicked Feature: Support of multiple repeater fields in one page Feature: Support of repeater Min/Max settings Feature: Configurable roles and fields Enhancement: Improved clipboard management Enhancement: Documentation improvement Enhancement: Corrected few typos #1 1.0.3 Feature: Live synchronization Enhancement: Load the module only in the backend Enhancement: Documentation improvement 1.0.2 Bug fix: Various bug fixes and improvements in JS functions Enhancement: Documentation improvement Enhancement: Corrected few typos 1.0.1 Bug fix: Various bug fixes and improvements in the duplication process 1.0.0 Initial release Support this module If this module is useful for you, I am very thankful for your small donation: Donate 5,- Euro (via PayPal – or an amount of your choice. Thank you!) Download this module (Version 2.0.1) > Github: https://github.com/FlipZoomMedia/InputfieldRepeaterMatrixDuplicate > PW module directory: https://modules.processwire.com/modules/inputfield-repeater-matrix-duplicate/ > Old stable version (1.0.4): https://github.com/FlipZoomMedia/InputfieldRepeaterMatrixDuplicate/releases/tag/1.0.41 point
-
dobro vece ? I think you'll have to define your input field as type "TextareaLanguage" or "TextLanguage". And in your module use something like $field->label = _('Title');1 point
-
Check your .htaccess file. It may be wrong or not existing. Check also if the /processwire path is valid. It's the name of the page of ID = 2 on the pages table on the website database.1 point
-
1 point
-
LostKobrakai is right. Laravel's dependency injection container automagically resolves type-hinted objects, see: https://laravel.com/docs/5.7/container#automatic-injection1 point
-
I'm mostly guessing. I've seen quite a bunch of tutorials using laravel, but never actually used it, so I try: I'm expecting that laravel uses reflection to know the type hint you put for the parameter and depending on the result do some boilerplate code you could've written on your own to retrieve the project and call the controller action with it.1 point
-
The only thing to add to LK's comments is that it's nice to have everything in the one place - I have my public repos on Github, so having the private ones there also is nice. That said, I like the OS nature of Gitlab and the unlimited collaborators, so it really depends on your needs and maybe these are project specific?1 point
-
Github is the bigger platform (potential for collaboration), it's interface is quite a bit faster and more productive than gitlabs and it has a better history in terms of availability. Also there are quite a lot of addons like linters, bots or CI options, which directly integrate with the github interface. All in all it's imho the more polished product in almost all areas. The only place I feel Gitlab is ahead is with it's integrated CI/CD tools.1 point
-
I've tried and double-checked, but no, for some reason, "has_parent" does not seem to work in this case, even if I manually replace $country with a valid parent id. "Parent" does work as expected, but it's of no use here. A bug, or a limitation with "has_parent" maybe?1 point
-
Thanks a lot @bernhard. You put me on the right track. I just changed "parent=yourcity" to "has_parent=$country". For posterity, here are the two selectors in my case: $cities = $pages->findIDs("template=city, has_parent=$country"); $photos = $pages->find([ 'template' => 'portfolio_image', 'city' => $cities, ]);1 point
-
If you don't change JS + CSS all the time, consider local tools like Grunt, Yarn, Gulp, Webpack etc. IDEs like PHPWebStorm can do LESS/SASS -> CSS compilations too.1 point
-
Thanks a lot for your input guys. I didn't know about the possibility to do this also with a dashboard in the admin area but I think I will go with the frontend route this time as there is really only showing and displaying content and not letting the users create or modify anything.1 point
-
Hi @Lance O., yes. This is how I did it; I used the LoginRegister module of Ryan on a Page with a "PageUserProfile" template: // Code on the template PageUserProfile $input->get->profile = 1; $loginRegister = $modules->get('LoginRegister'); $user->of(false); echo $loginRegister->execute(); Then I use my own module and inside the init() function, I add two hooks: <?php /** * © ICF Church – <web@icf.ch> */ namespace ProcessWire; class TemplateUser extends WireData implements Module { protected $template = 'user'; public function init() { // handle profile images $this->addHookBefore('Page(template=PageUserProfile)::render', $this, 'profileImageUpload', ['priority' => 6]); $this->addHookAfter('Page(template=PageUserProfile)::render', $this, 'profileImageRemove', ['priority' => 99]); } /** * getModuleInfo is a module required by all modules to tell ProcessWire about them. * * @return array */ public static function getModuleInfo() { return [ 'title' => 'Template User Controller', 'version' => '0.0.1', 'summary' => 'Helps with profile image', 'href' => '', 'singular' => true, 'autoload' => true, 'author' => 'Noël Bossart', 'icon' => 'unlock', ]; } /** * Hock to add profile image to user object. * * @param HookEvent $event */ public function profileImageUpload(HookEvent $event) { $user = wire('user'); $input = wire('input'); if ($input->post->profile_submit) { $upload_path = $user->filesManager->getTempPath(); // name of the inputfield from the LoginRegister Module: $f = new WireUpload('profile_image'); $f->setMaxFiles(1); //$f->setMaxFileSize(1 * 1024 * 1024); $f->setOverwrite(true); $f->setOverwriteFilename('userimage'); $f->setDestinationPath($upload_path); $f->setValidExtensions(['jpg', 'jpeg', 'png', 'gif']); // remove image… if (strpos(implode(array_keys($_POST)), 'delete_profile_image_') !== false) { $user->of(false); $user->image->removeAll(); $user->save(); } $files = $f->execute(); if ($f->getErrors()) { foreach ($files as $filename) { @unlink($upload_path.$filename); } foreach ($f->getErrors() as $e) { echo $e; } } elseif (is_array($files) && count($files)) { $user->of(false); $user->image->removeAll(); // wirearray (line added by @horst: explanation is three posts beneath) foreach ($files as $file) { $user->image->add($upload_path.$file); } $user->save(); foreach ($files as $file) { @unlink($upload_path.$file); } } } } /** * Hock to remove profile image from user * * @param HookEvent $event */ public function profileImageRemove(HookEvent $event) { // remove image… if (strpos(implode(array_keys($_POST)), 'delete_profile_image_') !== false) { $this->user->of(false); $this->user->image->removeAll(); } } }1 point
-
If you don't need minification then just don't use it ? RockLess is enough to make your uikit less work. I built it exactly for that... If you need minification for performance reasons I'd also recommend using ProCache, because it will not only minify your site but also deliver it via htaccess as static files and the performance benefit will be huge!1 point
-
This is an additional plugin for the CKE. You can download it below and simply configure it as an additional plugin in PW. ? https://ckeditor.com/cke4/addon/loremipsum1 point
-
Update: you don't need this method. See my next post below. ? ----- Suppose you have a Page Reference field "countries" in template "traveller" that contains any countries the traveller has visited. It's easy to find travellers who have visited Albania and Andorra... $matches = $pages->find("template=traveller, countries=Albania, countries=Andorra"); But what if you want to find travellers who have only visited Albania and Andorra and not visited any other countries? Then it's not so easy. There's no simple syntax for selectors that allows you to match an exact Page Reference field value, as @adrian highlighted recently. Within your selector you have to include all the countries that you don't want to be in the field value. That's a hassle to do manually, and in some circumstances where new pages are being added all the time you may not know in advance all the pages you need to exclude. So to make it an easier job to create an exact match selector for Page Reference fields, here is a helper method you can add in /site/ready.php: // Returns a selector string for matching pages that have the exact supplied value in the Page Reference field $wire->addHookMethod('Field(type=FieldtypePage)::getExactSelector', function(HookEvent $event) { $field = $event->object; $value = $event->arguments(0); if($value instanceof PageArray) $value = $value->explode('id'); if(!is_array($value)) throw new WireException('The $value argument supplied to getExactSelector() must be a PageArray or an array of page IDs.'); $table = $field->getTable(); $query = $this->database->query("SELECT data FROM $table GROUP BY data"); $field_values = $query->fetchAll(\PDO::FETCH_COLUMN); $exclude_ids = array_diff($field_values, $value); $selector = ''; foreach($value as $id) $selector .= "$field->name=$id, "; if(count($exclude_ids)) $selector .= $field->name . '!=' . implode('|', $exclude_ids); $event->return = rtrim($selector, ', '); }); And you use the method like this: // Get the Page Reference field you want to use in the selector $field = $fields->get('countries'); // Get the value you want to match (PageArray) $value = $pages->find('template=country, title=Albania|Andorra'); // Alternatively $value can be an array of page IDs // $value = [1105, 1107]; // Use the method to get a selector string $selector = $field->getExactSelector($value); // Optional: add anything else to the selector that you want $selector .= ', template=traveller'; // Find the matching pages $matches = $pages->find($selector);1 point
-
Nice! Interesting you figured out using count for an exact match. In the same multi-parameter selector mentioned in the linked post, I use count to find messages with either all the age categories checked, or alternatively, it has at least the user's age category checked. This is used because the user's age is not a mandatory field to be completed so if we don't know their age, we need the message to be for all ages, but if we do know their age, then it must match the ages tagged in the message. $selector[] = 'ages=(ages.count='.$pages->count('template=age'), ages=(ages='.$user->age.')'; Anyway, obviously getting away from the purpose of the thread, but it's another example of how using a count can be helpful for queries.1 point
-
Ha ha, you might have spoken too soon. ? I knew that as soon as I posted this a much simpler solution would present itself. You don't need to exclude anything to make an exact match - you just need to match all the pages and the count of the pages. So no helper method is needed really. $matches = $pages->find("template=traveller, countries=Albania, countries=Andorra, countries.count=2"); Or for a more complex match where the count isn't immediately obvious: $value = $pages->find('template=country, title=Albania|Andorra'); // imagine a more complex value than this $selector = $value->each('countries={id}, '); $selector .= "countries.count=$value->count, template=traveller"; $matches = $pages->find($selector);1 point
-
Sensational, Brilliant, Fabulous ? Now this is something we definitely need in the core - I can't believe someone hasn't realized that we can't already do this. I think we need to nominate Robin as PW support guru of the year!1 point
-
I'm probably a well-known nay-sayer around here already, but I'm still going to add my +1 to this discussion. I feel that (particularly when it comes to API design) one clean and consistent solution should be preferred, unless an alternative approach provides major benefits. Multiple options only tend to make things more fragmented and more difficult to grasp, and doubly so when you're a new user. As such, I'm always thrilled when an update enhances the core somehow (performance, security, scalability) or makes something new possible or more streamlined (in this update valid() and validate(), plus the whitelist feature) ... but I cringe a bit when a new way of doing the same old without (seemingly) notable benefits is added. Personally I wouldn't mind seeing the core get leaner in the future, perhaps in 4.x. There are, and always will be, features that should be added to the core (shameless plug: FiedltypeDecimal), but on the other hand alternatives without major benefits or major user base, and modules that may not be particularly often-used should, in my opinion, be stripped out. A module I've been working on recently has an "alias" feature (that isn't even that important to the module, to tell the truth), which was getting way out of hand. You could provide its arguments in five different formats, at least, which all required their own handler methods. I was working on this feature on Sunday (once again), and finally got fed up with it, stripped all those alternatives out and replaced them with one unified solution: callbacks. Sure, a bit of syntactic sugar might've been lost in the process, but I also got rid of probably 75% of the required code, and the documentation page is now actually readable. So, yeah – I know exactly what you're talking about ?1 point
-
Of course it is possible ? Just create a new folder and click reload on the laragon screen. If you enabled https it will even create the certificates for you. And you can even share your site over the web via ngrok instantly (eg for showing your work to clients). But of course you need to create a new database for every project as @jmartsch already mentioned. Well, I understand you, but those things happen ? But you have to be a little careful about your databases in general. If you mess something up, this can be a real nightmare to recover. I'd recommend you install https://modules.processwire.com/modules/process-database-backups/ combined with https://modules.processwire.com/modules/cronjob-database-backup/ . Then you'll always have db backups in your /site/assets folder. You'd still need to backup your files, of course. I wanted to use OneDrive for that, but it did not work well with laragon (too many files, too slow syncronisation, ...). I'm using GIT now for every project. It's complicated in the beginning, but it's the best solution in the long run. So, if you are willing to learn, there are lots of possibilities ?1 point
-
1 point
-
This should do it (cleaned from other module, so not sure if this works without fixing, but idea should be pretty clear): <?php class Elections extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => 'Elections', 'version' => 101, 'summary' => 'Simple module to demonstrate how to automatically create subpages.', 'singular' => true, 'autoload' => true, ); } public function init() { // add a hook after the $pages->save $this->pages->addHookAfter('save', $this, 'afterPageSave'); } public function afterPageSave($event) { $page = $event->arguments[0]; // We want to create subpage only when using if ($page->template == 'election' && $page->numChildren == 0) { $p = new Page(); $p->template = $this->templates->get("candidates"); $p->parent = $page; $p->title = "Candidates"; $p->sortfield = wire('fields')->get('v_candidate_number'); $p->save(); $p2 = new Page(); $p2->template = $this->templates->get("votes"); $p2->parent = $page; $p2->title = "Votes"; $p2->save(); $this->message("New election created."); } } }1 point