Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • AdvancedSearchForm
  • ContentControllerSearchExtension
  • EndsWithFilter
  • ExactMatchFilter
  • ExactMatchMultiFilter
  • FulltextFilter
  • FulltextSearchable
  • GreaterThanFilter
  • LessThanFilter
  • NegationFilter
  • PartialMatchFilter
  • SearchContext
  • SearchFilter
  • SearchForm
  • StartsWithFilter
  • StartsWithMultiFilter
  • SubstringFilter
  • WithinRangeFilter
  1 <?php
  2 /**
  3  * Base class for filtering implementations,
  4  * which work together with {@link SearchContext}
  5  * to create or amend a query for {@link DataObject} instances.
  6  * See {@link SearchContext} for more information.
  7  *
  8  * @package sapphire
  9  * @subpackage search
 10  */
 11 abstract class SearchFilter extends Object {
 12     
 13     /**
 14      * @var string Classname of the inspected {@link DataObject}
 15      */
 16     protected $model;
 17     
 18     /**
 19      * @var string
 20      */
 21     protected $name;
 22     
 23     /**
 24      * @var string 
 25      */
 26     protected $fullName;
 27     
 28     /**
 29      * @var mixed
 30      */
 31     protected $value;
 32     
 33     /**
 34      * @var string Name of a has-one, has-many or many-many relation (not the classname).
 35      * Set in the constructor as part of the name in dot-notation, and used in 
 36      * {@link applyRelation()}.
 37      */
 38     protected $relation;
 39     
 40     /**
 41      * @param string $fullName Determines the name of the field, as well as the searched database 
 42      *  column. Can contain a relation name in dot notation, which will automatically join
 43      *  the necessary tables (e.g. "Comments.Name" to join the "Comments" has-many relationship and
 44      *  search the "Name" column when applying this filter to a SiteTree class).
 45      * @param mixed $value
 46      */
 47     function __construct($fullName, $value = false) {
 48         $this->fullName = $fullName;
 49         // sets $this->name and $this->relation
 50         $this->addRelation($fullName);
 51         $this->value = $value;
 52     }
 53     
 54     /**
 55      * Called by constructor to convert a string pathname into
 56      * a well defined relationship sequence.
 57      *
 58      * @param string $name
 59      */
 60     protected function addRelation($name) {
 61         if (strstr($name, '.')) {
 62             $parts = explode('.', $name);
 63             $this->name = array_pop($parts);
 64             $this->relation = $parts;
 65         } else {
 66             $this->name = $name;
 67         }
 68     }
 69     
 70     /**
 71      * Set the root model class to be selected by this
 72      * search query.
 73      *
 74      * @param string $className
 75      */ 
 76     public function setModel($className) {
 77         $this->model = $className;
 78     }
 79     
 80     /**
 81      * Set the current value to be filtered on.
 82      *
 83      * @param string $value
 84      */
 85     public function setValue($value) {
 86         $this->value = $value;
 87     }
 88     
 89     /**
 90      * Accessor for the current value to be filtered on.
 91      * Caution: Data is not escaped.
 92      *
 93      * @return string
 94      */
 95     public function getValue() {
 96         return $this->value;
 97     }
 98     
 99     /**
100      * The original name of the field.
101      *
102      * @return string
103      */
104     public function getName() {
105         return $this->name;
106     }
107     
108     /**
109      * The full name passed to the constructor,
110      * including any (optional) relations in dot notation.
111      * 
112      * @return string
113      */
114     public function getFullName() {
115         return $this->fullName;
116     }
117     
118     /**
119      * Normalizes the field name to table mapping.
120      * 
121      * @return string
122      */
123     function getDbName() {
124         // Special handler for "NULL" relations
125         if($this->name == "NULL") return $this->name;
126         
127         // SRM: This code finds the table where the field named $this->name lives
128         // Todo: move to somewhere more appropriate, such as DataMapper, the magical class-to-be?
129         $candidateClass = $this->model;
130         while($candidateClass != 'DataObject') {
131             if(singleton($candidateClass)->hasOwnTableDatabaseField($this->name)) break;
132             $candidateClass = get_parent_class($candidateClass);
133         }
134         if($candidateClass == 'DataObject') user_error("Couldn't find field $this->name in any of $this->model's tables.", E_USER_ERROR);
135         
136         return "\"$candidateClass\".\"$this->name\"";
137     }
138     
139     /**
140      * Return the value of the field as processed by the DBField class
141      *
142      * @return string
143      */
144     function getDbFormattedValue() {
145         // SRM: This code finds the table where the field named $this->name lives
146         // Todo: move to somewhere more appropriate, such as DataMapper, the magical class-to-be?
147         $candidateClass = $this->model;
148         $dbField = singleton($this->model)->dbObject($this->name);
149         $dbField->setValue($this->value);
150         return $dbField->RAW();
151     }
152     
153     /**
154      * Traverse the relationship fields, and add the table
155      * mappings to the query object state. This has to be called
156      * in any overloaded {@link SearchFilter->apply()} methods manually.
157      * 
158      * @todo try to make this implicitly triggered so it doesn't have to be manually called in child filters
159      * @param SQLQuery $query
160      * @return SQLQuery
161      */
162     function applyRelation($query) {
163         if (is_array($this->relation)) {
164             foreach($this->relation as $rel) {
165                 $model = singleton($this->model);
166                 if ($component = $model->has_one($rel)) {   
167                     if(!$query->isJoinedTo($component)) {
168                         $foreignKey = $model->getReverseAssociation($component);
169                         $query->leftJoin($component, "\"$component\".\"ID\" = \"{$this->model}\".\"{$foreignKey}ID\"");
170                         
171                         /**
172                          * add join clause to the component's ancestry classes so that the search filter could search on its 
173                          * ancester fields.
174                          */
175                         $ancestry = ClassInfo::ancestry($component, true);
176                         if(!empty($ancestry)){
177                             $ancestry = array_reverse($ancestry);
178                             foreach($ancestry as $ancestor){
179                                 if($ancestor != $component){
180                                     $query->innerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\"");
181                                     $component=$ancestor;
182                                 }
183                             }
184                         }
185                     }
186                     $this->model = $component;
187                 } elseif ($component = $model->has_many($rel)) {
188                     if(!$query->isJoinedTo($component)) {
189                         $ancestry = $model->getClassAncestry();
190                         $foreignKey = $model->getRemoteJoinField($rel);
191                         $query->leftJoin($component, "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\"");
192                         /**
193                          * add join clause to the component's ancestry classes so that the search filter could search on its 
194                          * ancestor fields.
195                          */
196                         $ancestry = ClassInfo::ancestry($component, true);
197                         if(!empty($ancestry)){
198                             $ancestry = array_reverse($ancestry);
199                             foreach($ancestry as $ancestor){
200                                 if($ancestor != $component){
201                                     $query->innerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\"");
202                                     $component=$ancestor;
203                                 }
204                             }
205                         }
206                     }
207                     $this->model = $component;
208                 } elseif ($component = $model->many_many($rel)) {
209                     list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
210                     $parentBaseClass = ClassInfo::baseDataClass($parentClass);
211                     $componentBaseClass = ClassInfo::baseDataClass($componentClass);
212                     $query->innerJoin($relationTable, "\"$relationTable\".\"$parentField\" = \"$parentBaseClass\".\"ID\"");
213                     $query->leftJoin($componentBaseClass, "\"$relationTable\".\"$componentField\" = \"$componentBaseClass\".\"ID\"");
214                     if(ClassInfo::hasTable($componentClass)) {
215                         $query->leftJoin($componentClass, "\"$relationTable\".\"$componentField\" = \"$componentClass\".\"ID\"");
216                     }
217                     $this->model = $componentClass;
218                 
219                 // Experimental support for user-defined relationships via a "(relName)Query" method
220                 // This will likely be dropped in 2.4 for a system that makes use of Lazy Data Lists.
221                 } elseif($model->hasMethod($rel.'Query')) {
222                     // Get the query representing the join - it should have "$ID" in the filter
223                     $newQuery = $model->{"{$rel}Query"}();
224                     if($newQuery) {
225                         // Get the table to join to
226                         //DATABASE ABSTRACTION: I don't think we need this line anymore:
227                         $newModel = str_replace('`','',array_shift($newQuery->from));
228                         // Get the filter to use on the join
229                         $ancestry = $model->getClassAncestry();
230                         $newFilter = "(" . str_replace('$ID', "\"{$ancestry[0]}\".\"ID\"" , implode(") AND (", $newQuery->where) ) . ")";
231                         $query->leftJoin($newModel, $newFilter);
232                         $this->model = $newModel;
233                     } else {
234                         $this->name = "NULL";
235                         return;
236                     }
237                 }
238             }
239         }
240         return $query;
241     }
242     
243     /**
244      * Apply filter criteria to a SQL query.
245      *
246      * @param SQLQuery $query
247      * @return SQLQuery
248      */
249     abstract public function apply(SQLQuery $query);
250     
251     /**
252      * Determines if a field has a value,
253      * and that the filter should be applied.
254      * Relies on the field being populated with
255      * {@link setValue()}
256      * 
257      * @return boolean
258      */
259     public function isEmpty() {
260         return false;
261     }
262     
263 }
264 ?>
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0