Functional custom search by attribute
Permalink 5 users found helpful
A custom search by attributes block/form with user selected inputs (text, select boxes, radio/check buttons) will be greatly appreciated. The best I could do was a workaround a bug in the database_indexed_search and search by a single attribute variable. How to search by attributes with more variables (user inputs)?
If anyone can help me with this one, I'm happy to post the whole code for others to reuse. Maybe someone could make a proper installable block out of it.
If anyone can help me with this one, I'm happy to post the whole code for others to reuse. Maybe someone could make a proper installable block out of it.
I appreciate some need to be paid for their efforts. But I wish I had more help from the community. If I was working at a web design company or making a business out of my website, I wouldn't hesitate to purchase any add-ons especially if it's such a small investment you can later reuse a thousand times. But at this stage I can't afford US$20 for something I don't even know is going to do what I need.
I think Drupal and Joomla have such a huge community because there's a lot of free add-ons for them. If C5 had more free stuff I'm sure they'd build a big community too. And the bigger the community the bigger an opportunity to get paid for the service.
If only could they offered something like this:http://extensions.joomla.org/extensions/search-a-indexing/tags-a-cl...
I think Drupal and Joomla have such a huge community because there's a lot of free add-ons for them. If C5 had more free stuff I'm sure they'd build a big community too. And the bigger the community the bigger an opportunity to get paid for the service.
If only could they offered something like this:http://extensions.joomla.org/extensions/search-a-indexing/tags-a-cl...
if you don't want to spend $20 on that search tools block (which does look really nice) you can code something up with the help of the /concrete/models/page_list class, which has a bunch of built in methods for filtering search results. check out the pagelist block's controller ( /concrete/blocks/pagelist/controller.php ) for a usage example.
That's exactly what I was trying to do but looks like the page_list class doesn't do what it's supposed to do or it's got a bug (http://www.concrete5.org/community/forums/customizing_c5/how-to-change-search-block-to-filterbyattribute#51682/).
yeah, searching by attribute isn't as easy as it could be.
here's a code snippet:
$pagelist = new PageList();
$ak = CollectionAttributeKey::get( $akID );
$type = $ak->getAttributeType();
$cnt = $type->getController();
$cnt->setAttributeKey($ak);
$req = $pagelist->getSearchRequest();
$cnt->setRequestArray($req);
$cnt->searchForm($pagelist);
$results = $pagelist->get( $pageSize, $result_offset );
here's a code snippet:
$pagelist = new PageList();
$ak = CollectionAttributeKey::get( $akID );
$type = $ak->getAttributeType();
$cnt = $type->getController();
$cnt->setAttributeKey($ak);
$req = $pagelist->getSearchRequest();
$cnt->setRequestArray($req);
$cnt->searchForm($pagelist);
$results = $pagelist->get( $pageSize, $result_offset );
Tony,
What does this code do? For example, the PageList class doesn't have a function getSearchRequest(). Or it's in the DatabaseItemsList? How can I find all those classes? Do I need to load the other classes or it's done automatically?
Isn't this inventing the wheel? I mean, if the Search block's controller do_search() function already can filterByAttribute (as I showed here:http://www.concrete5.org/community/forums/customizing_c5/how-to-cha... can't it be somehow modified to do search to match all selected attributes?
Or you're saying there's no other way but to do this from scratch as you've showed?
Thank you for your help.
What does this code do? For example, the PageList class doesn't have a function getSearchRequest(). Or it's in the DatabaseItemsList? How can I find all those classes? Do I need to load the other classes or it's done automatically?
Isn't this inventing the wheel? I mean, if the Search block's controller do_search() function already can filterByAttribute (as I showed here:http://www.concrete5.org/community/forums/customizing_c5/how-to-cha... can't it be somehow modified to do search to match all selected attributes?
Or you're saying there's no other way but to do this from scratch as you've showed?
Thank you for your help.
the pagelist's parent class, DatabaseItemList ( /concrete/libraries/item_list.php ), does have a method called getSearchRequest(), which the pagelist class inherits.
If you look at the search block's controller, it uses IndexedPageList, which has the comment "@DEPRECATED - Just use PageList with filterByKeywords instead. We'll keep this around so people know what to expect", and it extends the PageList class anyway, so the example I'm showing would work probably work fine within the search block do_search. just replace the $pagelist from my example with $ipl.
If you look at the search block's controller, it uses IndexedPageList, which has the comment "@DEPRECATED - Just use PageList with filterByKeywords instead. We'll keep this around so people know what to expect", and it extends the PageList class anyway, so the example I'm showing would work probably work fine within the search block do_search. just replace the $pagelist from my example with $ipl.
like...
note, that this is expecting the search form to display data in the same way as an attribute's 'form' is displayed.
$attrKey->render('form', false, true);
Loader::library('database_indexed_search'); $ipl = new IndexedPageList(); $ipl->filterByKeywords($q); if( is_array($_REQUEST['search_paths']) ){ foreach($_REQUEST['search_paths'] as $path) { $ipl->addSearchPath($path); } } //make sure you set a value for $akID! $ak = CollectionAttributeKey::get( $akID ); $type = $ak->getAttributeType(); $cnt = $type->getController(); $cnt->setAttributeKey($ak); $req = $ipl->getSearchRequest(); $cnt->setRequestArray($req);
Viewing 15 lines of 17 lines. View entire code block.
note, that this is expecting the search form to display data in the same way as an attribute's 'form' is displayed.
$attrKey->render('form', false, true);
"make sure you set a value for $akID" - you mean an attribute name/handle in each page?
Thanks a lot for the example.
It's nearly impossible for those new to C5 to get their way around due to lack or incompleteness of documentation. I'm not blaming anyone, just stating the fact that it's so hard to understand the C5 way of doing things. This is why I'm asking so many dumb questions.
The best documentation IMO is for Qt toolkit. If only was C5's done in a similar way, it would have been priceless.
Thanks a lot for the example.
It's nearly impossible for those new to C5 to get their way around due to lack or incompleteness of documentation. I'm not blaming anyone, just stating the fact that it's so hard to understand the C5 way of doing things. This is why I'm asking so many dumb questions.
The best documentation IMO is for Qt toolkit. If only was C5's done in a similar way, it would have been priceless.
you can also grab the attribute key ($ak) by handle. As an example to display past and current events (event page type comes within the calendar package) in a page list, i copied just the page list controller to <web-root>/blocks/page_list to override it there and filter for past and current projects. This code is located before the $pl is set:
Just for explanation: If a page has the attribute 'calendar_event_date_time' set, we assume it's an event. However if we got an event_listing or an event_archiv page type handle active or past projects are outputted in the page list.
function getPages($query = null) { ... Loader::model('attribute/categories/collection'); $cak_end_date = CollectionAttributeKey::getByHandle('calendar_event_end_date_time'); if (is_object($cak_end_date)) { $localDateTime = Loader::Helper('date')->getLocalDatetime(); $cah = 'calendar_event_end_date_time'; if ( $c->getCollectionTypeHandle() == 'event_listing') { $pl->filterByAttribute( $cah, $localDateTime, '>='); } else if ( $c->getCollectionTypeHandle() == 'event_archiv') { $pl->filterByAttribute( $cah, $localDateTime, '<'); } } ... }
Just for explanation: If a page has the attribute 'calendar_event_date_time' set, we assume it's an event. However if we got an event_listing or an event_archiv page type handle active or past projects are outputted in the page list.
Thank you, I'll try these.
Aren't page attributes stored in the database? Wouldn't it be easier just to query the DB and get only those pages which match the whole query?
Aren't page attributes stored in the database? Wouldn't it be easier just to query the DB and get only those pages which match the whole query?
Attributes are stored in the db and are used for several table relation ships.
Page list inherits a lot stuff from DatabaseItemList and ItemList, which forces a very generic use of various filter methods for database items.
I don't think it's easier to setup the query by hand, but try it out :)
Page list inherits a lot stuff from DatabaseItemList and ItemList, which forces a very generic use of various filter methods for database items.
I don't think it's easier to setup the query by hand, but try it out :)
Well, I tried everything but got nowhere, I'm still missing something, most of those functions gave me errors like "Fatal error: Call to a member function getCollectionTypeHandle() on a non-object in /srv/www/htdocs/c5/blocks/search/controller.php", looks like I'm missing some class initiations but I don't know which classes I have to instansiate.
I've looked in /models/attribute/categories/collection.php, 'getCollectionTypeHandle()' doesn't exist. I've checked in key.php, it doesn't exist there either. What's the $c object by the way?
Well, the example above has
and none of these exist in neither of those classes. Please tell me how someone new to C5 can figure out what's going on?
I've looked in /models/attribute/categories/collection.php, 'getCollectionTypeHandle()' doesn't exist. I've checked in key.php, it doesn't exist there either. What's the $c object by the way?
Well, the example above has
getCollectionTypeHandle() filterByAttribute()
and none of these exist in neither of those classes. Please tell me how someone new to C5 can figure out what's going on?
I think I'm getting somewhere, I've got one more wall to climb over.
I've finally figured out how to get pages' attributes and can display any or all of them. My question is, if I have a page list, how can I get page attributes of individual pages in that list? What's the function?
Here's a part of my controller which gets a 'Meta Title' attribute string of the current page which I display in the view.php as a $temp:
As I understand it, the $res is a list of pages. How do I get each page's attribute from this list?
And how should I modify this
to display the results if IndexedSearchResult is not part of PageList? It says: "Fatal error: Cannot use object of type Page as array in /srv/www/htdocs/c5/blocks/search/controller.php"
Thank you.
I've finally figured out how to get pages' attributes and can display any or all of them. My question is, if I have a page list, how can I get page attributes of individual pages in that list? What's the function?
Here's a part of my controller which gets a 'Meta Title' attribute string of the current page which I display in the view.php as a $temp:
Loader::model('page_list'); $pl = new PageList(); if( is_array($_REQUEST['search_paths']) ){ foreach($_REQUEST['search_paths'] as $path) { //if(!strlen($path)) continue; $pl->addSearchPath($path); } } $res = $pl->getPage(); foreach ($this->getCustomFields() as $att) { $val = $this->getValue($att->akHandle); if ($att->akName == 'Meta Title'){ $temp = $att->akName . ': ' . $val; } }
As I understand it, the $res is a list of pages. How do I get each page's attribute from this list?
And how should I modify this
foreach($res as $r) { $results[] = new IndexedSearchResult($r['cID'], $r['cName'], $r['cDescription'], $r['score'], $r['cPath'], $r['content']); }
to display the results if IndexedSearchResult is not part of PageList? It says: "Fatal error: Cannot use object of type Page as array in /srv/www/htdocs/c5/blocks/search/controller.php"
Thank you.
"how do i get each page's attribute from this list?"
foreach($res as $page){
echo $page->getAttribute('my_attribute_handle');
}
I don't really understand what you're trying to do with a lot of that code, but the pagelist block should return fully instantiated Page object, not an array.
foreach($res as $page){
echo $page->getAttribute('my_attribute_handle');
}
I don't really understand what you're trying to do with a lot of that code, but the pagelist block should return fully instantiated Page object, not an array.
I'm trying to do the same thing - to filter a list of pages by a page type first, and then to filter the filtered list by page attributes.
Yes, the PageList returns a page of pages, and I need to show only those pages which match all selected attributes. If I don't do that, it shows every single page of the website. If I filterByAttribute, it only filters by one page attribute, I need to filter by at least 5. That's why I need to get a list of pages first, check one if all its attributes match the ones selected, if yes - show it, if not move to the next page and so on.
I guess I'll have to change the code to:
where the 'attribute_value_1' and 'attribute_value_2' are the selected values in the form.
Or you have a better idea how this can be done?
Yes, the PageList returns a page of pages, and I need to show only those pages which match all selected attributes. If I don't do that, it shows every single page of the website. If I filterByAttribute, it only filters by one page attribute, I need to filter by at least 5. That's why I need to get a list of pages first, check one if all its attributes match the ones selected, if yes - show it, if not move to the next page and so on.
I guess I'll have to change the code to:
foreach($res as $page){ if ($page->getAttribute('attribute_handle_1') == 'attribute_value_1' && $page->getAttribute('attribute_handle_2') == 'attribute_value_2' ) //show the page as in my above code }
where the 'attribute_value_1' and 'attribute_value_2' are the selected values in the form.
Or you have a better idea how this can be done?
No, it didn't work. Here's my do_search function:
If I use
$temp = $this->page->getAttribute('meta_title');
it shows the current (Search) page attribute. If I use
$temp = $r->page->getAttribute('meta_title');
is says "Fatal error: Call to a member function getAttribute() on a non-object".
function do_search() { $q = $_REQUEST['query']; $length = $_REQUEST['length']; $continent = $_REQUEST['continent']; $adventure = $_REQUEST['adventure']; $interest = $_REQUEST['interest']; $attributeKeyHandle = 'continent'; Loader::library('database_indexed_search'); //$pl = new IndexedPageList(); //$ipl->filterByKeywords($q); //Loader::library('indexed_search'); Loader::model('page_list'); $pl = new PageList(); $pl->filterByCollectionTypeHandle('Press Release'); if( is_array($_REQUEST['search_paths']) ){
Viewing 15 lines of 32 lines. View entire code block.
If I use
$temp = $this->page->getAttribute('meta_title');
it shows the current (Search) page attribute. If I use
$temp = $r->page->getAttribute('meta_title');
is says "Fatal error: Call to a member function getAttribute() on a non-object".
OK, I finally got it!!! I can filter the list of all articles by one attribute. This has been a freaking ride of my life for the past few week. Now all I need to do is to add 3-4 more && == to the if() and hope it's all gonna work as I hope.
Here's what did it in the controller:
in the view:
and in the overriden database_indexed_search:
Then when I polish everything, I'll submit the whole custom_search module to the Marketplace, hope they can help me make a free block out of it.
If you have better ideas how to do this, please feel free to suggest. I don't really like that idea of building that long array in the database_indexed_search class adding all possible attributes to it. I'm sure there's gotta be a simple solution. If only could I figure out how to get attributes directly from the $pl page list???...
Geez... gotta go and have a big glass of wine :)
Here's what did it in the controller:
foreach($res as $r) { if($r['attribute'] == $continent){ $results[] = new IndexedSearchResult($r['cID'], $r['cName'], $r['cDescription'], $r['score'], $r['cPath'], $r['content'], $r['attribute']); } } ... public function getSearchPage($p) { $results = array(); foreach($p as $c) { $results[] = array('cID' => $c->getCollectionID(), 'cName' => $c->getCollectionName(), 'cDescription' => $c->getCollectionDescription(), 'score' => $c->getPageIndexScore(), 'cPath' => $c->getCollectionPath(), 'content' => $c->getPageIndexContent(), 'attribute' => $c->getAttribute('continent')); } return $results; }
in the view:
<b><?php echo $r->getAttribute();?></b>
and in the overriden database_indexed_search:
class IndexedSearchResult { public function __construct($id, $name, $description, $score, $cPath, $content, $attribute) { $this->cID = $id; $this->cName = $name; $this->cDescription = $description; $this->score = $score; $this->cPath = $cPath; $this->content = $content; $this->attribute = $attribute; } public function getID() {return $this->cID;} public function getName() {return $this->cName;} public function getDescription() {return $this->cDescription;} public function getScore() {return $this->score;} public function getCollectionPath() {return $this->cPath;}
Viewing 15 lines of 18 lines. View entire code block.
Then when I polish everything, I'll submit the whole custom_search module to the Marketplace, hope they can help me make a free block out of it.
If you have better ideas how to do this, please feel free to suggest. I don't really like that idea of building that long array in the database_indexed_search class adding all possible attributes to it. I'm sure there's gotta be a simple solution. If only could I figure out how to get attributes directly from the $pl page list???...
Geez... gotta go and have a big glass of wine :)
my footprint come here.
this guy took 3 days to figure it out, how to get search result by useratribut.
thank you, i will try your code linux, i too want a search result by a real_name i define in user attribut.
this guy took 3 days to figure it out, how to get search result by useratribut.
thank you, i will try your code linux, i too want a search result by a real_name i define in user attribut.
lol no. That guy is crazy. You can just do a user list
Loader::model('user_list'); $ul = new UserList(); $ul->filterByAttribute('real_name'); $users = $ul->get(); if(!empty($users)) { foreach($users as $user) { //do some stuff } }
thanks for saving my day :D
http://www.concrete5.org/marketplace/addons/search-tools/...