Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 04/19/2024 in all areas

  1. This week we have ProcessWire 3.0.238 on the dev branch. This version contains 17 commits containing new features (some via feature requests) as well as issue fixes. Recent updates have been nicely covered in ProcessWire weekly issues #517 and #518. Also added this week are the following: Improvements to ProcessPageClone, which now supports some new options such as the ability to configure it to always use the full clone form (a requested feature), and an option to specify whether cloned children should be unpublished or not. New $datetime->strtodate($date, $format) method that accepts any PHP recognized date string and reformats it using the given format. It also supports date strings that PHP doesn't recognize if you give it a 3rd argument ($options array) with an `inputFormat`. The existing $datetime->strtotime() method now supports an `inputFormat` option as well. The Inputfield class now has new methods: $inputfield->setLanguageValue($language, $value) and $inputfield->getLanguageValue($language), added by the LanguageSupport module. Previously there were no dedicated methods for this, so you had to manage them with custom keys containing language IDs if you wanted to programmatically use an Inputfield in multi-language mode. The ProcessWire.alert() JS method has been updated with an auto-close option (`expire` argument) that automatically closes the alert box after a specified number of seconds. InputfieldDatetime has been updated with 7 new interactively configurable settings for the jQuery UI date picker. See the screenshot below for an example of a few. Plus, its API has been updated with the ability for you to specify or override any jQuery UI datepicker option, either from PHP or JS, as requested by Toutouwai/Robin S. See the new datepickerOptions() method and phpdoc for details. Next week we've also got some more updates to InputfieldTable that take the newly added actions even further and make them easier to use. Thanks for reading and have a great weekend!
    9 points
  2. Checking all 3 of these will probably do what you want, but I actually never use the second two because I make use of the "Enable Guest Dumps" button from the panel selector. Enable that, do what you need to do as a guest in a private window and then reload the PW admin where you are logged in as a superuser and you'll get the dumps recorded by the guest. Another thing is to make sure you have the "Tracy Exceptions" panel enabled so that if a guest user interaction results in an exception, the bluescreen with full stack trace will be made available for easy viewing. I rely on this for all my sites with Tracy's production mode. Hope that helps.
    3 points
  3. For handling datetime in Processwire, I've come to realize that the best way for me to avoid the most headache is leave EVERYTHING in UTC...the $config file, the ddev settings, etc. Then whenever I need to display times on the front end, I use a helper function I created that formats time in the Timezone of my choosing, at display time. With this method, the production server timezone or my dev environment timezone never matters. The added benefit is that you can have users save a timezone to their profile and then display times in their timezone. Any other way of dealing with timezone besides saving UTC to database has always created a mess for me later down the road. function UtcToCst($timestamp) { if(!$timestamp) return; return (new \DateTime("@" . $timestamp))->setTimezone(new \DateTimeZone("America/Chicago")); //Or use a timezone saved on your $user template. }
    2 points
  4. I had to do this just the other week. We had an old CMSMS site that we'd moved to PW. We had a db table with the old user names and passwords in which we stuck on the new site, and then just created our own login page which first checked to see if we had the user in PW already. If the user isn't already in PW then check the password against the old table; If it matches then use that information to add a new user to PW. The users don't really notice that anything has changed. Here's the code I used for that - you'll have to adapt to your circumstances of course but it worked well for our move. if ($input->post('ml_name')) { // when processing form (POST request), check to see if token is present // (we have a CSRF field on our login form) if ($session->CSRF->hasValidToken()) { // form submission is valid // okay to process } else { // form submission is NOT valid throw new WireException('CSRF check failed!'); } $ml_name = $sanitizer->name($input->post('ml_name')); $ml_pass = $input->post('ml_pass'); // dont allow the guest username if ($ml_name === 'guest') { throw new WireException('Invalid username'); }; // do we have this user in PW already? $u_exists = $users->get("$ml_name"); if ($u_exists->id) { // user exists // lets try and log them in try { if ($session->login($ml_name, $ml_pass)) { $session->redirect('/'); } else { $ml_feedback = 'Unknown details.'; } } catch (WireException $e) { // show some error messages: // $e->getMessage(); } } else { // ok - well do we have this user in the old CMS table? $query = $this->database->prepare("SELECT * FROM `cms_module_feusers_users` WHERE username = :login_name LIMIT 1;"); try { $query->execute(['login_name' => $ml_name]); } catch (PDOException $e) { die ('Error querying table : ' . $e->getMessage()); } // we've got a user from the old table if($query && $row=$query->fetch()) { $ml_feedback='Is in old table'; $hash=$row['password']; // handily the old auth just uses password_verify if(password_verify($ml_pass, $hash)){ // Add this user to the PW users $new_user=$users->add($ml_name); if($new_user){ $new_user->pass=$ml_pass; $new_user->addRole('members'); $new_user->save(); $log->save("new_members_site", $ml_name . " added as user"); $u = $session->login($ml_name, $ml_pass); if($u) { $session->redirect('/'); } else { die("Error in logging in. Please contact admin."); } $ml_feedback='new user added to PW'; }else{ $ml_feedback='Unable to add new user. Please let admin know'; } }else{ $ml_feedback='No matching records found.'; } }else{ $ml_feedback='No record found.'; } } } and this is the login form that the we had on our new login page - this and the above was all in a single template. <form id="ml_login_form" class="ml_login_form" method="POST"> <?php echo $session->CSRF->renderInput(); ?> <label for="ml_name">Username</label> <input id="ml_name" name="ml_name" type="text" value="<?= $ml_name ?>" required> <label for="ml_pass">Password</label> <input id="ml_pass" name="ml_pass" type="password" value="<?= $ml_pass ?>" required> <div style="display: none;"> <label for="ml_pass_bear">Password</label> <input id="ml_pass_bear" name="ml_pass_bear" type="text" value=""> </div> <button type="submit" name="ml_submit" class="butt">Submit</button> <div class="ml_feedback"><?= $ml_feedback ?></div> </form>
    2 points
  5. Hi @DrQuincy I think you can do this by hooking in to PW's login flow. Here's how I'd attempt to do this - untested - just to give you some inspiration... Create a new field on the User template for their hashed password from your old system. Write a script to populate this field where there's an email match between the old system and the new one. If the new system doesn't have users in it yet, then great, you'll be creating user records for everyone from the old system. Make sure you create a long, random, password for the initial processwire password on any new users you create and leave existing user passwords untouched. Store the hash from your system in the new field on the matching user and save any changes. You need a before hook on the session::login method. Use the name param (in $event->argument(0)) to match on emails if that's how you did it in the old system, if you have exactly one match, and this user has a non-empty value for your old password hash, use your existing pwd checking algorithm to authenticate them from the pass ($event->arguments(1)) parameter. If that works, the plaintext password they submitted to you is the correct password. Now set the PW password on that account using the plaintext password you just verified is correct AND delete the old password hash value. Now the correct password has been stored in the User's PW pass field, just exit the hook and PW should process the login as normal, or call the forceLogin method to log them in and do your own redirect. Basically, as people log in, they will auto-convert their accounts over to PWs way of doing things. Once they convert, the login process just reverts to using PWs method. Potential gotcha: more than one account in the PW system with the same email (if there are already accounts in there.) After a reasonable period has elapsed, you could then deal with seemingly dormant accounts by emailing non-converted users and asking them to login (or otherwise handle them as appropriate.) Hope that helps.
    2 points
  6. Loading a website after updating to PHP 8.x failed. The connection to the server was reset while the page was being loaded. NS_ERROR_NET_RESET PHP error was not catched, nothing displayed, nothing logged. After a long search, it turned out that the Gettext extension was responsible for the fatal error. Code example to reproduce: $locale = 'de_DE'; setlocale(LC_ALL, $locale); echo gettext("foo bar"); // NS_ERROR_NET_RESET After adding the environment variable LANG it worked as expected. $locale = 'de_DE'; setlocale(LC_ALL, $locale); putenv("LANG=$locale"); echo gettext("foo bar"); // foo bar Same behaviour with ngettext() and maybe other Gettext functions. Alternatively, the problem could also be solved with putenv("LC_ALL=$locale"); Possibly affected: ProcessWire\Languages::setLocale() I found little information about this behavior on the Internet.
    1 point
  7. That's really good, thanks for the suggestions! I'm going to keep that as a potential way. I like it, thinking outside the box. ? My somewhat liazier solution is simply to add a field to the user template: Legacy, Legacy converted, and New. If A Legacy user attempts a login it simply sends them to the password reset page. And when they do they become Legacy converted. Your way is far slicker so I'll present the client with both options.
    1 point
  8. Issue submitted: https://github.com/processwire/processwire-issues/issues/1911
    1 point
  9. Hello @BrendonKoz, On the code above I didn't copy the namespace and use statements but this is not the issue, instead of using ProcesswireTemplate I can directly use strings but this is not working either. EDIT: I've edited my code to remove this ambiguity, using strings instead of class ProcesswireTemplate.
    1 point
  10. There are several modules that might help. Most by Macrura here in the forums.
    1 point
  11. You can use https://processwire.com/modules/fieldtype-runtime-markup/ for that.
    1 point
  12. Depending on your requirements, the Stripe payment processor for FormBuilder might be worth investigating. You can set Paypal to be a payment option in Stripe so that users can pay using PayPal but you can keep all your transactions on the same plaform. Out of the box FormBuilder might not do everything you need (if you need a basket etc) but it is possible to hook into the module to update line items / prices etc. We recently built a basket system for a site that we'd wired directly into Stripe but then shifted over to hook into Formbuilder instead because it already did a lot of the heavy lifting in using the Stripe API.
    1 point
  13. I've only just spotted this but, in my ddev environment, if I modified a page the modified date was always recorded as one hour earlier. So appears on the settings tab immediately after doing a modification (at 14:55). The production environment was OK. I have $config->timezone = 'Europe/London'; throughout in config.php. It seems that you also need to set the timezone (in my case 'timezone: Europe/London') in the ddev config.yaml
    1 point
  14. Maybe open an issue in the issue tracker if not yet done?
    1 point
  15. I received so much help from kind people on this forum; it’s now my turn to contribute. ? I recently had a need for a customer. They have a long list of articles, and they wanted to find any article corresponding to some criteria. They could use the Pages > find tool, which use pageLister, but then they would have to each time select some filters, which can be quite intimidating when you have many fields. So my idea was to create an admin page with the right filters already set. The customer only needs to use some of them. As I found out, this is much simpler than I first thought. So my tutorial is not rocket science, but my hope is that it helps newbies like me to achieve this... ? So first, you need to create a simple custom admin page. Bernhard already made a nice tutorial about this, and it’s quite straightforward (like everything in PW ❤️). Here is the code to create this custom admin page in our situation (thanks to Bernhard) : namespace ProcessWire; class ProcessArticlesList extends Process { // Infos about your module and some settings public static function getModuleinfo() { return [ 'title' => 'Articles Admin Page', 'summary' => 'No need to be afraid of building custom admin pages. ;-)', 'author' => 'Your name goes here', 'version' => 1, // you can set permissions here 'permission' => 'meeting-view', 'permissions' => [ 'meeting-view' => 'View Meetings page', ], // page that you want created to execute this module 'page' => [ // your page will be online at /processwire/articles/ 'name' => 'articles', // page title for this admin-page 'title' => 'Articles', ], ]; } public function ___execute() { // the logic goes here } } So, with this code, you get a new module that will produce a blank page. You have now to create a ProcessArticlesList folder inside that file in the site/modules folder and put your new file inside. Then, activate your module. The ___execute() method will run on page load. It’s supposed to return a string which will be your page content. Now things get quite... simple! ? The idea is to run an instance of ProcessPageLister, with custom parameters. You can find all these parameters in /wire/modules/Process/ProcessPageLister/ProcessPageLister.module (there are comments that really help you) and you can also have a look at the API doc about ProcessPageLister. Here is an example that covered my needs : public function ___execute() { // this is to avoid error detection in your IDE /** @var \ProcessPageLister $lister */ // get the module, so that you can use it the way you want. $lister = $this->modules->get("ProcessPageLister"); // from here, the comments in the source file were self explanatory, so I found how to cover my needs // filter all pages with the "meeting" template. This filter is by default, won’t show // in the filter list and cannot be changed. $lister->set('initSelector', "template=meeting"); // Then I set 3 filters, left blank, so that the user has just to fill them – or leave them blank $lister->set('defaultSelector', "title%=, themes.title%=, text%="); // Disallow bookmark creation. In my use case, there was no sense for that. $lister->set('allowBookmarks', false); // let the table be full width $lister->set('responsiveTable', false); // use the custom order defined by the user by moving the pages in the page tree $lister->set('defaultSort', 'sort'); // and just show the configured filter on the page. return $lister->execute(); } As you can see, there are 7 lines of specific code to achieve that. How elegant! ? Hope that it is helpful to someone somehow. Cheers Thomas
    1 point
  16. Source: https://processwire.com/blog/posts/processwire-3.0.107-core-updates/#trash-for-all
    1 point
  17. My understanding is that there are three hookable methods that you can use if you want detailed control of which pages are viewable, editable and "listable". Viewable = user may view the page on the front-end. Example hook in /site/init.php: $wire->addHookAfter('Page::viewable', function(HookEvent $event) { $page = $event->object; // Return early if PW has determined that the page is not viewable if(!$event->return) return; // Your test here... if($page->title === 'foo') { // User may not view the page $event->return = false; } }); Editable = user may edit the page in ProcessPageEdit. Example hook in /site/init.php: $wire->addHookAfter('Page::editable', function(HookEvent $event) { $page = $event->object; // Return early if PW has determined that the page is not editable if(!$event->return) return; // Your test here... if($page->title === 'foo') { // User may not edit the page $event->return = false; } }); Listable = user can see the page in ProcessPageList. Note: superusers are not affected. Example hook in /site/init.php: $wire->addHookAfter('Page::listable', function(HookEvent $event) { $page = $event->object; // Return early if PW has determined that the page is not listable if(!$event->return) return; // Your test here... if($page->title === 'foo') { // User may not see the page in ProcessPageList // And therefore may not see its descendant pages either $event->return = false; } }); There's a catch to be aware of with this last hook. Page::listable only affects ProcessPageList and not other parts of the admin. If the existence or title of the page must remain private then you'll need to handle the following separately: Admin search (AJAX results) Admin search (results page if user hits enter) Lister (aka "Find") Maybe other places such as Page Reference fields I think @adrian has some experience with hiding pages from these places and might have some suggestions. Edit: In my opinion it would be nice if PW used Page::listable to automatically restrict the visibility of pages throughout the admin. I opened a request here: https://github.com/processwire/processwire-requests/issues/379 Besides Page::viewable, Page::editable and Page::listable there are also the following hookable methods that can be used in a similar way to the examples shown above. All of these methods are in PagePermissions.module. Page::publishable Page::deleteable (aka Page::deletable) Page::trashable Page::restorable Page::addable Page::moveable Page::sortable
    1 point
  18. Ok, you are right @Robin S. I think, now i understand you. ?‍♂️ Your idea to use a field for the parent-child relationship works great: I add a empty repeaterfield to template. In ready.php i add to a edit button (each children page) include pagination. It looks like it´s a repeater field items (old page situation). All happy. $this->addHookAfter('Inputfield::render', function (HookEvent $event) { $field = $event->object; $form = wire('modules')->get("InputfieldForm"); //get edit page $page_id = $this->input->get('id'); $page = $this->pages->get($page_id); //add child pages to empty repeater - items not moveable, deletable or to clone if ($field->name === 'products') { $childPages = $page->children('limit=15'); $pagination = $childPages->renderPager(['getVars' => array("id" => $page_id)]); foreach ($childPages as $childPage) { //build the fake-repeater header $wrap = $this->wire('modules')->get('InputfieldFieldset'); $wrap->addClass('InputfieldRepeaterItem InputfieldStateCollapsed InputfieldRepeaterMinItem InputfieldNoFocus'); $wrap->name = "repeater_item_{$childPage->id}"; $wrap->label = $childPage->index + 1 . ': ' . $childPage->product_title . ' Art.: ' . $childPage->title; //if all fields are filled out mark the header $isFilledOut = ($childPage->product_keyword && $childPage->product_type); ($isFilledOut ? $wrap->addClass("text-black", "headerClass") : ""); //add the edit button for the child page (class pw-modal for modal edit) $submit = wire('modules')->get("InputfieldButton"); $submit->attr("value", "Produkt Details bearbeiten"); $submit->attr('data-href', wire('config')->urls->admin . "page/edit/?id=" . $childPage->id); $submit->addClass('pw-modal uk-margin-bottom uk-margin-left'); $wrap->add($submit); $form->add($wrap); } $field_style = '<style>.text-black{color:black !important;}.InputfieldRepeaterItemControls{display:none !important;}</style>'; $event->return = $form->render(); $event->return .= $field_style; $event->return .= $pagination; } });
    1 point
  19. It's getting even more confusing - now it works even in /site/ready.php ? <?php namespace ProcessWire; class RockHook extends WireData implements Module { public static function getModuleInfo() { return [ 'title' => 'RockHook', 'version' => '0.0.1', 'summary' => 'RockHook', 'autoload' => true, 'singular' => true, 'icon' => 'bolt', 'requires' => [], 'installs' => [], ]; } public function init() { bd('init()'); bd(1); bd(2); bd(3); bd('add handleWebhook'); $this->addHookAfter("ProcessPageView::execute", $this, "handleWebhook"); bd('add hello-hook in init()'); $this->addHookAfter('hello', function(HookEvent $event) { bd('hello-hook fired in init()'); }); } public function ready() { bd('ready()'); bd('add hello-hook in ready()'); $this->addHookAfter('hello', function(HookEvent $event) { bd('hello-hook fired in ready()'); }); } public function handleWebhook($event) { bd('handlewebhook fired'); bd('trigger $this->hello()'); $this->hello("trigger hello() in execute() hook"); } public function ___hello($where) { bd('hello!', $where); } }
    1 point
  20. @MarkE - just be aware that hook doesn't prevent a user getting access to the pages via Pages > Find and also via the Live Search in the menu bar. There are some hooks in the Admin Restrict Branch module that will help you with that though.
    1 point
  21. The view permission controls viewing on the front-end, it doesn't relate to pages being listed in Page List. For more advanced control over page permissions try hooking after the following Page methods, returning true/false as needed. $page->listable() is the one related to which pages appear in Page List. $page->listable() bool Returns true if the page is listable by the current user, false if not. Can also be used as property: $page->listable $page->moveable() bool Returns true if the current user can move this page. Optionally specify the new parent to check if the page is moveable to that parent. Can also be used as property: $page->moveable $page->publishable() bool Returns true if the page is publishable by the current user, false if not. Can also be used as property: $page->publishable $page->restorable() bool Returns true if page is in the trash and is capable of being restored to its original location. @since 3.0.107 $page->sortable() bool Returns true if the current user can change the sort order of the current page (within the same parent). Can also be used as property: $page->sortable $page->trashable() bool Returns true if the page is trashable by the current user, false if not. Can also be used as property: $page->trashable $page->viewable() bool Returns true if the page (and optionally field) is viewable by the current user, false if not. Can also be used as property: $page->viewable An example of a Page::listable hook - note that such hooks do not restrict the superuser role: $wire->addHookAfter('Page::listable', function(HookEvent $event) { $page = $event->object; if($this->wire()->user->hasRole('editor') && $page->template == 'basic_page') $event->return = false; });
    1 point
×
×
  • Create New...