4ac63878b87a1bf888f35c7894a3529be4111583
3 * Represents a filter (used on ChangesListSpecialPage and descendants)
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 * @author Matthew Flaschen
26 * Represents a filter (used on ChangesListSpecialPage and descendants)
30 abstract class ChangesListFilter
{
39 * CSS class suffix used for attribution, e.g. 'bot'.
41 * In this example, if bot actions are included in the result set, this CSS class
42 * will then be included in all bot-flagged actions.
44 * @var string|null $cssClassSuffix
46 protected $cssClassSuffix;
49 * Callable that returns true if and only if a row is attributed to this filter
51 * @var callable $isRowApplicableCallable
53 protected $isRowApplicableCallable;
56 * Group. ChangesListFilterGroup this belongs to
58 * @var ChangesListFilterGroup $group
63 * i18n key of label for structured UI
70 * i18n key of description for structured UI
72 * @var string $description
74 protected $description;
77 * Callable used to check whether this filter is allowed to take effect
79 * @var callable $isAllowedCallable
81 protected $isAllowedCallable;
84 * List of conflicting groups
86 * @var array $conflictingGroups Array of associative arrays with conflict
87 * information. See setUnidirectionalConflict
89 protected $conflictingGroups = [];
92 * List of conflicting filters
94 * @var array $conflictingFilters Array of associative arrays with conflict
95 * information. See setUnidirectionalConflict
97 protected $conflictingFilters = [];
100 * List of filters that are a subset of the current filter
102 * @var array $subsetFilters Array of associative arrays with subset information
104 protected $subsetFilters = [];
107 * Priority integer. Higher value means higher up in the group's filter list.
109 * @var string $priority
114 * Create a new filter with the specified configuration.
116 * It infers which UI (it can be either or both) to display the filter on based on
117 * which messages are provided.
119 * If 'label' is provided, it will be displayed on the structured UI. Thus,
120 * 'label', 'description', and sub-class parameters are optional depending on which
123 * @param array $filterDefinition ChangesListFilter definition
125 * $filterDefinition['name'] string Name of filter
126 * $filterDefinition['cssClassSuffix'] string CSS class suffix, used to mark
127 * that a particular row belongs to this filter (when a row is included by the
129 * $filterDefinition['isRowApplicableCallable'] Callable taking two parameters, the
130 * IContextSource, and the RecentChange object for the row, and returning true if
131 * the row is attributed to this filter. The above CSS class will then be
132 * automatically added (optional, required if cssClassSuffix is used).
133 * $filterDefinition['group'] ChangesListFilterGroup Group. Filter group this
135 * $filterDefinition['label'] string i18n key of label for structured UI.
136 * $filterDefinition['description'] string i18n key of description for structured
138 * $filterDefinition['isAllowedCallable'] callable Callable taking two parameters,
139 * the class name of the special page and an IContextSource, and returning true
140 * if and only if the current user is permitted to use this filter on the current
141 * wiki. If it returns false, it will both hide the UI (in all UIs) and prevent
142 * the DB query modification from taking effect. (optional, defaults to allowed)
143 * $filterDefinition['priority'] int Priority integer. Higher value means higher
144 * up in the group's filter list.
146 public function __construct( array $filterDefinition ) {
147 if ( isset( $filterDefinition['group'] ) ) {
148 $this->group
= $filterDefinition['group'];
150 throw new MWException( 'You must use \'group\' to specify the ' .
151 'ChangesListFilterGroup this filter belongs to' );
154 $this->name
= $filterDefinition['name'];
156 if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
157 $this->cssClassSuffix
= $filterDefinition['cssClassSuffix'];
158 $this->isRowApplicableCallable
= $filterDefinition['isRowApplicableCallable'];
161 if ( isset( $filterDefinition['label'] ) ) {
162 $this->label
= $filterDefinition['label'];
163 $this->description
= $filterDefinition['description'];
166 if ( isset( $filterDefinition['isAllowedCallable'] ) ) {
167 $this->isAllowedCallable
= $filterDefinition['isAllowedCallable'];
170 $this->priority
= $filterDefinition['priority'];
172 $this->group
->registerFilter( $this );
176 * Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
178 * WARNING: This means there is a conflict when both things are *shown*
179 * (not filtered out), even for the hide-based filters. So e.g. conflicting with
180 * 'hideanons' means there is a conflict if only anonymous users are *shown*.
182 * @param ChangesListFilterGroup|ChangesListFilter $other Other
183 * ChangesListFilterGroup or ChangesListFilter
184 * @param string $globalKey i18n key for top-level conflict message
185 * @param string $forwardKey i18n key for conflict message in this
186 * direction (when in UI context of $this object)
187 * @param string $backwardKey i18n key for conflict message in reverse
188 * direction (when in UI context of $other object)
190 public function conflictsWith( $other, $globalKey, $forwardKey,
193 if ( $globalKey === null ||
$forwardKey === null ||
194 $backwardKey === null ) {
196 throw new MWException( 'All messages must be specified' );
199 $this->setUnidirectionalConflict(
205 $other->setUnidirectionalConflict(
213 * Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with
218 * @param ChangesListFilterGroup|ChangesListFilter $other Other
219 * ChangesListFilterGroup or ChangesListFilter
220 * @param string $globalDescription i18n key for top-level conflict message
221 * @param string $contextDescription i18n key for conflict message in this
222 * direction (when in UI context of $this object)
224 public function setUnidirectionalConflict( $other, $globalDescription,
225 $contextDescription ) {
227 if ( $other instanceof ChangesListFilterGroup
) {
228 $this->conflictingGroups
[] = [
229 'group' => $other->getName(),
230 'globalDescription' => $globalDescription,
231 'contextDescription' => $contextDescription,
233 } elseif ( $other instanceof ChangesListFilter
) {
234 $this->conflictingFilters
[] = [
235 'group' => $other->getGroup()->getName(),
236 'filter' => $other->getName(),
237 'globalDescription' => $globalDescription,
238 'contextDescription' => $contextDescription,
241 throw new MWException( 'You can only pass in a ChangesListFilterGroup or a ChangesListFilter' );
246 * Marks that the current instance is (also) a superset of the filter passed in.
247 * This can be called more than once.
249 * This means that anything in the results for the other filter is also in the
250 * results for this one.
252 * @param ChangesListFilter The filter the current instance is a superset of
254 public function setAsSupersetOf( ChangesListFilter
$other ) {
255 if ( $other->getGroup() !== $this->getGroup() ) {
256 throw new MWException( 'Supersets can only be defined for filters in the same group' );
259 $this->subsetFilters
[] = [
260 // It's always the same group, but this makes the representation
261 // more consistent with conflicts.
262 'group' => $other->getGroup()->getName(),
263 'filter' => $other->getName(),
268 * @return string Name, e.g. hideanons
270 public function getName() {
275 * @return ChangesListFilterGroup Group this belongs to
277 public function getGroup() {
282 * @return string i18n key of label for structured UI
284 public function getLabel() {
289 * @return string i18n key of description for structured UI
291 public function getDescription() {
292 return $this->description
;
296 * Checks whether the filter should display on the unstructured UI
298 * @param ChangesListSpecialPage $specialPage Current special page
299 * @return bool Whether to display
301 abstract public function displaysOnUnstructuredUi( ChangesListSpecialPage
$specialPage );
304 * Checks whether the filter should display on the structured UI
305 * This refers to the exact filter. See also isFeatureAvailableOnStructuredUi.
307 * @param ChangesListSpecialPage $specialPage Current special page
308 * @return bool Whether to display
310 public function displaysOnStructuredUi( ChangesListSpecialPage
$specialPage ) {
311 return $this->label
!== null && $this->isAllowed( $specialPage );
315 * Checks whether an equivalent feature for this filter is available on the
318 * This can either be the exact filter, or a new filter that replaces it.
320 public function isFeatureAvailableOnStructuredUi( ChangesListSpecialPage
$specialPage ) {
321 return $this->displaysOnStructuredUi( $specialPage );
325 * @return int Priority. Higher value means higher up in the group list
327 public function getPriority() {
328 return $this->priority
;
332 * Checks whether the filter is allowed for the current context
334 * @param ChangesListSpecialPage $specialPage Current special page
335 * @return bool Whether it is allowed
337 public function isAllowed( ChangesListSpecialPage
$specialPage ) {
338 if ( $this->isAllowedCallable
=== null ) {
341 return call_user_func(
342 $this->isAllowedCallable
,
343 get_class( $specialPage ),
344 $specialPage->getContext()
352 * @return string|null CSS class, or null if not defined
354 protected function getCssClass() {
355 if ( $this->cssClassSuffix
!== null ) {
356 return ChangesList
::CSS_CLASS_PREFIX
. $this->cssClassSuffix
;
363 * Add CSS class if needed
365 * @param IContextSource $ctx Context source
366 * @param RecentChange $rc Recent changes object
367 * @param Non-associative array of CSS class names; appended to if needed
369 public function applyCssClassIfNeeded( IContextSource
$ctx, RecentChange
$rc, array &$classes ) {
370 if ( $this->isRowApplicableCallable
=== null ) {
374 if ( call_user_func( $this->isRowApplicableCallable
, $ctx, $rc ) ) {
375 $classes[] = $this->getCssClass();
380 * Gets the JS data required by the front-end of the structured UI
382 * @return array Associative array Data required by the front-end. messageKeys is
383 * a special top-level value, with the value being an array of the message keys to
384 * send to the client.
386 public function getJsData() {
388 'name' => $this->getName(),
389 'label' => $this->getLabel(),
390 'description' => $this->getDescription(),
391 'cssClass' => $this->getCssClass(),
392 'priority' => $this->priority
,
393 'subset' => $this->subsetFilters
,
397 $output['messageKeys'] = [
399 $this->getDescription(),
402 $conflicts = array_merge(
403 $this->conflictingGroups
,
404 $this->conflictingFilters
407 foreach ( $conflicts as $conflictInfo ) {
408 $output['conflicts'][] = $conflictInfo;
410 $output['messageKeys'],
411 $conflictInfo['globalDescription'],
412 $conflictInfo['contextDescription']