1 <?php
2
3 /**
4 * AfBaseFilter class file.
5 *
6 * @author Keith Burton <kburton@kappasoft.net>
7 * @package advancedfilters.filters
8 */
9
10 /**
11 * All filter classes should extend AfBaseFilter. It contains abstract functions
12 * that must be implemented by child classes, as well as some helper functions
13 * for common filter operations.
14 */
15 abstract class AfBaseFilter extends CComponent
16 {
17 /**
18 * @var boolean whether this filter should be included when processing
19 * filter expressions.
20 */
21 public $active = true;
22
23 /**
24 * @var integer the priority with which this should be processed. Smaller
25 * numbers are processed first.
26 */
27 public $priority = 0;
28
29 /**
30 * @var boolean whether null results from column expressions should be
31 * coalesced to empty strings. This allows the columns to be included in
32 * filter results.
33 */
34 public $treatNullAsEmptyString = true;
35
36 private $columnExpression;
37 private $filterExpression;
38 private $invertLogic = false;
39 private $dbHelper;
40 private $dbConnection;
41
42 /**
43 * Constructor. Subclasses of AfBaseFilter should not be instantiated
44 * directly, but by using methods of the application component.
45 *
46 * @param string $columnExpression the disambiguated column name (or a
47 * valid SQL expression).
48 * @param string $filterExpression the entered filter expression.
49 * @param boolean $invertLogic whether the condition logic should be
50 * inverted to return the opposite results.
51 * @param CDbConnection $dbConnection the database connection object.
52 * @param AfBaseDbHelper $dbHelper the database helper object.
53 */
54 public function __construct($columnExpression, $filterExpression,
55 $invertLogic, $dbConnection, $dbHelper)
56 {
57 $this->columnExpression = $columnExpression;
58 $this->filterExpression = $filterExpression;
59 $this->invertLogic = $invertLogic;
60 $this->dbConnection = $dbConnection;
61 $this->dbHelper = $dbHelper;
62 }
63
64 /**
65 * Gets the column expression, with the result coalesced to an empty string
66 * if $this->treatNullAsEmptyString is true.
67 *
68 * @return string the column expression.
69 */
70 protected function getColumnExpression()
71 {
72 return $this->treatNullAsEmptyString
73 ? $this->getDbHelper()
74 ->convertNullToEmptyString($this->columnExpression)
75 : $this->columnExpression;
76 }
77
78 /**
79 * Gets the filter expression segment entered by the user.
80 *
81 * @return string the entered filter expression segment.
82 */
83 protected function getFilterExpression()
84 {
85 return $this->filterExpression;
86 }
87
88 /**
89 * Specifies whether the filter logic should be inverted in the resulting
90 * database criteria.
91 *
92 * @return boolean whether the filter logic should be inverted.
93 */
94 protected function getInvertLogic()
95 {
96 return $this->invertLogic;
97 }
98
99 /**
100 * Gets a database helper class to provide database specific syntax for
101 * filter criteria.
102 *
103 * @return AfBaseDbHelper the database helper object.
104 */
105 protected function getDbHelper()
106 {
107 return $this->dbHelper;
108 }
109
110 /**
111 * Gets a connection to the relevant database so that filters can perform
112 * queries directly, in order to validate syntax.
113 *
114 * @return CDbConnection the connection object.
115 */
116 protected function getDbConnection()
117 {
118 return $this->dbConnection;
119 }
120
121 /**
122 * Instantiate and return the first filter that can process the provided
123 * filter expression.
124 *
125 * This is guaranteed to return a valid class as the AfDefaultFilter
126 * responds to any expression and is returned if no other filter can process
127 * the expression.
128 *
129 * @param string $columnExpression the column or expression to which to
130 * apply this filter.
131 * @param string $filterExpression the string containing the filter pattern.
132 * @param boolean $invertLogic whether the condition logic should be
133 * inverted.
134 * @param CDbConnection $dbConnection the database connection object.
135 * @param AfBaseDbHelper $dbHelper the helper to use when dealing with
136 * database specific syntax.
137 * @param array $filterConfig an array of configuration for all available
138 * filter types.
139 * @return AfBaseFilter an instance of a subclass of AfBaseFilter.
140 */
141 public static function createFilter($columnExpression, $filterExpression,
142 $invertLogic, $dbConnection, $dbHelper, $filterConfig)
143 {
144 // Sort the available filters by priority value
145 usort($filterConfig, function($a, $b){
146 $aPriority = isset($a['priority']) ? $a['priority'] : 0;
147 $bPriority = isset($b['priority']) ? $b['priority'] : 0;
148
149 return $aPriority == $bPriority ? 0
150 : ($aPriority < $bPriority ? -1 : 1);
151 });
152
153 foreach ($filterConfig as $classConfig)
154 {
155 // Ignore filters that have been marked as not active
156 if (isset($classConfig['active']) && !$classConfig['active'])
157 continue;
158
159 $filter = Yii::createComponent($classConfig, $columnExpression,
160 $filterExpression, $invertLogic, $dbConnection,
161 $dbHelper);
162
163 if ($filter->acceptsFilterExpression())
164 return $filter;
165 }
166
167 // If no matching filter has been found, return the default filter
168 return Yii::createComponent('AfDefaultFilter', $columnExpression,
169 $filterExpression, $invertLogic, $dbConnection, $dbHelper);
170 }
171
172 /**
173 * A generic helper function to strip a specified prefix and suffix from
174 * a string if both are found.
175 * The prefix and/or suffix can be an empty string, to allow matching at
176 * only one side of the string.
177 * Comparison of the prefix and suffix is case insensitive.
178 *
179 * If the string is surrounded by the specified prefix and suffix, the
180 * stripped string will be returned with any whitespace intact. Otherwise,
181 * boolean false is returned.
182 *
183 * @param string $string the string to test and strip.
184 * @param string $prefix the prefix to compare. Use an empty string to
185 * ignore the prefix.
186 * @param string $suffix the suffix to compare. Use an empty string to
187 * ignore the suffix.
188 * @return boolean|string the stripped string if the specified prefix and
189 * suffix are matched. Boolean false otherwise.
190 */
191 protected static function stripPrefixSuffixString($string, $prefix, $suffix)
192 {
193 $prefix = strtolower($prefix);
194 $suffix = strtolower($suffix);
195
196 $prefixLength = strlen($prefix);
197 $suffixLength = strlen($suffix);
198 $stringLength = strlen($string);
199
200 // Not accepted if the string is shorter than the prefix and suffix
201 if ($stringLength < $prefixLength + $suffixLength)
202 return false;
203
204 // Not accepted if the prefix doesn't match
205 if (strtolower(substr($string, 0, $prefixLength)) !== $prefix)
206 return false;
207
208 // Not accepted if the suffix doesn't match
209 if (strtolower(substr($string, -$suffixLength, $suffixLength))
210 !== $suffix)
211 return false;
212
213 // Return empty string if only the prefix and suffix are found
214 if ($stringLength === $prefixLength + $suffixLength)
215 return '';
216
217 // Strip the prefix and suffix and return the resulting string
218 return substr($string, $prefixLength,
219 $stringLength - $prefixLength - $suffixLength);
220 }
221
222 /**
223 * The implementation of this method should analyse the filter expression
224 * to determine whether this filter accepts the expression.
225 *
226 * @return boolean whether the filter accepts the provided expression.
227 */
228 abstract public function acceptsFilterExpression();
229
230 /**
231 * Gets a CDbCriteria object with the filter conditions applied. The
232 * criteria can be merged with an existing criteria object.
233 *
234 * @return CDbCriteria the new criteria object.
235 */
236 abstract public function getCriteria();
237 }
238