Jump to content

thetuningspoon

Members
  • Posts

    658
  • Joined

  • Last visited

  • Days Won

    3

Posts posted by thetuningspoon

  1. @Noel Boss For some reason this doesn't work with your change from using add() to using $page->set() (PW 3.0.178). I got it to work and figured out a way to clear the orphans message:

    $field->setOrphans(new PageArray());
    $page->{$field->name}->add($page->children('include=all, check_access=0'));

    I also added include=all and check_access=0 to make sure all children get added.

    Here's the resulting full hook method:

    /**
     * Fill pagetable fields with children before editing….
     *
     * @param HookEvent $event
     */
    public function addChildrenToPageTableFieldsHook(HookEvent $event)
    {
    	$field = $event->object;
    
    	// on ajax, the first hook has no fieldname
    	if (!$field->name) {
    		return;
    	}
    	
    	// get the edited backend page
    	$editID = $this->wire('input')->get->int('id');
    	if (!$editID && $this->wire('process') instanceof WirePageEditor) {
    		$editID = $this->wire('process')->getPage()->id;
    	}
    	$page = wire('pages')->get($editID);
    	
    	// disable output formating – without this, the ajax request will not populate the field
    	$page->of(false);
    	
    	// you could also insert a check to only do this with sepcific field names…
    	$field->setOrphans(new PageArray()); // Clear out the "orphans" notice
    	$page->{$field->name}->add($page->children('include=all, check_access=0'));
    }

     

  2. This sounds fantastic. A couple of our clients have been wishing for something like this and have tried using the clone/copy feature to try to achieve it, but then have issues such as the CkEditor fields pointing to the wrong page's files. Would this feature resolve that issue?

    • Like 1
  3. 22 hours ago, MarkE said:

    Great workhorse module. Particularly useful with ConnectPageFields. I have a similar question to the permissions matter. I don’t want certain users to add or remove links (i.e. the page field itself), so I have given that role view-only access. However I do want the user to be able to view the linked page. But that seems to only be possible if they have edit access to the field. Am I missing something, or do I need to write some code to fix this?

    Glad you're finding it useful! You are correct--I don't believe I ever added support for the read-only state of the various inputfields. Feel free to submit a pull request if you do work on this.

    • Like 1
  4. Posted by:
    
    <?php if($page->insight_author): ?>
    	<a href='<?= $page->insight_author->url ?>'><?= $page->insight_author->title ?></a>
    <?php endif ?>
    ,
    <?php foreach ($page->insight_author->roles as $role): ?>
    	<?= $role->staff_role ?>
    <?php endforeach ?>

    I would suggest something like the above, though there are certainly other templating styles. Like @monollonom said, I'm not exactly sure what role represents or how that is structured.

  5. @ryan I'm trying out the new join feature within a regular page selector. I believe you mentioned that it is possible to join subfields of page fields. I'm wondering if you can explain that in a bit more detail. Does this still all happen within a single SQL query?

    If I want to join the id and the title of a page in a page reference field, which of the following is the proper way to do this?

    1. $pages->find("template=foo, join=pageField|pageField.title");
    2. $pages->find("template=foo, join=pageField.title"); // Can I join pageField.title without also joining the pageField? If so, would I still have access to the page id?
    3. $pages->find("template=foo, join=pageField.id|pageField.title") // Is joining pageField.id the same thing as joining pageField?

    I could answer these questions for myself if I knew of a good way of checking which data has already been loaded in the resulting page. What is the best way to double check what field data is already loaded on the page objects after performing a find (in order to make sure the join worked properly)?

    Edit: I just realized I wasn't thinking clearly when I wrote this. Being able to join subfields of page reference fields would require loading those pages as their own separate page objects, which would always include the page ID, name, modified, and created dates. I am still wondering whether doing either join=pageField or join=pageField.title would actually cause those pages to be preloaded as part of the find, or whether it would only lazy load them when first requested, per the normal PW behavior.

    • Like 1
  6. On 2/6/2021 at 8:19 AM, ryan said:
    Quote

    I remember that there were some issues with autojoining of multi-value fields with 'loadOptions' option in find method (like page reference or options fields etc). Are there any changes? 

    No changes here. There's only so much that can be joined in one query per field, so autojoin is not always possible, especially on multi-value (FieldtypeMulti) fields with lots of values. It should be possible on most Page reference and options fields though, so long as they don't have huge amounts of selections. If there's need for it though, I may be able to have fields (where applicable) store a cache of data (like FieldtypeCombo does) that can be autojoined. 

    I recall that in the past auto joining multi-value page fields would fail quietly and simply result in bad data being returned (like a single page instead of all pages). Can this be changed so that it throws an exception (or at least skips trying to auto join it) if you try to auto join a field that can't be auto joined? That would help avoid nasty issues for programmers who aren't familiar with the internal workings here.

    • Like 1
  7. Here is a cleaner and more thoroughly tested version, which eliminates some of the code duplication. The Field::viewable hook is no longer needed. I'm not sure why the Field::editable is still necessary, but it doesn't work without it.

    /site/ready.php

    /**
     * Implements a custom page-edit-edit-fields permission. By default, all fields will be view only when the page-edit permission is granted. Adding the page-edit-edit-fields permission makes all fields on the page editable.
     * This could be accomplished by implementing access settings on every field of the template, but this makes it much easier.
     *
     * Individual field access settings, if enabled, will override this feature.
     */
    $this->wire()->addHookBefore('Field::getInputfield', function(HookEvent $event) {
    
        $page = $event->arguments(0);
    
        if($page instanceof User) return;
    
        // Exclude any field types we don't want to make uneditable by default
        $excludedFieldtypes = [
            'FieldtypeFieldsetClose',
            'FieldtypeFieldsetOpen',
            'FieldtypeFieldsetTabOpen',
            'FieldtypeFieldsetPage',
            'FieldtypeFieldsetGroup',
        ];
    
        $field = $event->object;
        $context = $event->arguments(1);
        $user = $event->wire('user');
    
        if($context || in_array($field->type, $excludedFieldtypes)) return;
    
        // If the current user does not have page-edit-edit-fields permission, disable edit regardless of the field's roles
        if(!$field->useRoles && !$user->hasPermission('page-edit-edit-fields', $page)) {
            $field->setRoles('view', [$event->wire('config')->guestUserRolePageID]); // Set all roles to have view permission by passing in the guest role
            $field->setRoles('edit', []);
            $field->addFlag(Field::flagAccessAPI);
            $field->addFlag(Field::flagAccessEditor);
            $field->set('useRoles', true);
            $field->set('disabledByHook', true);
        }
    });
    
    /**
     * Only the above Field::getInputfield hook should be required since it sets $field->setRoles('edit', []); but for some reason the main content tab will still appear editable unless we add this hook
     */
    $this->wire()->addHookAfter('Field::editable', function() {
        $return = $event->return;
        $field = $event->object;
    
        // If the current was field disabled by our other hook then reflect that here as well
        if($field->disabledByHook) {
            $return = false;
        }
    
        $event->return = $return;
    });

     

  8. I figured out a way to do this ( @bernhard's original request, solution #3), but it wasn't easy. I added a page-edit-edit-fields permission which can be added or revoked per-template, and without which none of the fields are editable in the page editor. It required 3 hooks to make the permission work.

    /site/ready.php

    /**
     * Implements a custom page-edit-edit-fields permission. By default, all fields will be view only when the page-edit permission is granted. Adding the page-edit-edit-fields permission makes all fields on the page editable.
     * This could be accomplished by implementing access settings on every field of the template, but this makes it much easier.
     *
     * Individual field access settings, if enabled, will override this feature.
     *
     * Both this hook and the one below are required to make this work.
     */
    $this->wire()->addHookAfter('Field::editable', 'fieldEditableHook');
    function fieldEditableHook(HookEvent $event) {
    	$return = $event->return;
    	$page = $event->arguments(0);
    	$user = $event->arguments(1);
    	$field = $event->object;
    
    	if($return === true && $page !== null) { // No need to check our page-edit-edit-fields permission if the field is already not editable or we're not checking page context
    		if(!$user) $user = $event->wire('user'); // If no user passed in, use the current user
    
    		// If the current user does not have page-edit-edit-fields permission, disable edit
    		if(!$user->hasPermission('page-edit-edit-fields', $page)) {
    			$return = false;
    		}
    	}
    
    	$event->return = $return;
    }
    
    $this->wire()->addHookAfter('Field::viewable', 'fieldViewableHook');
    function fieldViewableHook(HookEvent $event) {
    	$return = $event->return;
    	$page = $event->arguments(0);
    	$user = $event->arguments(1);
    	$field = $event->object;
    
    	if($return === false && $page !== null) { // No need to check our page-edit-edit-fields permission if the field is already viewable
    		if(!$user) $user = $event->wire('user'); // If no user passed in, use the current user
    
    		// If the current user does not have page-edit-edit-fields permission, enable viewable permission
    		if(!$user->hasPermission('page-edit-edit-fields', $page)) {
    			$return = true;
    		}
    	}
    
    	$event->return = $return;
    }
    
    $this->wire()->addHookBefore('Field::getInputfield', function(HookEvent $event) {
    	// Exclude any field types we don't want to make uneditable by default
    	$excludedFieldtypes = [
    		'FieldtypeLeafletMapMarker',
    	];
    
    	$field = $event->object;
    	$page = $event->arguments(0);
    	$user = $event->wire('user');
    
    	if(in_array($field->type, $excludedFieldtypes)) return;
    
    	// If the current user does not have page-edit-edit-fields permission, disable edit regardless of the field's roles
    	if(!$field->useRoles && !$user->hasPermission('page-edit-edit-fields', $page)) {
    		$field->set('useRoles', true);
    		//$field->addFlag(Field::flagAccess); // May not be necessary
    		//$field->addFlag(Field::flagAccessAPI); // May not be necessary
    		$field->addFlag(Field::flagAccessEditor);
    	}
    });

     

    • Like 1
  9. I'm curious what the rationale is for the default behavior of a page save uncaching all pages in the system, as this sometimes causes problems when editing and saving more than one page during the same request. I'm finding myself having to add ['uncacheAll' => false] to most of my page saves. But if there is some important reason for clearing the cache, I want to be aware of it!

  10. On 10/24/2020 at 10:29 AM, antpre said:

    Hi thanks for the module,

    It 's seems the permission issue mentioned earlier (if user doesn't have an edit permission error messages show up in the modal window) is still there.

    It would be great if the create button would be permissions aware and not been displayed if user doesn't have the edit rigths. Is this something that can be added at some point ? Thanks

    Hi @antpre. Making the module permissions-aware may be possible, but it would be a bit tricky since it would involve hooking the markup for each of the inputfield option types and injecting a data attribute to signify whether the page is editable/addable. If I get some free time I will see if this is possible.

     

    On 11/12/2020 at 9:54 AM, MarkE said:

    I've been using this module successfully for a long time, but have just come across one issue. On older versions of IOS, the modal box can extend beyond the screen and not all be viewable. In particular, the "Save" button is not accessible. Is there any way I can put a save button at the top of the modal?

    This sounds like it would be an issue with any modal window in the back end. Unfortunately, iframes are basically unusable on iOS. On all of my sites that use iframe modals, I use browser detection to disable them on iOS.

    • Like 1
  11. @horst Thank you! The image access control was indeed the problem. At some point the client made a copy of the page and trashed the original, and the clone was still referencing the images on the original page.

    Is this how it's supposed to work? It seems like if the page clone clones all of the images on the page, it should also update the image references inside of its textareas 🧐

  12. This is a weird one. I have a page with a repeater matrix field on it. Several of the items have CKEditor fields with images in them. When I view the page while logged in, the images are visible. When I log out, all but one of them is stripped from the output.

    I double checked that the IDs of the repeater items remain the same--they do.

    The image tags are completely absent when I view source.

    I used to have ProDrafts installed, but have now remove it. I have ProCache installed, but temporarily disabled it to test this.

    I'm at a loss. Any ideas?

  13. Hi @bernhard,

    We've been running up against the limitations of ProcessWire's native method of handling subfields in selectors. Basically, every subfield in a selector results in a separate query that runs to return all IDs that match the subfield, and then those IDs are used in the where of the final sql query (as opposed to joining the tables and then doing a where on the data all in a single query). This fails at scale because the individual subfield queries start returning so many page Ids that memory is exhausted or MySQL even crashes. There are ways to work around this in some cases by using separate selectors instead of subfields, but it's not terribly elegant. My question is, can we use RockFinder to solve this problem?

    In this particular use case, we would still want to end up with Pages in the end. I am guessing that using subfields in a RockFinder selector would behave the same way as it does in native PW (I assume RockFinder just gets the final query that PW creates for it--which may have taken multiple queries to construct--and proceeds from there). But if the join function would allow us to limit results based on the values of subfields all in a single query, then that would be golden.

    At that point all we would want back from RockFinder would be an array of page ids, which we could pass to Pages::getById() to get the pages back. Perhaps a method call for doing this is worth putting into RockFinder? This would be much more efficient than using the callback since it would be one SQL query to get all the pages.

    Another thing which I mentioned back with RockFinder1 (and implemented locally) is the option of getting a WireArray of WireData objects instead of standard objects or a plain array. This is still way faster than Pages but allows you to use runtime selectors on the results.

    Anyway, thanks for your work on this and looking forward to your thoughts.

  14. 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 5
    • Thanks 1
  15. 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
  16. 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?

×
×
  • Create New...