Jump to content

poljpocket

Members
  • Posts

    83
  • Joined

  • Last visited

  • Days Won

    2

Posts posted by poljpocket

  1. What was the version you updated from? We could use this to look for anything that has changed since then.

    If this is a production site, I would advise against using the dev branch. The latest master version is 229 which should be stable for production.

  2. Hi Webjack 🙂 Interesting project you have here!

    Some more pointers:

    • As other people have already pointed out, you can use the API to create new pages (let's call them subscriber) with a specific parent (let's call that subscribers) which will hold your email addresses. Don't forget to look at the EU law and whatever this entails once you go to production with this. Here, FrontendForms or also FormBuilder are good starting points, but also have a look at LoginRegisterPro. Both of them are paid modules, but you get a lot of functionality in either case. Both paid modules can also handle saving new users to the database automatically, just like FrontendForms. I am always using custom forms and submission handlers but then, you need to code a lot more.
    • Further, say you want to notify any subscriber when a new article (let's call them post) has been published, you need a hook like Pages::saved(template=post) to check if a new post has been saved and then send out your notification email to all subscribers. A hint on how to decide if a page is new can be found here.
  3. 46 minutes ago, bernhard said:

    Why is creating pages any worse than populating a repeater? I'd prefer pages I guess.

    Which, in turn, is creating pages too, albeit invisible to the user!

    I am with Bernhard on this: use pages and use them as tags. I love reusability.

    Also, considering you having multiple BnB locations, won't a repeater approach just make the user enter the same items again and again (the exact reason to go with tags instead of repeaters)?

    Another reason for tags: this ensures consistency and avoids typing mistakes, items which mean the same but are a bit different, AND most importantly: you can easily filter by amenity using the API.

  4. If you are into Docker, you can use my template to create your own local installation (although intended for development). It maps the site directory outside of the container for development. But you should easily be able to just upload your zip to the container and extract it over whatever is there already.

    For the DB, you can put the dump into the data folder and name it 'database.sql' and then run the 'dbrestore' script (for this, the composition must be running).

    poljpocket/processwire-docker: Docker installation of ProcessWire for local development (github.com)

    • Like 2
  5. Just from skimming through the code, I can see that you are creating an exponential endless loop. You are hooking the saved event for a page and then - for every file field - saving said page again inside the hook. This will trigger another saved hook for every file field which will loop endlessly.

    I think this is why you are getting the out-of-memory error from your second post. I would try the saveReady hook to accomplish this. Don't call page->save() in there because you factually change the stuff before the page is ever saved (your changes will be saved automatically).

    • Like 1
  6. Let me share my story first, you will see why it's important for this discussion. I am a fullstack web developer and I have always found CMSes to be very limiting because usually, their philosophy plays a big part in what you can do and if at all, how it should be done. Most of them even force you into a lot of predefined concepts for the frontend, too. Take WordPress (which to be honest, is not a CMS at all), you start with posts, comments and pages and are stuck with a weird loop concept for the frontend in which posts always get a "special treatment". In 90% of cases if you want to use WordPress as a true CMS, you can only do that by disabling most of the predefined stuff outright and installing a crapton of extensions to just have some custom structures available. 

    With ProcessWire, all of that is different. You start with a clean slate. No predefined content, no structures, no nothing. You can get some niche output with a site profile but changing just anything about the frontend of these site profiles takes you straight into the code. You can't install a plugin to have a fully-fledged shop, add some products and then install a $50 theme, upload the logo and change some colors and call it a day. You have this wonderful API which - as a developer - really is a godsent and allows you to create the most complex structures and have complete control and freedom in the frontend because it literally allows you to do whatever you want. And in the end, we can offer an editing experience to our clients which is a hundred times easier to understand and use than anything done with e.g. WordPress.

    I think that exactly is what PW's message on the homepage is: It is easy to use for developers while creating the project and easy to use for the clients of the developer when using the finished product. 

    So, to go full circle: I think you are used to systems like WordPress, Squarespace or Wix and expect to have a working website after installing ProcessWire. But this is not the case since ProcessWire is taylored towards web developers. This is also why you need to learn at least a considerable part of the API to then go into the code and create your website, app, own API or whatever your project entails.

    I don't want do discourage you and seeing new people getting into PW is always a blast. But I fear that without coding, ProcessWire might not be the right choice.

    • Like 6
    • Thanks 2
  7. Since you are already using markup regions, I would offload all scripts and styles to an array which gets filled along the way by all templates and then in the end, output in the main file.

    You can - anywhere in the code - add something like this:

    $config->styles->add($config->urls->templates . "path/to/file.css");

    or for scripts, this:

    $config->scripts->add($config->urls->templates . "path/to/script.js");

    And then in your _main.php file, e.g. in the header, do this:

    <?php foreach ($config->styles as $file) {
        echo "<link type=\"text/css\" href=\"$file\" rel=\"stylesheet\">";
    } ?>

    and for scripts, e.g. before the end body tag, this:

    <?php foreach ($config->scripts as $file) {
        echo "<script type=\"text/javascript\" src=\"$file\"></script>";
    } ?>

    I am using this every time and the solution is very flexible (I think I copied this from the Admin Theme files back in the day).

  8. I assume, you want the $regios to be an array of all available regions.

    Let me start with how I would solve this:

    • Create two templates, 'regions' and 'region'. Configure the family settings to:
      • 'regions' only 1 allowed and only template 'region' as children allowed
      • 'region' many allowed, no children allowed, parent only 'regions' allowed
    • Use a page reference field with text tags option, limited to the template 'region' and parent 'regions'.
      • You can enable the 'create pages from field' option to instantly create new 'region' pages when not yet available

    This method is the easiest from a frontend point of view, because your query boils down to:

    $regios = $pages->find('template=region,sort=title');

    For your case, it is more complicated because you cannot just query all the unique region names with your solution.

    You need to get into directly querying the database. I found this (a few years old, but still accurate I think):

     

     

  9. As far as I can see and understand in the PW core code, there is no problem with consistency since the $page does contain the most recent version of the data. The issue really is that in the hooks save (after only) and sorted, the cache has been invalidated due to previous database writes, and thus, subsequent queries will generate new instances. This all makes sense. I believe this is to be expected.

    In your code, you are updating the title on the "new" instance which is factually another object and then trying to save the "old" page instance. This doesn't work of course. What is wrong with your commented-out line instead of $page->save() outside the loop?

    • Like 1
  10. I think saving the page again in the sorted hook is no problem and most probably what you must do anyway. What PW does when the user is reordering pages in admin is actually not that complicated:

    In module ProcessPageSort the method sortPages gets called whenever an appropriate ajax request is dispatched.

    At the very end, both the page and it's parent is saved after the 'sort' property has been updated for the page and subsequent children. This is also why the whole thing shown in the log in one of my last posts happens (line 172+ is shown here).

    // trigger hooks
    $page->set('sort', $pageSort);
    $page->trackChange('sort');
    $page->save();
    $parent->trackChange('children');
    $parent->save();
    $this->wire()->pages->sorted($page, false, $changes);

    You can see that there will be a saveReady and save hook triggered on the page immediately before the sorted hook is triggered.

  11. The thing is, the ProcessPageSort module is in itself saving the page without any of the options you mentioned. And this is why your problem arises.

    But as far as I know, there is nothing wrong with you changing the page in the hook (using the "wrong" page instance) and saving it afterwards in the very same hook. But be careful about this in saveReady or save since this could create an endless loop. Because the page instance gets updated in memory, then saved to the database, your newly created version from find or get has the same data anyway. After the hooks are done, your "old" instance should get discarded. Subsequent finds or gets then will use the cached version.

    Also, you always should compare IDs instead of instances of a page, e.g.:

    $page1->id === $page2->id;
  12. Hey. Interesting topic.

    First, and not to lecture you, Pages::find doesn't have a getFromCache parameter. The find method decides to fetch from cache based on what is queried.

    I did a little further digging and code reading. What I can come up with is that the cache gets invalidated every time a save is performed on the page itself or on it's parent. Move operations are handled by the ProcessPageSort module (part of core) and before the sorted hook is called, the functions in this module save the page and after, it's parent. Save operations seem to invalidate the cache from the page down across its children. This makes sense as the database doesn't necessarily hold the same data as the cached versions.

    To illustrate this, I have the following hooks with child ID 1017 and parent ID 1015 and performing the same move operation as in your example:

    <?php
    
    $wire->addHook(['Pages::saveReady(1017)', 'Pages::save(1017)', 'Pages::sorted(1017)'], function (HookEvent $event) {
        $eventPage = $event->arguments(0);
        $wire = $event->wire;
        $pages = $wire->pages;
    
        be($event->method . ' - ' . $event->when);
    
        $findPage = $pages->find(1017)[0];
        $getPage = $pages->get(1017);
    
        be('$eventPage ' . spl_object_id($eventPage));
        be('$findPage ' . spl_object_id($findPage));
        be('$getPage ' . spl_object_id($getPage));
    }, ['before' => true, 'after' => true]);
    
    $wire->addHook(['Pages::save(1015)'], function (HookEvent $event) {
        $wire = $event->wire;
        $pages = $wire->pages;
    
        be('parent' . ' / ' . $event->method . ' - ' . $event->when);
    
        $findPage = $pages->find(1017)[0];
        $getPage = $pages->get(1017);
    
        be('$findPage ' . spl_object_id($findPage));
        be('$getPage ' . spl_object_id($getPage));
    }, ['before' => true, 'after' => true]);

    This gives me the following event log (the number is the object instance id):

    save - before
    $eventPage 243
    $findPage 243
    $getPage 243
    saveReady - before
    $eventPage 243
    $findPage 243
    $getPage 243
    saveReady - after
    $eventPage 243
    $findPage 243
    $getPage 243
    save - after
    $eventPage 243
    $findPage 187
    $getPage 187
    parent / save - before
    $findPage 187
    $getPage 187
    parent / save - after
    $findPage 196
    $getPage 196
    sorted - before
    $eventPage 243
    $findPage 196
    $getPage 196
    sorted - after
    $eventPage 243
    $findPage 196
    $getPage 196

    You can see that after "save - after", the find call loads a new version into cache. The instance IDs change a second time after  "parent / save - after" which must be doing the same. In between these, the IDs stay the same.

    This should explain why you do not get your situation on saveReady, but then afterwards in sorted, you do get it since the sort functions save the pages in question before calling the hook.

    • Like 1
  13. Also, your cache function only works as expected if the $nameid variable is the same every time you use a cached version of the query. To get around this, you should make the cache key depend on it, e.g.:

    $name = $cache->get("cachedname-$nameid", "+2 minutes", function($pages) use($nameid) { 
        return $pages->find("template=names, parent=1195, id=$nameid, status=published, limit=3");
    });

     

    • Like 2
  14. I would assume the $nameid variable is not available in your closure unless you add use ($nameid) to include it. $pages is available only because WireCache supports optionally passing API variables as arguments.

    This should work:

    $name = $cache->get('cachedname', "+2 minutes", function($pages) use($nameid) { 
        return $pages->find("template=names, parent=1195, id=$nameid, status=published, limit=3");
    }); 

    Here is some documentation about closures: PHP: Anonymous functions - Manual

    • Like 1
  15. You can use my composition as a starting point. It has a configurable ProcessWire version (must be a tagged one on GitHub) and supports DB dumps for complete version control.

    Be advised this is intended for local development and NOT for production! You should be able to add SSL and some more hardening to make it production-ready though (for sure remove the phpmyadmin container!).

    poljpocket/processwire-docker: Docker installation of ProcessWire for local development (github.com)

    • Like 1
  16. You souldn't abuse the unpublished state for that. A very watertight solution is to make use of Patreon's OAuth to make sure the page viewer is actually one of your supporters. This also eliminates the problem that any link out there which relies on "not being known to the public" doesn't add any form of security or "privateness". Imagine one of your Patreons sharing the direct link to his friends.

    EDIT: This would be a real candidate for a module 🙂

  17. Interesting topic. Assuming you have enabled the override for "Details: fields in fieldset", the warning you are talking about applies to your case. It simply means that any fields not rendered bold in the Overrides list are untested and may cause problems when using an override on them. It doesn't say it wouldn't work, though. It says you must test whatever you enable.

    Now, I don't get how you achieved Overrides outside the template context. As far as I know, this isn't possible and from a frontend point of view also doesn't make any sense. You can easily create multiple templates (and their respective template files) which only differ in Overrides for a specific field. There is simply no need to have Overrides on a per-page basis.

    Can you clarify on that last part?

  18. Exactly, I was going to suggest something like that. But be aware of any side effects this might have because this will act on ANY page added and renders the checkmarks on the "Add New Page" screen useless.

    You might want the hook to be a bit more specific e.g. 

    $wire->addHookAfter('Pages::added(0:template=_YOUR_TAGS_TEMPLATE_NAME_)', ...

    or also

    $wire->addHookAfter('Pages::added', function($event) {
      $page = $event->arguments(0);
    
      if ($page->template->name !== '_YOUR_TAGS_TEMPLATE_NAME_') return;
    
      foreach ($this->wire->languages as $lang) $page->set("status$lang", 1);
      $page->save();
    });

    Still, the better solution would be to eliminate this inconsistency in core / core modules.

    EDIT: You should only do this for non-default languages, e.g. with $languages->findNonDefault():

    $wire->addHookAfter('Pages::added(...)', function($event) {
      ...
      foreach ($languages->findNonDefault() as $lang) $page->set("status$lang", 1);
      ...
    });

     

    • Like 1
  19. Sorry for the late reply!

    Can you post some more context about how exactly you are accessing / rendering the output with Twig?

    Why am I asking this:

    Your first line with render(title) does probably not use any Twig output and thus works as intended. Your second render line does use Twig! I have found, there are some problems with Twig's way of looking for methods' and properties' existence using PHP's reflection API. And this is not always very compatible with the way PW is able to "invent methods" or "add properties" using hooks. Especially stuff like Pageimages::first methods don't behave as they would in plain PHP.

    In your case, there might be a problem with hooks not being executed when Twig is accessing your data. But this is just an idea.

    You can try to debug this by adding something like var_dump($page->title) in your template outside of any Twig output. Then, check if the title is properly rendered in multiple languages but the Twig's content is not. If not, the problem lures somewhere else.

×
×
  • Create New...