Jump to content

thetuningspoon

Members
  • Posts

    691
  • Joined

  • Last visited

  • Days Won

    6

Posts posted by thetuningspoon

  1. I wrote this a while back for my company's internal documentation and just realized it might be a benefit to others on the forum, so here it is!

    Checking whether a field was changed

    To check whether a specific field was changed you can do $page->isChanged(‘field’). This can only be done before Pages::save, since the save clears change tracking by default.

    Getting the values of a page before they were changed, from within a hook before Pages::save

    Method 1: The hacky way

    $clone = clone($page);
    $e->wire('pages')->uncache($clone);
    $oldP = $e->wire('pages')->get($clone->id);

    $oldP then represents the page and all its values as they were before any changes were made. Again, this will only work for hooks that run prior to Pages::save.

    Method 2: Using change tracking to get the old values

    If change tracking is set to track values, then the above is not necessary. In this case, calling $page->getChanges(true) will return an associative array of fields that changed and each field's prior value. Note that the value itself is also an array, since it is designed to be able to track multiple changes over the course of a single request/response cycle.

    Tracking of values is turned off by default (for efficiency sake), and there is no global switch to turn it on and off. To enable tracking of values, you must call $page->setTrackChanges(Wire::trackChangesOn | Wire::trackChangesValues) before any changes are made to the page that you want to track.

    Here is an example of how you can enable value tracking in ProcessWire's page editor:

    $this->addHookBefore("ProcessPageEdit::execute", null, function($e) {
    	$p = $e->pages->get((int)$e->input->get('id'));
    	if($p->template == 'event-registration') {
    		$p->setTrackChanges(Wire::trackChangesOn | Wire::trackChangesValues);
    	}
    });


    Running hooks on both page saves and field saves

    Please note that using the following hooks is preferable if you want the hook to run both when individual fields are saved as well as the whole page:

    Pages::savePageOrFieldReady https://processwire.com/api/ref/pages/save-page-or-field-ready/ (for before a save)

    Pages::savedPageOrField https://processwire.com/api/ref/pages/saved-page-or-field/ (for after a save)

    Getting changes in a hook after Pages::save

    Note the $changes parameter passed to Pages::saved and Pages::savedPageOrField allows you to check which fields were changed during the save, even though the save is already completed. Using this is sometimes preferable to using a Pages::saveReady or Pages::savePageOrFieldReady hook since you don’t have to worry about the page cache or something else in your code preventing the original save from completing. A $values array is also provided, which is populated only if change tracking is set to track values (see above)

    Page cache issues when hooking before Pages::save

    Page cache issues may occur when you try to save another page inside of a before save hook (Pages::saveReady or Pages::save). A page save normally clears the entire page cache, which means any page fields on the page you were originally trying to save will have their values reset. So if you have to save another page inside of a before save hook, just use $page->save([‘uncacheAll’ => false]) to prevent the cache from being cleared.

    Preventing hooks from running on a particular save

    To prevent any hooks from running on a particular page save, you can use $pages->save($page, [‘noHooks’ => true])

    • Like 6
    • Thanks 1
  2. Temporarily comment out the following line in Page.php:

    throw new WireException("You may not modify '$key' on page '{$this->path}' because it is a system page");

    This will allow you to clone the home page via the API:

    $homePage = $pages->get('/');
    $pages->clone($homePage, $homePage, false);

     

    • Like 3
  3. At my job we have our own profile that we've created using the most recent version of the profile exporter (v3). Recently we added some new features including some repeater matrix fields to the site that the profile is based on, and re-exported the profile. Now when we go to install the site (submit the database configuration details), the installation times out. I tried upping the timeout in php but it just chugs and chugs. I tried installing using both the current master (3.0.148) and the current dev (3.0.153) version.

    The blank profile that comes with PW installs no problem.

    One point of interest here is that we typically use InnoDB for our sites, but the site that is the source of the profile was MyISAM. I tried installing the profile using MyISAM, and it WORKED. So my thought was that maybe if I converted the profile source site to InnoDB and re-exported, then I could install the site with InnoDB. Unfortunately, after doing so, now I cannot install the profile with either InnoDB OR MyISAM (both just hang).

    Any thoughts on what could be at the root of this, or some way that we could troubleshoot it?

  4. Hi huseyin,

    1. Use the appropriate Sanitizer methods to test user input from post and get. Depending on the circumstance, you'll either want to validate (reject it completely if there's something wrong with it), or filter/sanitize it (accept it but strip out unwanted characters). If doing both filter and validate, do your validation AFTER your filtering.

    2. Even more important than step #1 is to use escaping on your output. This means using htmlspecialchars() or htmlentities() or $sanitizer->entities() when you output any field from the database or user input to the page (if you have htmlentities setup on your field's output formatting, then you can skip this step for those fields). Even if you mess up on the filter/validation from #1, as long as you've escaped all of the html, you should be ok. 

    3. When using user input (get or post variables) inside ProcessWire selector strings, use the Sanitizer::selectorValue() method on the value first. Even better, just use Selector Arrays since selectorValue can sometimes strip out characters (quotes and commas) that you actually want to search for.

    4. If you're using any SQL directly, you must use prepared statements to bind any user input, which automatically escapes the input for SQL.

    5. For protection against Cross Site Request Forgery (CSRF), use ProcessWire's SessionCSRF class when building custom forms. See https://processwire.com/api/ref/session-c-s-r-f/ for details on how to use this.

    6. Don't use GET for secret data (passwords, security codes, etc). That data can get picked up by browser extensions or appear in server logs that might get compromised.

    7. Use SSL/https on your whole site.

    • Like 12
    • Thanks 1
  5. I'm a little unclear what the question is here. A ProcessWire selector ($pages->find($selector)) gets converted to a "direct database search", so there is no issue selecting from millions of pages, unless you're expecting to get thousands of pages back from it. Can you clarify what you're trying to accomplish?

    Edit: Sorry, I should have read your second question more carefully. As others have mentioned, only fields with autojoin turned on would be loaded with the pages (names are always included). After loading you could use WireArray::explode() to get an array with just the names. You might also try Pages::findMany() to prevent memory overload (though that will probably not work with explode) . But if you're talking about thousands or more you'd be off going to RockFinder or straight SQL.

  6. @LostKobrakai Ahhh... I understand now. Thank you for explaining that.

    Having said that, I'm having a hard time thinking of many cases where I wouldn't want the local time/wall time to change when the definition of the timezone has changed. Otherwise, the value is no longer correct according to the new definition. I'm sure that there are cases where you would want the old wall time preserved (even in your example it is not clear to me whether you would want the wall time or the absolute time preserved for your appointment), but it seems like they would be be few and far between compared to the cases where you would want the values to be able to update dynamically and to be able to easily convert between time zones.

    My current solution to this problem is to set $config->timezone to UTC at the start of a project that needs to work with multiple time zones and leave it there, converting values to local time as needed on the front end. Perhaps this is what @ryan always had in mind for such situations, but it does require some foresight. Otherwise you do end up with a real mess.

    Can we all just agree that time zones should be abolished? ?

    • Like 1
  7. Are you saying that there is something wrong with my proposed solution, or are you saying that there is something else wrong with the existing field type? I think either your post went over my head, or you may be misunderstanding my proposed solution.

    I think the solution is not actually that complicated. ProcessWire natively works with timestamps at runtime, which are always UTC-based. If I save something as a certain timestamp and then my PW/server/php time changes for any reason, I should be able to expect that the timestamp I get back from the field remains the same as the one I put into it. This is how it would work if the field stored a UTC string instead of a local string. Currently, what I get back is effectively a corrupted/meaningless value. I cannot change my $config->timezone once I've initially set it.

    What timezone the user sees or inputs a date in on the site's front end is a separate issue and is up to the programmer to determine and make clear to the user and convert to/from as necessary. But the programmer should be able to trust the timestamp they are working with when they save and retrieve it from the database.

    45 minutes ago, LostKobrakai said:

    At midnight an OS updates happen and a new timezone db is installed.

    What does this mean?

    • Like 1
  8. These updates sound great. Thanks, Ryan!

    On a related subject, I recently (and painfully) discovered that the DateTime field stores dates in the database as a string (MySQL DateTime) in whatever timezone PW is currently configured to. So if you change your PW time zone, the date string you get back from the DB is now interpreted to be in that new time zone rather than the one it was originally entered as. In other words, the unix timestamp you get out of it is no longer the same as the one you put into it.

    It seems to me that the "correct" way to handle this would be for the unix timestamp to always be converted to and stored as a UTC string in the MySQL DateTime field, and then converted back to PW's current timezone at run time. This would be an extremely simple change to PW's DateTime field (using gmdate() instead of date() when storing and using php's DateTime class with UTC timezone specified when getting the value back).

    Then when someone changes their timezone in PW, the absolute values of the dates would stay the same. Only the time zone (how they are represented on the front end) would change. So different users could have different time zone settings and view the PW back end in their own time zone, etc.

    The problem, of course, is that this would break existing installations, so this would have to be added as a new module or as an alternative version included in the core. Ideally when you converted an existing datetime field to the new version it could automatically update your database values from the current PW timezone to UTC.

    • Like 2
  9. On 9/26/2019 at 12:25 PM, jploch said:

    Reading the whole thread I now understand that the option to auto select the new page is already implemented.
    However, for me this is not working. I have the page reference field inside a repeater matrix field.

    When I create a new page, save and close the modal, the new page is not selected.
    Is there something that Iam missing?

    EDIT:
    It works with page auto complete. This is fine for my use case, but it wasn't clear that ist not working with the other options. Maybe this could be added to the module description.

    Hi jploch,

    Sorry I missed this before. Which input field(s) is the auto-select not working for? Also, which PW version and admin theme are you using?

    Glad you're finding the module useful!

    • Like 1
  10. On 9/5/2019 at 8:31 AM, MoritzLost said:

    @szabesz Fair enough! ?

    @adrian As a user, I agree - i don't like it when I try to navigate away from a page using a link, and it opens in a new page, forcing me to go back to close the tab. But we're power users, I'm not sure everyone knows how to open links in a new tab (especially on mobile, not everyone gets the long-press interaction!). As for why I still use target blank basically for all external links, you know, business reasons. Almost every client sees red when they encounter a link that leads them away from their site. Not sure why everyone expects their audience to have the attention span of a mouse, but oh well ... it's not something I want to spend energy discussing every time. I can see both sides though.

    I've encountered users who don't even know what a tab is, and are confused when they cannot get back to the site they were on before by just hitting the back button! It's no use keeping your site up in the background if your users don't know how to get back to it ?  So on principle I agree with @adrian on this, but our clients still keep asking for target="_blank", so that's that.

    • Like 4
  11. On 2/21/2018 at 3:16 PM, Robin S said:

    Nice tip!

    You can also turn autojoin on and off for selected fields whenever you need via API methods $field->addFlag(Field::flagAutojoin) and $field->removeFlag(Field::flagAutojoin).

     

    Something else important to note here:  If you are returning pages with your find that have page fields on them, and you are subsequently accessing fields from those subpages that you also want to have autojoined, you should use Robin's method rather than passing in the joinFields option into your find.

    It's worth noting that autojoining a page reference field only automatically joins its id. It still requires a separate database query to get the page, which occurs the first time you try to access that field or one of its subfields.

    If you have the fields you want to be autojoined flagged, they will be loaded along with the page at that time. 

  12. The problem is in ProcessPageAdd on line 1021:

    https://github.com/processwire/processwire/blob/649d2569abc10bac43e98ca98db474dd3d6603ca/wire/modules/Process/ProcessPageAdd/ProcessPageAdd.module#L1021

    It is calling Page::setEditor, which is overridden by User::setEditor, which performs a redirect instead of actually setting the editor.

    Removing this line fixes the issue.

    Github issue: https://github.com/processwire/processwire-issues/issues/977

    • Like 3
  13. I found the commit where that line was added, and the issue that it was related to:

    Commit: https://github.com/ryancramerdesign/ProcessWire/commit/8d126246772633be773d72f5262acdf14f4c1e31#diff-bbe4731226c86c286f0e4e95a4756eda

    Issue: https://github.com/ryancramerdesign/ProcessWire/issues/1942

    One question that comes to mind is whether a clone should be considered a "moved" page (have a parentPrevious property set), since the original page still exists. If it were considered new instead of moved, I think the line in question would not apply.

    It still seems like there is a lot of overhead here for a move operation. But I still need to study the issue report linked above more closely. 

    • Like 3
  14. I know this is ancient, but I've also run into a bottleneck at PagesEditor::saveParents() on one of my projects running PW 3.0.123. 

    The scenario is as follows:

    1. I am cloning a single page (not recursively) which is in a list with several thousand siblings. 

    2. I am manually looping through the original page's children and cloning them over as children of the new page. (I am doing this manually because sometimes I don't want to clone some of the children)

    I've timed this entire process at around 7 seconds for a page with just 2 children. Using the Debug timer, I discovered that the initial page and its first child clone over at just .1s each, but the second child clone (and any subsequent children) each take around 7 seconds to complete!

    Although I am manually looping through the children and cloning them, using PW's recursive clone seems to encounter the same issue. In fact, my test came in at 8 seconds for the same set of pages.

    The relevant code begins at line 733 of PagesEditor.php where saveParents() is called and passed the parent’s parent when a page has changed parents and it has more than one sibling. It seems that the code is recursing into all of the parent page's thousands of siblings, perhaps unnecessarily.

    It looks like this line was added in at some point after the fix ryan provided in this thread.

    • Like 1
×
×
  • Create New...