Giter Club home page Giter Club logo

silverstripe-searchable-dataobjects's Introduction

Searchable DataObjects

Searchable DataObjects is a module that permit to include DataObjects into frontend search.

Introduction

Pages are not always the better way to implement things. For example site news can grow rapidly and the first side effect would be a big and difficult to manage SiteTree. DataObjects help maintaining things clean and straight, but unfortunately they are not included in frontend search. This module let you insert DataObject in search.

Requirements

  • SilverStripe 4.1
  • g4b0/htmlpurifier

For SilverStripe 3.1 usage please referr to version 3.0 and below. For SilverStripe >3.1 & <4.0 usage please referr to version 4.x.

Installation

Install the module through composer:

composer require g4b0/searchable-dataobjects
composer update

Make the DataObject (or Pages) implement Searchable interface (you need to implement Link(), getSearchFilter(), getTitleFields(), getContentFields()):

Note: getSearchFilterByCallback() is an optional filter. If you don't plan on calculating any value to determine a returned true or false value it is suggested you don't add this function to your DataObject or Page type.

use g4b0\SearchableDataObjects\Searchable;

class DoNews extends DataObject implements Searchable {

    private static $db = array(
        'Title' => 'Varchar',
        'Subtitle' => 'Varchar',
        'News' => 'HTMLText',
        'Date' => 'Date',
    );
    private static $has_one = array(
        'Page' => 'PghNews'
    );

    /**
     * Link to this DO
     * @return string
     */
    public function Link() {
        return $this->Page()->Link() . 'read/' . $this->ID;
    }

    /**
     * Filter array
     * eg. array('Disabled' => 0);
     * @return array
     */
    public static function getSearchFilter() {
        return array();
    }

    /**
     * FilterAny array (optional)
     * eg. array('Disabled' => 0, 'Override' => 1);
     * @return array
     */
    public static function getSearchFilterAny() {
        return array();
    }

    /**
     * FilterByCallback function (optional)
     * eg. function($object){
     *  return ($object->StartDate > date('Y-m-d') || $object->isStillRecurring());
     * };
     * @return array
     */
    public static function getSearchFilterByCallback() {
        return function($object){ return true; };
    }

    /**
     * Fields that compose the Title
     * eg. array('Title', 'Subtitle');
     * @return array
     */
    public function getTitleFields() {
        return array('Title');
    }

    /**
     * Fields that compose the Content
     * eg. array('Teaser', 'Content');
     * @return array
     */
    public function getContentFields() {
        return array('Subtitle', 'Content');
    }
}

Here you are a sample page holder, needed to implement the Link() function into the DataObject:

class PghNews extends Page {

    private static $has_many = array(
        'News' => 'DoNews'
    );

    public function getCMSFields() {
        $fields = parent::getCMSFields();

        /* News */
        $gridFieldConfig = GridFieldConfig_RelationEditor::create(100);
        // Remove unlink
        $gridFieldConfig->removeComponentsByType('GridFieldDeleteAction');
        // Add delete
        $gridFieldConfig->addComponents(new GridFieldDeleteAction());
        // Remove autocompleter
        $gridFieldConfig->removeComponentsByType('GridFieldAddExistingAutocompleter');
        $field = new GridField('Faq', 'Faq', $this->News(), $gridFieldConfig);
        $fields->addFieldToTab('Root.News', $field);

        return $fields;
    }
}

class PghNews_Controller extends Page_Controller {

    private static $allowed_actions = array(
        'read'
    );

    public function read(SS_HTTPRequest $request) {
        $arguments = $request->allParams();
        $id = $arguments['ID'];

        // Identifico la faq dall'ID
        $Object = DataObject::get_by_id('DoNews', $id);

        if ($Object) {
            //Popolo l'array con il DataObject da visualizzare
            $Data = array($Object->class => $Object);
            $this->data()->Title = $Object->Title;

            $retVal = $this->Customise($Data);
            return $retVal;
        } else {
            //Not found
            return $this->httpError(404, 'Not found');
        }
    }
}

Extend Page and the desired DataObjects through the following yaml:

Page:
  extensions:
    - g4b0\SearchableDataObjects\SearchableDataObject
DoNews:
  extensions:
    - g4b0\SearchableDataObjects\SearchableDataObject

Run a dev/build and then populate the search table running PopulateSearch task:

sake dev/build "flush=all"
sake dev/tasks/PopulateSearch

Enjoy the news into the search results :)

Modifying

Set the number of search results per page

Setting the g4b0\SearchableDataObjects\CustomSearch.items_per_page config setting you can define, how many search results per page are shown. Default is 10

By default the search result is shown at the same page, so if you're searching e.g. on the /about-us/, the results are shown on /about-us/SearchForm/?s=foo. If you don't like that, you can define any Page or Controller class in the g4b0\SearchableDataObjects\CustomSearch.search_controller setting. If you set this setting to this, the current page will be used. Defaults to SearchPage and falls back to the current page if no SearchPage is found.

g4b0\SearchableDataObjects\CustomSearch:
  items_per_page: 15
  search_controller: g4b0\SearchableDataObjects\SearchPage #page type to show the search

Note

Searchable DataObjects module use Mysql NATURAL LANGUAGE MODE search method, so during your tests be sure not to have all DataObjetcs with the same content, since words that are present in 50% or more of the rows are considered common and do not match.

From MySQL manual entry [http://dev.mysql.com/doc/refman/5.1/en/fulltext-search.html]:

A natural language search interprets the search string as a phrase in natural human language (a phrase in free text). There are no special operators. The stopword list applies. In addition, words that are present in 50% or more of the rows are considered common and do not match. Full-text searches are natural language searches if the IN NATURAL LANGUAGE MODE modifier is given or if no modifier is given.

TODO

  • Add other search method in configuration

Suggested modules

silverstripe-searchable-dataobjects's People

Contributors

antons- avatar g4b0 avatar muskie9 avatar nblum avatar ntsim avatar rotassator avatar wernerkrauss avatar yusuf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

silverstripe-searchable-dataobjects's Issues

Error : Class 'HTMLPurifier_Config' not found

Running Task Populate Search

[Emergency] Uncaught Error: Class 'HTMLPurifier_Config' not found
GET /dev/tasks/PopulateSearch

any help?
OK. I found out:
Thats more an issue (?) on Purifier.php

i had to add a require_once right after 'namespace g4b0\HtmlPurifier;':

namespace g4b0\HtmlPurifier;
require_once '../vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php';

sepp.

Possibility to filter by language or Subsite?

Any chance to get additional fields in the search table, e.g. for subsite/multisite environment or for locale?

How could we easily alter the seach DB? Should we use a DataObject instead? You could still query it manually... Or just with extensions hooks when we generate the table / insert / search ???

Possibility to search substrings / use wildcard

At the moment mysql only returns full words found.

In German language we have a lot of combined words, like "Audittermine" which is combined by "Audit" and "Termine"

It would be great if a search for "Audit" would also match "Audittermine", or search for singular "Termin" would also match "Termine", maybe configurable?

From a short research we'd need a wildcard in the query and switch to BOOLEAN MODE, see http://stackoverflow.com/a/9284693/4137738

SS 4.5 populate search issue

Edit
After adding implements Searchable to my dataobject, dev/build fails.
/Edit

After installation on SS4.5.1 I get the following error when I run the populate search task

Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: TRUNCATE TABLE SearchableDataObjects Table ... doesn't exist

Thanks in advance

PaginatedList properties are protected..

.. so they can't be set directly in CustomSearch::getSearchResults();

You need to use :
$ret = new PaginatedList($list);
$ret->setPageLength(10);
$ret->setPageStart($s);
$ret->setTotalItems($list->count());
$ret->setLimitItems(10);

Make search form configurable

How about generating search form's fields and actions in seperate methods with hooks for modifying them? E.g. for putting default search text as placeholder instead of value etc...

If you want i can provide a PR for that.

SearchableDataObject DB table has multiple keys/indexes

I am getting the following error when attempting to /dev/build with the silverstripe-searchable-dataobjects module installed:

ALTER TABLE SearchableDataObjects ADD FULLTEXT (`Title` ,`Content`) | Too many keys specified; max 64 keys allowed

screen shot 2015-10-07 at 11 08 00

Based on the error I am assuming the issue is in someway related to the following line in SearchableDataObject::augmentDatabase():

DB::query("ALTER TABLE SearchableDataObjects ADD FULLTEXT (Title ,Content)");

I am guessing this query is being run multiple times and a new index/key is being created in the DB each time it is run.

As you can see from the following screenshots (taken in Navicat as MySQL DB management tool) after initially installing the module running /dev/build and /dev/tasks/PopulateSearch I have 24 indexes, then some time later (possibly having changed the current locale for the site) I run another /dev/build and this attempts to increase to > 63 indexes which is what triggers the SQL error.

After initial set up:
screen shot 2015-10-06 at 19 11 28

After a subsequent /dev/build some time later (having switched locale in my config files):
screen shot 2015-10-06 at 18 59 13

I am not familiar with the augmentDatabase() method or where and when it is called otherwise I would investigate further and propose a fix.

This article may be of some help, at very least it helped me track down the issue itself - https://www.safaribooksonline.com/blog/2012/11/06/avoiding-too-many-keys-in-mysql/

Any ideas?

mysql error on a fresh install

tryting to deploy my website i get the following error on the very first dev/build on a clean database:

[User Error] Couldn't run query: INSERT INTO SearchableDataObjects (ID, ClassName, Title, Content) VALUES (2, 'Page', 'Über uns', 'Sie können diese Seite mit Ihren eigenen Inhalten füllen, oder sie löschen und Ihre eigenen Seiten erstellen.') ON DUPLICATE KEY UPDATE Title='Über uns', Content='Sie können diese Seite mit Ihren eigenen Inhalten füllen, oder sie löschen und Ihre eigenen Seiten erstellen.' Table 'silverstripe.SearchableDataObjects' doesn't exist

Guess it's cause onAfterPublish hook tries to update the non existent table. Any way to check if the table exists before trying to update?

Provide own search controller

searching on the current page might be simple but not the most elegant solution.

Also it conflicts with custom $url_handlers like we use when trying to eleminating actions (aka url slug).

readme.md example DataObject class code bug

Hi Gabriele,

Just getting to grips with your module which looks good so far, one thing that has had me stuck trying to get it working for some time was that the readme.md example code of how to set up a DataObject to be searchable contains the following:

    /**
     * FilterByCallback function (optional)
     * eg. function($object){
     *  return ($object->StartDate > date('Y-m-d') || $object->isStillRecurring());
     * };
     * @return array
     */
    public static function getSearchFilterByCallback() {
        return function($object){};
    }

having investigated, I can see that the PopulateSearch class (which seems to be run via /dev/tasks/PopulateSearch and onAfterWrite of any DataObject which is being made searchable) contains the following conditional statement for each DataObject marked as being searchable:

if(method_exists($class, 'getSearchFilterByCallback')){
                $dos = $dos->filterByCallback($class::getSearchFilterByCallback());
}

Although I do appreciate your comments above the getSearchFilterByCallback() method do provide an example of a callback method, I had assumed that the default function in your example ( function($object){}; ) would have ben sufficient, but the lack of any return statement means that this example effectively returns false every time it is run.

This means that none of my DataObjects were being stored in the SearchableDataObjects database table (as getSearchFilterByCallback() was excluding them all) and therefore none were showing up in the search.

I would suggest making the example method contain return true; would be a logical addition and hopefully save others some time and not following the same path as I did. For example:

    /**
     * FilterByCallback function (optional)
     * eg. function($object){
     *  return ($object->StartDate > date('Y-m-d') || $object->isStillRecurring());
     * };
     * @return array
     */
    public static function getSearchFilterByCallback() {
        return function($object){return true;};
    }

SearchableDataObject does take into account SiteTree versioned

SearchableDataObject::onAfterWrite gives ambigous ID when saving a Page with extra fields.
This fixes the problem for me:

$table = $this->owner->class;
if(is_a($this->owner, 'SiteTree') && Versioned::current_stage() == 'Live') {
$table = 'SiteTree_Live';
}
$filterID = $table . ".ID={$this->owner->ID}";

Deprecation warning after PHP7.2

[Deprecated] class_exists(): Passing null to parameter #1 ($class) of type string is deprecated

This issue has been fixed with this PR on master branch : 1305d68

But release tags were not updated to have these changes.

Support for SIlverstripe 5

Many SS4 modules simply require changes to dependency requirements that allow for 5.0 versions of silverstripe core modules in order to run correctly in SS5. That may suffice here, but it will also require upgrading the htmlpurifier module.

Return 1 Page when Multiple DataObject ComeBack

I'm using the modules to search content blocks on my site, but it returns a seperate entry for each dataobject, I'd like to only return the page once if the word is found. Is this possible?

getSearchFilter null results in addFilter warning

When setting :
public static function getSearchFilter() {
return null;
}

This will result in a warning :
[Warning] Invalid argument supplied for foreach() in DataList->addFilter() ;

using an empty array will fix this :

public static function getSearchFilter() {
return array();
}

SearchableDataObject onBeforeDelete override calls parent::onAfterDelete()

I was looking at the code for the onBeforeDelete function on the SearchableDataObject class.

I'm not sure if this is done on purpose or not, but on line 45, it calls parent::onAfterDelete().

It seems to me like parent::onBeforeDelete() should be called in this instance, not parent::onAfterDelete().

Thanks a lot for this plugins. It's really helpful and saved me a lot of time.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.