Jump to content

Trying to make a hook that prevents path, name and template from being changed


DrQuincy
 Share

Recommended Posts

I have the following hook but it doesn't quite work. The idea is I can specify a selector and it prevents the client from moving matched pages and changing their name and template. I know there are already options that allow you to do some of this but I wanted to cover it in a single hook. It so the client doesn't break things by mistake and by fixing the name and location of something like /settings/ it means I can do $pages->get('/settings/') instead of $pages->get(1063).

So I'd set the hook like this:

Hooks::freezePages('template=settings|site-map|emails|contact|downloads');

And then my function looks like this:

	public static function freezePages($selector) {
		
		\Processwire\wire()->addHookAfter('Pages::saveReady, ProcessPageSort::execute', function(\Processwire\HookEvent $event) use($selector) {
			
			$page = $event->arguments(0);
			
			// When sorting pages this can be null so do nothing in that case
			if ($page === null) {
				
				return;
				
			}
			
			// $page->created !== - filters out new pages as when you create them; only do this on pages alrady created
			if ($page->created !== 0) {
				
				$page->of(false);
				
				if ($page->matches($selector) === true) {
					
					// Get previous values (see https://processwire.com/talk/topic/24216-how-to-get-old-page-values-in-a-processwire-page-save-hook-and-some-other-notes-on-save-hooks/)
					$clone = clone($page);
					\Processwire\wire('pages')->uncache($clone);
					$origPage = \Processwire\wire('pages')->get($clone->id);
					
					// Debugging to see how any times it's called
					\Processwire\wire('session')->warning($origPage->template . ', ' . $page->template);
					
					if ($origPage->name != $page->name || $origPage->template != $page->template) {
						
						\Processwire\wire('session')->warning('**' . $origPage->path. '** is frozen and cannot have its name or template changed', \Processwire\Notice::allowMarkdown);
						
						$page->name 		= $origPage->name;
						$page->template		= $origPage->template;
						
					}

					// Handles drag-and-drop
					if ($origPage->path != $page->path) {
						
						throw new \Processwire\WireException($page->path . ' is frozen and cannot change its path');
						
					}
					
				}
				
			}
			
		});
		
	}

It kind of works. It works when dragging and dropping in the page tree and it seems to prevent name changes. It only seems to be problematic when changing the template.

If you remove all template references above (i.e. just check the name) it works fine. When you use the code as above and change the template it only seems to work if you change it to a template not matched by the original selector. It's almost like if you do anything with the template it gets called twice and on the second call the templates are the same. I know this because if you look at the debugging line it adds two warnings to the session if you change the template to something not in the original selector.

Can any of you expert PWers spot an elementary mistake in my code? Is there something special I need to do if changing the template?

I hope that makes sense — do ask if you need more context or information. I've been staring at it for ages and it's driving me mad!

Thanks.

Link to comment
Share on other sites

Not a response to your code, but more a general suggestion...

If the point of this is to restrict what editors can do in the PW admin then I think it would be cleaner and a better experience for editors if you focus on making the admin UI conform to your restrictions. In other words, if the user may not change a page's name or template then don't show those fields in Page Edit, or set them to readonly (I seem to remember that the name input needs to remain in the Page Edit form). And if the user may not sort a page then don't show the "Move" item in page lists.

You should be able to achieve this with hooks to methods such as ProcessPageEdit::buildForm() and ProcessPageListActions::getActions().

  • Like 1
Link to comment
Share on other sites

That's a much better idea, thanks @Robin S ?

I have nearly got it working. My only issue is in the ProcessPageEdit::buildForm hook, how do I get the current page so I can match to my selector?

I.e. how do I get to $page from here:

\Processwire\wire('pages')->addHookAfter('ProcessPageEdit::buildForm', function(\Processwire\HookEvent $event) use($selector)  {
			
	$form = $event->arguments(0);

	// ...

$form is of type ProcessWire\InputfieldForm. How can I get to the $page being edited from that?

Once I get this I can post the full working code.

Thanks.

  • Like 1
Link to comment
Share on other sites

Ah, perfect, thanks!

So, this is what I've got and seems to work so far but I need to test it a bit more.

public static function freezePages($selector) {
	
	// Make name and template read-only
	\Processwire\wire('pages')->addHookAfter('ProcessPageEdit::buildForm', function(\Processwire\HookEvent $event) use($selector)  {
		
		$wrapper 	= $event->return;
		$page 		= $event->object->getPage();
		
		if ($wrapper->has('_pw_page_name') === false || 
			$wrapper->has('template')      === false || 
			$wrapper->has('parent_id')     === false)  {
			
			return;
			
		}
		
		if ($page->matches($selector) === true) {

			$inputState 							= \Processwire\Inputfield::collapsedNoLocked;
			$labelPostfix 							= ' (page frozen, value not editable)';
			
			$wrapper->_pw_page_name->collapsed 		= $inputState;
			$wrapper->_pw_page_name->label 	   	   .= $labelPostfix;
			$wrapper->_pw_page_name->description 	= null; // Hide note about 09, etc
			
			$wrapper->template->collapsed			= $inputState;
			$wrapper->template->label 	   		   .= $labelPostfix;
			
			$wrapper->parent_id->label 	   		   .= $labelPostfix;
			$wrapper->parent_id->contentClass 		= 'parent-disabled'; // Setting to Inputfield::collapsedNoLocked shows the ID so instead just disable via a CSS class from custom CSS file
			
		}
		
	});

	// Remove “Move” button
	\Processwire\wire('pages')->addHookAfter('ProcessPageListActions::getActions', null, function(\Processwire\HookEvent $event) use($selector)  {
		
		$page = $event->arguments(0);
		
		$actions = $event->return;
		
		if ($page->matches($selector) === true) {
			
			unset($actions['move']);
			
		}
		
		$event->return = $actions;
		
	});

}

Usage is then like this:

Hooks::freezePages('template=settings|site-map');

Any matching pages cannot be moved or have their name, parent or template changed.

Note the parent part is blocked via a CSS class so in my case I have some custom CSS included from admin.php:

.parent-disabled {
    opacity: 0.5;
    pointer-events: none;
}

I would've used an inline style but couldn't find that option.

  • Like 2
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...