Jump to content

Prevent page delete


matjazp
 Share

Recommended Posts

I would like to prevent certain page delete (or move to trash) if it is still "in use" in other pages. I would like to hook in admin.php similar to https://processwire.com/talk/topic/5027-hide-page-instead-of-trash-or-delete/ 

wire()->addHookBefore('Pages::trash', function ($event) {
  $page = $event->arguments[0];
  //do some checking
  wire('session')->message('just testing');
  $event->replace = true;
  }
);

but page is moved to trash and message is not displayed when I'm in the Page list (I click Move then Trash icon on the right). If I delete the page while editing it (In tab Delete and then confirm), it works as expected. What am I doing wrong? In debug tools hook is listed:

before Pages::trash() anonymous function() class method 111

Link to comment
Share on other sites

The PageList does initiate trashing via ajax, therefore the missing message. Also your hook does not throw any error or return false, therefore the ajax call can't determine, that you don't want the page to be trashed. 

The more failsave way of preventing trashing would be by hooking Page::deleteable, as this is checked everywhere throughout ProcessWire no matter from where it's called.

Link to comment
Share on other sites

I noticed something: when you enable advanced settings in config.php and set "Disable trash option" in templates System tab (API: $template->noTrash = 1; // or 0 to disable) the trash icon is not present in page list (when you click on move), that is ok. But, you can move page to the trash... is this expected behaviour?  

Link to comment
Share on other sites

I tried this:

wire()->addHookAfter("Page::trashable", function($event) {
$page = $event->object;
wire('session')->message('trashable: '.$page->id);
$event->return = false;
});

With that hook the trash icon in page list is gone (for all pages of course), but I can still move the page to the trash. Also, I can trash the page in Delete tab (while editing the page) unless I hook to Page::deleteable. 

Link to comment
Share on other sites

Please keep in mind, that the trash is only visible to superusers. Therefore normal users can't move any pages to the trash. The trash is implemented as security measurement, so that users can't permanently destroy content. They just have the option to delete a page, but normally no knowledge about the trash. Under this circumstances it's understandable that there may be quirks with moving a page to the trash and that the delete tab on the edit page responds to deletable() and not trashable(). The deletable() permission really is the function you're looking for.

Link to comment
Share on other sites

Yeah, trash is just for admins, forgot that. Now I have two hooks: one on Page::trashable intended for admins, to remove trash icon, as a reminder not to shoot myself in the foot, and one on ProcessPageEdit::buildFormDelete, where I can inform the user about possible consequences about deleting the page that is referenced from another page(s). I think it would be good to have that sort of functionality available in the core or installable core module (for example user should not get deleted if he owns pages). LostKobrakai, thank you for your suggestions.

Link to comment
Share on other sites

  • 9 months later...

...

The more failsave way of preventing trashing would be by hooking Page::deleteable

...

That's not a hookable method. I'm trying to do the same thing but am failing when it comes to the ajax trashing as superuser. (I can prevent the actual delete/trash but am unable to show some error message to the admin so he knows that he's got cheated by the system)

Any help?

What i currently have:

public function init() {
  $this->pages->addHookBefore('delete', $this, 'deletePage');
  $this->pages->addHookBefore('trash', $this, 'deletePage');
}

public function deletePage(HookEvent $event) {
  // if page has ... some condition
  $this->error('Deleting this page is forbidden');
  $event->replace = true;
}
Link to comment
Share on other sites

Page::deletable has to be hookable because PagePermissions::init does exactly that to implement the default permission setup.

You're right I'm sorry. I couldn't find it on https://processwire.com/api/hooks/captain-hook/

Anyways I can't get this working :/ I'm currently using following Hooks to prevent page deletion and trashing. I even remove the delete tab from the page editor:

$this->addHookBefore('Pages::trash', $this, 'preparePagesTrash');
$this->addHookBefore('Pages::delete', $this, 'preparePagesDelete');
$this->addHookAfter('ProcessPageEdit::buildFormDelete', $this, "removePageDeleteButton");

This works flawlessly. Anyways I couldn't get the /admin/page/ Page List Ajax trash button to alert the user. (Currently the system tells you that the page has been trashed even thought it didn't. Reload and I can see it's never been trashed)

Is this even possible? I've tryed Page::deletable and even Page::trashable. There seems to be no way :-/

Maybe you can provide a brief example of how this should be working?

Thank you very much.

Link to comment
Share on other sites

  • 2 years later...

A hook for anyone still wanting a solution for this:

// Prevent the trashing of pages referenced by other pages
$pages->addHookBefore('trash', function(HookEvent $event) {
    $page = $event->arguments(0);
    // Find non-system Page Reference fields
    $pr_fields = $this->fields->find("type=FieldtypePage, flags=0");
    // Implode for selector string
    $pr_fields_str = $pr_fields->implode('|', 'name');
    // Find any referencing pages
    $referenced_on = $this->pages->find("$pr_fields_str=$page->id, include=all");
    if($referenced_on->count) {
        // Replace the trash method
        $event->replace = true;
        // Link markup for referencing pages
        $referenced_on_str = $referenced_on->implode(', ', "<a href='{editUrl}'>{name}</a>");
        $plural = $referenced_on->count > 1 ? 's' : '';
        // Trigger an error message (using $session in case a superuser is trashing from ProcessPageList)
        $this->session->error("You cannot trash page $page->name because it is referenced in a Page Reference field on page$plural $referenced_on_str.", Notice::allowMarkup);
        // Don't allow the trashing of this page
        $event->return = false;
    }
});

When attempting to trash a referenced page from ProcessPageList the page will at first appear to be trashed but in fact it is not, and on the next admin page load a notice will be displayed explaining the situation. I don't think this detail matters much because it only affects superusers.

  • Like 6
Link to comment
Share on other sites

1 hour ago, adrian said:

just wondering about API calls to delete, rather than trash. Also, what about AOS's "delete" page list action button?

The same code will work in a hook to Pages::delete.

Adapted to keep it DRY:

// Prevent the trashing/deleting of pages referenced by other pages
$pages->addHookBefore('trash', null, 'protectReferencedPages');
$pages->addHookBefore('delete', null, 'protectReferencedPages');

function protectReferencedPages(HookEvent $event) {
    $page = $event->arguments(0);
    // Find non-system Page Reference fields
    $pr_fields = wire('fields')->find("type=FieldtypePage, flags=0");
    // Implode for selector string
    $pr_fields_str = $pr_fields->implode('|', 'name');
    // Find any referencing pages
    $referenced_on = wire('pages')->find("$pr_fields_str=$page->id, include=all");
    if($referenced_on->count) {
        // Replace the trash/delete method
        $event->replace = true;
        // Link markup for referencing pages
        $referenced_on_str = $referenced_on->implode(', ', "<a href='{editUrl}'>{name}</a>");
        $plural = $referenced_on->count > 1 ? 's' : '';
        // Trigger an error message (using $session in case a superuser is trashing/deleting from ProcessPageList)
        wire('session')->error("You cannot $event->method page $page->name because it is referenced in a Page Reference field on page$plural $referenced_on_str.", Notice::allowMarkup);
        // Don't allow the trashing/deleting of this page
        $event->return = false;
    }
}

 

  • Like 5
Link to comment
Share on other sites

  • 3 years later...

The above is great @Robin S, thanks.

The issue I have though is if Page A references Page B, you can delete Page A. But then when you come to delete Page B it won't let you because it sees a reference between Page A, which is in the trash, and page B.

I'm struggling to come up with a solution that will always work in a user-friendly way. If you only run the above just on delete (and not trash) then that fixes my initial problem but what could happen is a page could be restored while the page it references is still in the trash.

What do you think would be best practice here? Run your code above on delete only and then do a similar check on restore — only on restore simply issue a warning since at that point the reference is broken.

I'm just finding when thinking through all the scenarios it gets complicated and I'm wondering if you run that hook above without adding parent!=7 to the selector (to ignore trashed items) it can get messy and prevent legitimate deletes and trashes. Maybe it's just better to enforce on delete and on the edge cases where page are restored issue a warning.

Hope that makes sense and I've understood it correctly!

Link to comment
Share on other sites

On 11/8/2021 at 10:01 PM, DrQuincy said:

The issue I have though is if Page A references Page B, you can delete Page A. But then when you come to delete Page B it won't let you because it sees a reference between Page A, which is in the trash, and page B.

I guess you could exclude pages that are in the trash by changing...

$referenced_on = wire('pages')->find("$pr_fields_str=$page->id, include=all");

...to...

$referenced_on = wire('pages')->find("$pr_fields_str=$page->id, include=all, status!=trash");

 

  • Like 1
Link to comment
Share on other sites

Thanks. ?

I'm going to try and implement this when I get the time. I'm thinking use your code running only on Pages::delete with status!=trash added to the selector — and then issuing a warning on Pages::restore if there are any broken references (possibly also setting the page to unpublished). I'll post something once I've had a go.

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...