Leaderboard
Popular Content
Showing content with the highest reputation on 06/10/2020 in all areas
-
You can achieve what you want with ProFields: Table. It is excactly what I did for a customer portal: You can add as many items as you want, and define the type of each field of a row. For example the field "Eigenschaft" is a FieldtypePage, which pulls it options from a list, that my customer can manage himself. Additionally one requirement was, that when one item was already selected, then it should not be selectable in the other rows. I wrote a blog post how to do this: https://dotnetic.de/blog/disable-options-in-select-elements-that-are-already-selected-in-another-select-element/5 points
-
I took the liberty of opening a GitHub issue for this problem: https://github.com/processwire/processwire-issues/issues/1192 As a workaround for now you could use this hook in /site/ready.php: $pages->addHookBefore('added', function(HookEvent $event) { /** @var Page $page */ $page = $event->arguments(0); /** @var Pages $pages */ $pages = $event->object; // Only for a particular template if($page->template != 'your_template') return; // Check if there is a name clash with an existing sibling page $default_name = $this->wire('sanitizer')->pageName($page->title, true); if($pages->count("parent=$page->parent, id!=$page->id, name=$default_name, include=all")) { // Manually create a proposed name $name = $default_name . '-1'; // Auto-adjust the name when needed $name = $pages->names()->uniquePageName($name, $page); // Set the name $page->setAndSave('name', $name); } });3 points
-
The image field have a builtin tag field, activate it on the field config. Well you could approach this by setting a form with a select input with the possible categories, then on the backend: <?php //form submits to domain.com/search/?tag=sometag $tag = $input->get->text('tag'); //All pages with image field and certain tag $found = $pages->find("images.tags~=$tag"); foreach($found as $p){ $imagesWithTag = $p->images->findTag($tag); //do your stuff }2 points
-
You'll have to look through the $_COOKIE superglobal to find the cookie name that matches your format. You can do that with regular array functions. For example: // just for testing $_COOKIE['name_xyz'] = 'Test value'; $_COOKIE['name_foo'] = 'Bar'; $cookieNames = array_filter(array_keys($_COOKIE), function ($key) { // check if the cookie name starts with "name_" return preg_match('/^name_/', $key) === 1; })); print_r($cookieNames); // -> Array ( [6] => name_xyz [7] => name_foo ) print_r($_COOKIE[current($cookieNames)]); // -> Test value Adjust the preg_match pattern to match the pattern you're looking for. The result will be an array of keys that match your format. Note that this array is NOT zero-indexed, because array_filter doesn't change the keys, hence the use of current to get the first value. Alternatively, use array_values to zero-index the array, or foreach to loop through all matching cookies. If you dislike the raw use of $_COOKIE, you can also use $input->cookie()->getArray() (docs).2 points
-
Hi all, I just added the ability to restrict access to custom FieldsetTabs which makes it easier to block certain user roles from having edit access to entire sets of fields (in the selected tabs). I also added a new "Exempt Roles" setting that makes it easy to make sure certain roles are not affected by this module - particularly useful if you use the "View" permissions approach.2 points
-
Combine the power of ProcessWire selectors and SQL Differences to previous RockFinder modules RockFinder3 comes with extensive docs ? RF3 supports chaining: $RockFinder3->find("template=foo")->addColumns(['foo']). RF3 fully supports multi-language. RF3 makes it super-easy to add custom columnTypes. RF3 makes it easier to use custom SQL statements. No bloat! The module does just do one thing: Finding data. Differences to findRaw Background: RockFinder has been there before findRaw existed findRaw makes it a little easier to query page fields, but RockFinder is more flexible in that regard RockFinder might be faster than findRaw: see here DOCS & DOWNLOAD: https://github.com/baumrock/rockfinder3 Thanks for using RockFinder3. If you find RockFinder3 helpful consider giving it a star on github or saying thank you. I'm also always happy to get feedback in the PW forum! Happy finding1 point
-
Hello, I would like to present you a new module which aim to facilitate the productivity of your editors/publishers when working on ProcessWire. The idea begun when my co-worker told me that when typing in ProcessWire CkEditor field he was feeling "loosing motivation" when writing big wall of text and/or inspiration. So he opened his web-browser and show me a site looking to Wordpress - feel free to put your preferred emoji here - then he opened Gutenberg... typed some text and moving some "blocks". I understood immediately why he got this feeling with CkEditor. If you or your client feel like this guy, then you will love this module ! What is currently supported ? Features Auto-save Medias upload support HannaCode support Blocks Implemented Heading Image Paragraph Embed Quote Code Link Table (beta) Block Delimiter Raw HTML Note (custom block markup) Feature Request Frontend Edition And there you go for the preview - sorry I am to lazy and bad at typing text so I had a copy/pasta moment : Module featured in the ProcessWire Weekly #317 - Thanks @teppo1 point
-
Hi all, Introducing a new GDPR Cookie Management Banner module. https://github.com/adrianbj/CookieManagementBanner https://modules.processwire.com/modules/cookie-management-banner/ This module was sponsored by VentureWeb in Squamish, BC, Canada. I converted a Drupal module written by Oliver Walker from VentureWeb into what you see here. The Drupal module requires jQuery so at the moment, this module also requires jQuery. I will probably remove this sometime soon. This module certainly has similarities to MarkupCookieConsent but provides the user with the following features: The user can accept all cookies or they can choose to not accept tracking/marketing cookies. Module config options allow you to: define all text and button labels (multi-language support) manually increment the cookie policy version which forces the user to review their consent again select whether users can manage their cookies, or just accept all option to limit display of banner to users only in European Union (by IP address) position selection (top or bottom overlay, or content pushed down from the top) It comes with basic default styling which is easily overwritten by site CSS The module sets various values to the dataLayer array which works together with Google Tag Manager - please read through the code in /assets/js/CookieManagementBanner.js to get a better idea of how this works and what is made available for GTM. You can wrap your tracking/marketing cookie code in a check for the localstorage key of: pwcmbAllowCookies if(localStorage.getItem('pwcmbAllowCookies') == 'y') You can also provide a link on your site (probably in the footer) like this that will allow the user to show the banner even after they have saved their preferences / accepted. <a href="#cookies" class="js-pwcmb-notice-toggle">Manage Your Cookies</a> Would love to hear feedback from anyone who gives this a go.1 point
-
Finally, a blog post this time ? — ProcessWire 3.0.159 brings some useful and time-saving upgrades to the core two-factor authentication system: https://processwire.com/blog/posts/pw-3.0.159/1 point
-
Wow thanks dotnetic! ? That's exactly what I was looking for: someone to point me in the right direction. The ProFields: Table was on my shortlist of options and just wanted to be sure that I what I was trying to do couldn't be achieved without ProFields. Thanks! ? Now all I need is to find some indication/examples of some kind of nested templates/hierarchical templates implementation... will keep looking.. I'm pretty sure that with ProFields + nested templates I should have the main ingredients for what I need to do. Cheers!1 point
-
@digitex - I agree that there are still some big issues with this. Setting widths to use percentage-based widths in the Uikit theme settings seems to help with many of the issues, but definitely not all. Have you seen this mammoth issue thread: https://github.com/processwire/processwire-issues/issues/480 (linked above by Robin). The default admin theme definitely works better than Uikit on this front.1 point
-
Hi! I was trying RF3 with PW 3.0.159 and I got the error that it is not compatible. I fixed it changing the <> on the RockFinder3.module.php Lines 658 and 664 Also, the I tried a join with a single page reference field and only get the id field instead of "title", I used the fragment as base for the query: Thank you! $owners = $rockfinder ->find("template=person") ->addColumns(['title', 'age']) ->setName('owner'); // set name of target column $rockfinder ->find("template=cat") ->addColumns(['title', 'owner']) ->join($owners) ->dump();1 point
-
Not a direct answer but have a look at this intrepid work by @gebeer and especially this sister topic:1 point
-
THANKS I WILL IM READNG ABOUT PHP TO HAVE TOOLS FOR DEAL WITH IT1 point
-
1 point
-
Thank you Kongondo, this a bug from me, I am testing my site on another server, removing my templates engine extensions that cause problems when upgrading. This is definitely not a PW bug. I found my file template had a remnants of the twig template engine.1 point
-
The normal way to match content in a Repeater is by searching the subfields of the Repeater. Instead of... my_field%=foo, template=repeater_my_repeater_field ...you would do... my_repeater_field.my_field%=foo That way you are matching the pages that contain the repeater field so you don't need getForPage(), plus you don't have to deal with the access restrictions on the Repeater template, unpublished repeater items, sorting of the container pages, etc. Have you considered merging the text of all the fields you want to search into a hidden "index" textarea field? It's not too difficult to do this with a Pages::saveReady hook, but to make it really easy you can use the SearchEngine module which takes this approach. Doing this has the potential to solve the other issues you raised here and here (you could strip apostrophes from text that is saved to the index field).1 point
-
Looks great! Love the design! Nice to see a webshop where some effort has obviously been put into making a custom design ?1 point
-
As an alternative to the image tags, you could use the new image custom fieds https://processwire.com/blog/posts/pw-3.0.142/1 point
-
Thanks for the update @ryan! Could you please take a look at what might be causing the main download button for the dev version to be stuck on 3.0.153? It's been this way for several weeks. I know that the button always links to the current state of the dev branch but it's potentially confusing to users having the version number out of sync like this. Thanks ?1 point
-
Interesting, I was thinking that ProcessWire could do with a couple of alternatives to the dated CK Editor. EditorJS is one of those 'modern' editors (I was also looking at Quill), I definitely like the block-based output in JSON format. In the end I think it would be nice to be able to change the editor implementation library for textarea fields on the fly – like having a textarea field where you can select between CK Editor, EditorJS, TinyMCE etc. and being able to easily add other editors through a hook or something. Edit: Obviously this should be changed in the field settings per site, not configurable by editors themselves!1 point
-
Very good news for the weekend: RockFinder3 does now work again with the latest dev version of PW (thx to @adrian for making Ryan aware of the issue that also affected tracy!) ? v1.0.6 is up and running smoothly and ready for a long future ? $owners = $rockfinder3 ->find("template=person") ->addColumns(['title', 'age']) ->setName('owner'); // set name of target column db($rockfinder3 ->find("template=cat") ->addColumns(['title', 'owner']) ->join($owners) ->getSQL()); SELECT `pages`.`id` AS `id`, `_field_title_5ed16c868e555`.`data` AS `title`, GROUP_CONCAT(DISTINCT `_field_owner_5ed16c868e66d`.`data` ORDER BY `_field_owner_5ed16c868e66d`.`sort` SEPARATOR ',') AS `owner`, GROUP_CONCAT(DISTINCT `join_owner_5ed16c868ea01`.`title`) AS `owner:title`, GROUP_CONCAT(DISTINCT `join_owner_5ed16c868ea01`.`age`) AS `owner:age` FROM `pages` LEFT JOIN `field_title` AS `_field_title_5ed16c868e555` ON `_field_title_5ed16c868e555`.`pages_id` = `pages`.`id` LEFT JOIN `field_owner` AS `_field_owner_5ed16c868e66d` ON `_field_owner_5ed16c868e66d`.`pages_id` = `pages`.`id` LEFT JOIN ( SELECT `pages`.`id` AS `id`, `_field_title_5ed16c868de1f`.`data` AS `title`, `_field_age_5ed16c868dea8`.`data` AS `age` FROM `pages` LEFT JOIN `field_title` AS `_field_title_5ed16c868de1f` ON `_field_title_5ed16c868de1f`.`pages_id` = `pages`.`id` LEFT JOIN `field_age` AS `_field_age_5ed16c868dea8` ON `_field_age_5ed16c868dea8`.`pages_id` = `pages`.`id` WHERE (pages.templates_id=48) AND (pages.status<1024) GROUP BY pages.id ) AS `join_owner_5ed16c868ea01` ON `join_owner_5ed16c868ea01`.`id` = `_field_owner_5ed16c868e66d`.`data` WHERE (pages.templates_id=44) AND (pages.status<1024) GROUP BY pages.id PS: I decided to change the case of the API variable from $RockFinder3 to $rockfinder3 (all lowercase)!1 point
-
v1.0.3 adds two new features: addPath() each() Callbacks RockFinder3 supports row callbacks that are executed on each row of the result. Usage is simple: each() $RockFinder3 ->find("template=cat") ->addColumns(['title', 'weight']) ->each(function($row) { $row->myTitle = "{$row->title} ({$row->weight} kg)"; }) ->dump(); These callbacks can be a great option, but keep in mind that they can also be very resource intensive! That applies even more when you request page objects from within your callback (meaning there will be no benefit at all in using RockFinder compared to a regular $pages->find() call). addPath() A special implementation of the each() method is the addPath() method that will add a path column to your result showing the path of every page. This will not load all pages into memory though, because it uses the $pages->getPath() method internally. $RockFinder3 ->find("template=cat") ->addColumns(['title', 'weight']) ->addPath("de") ->dump(); If you need the path for linking/redirecting from your data to the pages it might be better to build a custom redirect page that works with the page id, so you don't need the overhead of getting all page paths: <a href='/your/redirect/url/?id=123'>Open Page 123</a> If you really need to access page objects you can get them via the $finder parameter of the callback: $finder->each(function($row, $finder) { $row->foo = $finder->pages->get($row->id)->foo; }1 point
-
Thanks for the feedback! I'm glad to hear that they are useful ? although a bit complex to use. Tasker has a few small improvements, I think I pushed the latest version to the GitHub repo. DataSet changed a bit more, and some modified parts still need review and testing. Thanks for reminding me to finish them. My DataSet project is still running. We have like 150k+ (mostly complex) data pages interconnected with many references and getting to hit the wall with MySQL during imports and complex page reference lookups.1 point
-
Hi there, while developing a sideproject which is completly build with ProcessModules i suddenly had the urge to measure the performance of some modules ? as a result, say welcome to the FlowtiAppPerformance module. It comes bundled with a small helper module called FlowtiModuleProfiler. In the first release, even though you could select other modules, it will track the execution of selected Site/ProcessModules. This will give you the ability to gain insights how your Application behaves. The Main Module itself will come with 2 Logging Options, Database or PW Logs. Select Database for Charts and Logs...well If you just want your profiles as a simple log file in PW. You also could choose to dump the request profile into TracyDebugger as shown here: Dont wonder about my avg_sysload, somehow my laptop cant handle multiple VMs that good ? Settings Screen Monitoring FlowtiLogs again, dont look at the sysload ? I will update the Module in the future to give some filter options and aggregation, but for now it satisfies my needs. I hope it is helpfull for some. Module is submited to the directory and hosted at github https://github.com/Luis85/FlowtiAppPerformance Any suggestions, wishes etc. are much appreciated. Cheers, Luis1 point
-
During testing of the https://processwire.com/talk/topic/22847-processwire-dashboard/ module I've created this simple demo inputfield to show how Inputfields can be used as presentation blocks in ProcessModules. The result is this little inputfield that shows the getting started chart of chartjs: https://www.chartjs.org/docs/latest/ https://github.com/BernhardBaumrock/InputfieldChartDemo It comes with 2 ProcessModules 1) One single chart inputfield 2) Grid Demo Creating an Inputfield is really nothing more than creating a module file having 3 methods: <?php namespace ProcessWire; class InputfieldChartDemo extends InputfieldMarkup { public static function getModuleInfo() { ... } public function ___render() { ... } public function ___processInput($input) { return false; } } Place this in /site/modules/YourModuleName, name the class exactly the same as your folder and you get an installable Inputfield that you can use everywhere in your admin and share across projects! Creating a ProcessModule is also very simple (see this old post): You do even need only 2 methods in this module! <?php namespace ProcessWire; class ProcessInputfieldChartDemo extends Process { public static function getModuleInfo() { ... } public function execute() { ... } } Using these internal tools it is really easy to create totally tailored user experiences for your clients in the PW backend!1 point
-
Hi @ukyo I just created a PR to support easy value setting via setAndSave(). Before: After: $page->setAndSave('contact', ['str' => 'foo']); https://github.com/trk/Mystique/pull/31 point
-
While working on optimizing Comments field for my blog, I noticed it could have been useful to have a GDPR checkbox in the form, to get privacy acceptance before a comment is submitted. At the beginning I though to inject the checkbox processing the rendered comment form, but as I already modified FieldtypeComments to create a Language field, I decided to continue on that road to add the GDPR checkbox. We will not modify the original FieldtypeComments in wire/modules/Fieldtype/FieldtypeComments, but copy it to site/modules/FieldtypeComments. Please refer to the initial part of this tutorial for the detailed steps to duplicate the module and make PW aware of which module version to use. Now that FieldtypeComments is duplicated, we can proceed with the necessary modifications. Inside the module there are 14 files, but do not worry ... only ContactForm.php and (optionally) comments.css will have to be modified. First we will modify the $options class property of ContactForm.php to add a new 'gdpr' label. Later we will use this option to pass the label's text associated with the checkbox. protected $options = array( … 'labels' => array( 'cite' => '', // Your Name 'email' => '', // Your E-Mail 'website' => '',// Website 'stars' => '', // Your Rating 'text' => '', // Comments 'submit' => '', // Submit 'starsRequired' => '', // Please select a star rating 'gdpr' => '', // >>>>> ADD THIS LINE ), As a second step it will be necessary to create the markup of the checkbox and of its label. We will do that by modifying function renderFormNormal() in ContactForm.php. Uikit 3 Site/Blog Profile is indirectly calling this function, so for my purpose it was enough. In case your application is using threaded comments, it will be necessary to modify also renderFormThread(). "\n\t\t<textarea name='text' class='required' required='required' id='{$id}_text' rows='$attrs[rows]' cols='$attrs[cols]'>$inputValues[text]</textarea>" . ... "\n\t</p>" . "\n\t<p class='CommentFormGdpr {$id}_gdpr'>" . //>>>>> ADD THIS BLOCK - START "\n\t\t<input class='uk-checkbox' type='checkbox' name='gdpr' value='checked' required='required'>" . "\n\t\t<label for='{$id}_gdpr'>$labels[gdpr]</label>" . "\n\t</p>" . //>>>>> ADD THIS BLOCK - END $this->renderNotifyOptions() . ... The last ContactForm.php modification will include our checkbox in processInput() to block comments submissions if GDPR checkbox is not filled. Please note this will operate in case you do not place "required" in the <input> directive. ... $errors = array(); foreach(array('cite', 'email', 'website', 'stars', 'text', 'gdpr') as $key) { //>>>>> ADD 'gdpr' in the array ... Now let's see how to call the modified form. If you are using Uikit 3 Site/Blog Profile you will have simply to modify the template file where ukCommentForm() is called (example: blog-post.php template). There we will prepare our checkbox message and pass it to ukCommentForm() as an argument option. echo ukHeading3(__('Join the discussion'), "icon=comment"); $gdpr = __('I agree with the processing of my personal data. I have read and accept the Privacy Policy.'); echo ukCommentForm($comments, ['labels' => ['gdpr' => $gdpr]]); However, if you are using comments in multiple template files, it makes more sense to directly modify ukCommentForm() presetting the new options inside the function body: $defaults = array( 'headline' => '', 'successMessage' => __('Thank you, your comment has been posted.'), 'pendingMessage' => __('Your comment has been submitted and will appear once approved by the moderator.'), 'errorMessage' => __('Your comment was not saved due to one or more errors.') . ' ' . __('Please check that you have completed all fields before submitting again.'), // >>>>> SEE THE HONEYPOT TUTORIAL 'requireHoneypotField' => 'email2', //>>>> ADD THESE FOUR LINES 'labels' => array( 'gdpr' => __('I agree with the processing of my personal data. I have read and accept the Privacy Policy.'), ), ); Before testing, we will modify the file comments.css adding these two directives (that's purely optional): .CommentFormGdpr input[type=checkbox] { background-color: white; } .CommentFormGdpr label { padding-left: 10px } Now let's test our form. Once it is filled with our data and comment it will look like this: If the user is pressing the submit button without accepting the GDPR checkbox, the comment will not be submitted and an error is displayed (in case you have used "required" in <input> you get this tooltip box, otherwise you will get an alert message): Now we accept the box After acceptance and submission, the comment form will be successfully sent. The standard success message is shown and the form is again displayed empty (with just cite and email pre-filled) ready for a next comment submission. Again I hope you will find some useful hint with that.1 point
-
@Soma Yeah this is my issue with Vue.JS - I use Vue.JS often for two way data-binding which is amazing to be able to easily update content and do visual changes to the site based on those updates. For example adding something to a basket. It's much easier with Vue than jQuery or JavaScript. My issue with Vue is exactly that. You can use it just on a component basis or you can use it to render your entire UI. To me it just doesn't make sense to use it to render an entire UI with a website. A web app sure, but a website it's often overkill and introduces that exact problem. You can introduce server side rendering of Vue with Nuxt but then it just seems like another layer of 'over complication'. I personally stopped using Vue for websites unless I absolutely needed two way binding. It just didn't make sense and the extra complication of implementing it such as having to convert everything from ProcessWire into a JSON feed. EDIT: Noticed this post still gets likes, so what's changed since then? AlpineJS. AlpineJS is a solution to this exact problem, worth a look for anyone wanting to do some cool UI stuff without including all of Vue features and overhead.1 point
-
1 point
-
Dear all, I want to apologize for my harsh comment which was a spontaneous reaction and not well thought-out. I highly appreciate the spirit of this friendly community and didn't want to hurt anybody. Those of you who have read more of my posts know that usually I try to be constructive and not a troll. Best wishes ottogal1 point
-
1 point
-
Something like this in your /site/init.php $this->addHookAfter('ProcessPageListActions::getActions', null, function(HookEvent $event) { $p = $event->arguments[0]; $actions = $event->return; if($p->template->name == 'aaa') { unset($actions['edit']); } $event->return = $actions; }); Just replace the $p->template->name with your condition - whether it's a real template name, or change to $p->parent->id to get the parent of the page table items - whatever works best for you.1 point
-
Had to do this yesterday. But this works only on the newest dev version of processwire, because Ryan just implemented the removeTab() method. Older versions will still show the tab, but no actual form to delete something. <?php /** * ProcessWire 'Hello world' demonstration module * * ProcessWire 2.x * Copyright (C) 2014 by Ryan Cramer * Licensed under GNU/GPL v2, see LICENSE.TXT * * http://processwire.com * */ class RemoveDeleteTab extends WireData implements Module { /** * getModuleInfo is a module required by all modules to tell ProcessWire about them * * @return array * */ public static function getModuleInfo() { return array( 'title' => 'RemoveDeleteTab', 'version' => 1, 'summary' => '…', 'singular' => true, 'autoload' => true, ); } public function init() { // Remove Settings Tab in Global settings for non superadmins $this->addHookAfter('ProcessPageEdit::buildForm', $this, "removeDeleteTab"); } public function removeDeleteTab(HookEvent $event){ // check what role the user has, if superuser do nothing if($this->user->isSuperuser()) return; $page = $event->object->getPage(); if($page->template->name === "settings"){ $form = $event->return; $fieldset = $form->find("id=ProcessPageEditDelete")->first(); $form->remove($fieldset); $event->object->removeTab("ProcessPageEditDelete"); $event->return = $form; } } }1 point
-
I think these guys covered it really well, but here's an overly simple alternate example in case it helps. class YourModule extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array('title' => 'Your Module', 'version' => 1); } const defaultValue = 'renobird'; public function __construct() { $this->set('yourname', self::defaultValue); // set default value in construct } public function init() { // while you need this function here, you don't have to do anything with it // note that $this->yourname will already be populated with the configured // value (if different from default) and ready to use if you want it } public function execute() { // will return configured value, or 'renobird' if not yet configured return $this->yourname; } public static function getModuleConfigInputfields(array $data) { // if yourname isn't yet in $data, put our default value in there if(!isset($data['yourname'])) $data['yourname'] = self::defaultValue; $form = new InputfieldWrapper(); $f = wire('modules')->get('InputfieldText'); $f->name = 'yourname'; $f->label = 'Enter your name'; $f->value = $data['yourname']; $form->add($f); return $form; } } If you have the need to manage several configuration values, then you can save yourself some time by using a static array of default values like in the examples before mine. Also this: foreach(self::$configDefaults as $key => $value) { if(!isset($data[$key]) || $data[$key]=='') $data[$key] = $value; } could also be written as this: $data = array_merge($data, self::$configDefaults);1 point
-
1 point