PageList Filter by page custom attributes

Permalink 5 users found helpful
I'm in the process of extending the basic page list block to give a few more options for filtering -- for example to filter by date and also to filter by any page attribute.

It's pretty straightforward so far, but I could use a little help in finding the right class methods/variables in c5 for grabbing custom collection attributes, particularly now that the attributes framework has changed with the release of 5.3.3. In particular, those attributes that are multi-select checklists are driving me insane.

It looks like there are a few functions for the object DatabaseItemList (which the PageList object inherits) that seem to fit the bill, but my efforts so far have been unsuccessful. Anyone who is really familiar with the API care to point me in the right direction?

Many thanks.

brennaH
 
okhayat replied on at Permalink Reply
okhayat
Using this method you can get the needed attribute of any type. Since you're working with lists, you just have to deal with it as an array. Example:
<?php
$atts = $cobj->getAttribute('select_attribute');
foreach ($atts as $att) echo "* $att<br/>";?>

So, change that output to whatever you need to allow the selection.
andrew replied on at Permalink Reply
andrew
As far as getting the attributes, okhayat is right. That method should usually be able to return what you're looking for.

Some of the attributes returned are a bit exotic. For example, if you run $c->getAttribute('thumbnail'); and thumbnail is an "image_file" attribute type, you're going to get a File object back. If it's a select attribute type, you're going to get either a SelectAttributeTypeOption object (if it's a single select) or a SelectAttributeTypeOptionList object (if it's multiple). Both of these, if printed out, will just show the text version (either a single option, or a list of options, separated by newline characters.)

As far as _filtering_ goes, it's more complicated. The PageList class should be what you want, and in some cases it's as easy as going

$pl = new PageList();
$pl->filterByAttribute('exclude_nav', 1); // filters out all items where exclude_nav == 1


Or even

$pl = new PageList();
$pl->filterByExcludeNav(1);


However, the multi-select is more challenging than this. Ideally, we'd have a nice way through API of passing options, as well as whether pages with those options should be included or excluded from the result. Unfortunately, we don't. In its place, I can tell you this: whenever you use the multi-select type, and the search index is saved, we take the text version of all the select values, and we separate them with newlines in the search index.

What does that mean? Let's say you have three colors in a select attribute type. Red, Yellow, Green. If you want to grab only those where there are Red selected, you'd do

$pl = new PageList();
$pl->filterByAttribute('color', "%\nGreen\n%', 'like');


Does that help?
PageList::filterByCollectionAttribute
okhayat replied on at Permalink Reply
okhayat
Is there a way to know what type of object is returned? I mean when using gettype($att) is will just say 'object'. How do I know what type of Attribute it is?
VortexSurfer replied on at Permalink Reply
VortexSurfer
hello,
i did search the froum now for hours but didn't get it work.

i have the same problem ... i added a custom field with select values to some pages and want to filter them in the page list.

for this i added a custom template for the pagelist (which works fine, some other edits i did apply perfectly).

so for example:
i want the pagelist to show the pages with the seleted value "Auto" in the custom field "branche".

so i added following code (and also in many other variations) in the "view.php" in the template folder.

<?php>
$pl = new PageList();
$pl->filterByAttribute('branche','Auto','like')
?>


but it does not affect anything!!

what i'm doing wrong? did i forget something? or are there some more canges to make somewhere else to achive the filtered pagelist results?
ryan replied on at Permalink Reply
ryan
$pl = new PageList();
$pl->filterByAttribute('branche','%Auto%','like')
// or
$db = Loader::db();
$val = 'Auto';
$val = '%'.$db->quote($val).'%';
$pl = new PageList();
$pl->filterByAttribute('branche',$val,'like');
VortexSurfer replied on at Permalink Reply
VortexSurfer
thanks for your help, but doesn't work.

first solution i already tried.

second gives me the same results as always, all pages without any filtering.

is this really the only code i have to add to view.php? nothing else?
VortexSurfer replied on at Permalink Reply
VortexSurfer
i finally got it working putting the first code ...
$pl = new PageList();
$pl->filterByAttribute('branche','%Auto%','like');

... into the controller.php in the /blocks/page_list folder.

but in the end i want to create many pages all with pagelists in it, which all should filter for different values. how could i achieve this?
i thought different custom templates would be the solution. but i guess i'm wrong with this, as i didn't ge the code working in the view.php.

any ideas?
(i'm a real php-newbie, so sorry for maybe stupid questions)
andrew replied on at Permalink Reply
andrew
Hopefully someday soon we're going to be modifying the page list block to be able to do some filtering by custom attribute. Then you'd be able to do this kind of thing without having to touch code at all. However, in the meantime, what about this approach:

In your template, do what you're currently doing. However instead of hard-coding the actual value that you're searching for, making it based on an attribute as well:

e.g. setup the template like this:

$pl = new PageList();
$pl->filterByAttribute('branche', '%' . $c->getAttribute('search_value') . '%', 'like');

And then, create a new page attribute. Give it the handle "search_value." Finally, in the custom attributes tab in the page properties dialog/pane, choose the value that you want to filter by. Then, you can add multiple pages of this type, and control what they filter by using different custom attributes.
VortexSurfer replied on at Permalink Reply
VortexSurfer
hhhm, sounds good, but does not work.

when i add

$pl->filterByAttribute('branche', '%' . $c->getAttribute('search_value') . '%', 'like');

in the controller, i get error message

Fatal error: Call to a member function getAttribute() on a non-object in

(if i put it in view.php i anyway get a error, same with the old hard-coding value)


and also i do not really understand how this method can be heplfull for me.

i have a site with a lot of different companies, each got its own page.
so in some cases i need pagelists where they all are dispayed, and in this specia case i want to filter them out (which i tried now by the custom attributes) by the business they are working in. so the value that should be filtered for changes for each pagelist i want to create.

the main thing i maybe do not understand/or maybe understood wrong:
i can easily set up some different custom templates for the pagelist with view.php, but they all use the same controller.php!?
VortexSurfer replied on at Permalink Reply
VortexSurfer
ok, wonderful, now i got it working.

but i had to put in the code in this way:

global $c;
$c->getCollectionAttributeValue('search_value');
$pl->filterByAttribute('branche', '%' . $c->getAttribute('search_value') . '%', 'like');


then it works like how i wanted.

now i got another problem.
i don't want this custom modification affect all my pagelists.

my idea was:
duplicating the pagelist-block in my blocks-folder and renaming it f.e. branchen_page_list (including doing all the necessary canges to controller.php and db.xml also) and uploading it as a new block, and use this one then, where i need the special filtering.

but when i insert this "branchen_page_list"-bloc somewhere on my page, it always has the same behaviour as the normal pagelist-block.

anything i forgot?
VortexSurfer replied on at Permalink Reply
VortexSurfer
ok ok, my fault, i forgot some controller add/edit-modifications.

works wonderfull now for me, thanks a lot.
VortexSurfer replied on at Permalink Reply
VortexSurfer
one more thing with the custom attributes.

what is the method for getting/displaying the name (NOT the handle, NOT the value) of a custom attribute?
VortexSurfer replied on at Permalink Reply
VortexSurfer
mmmhhh i thought everything is fine, but the filtering does only happen when i'm logged in as admin.

when i view the site as normal visitor it does no filtering.

any idea what might be my fault?
rainmaker replied on at Permalink Reply
rainmaker
Okay I got the filterbyAttribute working, but I'm having trouble passing a secondary argument into the getPages() function. It's not getting set for some odd reason. The filterbyAttribute works (I only have one argument for the moment), but the blasted $filter_legend is not working! Anyone else seeing why it's not getting set?

function getPages($query = null, $filter_legend = null) {
         Loader::model('page_list');
         $db = Loader::db();
         $bID = $this->bID;
         if ($this->bID) {
            $q = "select num, cParentID, cThis, orderBy, ctID, displayAliases, rss from btPageList where bID = '$bID'";
            $r = $db->query($q);
            if ($r) {
               $row = $r->fetchRow();
            }
         } else {
            $row['num'] = $this->num;
            $row['cParentID'] = $this->cParentID;
            $row['cThis'] = $this->cThis;
            $row['orderBy'] = $this->orderBy;
andrew replied on at Permalink Reply
andrew
Any time you're using any of the classes that extend databaseitemlist, you can do:

$pl = new PageList();
$pl->debug();


And you'll see the final SQL query that's printing out. This is very helpful. Sometimes the SQL query will look imposing, especially in the page list class, because we're trying to include permissions AND aliases in the page list query, which makes it confusing and long, but hopefully this will help debugging.
brennaH replied on at Permalink Reply
brennaH
Many thanks, Andrew -- that's exactly the information that I was looking for.

Outputting attributes is a no-brainer, but the dynamic filtering (in precisely the way that you were describing) was stalling me out for the multi-select.

This is enormously helpful.
brennaH replied on at Permalink Reply
brennaH
Everything is working beautifully, but I have one last (quick, hopefully) question: in the block add/edit form I'd like to pull back all the multi-select options for a given attribute.

Using your example, if my select attribute is Color, I'd like to output Red, Yellow, Green in a select form dropdown (or checkboxes or whatever). Right now I am just using an open text field, which is less user friendly.

I've got the SQL query to pull back the values, but again I'd prefer a function, in case during an upgrade something changes. Suggestions?

Many thanks again for the help.
braincramp replied on at Permalink Reply
braincramp
I would love to see this in action, any chance you would be releasing this page list filter block?
davygee replied on at Permalink Reply
This is exactly what I am trying to do. We need this kind of page_list functionality for a Cottage site we are building to search through custom attributes attached to each cottage page. I would be grateful for any information on how this is done. Even in a simpler fashion, we really may only need to have a defined custom page_list view for each type of search and working out how to call up custom attributes is a necessity, so any help would be gratefully received.

Cheers in advance.
rafaelsm replied on at Permalink Reply
I'm trying to do the exact same thing, a page list filter by a custom attibute, but I can't get andew's code to work in the filter.
I'm using it in the view of the block I'm creating, and it's returning an empty array.
Maybe it's because the custom attribute I'm trying to get is a select type, is there another function to that?

Thanks in advance!
rafaelsm replied on at Permalink Reply
Made that work, I was missing some syntax

it worked like that:
$page_list_obj->filterByAttribute('custom_attribute', '%attribute%', 'like');


My bad :(
davygee replied on at Permalink Reply
Have managed to work out how to kind of do it. Although the code isn't great, it works for the moment, and seeing as there is only going be be around 40 pages to show in the pagelist, it shouldn't cause us a problem.

The code I have used is below:

<?
   defined('C5_EXECUTE') or die(_("Access Denied."));
   // now that we're in the specialized content file for this block type, 
   // we'll include this block type's class, and pass the block to it, and get
   // the content  ?>
    <style type="text/css">
   #select {
      color:#333333;
      font-family:Verdana, Arial, Helvetica, sans-serif;
      font-size:10px;
      padding:2px;
      width:100px;
      border:dotted #333333 1px;
   }
   #title {


Although it would be idea if we could include an ascend/descend sort feature as well, but not sure how to do it, and would like to know how to show a full list on the first load without the form submit.

Any help would be great.
ceyhuncenger replied on at Permalink Reply
ceyhuncenger
You have something like this in your template
<?php
   if (isset($_POST['submit']))
   {
     ..........
   }
?>

and if you want to show all the pages before the user post the form, put "else" at the end of that "if" and put the same code inside that "else", and don't forget to delete $p = $_POST['pets'] and others including the "if" statement under them.
else {
      ...............
      in here delete
      $p = $_POST['blabla'];
      ....
      ....
      also delete this
      if (($pets == $p or $p == 'All').......
   }


But there should be a better way to do filtering and sorting.

I think instead of using action="#" for the POST, we should use ajax actions as Andrew mentioned in like-this block tutorial. That is why I copied my page-list controller to my root/blocks/page_list folder and inside that controller we have to describe actions but I don't know how because of my lack of knowledge of PHP and OOP.

Can someone help me about that please?
How to get pages that the page-list already found into an array and do the filtering action just in that array of pages?
dancer replied on at Permalink Reply
dancer
Hello,

So I have searched around and come up with this thread as the best for my answer. I thought I would post here just incase anyone else is following the thread and they see this and think:

"No stupid, this is how you do it"

So. I am doing my sports team website where we write match reports for each team. I would like to output a page list of Match reports by team only.

So I have a select attribute (called 'team') with all the options in (a dropdown)
- Mens 1s
- Mens 2s
- Mens 3s
- Ladies 1s
- Ladies 2s
etc.
(only one can be selected)

Now I would like to have a pagelist output of "Mens 1s" reports.

Also in an ideal world I would like to use the same Custom template for every team, so it looks for the parent attribute and then knows what to filter it by.

Happy to start with the basics though.
RadiantWeb replied on at Permalink Reply
RadiantWeb
Related Pages block does this. very simply.

ChadStrat
dancer replied on at Permalink Reply
dancer
Hey chadstrat,
Thanks for the response!
Yes I had seen this addon. I suppose I wanted to learn what I needed
to do instead of using an add-on I figure that if I bought every add-
on I would always be none the wiser.
Cheers
D
kirkroberts replied on at Permalink Reply
kirkroberts
Just wanted to mention that over two years later this thread was enormously helpful for me. I might never have figured out the % and "like" otherwise. It would be nice to have this information in the documentation or API reference.
BinaryFold4 replied on at Permalink Reply
BinaryFold4
This is all great. But two years on there is still no way of doing an OR operation on the PageList filter?
ScottC replied on at Permalink Reply
ScottC
its not api'y, i found it through reading the forums

$imploded = implode(' OR ',$queryArr); //imploded is your or statement
$pl->filter(false, '('.$imploded.')');

I haven't traced down what the first parameter false does but I was able to use or's with the second line
BinaryFold4 replied on at Permalink Reply
BinaryFold4
Managed to work this out myself after some time. Thanks though.
dancer replied on at Permalink Reply
dancer
Would be good if you could post your solution in this thread as it does get visited by quite a few people and would help others in the future!

Many thanks :)

=-=-=-=-=-=-=-=-=
Sent from my Generic Handheld device.
Usually on the move so please excuse typos!
BinaryFold4 replied on at Permalink Reply
BinaryFold4
My first attempt used filter() but I didn't know about the trick of using false as the first param, I passed it three params like so:

$pl->filter("ak_tags LIKE '%something%' OR ak_tags LIKE '%somethingelse%' ", false, false);


The issue with this was that one of the false params creates a blank string in the query, so I resorted to using SQL injection (!) to make the query work:

$pl->filter("ak_tags LIKE '%something%' OR ak_tags LIKE '%somethingelse%' AND '' = ", false, false);


This ensured that the final query was valid and looked like this

"ak_tags LIKE '%something%' OR ak_tags LIKE '%somethingelse%' AND '' = '' "


ScottC's use of filter with false as the first param works too and isn't as messy. Thank you Scott.

Jake
RadiantWeb replied on at Permalink Reply
RadiantWeb
if you want the search to be more "strict" you will want to include '\n' on either side of the tag.

otherwise when searching for "something" and "else" , results "something here" and "here else" will return.

if you include the '\n' in your filter, searching "something" and "else" will only return "something" match or "else".

so the filter would look like this:

$pl->filter(false,"ak_tags LIKE '%\n$tagname\n%'");


just thought I would through that out there. I have seen people ask for more strict searching before. "\n" is the ticket respective to the select attribute.

ChadStrat
BinaryFold4 replied on at Permalink Reply
BinaryFold4
Thanks Chad. I do actually do this, I just didn't add it in my quick codey examples above. Thanks again.
melat0nin replied on at Permalink Reply
melat0nin
Anyone know how to filter by a multiselect (or multi-checkbox) custom attribute?

I had a look through the searchForm() function in the controller for the select attribute:

public function searchForm($list) {
      $options = $this->request('atSelectOptionID');
      $optionText = array();
      $db = Loader::db();
      $tbl = $this->attributeKey->getIndexedSearchTable();
      if (!is_array($options)) {
         return $list;
      }
      foreach($options as $id) {
         if ($id > 0) {
            $opt = SelectAttributeTypeOption::getByID($id);
            if (is_object($opt)) {
               $optionText[] = $opt->getSelectAttributeOptionValue(true);
               $optionQuery[] = $opt->getSelectAttributeOptionValue(false);
            }


but the last part isn't very clear to me. I can't find any API docs on the Pagelist->filter() method, or what form $multiString should take.

Any thoughts?
SkyBlueSofa replied on at Permalink Reply
SkyBlueSofa
This is code from one of my addons. It's untested, but might get you in the right direction:
$searchValue = 'John Doe';
$handle = 'my_attribute';
Loader::model('page_list');
$pl = new PageList();
$akhandle = 'ak_'.$handle;
$pl->filter(false, "(".$akhandle."='".searchValue."' OR ".$akhandle." LIKE '%\\n".searchValue."\\n%')");

What this does is:
1. Sets the 'search value' to 'John Doe'. This could be provided via GET or POST.
2. Set the handle variable to the name of the attribute handle (my_attribute). You can find this if you edit the page attribute.
3. Load the Page List model
4. Create a new Page List
5. Prepends 'ak_' to the handle for use in the page list.
6. Filters the page list ($pl) using a SQL snippet that only includes records that are either equal to 'John Doe' or contains 'John Doe'. (The first value is set to false and the section value is set to a SQL snippet.)

FYI, I believe that multiselect values are saved in a 'newline' separated list (each value is stored on a separate line).
hutman replied on at Permalink Reply
hutman
From trying to make page search more accurate over here:
http://www.concrete5.org/index.php?cID=379811...

It appears that this is actually a security hole because the $tagname variable is not escaped. Safer code would look like this:

$pl->filter(false, 'ak_tags LIKE "%\n' . $db->escape($tagname) . '\n%"');