Jump to content

Hide uneditable Pages in Admin Tree/ProcessPageList


Orkun
 Share

Recommended Posts

Hi Guys

A long time ago (2-3 years ago) I had written the following hook (based on this gist https://gist.github.com/somatonic/5595081):

public function init() {
	$this->addHookAfter('ProcessPageList::execute', $this, 'hidePages');
}

public function hidePages(HookEvent $event){
		if($this->wire('config')->ajax){
			$excludePages = new PageArray();
			$the_pages = $this->wire('pages')->find("template=newsletter-clinic|sender_2016|sender_2016_collab|inside-news-folder|basic-page|event-clinic|domain_root|brandportal-index|key-figures|jobs|news|media-mirror|worker|xmlexport|weekly-menu|intranet_domain_root|weekly-menu-index|daily-menu|daily-menu-index|benefit|benefits-index|pdiatres-de-garde-index|xml-feed-index|courses-index|help-doc|stocks-index|dictionary|iPad-PDF-Index|pkb-pikettdienst-index|heisser-stuhl-index|heisser-stuhl");

			
			foreach ($the_pages as $the_page) {
				if(!$the_page->editable() && !$the_page->addable()) {
				  $excludePages->add($the_page);
				}
			}

			foreach ($this->wire('pages')->find("template=specialties") as $sp) {
				if($sp->numChildren > 0){
					$editableFound = 0;
					foreach ($sp->children() as $spc) {
						if($spc->editable()){
							$editableFound++;
						}else{
							$excludePages->add($spc);
						}
					}
					if($editableFound == 0){
						$excludePages->add($sp);
					}
				}else{
					$excludePages->add($sp);
				}
			}

			$hidden = explode('|', $excludePages);


			$json = json_decode($event->return, true);
			foreach($json['children'] as $key => $child){
				if(in_array($child['id'],$hidden)) unset($json['children'][$key]);
			}
			$json['children'] = array_values($json['children']);
			$event->return = json_encode($json);
		}
}

It hides all the uneditable and unaddable (can't add children to page) pages for non-superusers. But this code slows down the page tree view heavily.
Because of this I wanted to ask if there is better way (possibly a new added hook in PW3?) to hide uneditable/unaddable pages for non-superusers.

KR
Orkun

Link to comment
Share on other sites

Can't you just add a CSS class to uneditable pages, and use display: none on these?

Or maybe check with JS for absence of li.PageListActionEdit and then add a hiding class in the parent div.

As for unaddable pages, check for absence of li.PageListActionNew.

  • Like 1
Link to comment
Share on other sites

1 hour ago, dragan said:

Can't you just add a CSS class to uneditable pages, and use display: none on these?

Or maybe check with JS for absence of li.PageListActionEdit and then add a hiding class in the parent div.

As for unaddable pages, check for absence of li.PageListActionNew.

Hi @dragan

Wouldn't be there problems when using a css or js solution, because of the ajax loading and pagination?

I mean there is also still the problem of false child count (also with the current code).

KR
Orkun

Link to comment
Share on other sites

It looks like a hook to ProcessPageList::find is working.

Here is the code

$this->addHookAfter('ProcessPageList::find', function(HookEvent $event) {
                    // Get the object the event occurred on, if needed
                    $ProcessPageList = $event->object;
                  
                    // An 'after' hook can retrieve and/or modify the return value
                    $return = $event->return;
                  
                    // Get values of arguments sent to hook (if needed)
                    $selectorString = $event->arguments(0);
                    $page = $event->arguments(1);

                    $excludePagesByTemplate = array(
                        "newsletter-clinic",
                        "sender_2016",
                        "sender_2016_collab",
                        "inside-news-folder",
                        "basic-page",
                        "event-clinic",
                        "domain_root",
                        "brandportal-index",
                        "key-figures",
                        "jobs",
                        "news",
                        "media-mirror",
                        "worker",
                        "xmlexport",
                        "weekly-menu",
                        "intranet_domain_root",
                        "weekly-menu-index",
                        "daily-menu",
                        "daily-menu-index",
                        "benefit",
                        "benefits-index",
                        "pdiatres-de-garde-index",
                        "xml-feed-index",
                        "courses-index",
                        "help-doc",
                        "stocks-index",
                        "dictionary",
                        "iPad-PDF-Index",
                        "pkb-pikettdienst-index",
                        "heisser-stuhl-index",
                        "heisser-stuhl"
                    );
    
                    if($return instanceof PageArray){
                        foreach($return as $p){
                            if(in_array($p->template->name, $excludePagesByTemplate)){
                                if(!$p->editable() && !$p->addable()) {
                                    $return->remove($p);
                                }
                            }
                            
                        }
                    }
                  
                    $event->return = $return;
});

KR

Orkun

 

  • Like 2
Link to comment
Share on other sites

Just played around with your example and did a little refactoring ? Thx for sharing your code!

$this->addHookAfter('ProcessPageList::find', function(HookEvent $event) {
  $pages = $event->return;
  $excludePagesByTemplate = array('admin', 'basic-page');
  
  $pages->each(function($p) use($pages, $excludePagesByTemplate) {
    if(!in_array($p->template, $excludePagesByTemplate)) return;
    if(!$p->editable() && !$p->addable()) $pages->remove($p);
  });
  
  $event->return = $pages;
});
  • Like 4
Link to comment
Share on other sites

12 minutes ago, bernhard said:

Just played around with your example and did a little refactoring ? Thx for sharing your code!


$this->addHookAfter('ProcessPageList::find', function(HookEvent $event) {
  $pages = $event->return;
  $excludePagesByTemplate = array('admin', 'basic-page');
  
  $pages->each(function($p) use($pages, $excludePagesByTemplate) {
    if(!in_array($p->template, $excludePagesByTemplate)) return;
    if(!$p->editable() && !$p->addable()) $pages->remove($p);
  });
  
  $event->return = $pages;
});

Hi Bernhard, Thank you for refactoring :)

KR
Orkun

Link to comment
Share on other sites

@Orkun, do you know about $page->listable()? It determines whether a page may appear in Page List (and also PageListSelect inputfields, which is probably something you want too).

So you could do:

$wire->addHookAfter('Page::listable', function(HookEvent $event) {
	/* @var Page $page */
	$page = $event->object;
	if(!$page->editable() && !$page->addable()) $event->return = false;
});

 

10 hours ago, Orkun said:

I mean there is also still the problem of false child count

You can hook ProcessPageListRender::getNumChildren to customise the child count. But in some cases depending on what is determining a page's listable status you might decide it's not worth the trouble.

Here's one way you might do it (in /site/ready.php):

// Work out which templates are editable or addable for the user and store them on the $user object
// Doing this here to avoid doing it repeatedly in the getNumChildren hook
if(!$user->isSuperuser() && $page->template == 'admin') {
	$user_roles_ids = $user->roles->explode('id');
	$allowed_templates = [];
	foreach($templates as $template) {
		$edit_add_roles = array_merge($template->editRoles, $template->addRoles);
		if(count(array_intersect($user_roles_ids, $edit_add_roles))) $allowed_templates[] = $template->name;
	}
	$user->allowed_templates = $allowed_templates;
}

// Set a selector to correct the children count
$wire->addHookBefore('ProcessPageListRender::getNumChildren', function(HookEvent $event) {
	$user = $event->wire('user');
	if($user->isSuperuser()) return;
	$selector = 'template=' . implode('|', $user->allowed_templates);
	$event->arguments(1, $selector);
});

Be aware that there are some other areas you probably want to consider if you are hiding pages in Page List. Here are some pointers to get you started:

  • Pages that can be found in the admin search - look at manipulating the selector by hooking ProcessPageSearch::findReady
  • Pages that can be found in Lister - look at manipulating the selector by hooking ProcessPageLister::getSelector
  • Like 4
Link to comment
Share on other sites

On 10/5/2019 at 12:28 AM, Robin S said:

@Orkun, do you know about $page->listable()? It determines whether a page may appear in Page List (and also PageListSelect inputfields, which is probably something you want too).

So you could do:


$wire->addHookAfter('Page::listable', function(HookEvent $event) {
	/* @var Page $page */
	$page = $event->object;
	if(!$page->editable() && !$page->addable()) $event->return = false;
});

 

You can hook ProcessPageListRender::getNumChildren to customise the child count. But in some cases depending on what is determining a page's listable status you might decide it's not worth the trouble.

Here's one way you might do it (in /site/ready.php):


// Work out which templates are editable or addable for the user and store them on the $user object
// Doing this here to avoid doing it repeatedly in the getNumChildren hook
if(!$user->isSuperuser() && $page->template == 'admin') {
	$user_roles_ids = $user->roles->explode('id');
	$allowed_templates = [];
	foreach($templates as $template) {
		$edit_add_roles = array_merge($template->editRoles, $template->addRoles);
		if(count(array_intersect($user_roles_ids, $edit_add_roles))) $allowed_templates[] = $template->name;
	}
	$user->allowed_templates = $allowed_templates;
}

// Set a selector to correct the children count
$wire->addHookBefore('ProcessPageListRender::getNumChildren', function(HookEvent $event) {
	$user = $event->wire('user');
	if($user->isSuperuser()) return;
	$selector = 'template=' . implode('|', $user->allowed_templates);
	$event->arguments(1, $selector);
});

Be aware that there are some other areas you probably want to consider if you are hiding pages in Page List. Here are some pointers to get you started:

  • Pages that can be found in the admin search - look at manipulating the selector by hooking ProcessPageSearch::findReady
  • Pages that can be found in Lister - look at manipulating the selector by hooking ProcessPageLister::getSelector

Hi @Robin S

Thank you for the tip! 
I didn't know about the listable method. I am using now the listable hook to hide pages in the admin tree (and also in PageListSelect Inputfields).

About the false child count problem. You are probably right, that this is not worth the trouble so I will leave it as it is, since I didn't heard any complains from the users concerning false child count.

What is the benefit from hooking Page::listable() instead of ProcessPageList::find()?

KR
Orkun

Link to comment
Share on other sites

  • 4 weeks later...
On 10/4/2019 at 3:02 PM, bernhard said:

Just played around with your example and did a little refactoring ? Thx for sharing your code!


$this->addHookAfter('ProcessPageList::find', function(HookEvent $event) {
  $pages = $event->return;
  $excludePagesByTemplate = array('admin', 'basic-page');
  
  $pages->each(function($p) use($pages, $excludePagesByTemplate) {
    if(!in_array($p->template, $excludePagesByTemplate)) return;
    if(!$p->editable() && !$p->addable()) $pages->remove($p);
  });
  
  $event->return = $pages;
});

I refactored your refactored code ? One could also remove the pages that should be excluded beforehand…

$this->addHookAfter('ProcessPageList::find', function (HookEvent $event) {
	$excludePagesByTemplate = ['admin', 'basic-page'];

	$event->return->find(["template!=" => $excludePagesByTemplate])->each(function ($p) use ($event) {
		if (!$p->editable() && !$p->addable()) {
			$event->return->remove($p);
		}
	});
});

 

  • Like 3
Link to comment
Share on other sites

58 minutes ago, Noel Boss said:

I refactored your refactored code ?

?? At first I thought my version was better readable. But I quite like this one:

$this->addHookAfter('ProcessPageList::find', function (HookEvent $event) {
	$event->return->find([
		"template!=" => ['admin', 'basic-page'], // keep them
	])->each(function($p) use ($event) {
		if(!$p->editable() AND !$p->addable()) $event->return->remove($p);
	});
});
  • Like 3
Link to comment
Share on other sites

  • 2 years later...
On 11/3/2019 at 1:22 PM, bernhard said:
$this->addHookAfter('ProcessPageList::find', function (HookEvent $event) {
	$event->return->find([
		"template!=" => ['admin', 'basic-page'], // keep them
	])->each(function($p) use ($event) {
		if(!$p->editable() AND !$p->addable()) $event->return->remove($p);
	});
});

Dear @bernhard & @Noel Boss, I LOVE this wonderful hook! Works perfectly. Huge thanks! ?

  • Like 1
Link to comment
Share on other sites

Actually I'd probably do it this way today (refactoring again ? )

<?php
$this->addHookAfter('ProcessPageList::find', function (HookEvent $event) {
    $event->return->each(function($p) use ($event) {
        if($p->template == 'admin') return; // keep
        if($p->template == 'basic-page') return; // keep
		if(!$p->editable() AND !$p->addable()) $event->return->remove($p);
	});
});

 

Link to comment
Share on other sites

1 hour ago, bernhard said:

...refactoring again ?

??

Just checked it - works properly as well.

Any particular reason of resigning from 'find' in $event?
My programming skills ends somewhere around 'if' statements and playing with PW selectors... ?
Efficiency in queries? Structure of methods?

  • Like 1
Link to comment
Share on other sites

No - I was just looking at the old version and did not instantly understand it, so I thought I'd change it a little bit. But there are still 1000 other options ? 

In terms of performance I don't think that there is any noticable difference between both versions.

  • Like 1
Link to comment
Share on other sites

  • 1 year later...

Hi folks,
My research about hiding pages in the page tree led me to this thread.
In the ProcessPageList module, there is a "Hide these pages in page list(s)" section where you can "select one or more pages that you do not want to appear in page list(s)."
However, when I select a page, it’s still visible in the page tree. Is it normal? In which situation these pages would be hidden? I don’t get that.
As always, thanks a lot.

Link to comment
Share on other sites

3 hours ago, TomPich said:

However, when I select a page, it’s still visible in the page tree. Is it normal? In which situation these pages would be hidden? I don’t get that.

Yeah, I've noticed that too and I think it's a bug. I normally select the "except when superuser" option and it does work then but this shouldn't be necessary. I opened an issue here: https://github.com/processwire/processwire-issues/issues/1879

  • Like 2
Link to comment
Share on other sites

11 hours ago, Robin S said:

I normally select the "except when superuser" option and it does work then but this shouldn't be necessary.

For me, whether I select this option or not, it doesn’t work. Not a big deal though. I just wanted to know if I got this functionality wrong.

Link to comment
Share on other sites

  • 2 weeks later...

Just to add another solution to the original question, this is how I hide not editable pages in admin tree:

$this->addHookAfter('Page::listable', isPageListable(...));

function isPageListable(HookEvent $event): void
{
    if ($event->page->path == "/")
        $event->return = true;
    else
        $event->return = wire()->user->hasPermission("page-edit", $event->page);
}

 

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