Merge "Remove usages of RequestContext::getStats()"
[lhc/web/wiklou.git] / includes / changes / ChangesListFilter.php
index 4ac6387..9af9adc 100644 (file)
@@ -73,13 +73,6 @@ abstract class ChangesListFilter {
         */
        protected $description;
 
-       /**
-        * Callable used to check whether this filter is allowed to take effect
-        *
-        * @var callable $isAllowedCallable
-        */
-       protected $isAllowedCallable;
-
        /**
         * List of conflicting groups
         *
@@ -110,8 +103,11 @@ abstract class ChangesListFilter {
         */
        protected $priority;
 
+       const RESERVED_NAME_CHAR = '_';
+
        /**
-        * Create a new filter with the specified configuration.
+        * Creates a new filter with the specified configuration, and registers it to the
+        * specified group.
         *
         * It infers which UI (it can be either or both) to display the filter on based on
         * which messages are provided.
@@ -122,7 +118,8 @@ abstract class ChangesListFilter {
         *
         * @param array $filterDefinition ChangesListFilter definition
         *
-        * $filterDefinition['name'] string Name of filter
+        * $filterDefinition['name'] string Name of filter; use lowercase with no
+        *  punctuation
         * $filterDefinition['cssClassSuffix'] string CSS class suffix, used to mark
         *  that a particular row belongs to this filter (when a row is included by the
         *  filter) (optional)
@@ -135,11 +132,6 @@ abstract class ChangesListFilter {
         * $filterDefinition['label'] string i18n key of label for structured UI.
         * $filterDefinition['description'] string i18n key of description for structured
         *  UI.
-        * $filterDefinition['isAllowedCallable'] callable Callable taking two parameters,
-        *  the class name of the special page and an IContextSource, and returning true
-        *  if and only if the current user is permitted to use this filter on the current
-        *  wiki.  If it returns false, it will both hide the UI (in all UIs) and prevent
-        *  the DB query modification from taking effect. (optional, defaults to allowed)
         * $filterDefinition['priority'] int Priority integer.  Higher value means higher
         *  up in the group's filter list.
         */
@@ -151,6 +143,18 @@ abstract class ChangesListFilter {
                                'ChangesListFilterGroup this filter belongs to' );
                }
 
+               if ( strpos( $filterDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
+                       throw new MWException( 'Filter names may not contain \'' .
+                               self::RESERVED_NAME_CHAR .
+                               '\'.  Use the naming convention: \'lowercase\''
+                       );
+               }
+
+               if ( $this->group->getFilter( $filterDefinition['name'] ) ) {
+                       throw new MWException( 'Two filters in a group cannot have the ' .
+                               "same name: '{$filterDefinition['name']}'" );
+               }
+
                $this->name = $filterDefinition['name'];
 
                if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
@@ -163,10 +167,6 @@ abstract class ChangesListFilter {
                        $this->description = $filterDefinition['description'];
                }
 
-               if ( isset( $filterDefinition['isAllowedCallable'] ) ) {
-                       $this->isAllowedCallable = $filterDefinition['isAllowedCallable'];
-               }
-
                $this->priority = $filterDefinition['priority'];
 
                $this->group->registerFilter( $this );
@@ -227,6 +227,7 @@ abstract class ChangesListFilter {
                if ( $other instanceof ChangesListFilterGroup ) {
                        $this->conflictingGroups[] = [
                                'group' => $other->getName(),
+                               'groupObject' => $other,
                                'globalDescription' => $globalDescription,
                                'contextDescription' => $contextDescription,
                        ];
@@ -234,6 +235,7 @@ abstract class ChangesListFilter {
                        $this->conflictingFilters[] = [
                                'group' => $other->getGroup()->getName(),
                                'filter' => $other->getName(),
+                               'filterObject' => $other,
                                'globalDescription' => $globalDescription,
                                'contextDescription' => $contextDescription,
                        ];
@@ -295,20 +297,18 @@ abstract class ChangesListFilter {
        /**
         * Checks whether the filter should display on the unstructured UI
         *
-        * @param ChangesListSpecialPage $specialPage Current special page
         * @return bool Whether to display
         */
-       abstract public function displaysOnUnstructuredUi( ChangesListSpecialPage $specialPage );
+       abstract public function displaysOnUnstructuredUi();
 
        /**
         * Checks whether the filter should display on the structured UI
         * This refers to the exact filter.  See also isFeatureAvailableOnStructuredUi.
         *
-        * @param ChangesListSpecialPage $specialPage Current special page
         * @return bool Whether to display
         */
-       public function displaysOnStructuredUi( ChangesListSpecialPage $specialPage ) {
-               return $this->label !== null && $this->isAllowed( $specialPage );
+       public function displaysOnStructuredUi() {
+               return $this->label !== null;
        }
 
        /**
@@ -317,8 +317,8 @@ abstract class ChangesListFilter {
         *
         * This can either be the exact filter, or a new filter that replaces it.
         */
-       public function isFeatureAvailableOnStructuredUi( ChangesListSpecialPage $specialPage ) {
-               return $this->displaysOnStructuredUi( $specialPage );
+       public function isFeatureAvailableOnStructuredUi() {
+               return $this->displaysOnStructuredUi();
        }
 
        /**
@@ -328,24 +328,6 @@ abstract class ChangesListFilter {
                return $this->priority;
        }
 
-       /**
-        * Checks whether the filter is allowed for the current context
-        *
-        * @param ChangesListSpecialPage $specialPage Current special page
-        * @return bool Whether it is allowed
-        */
-       public function isAllowed( ChangesListSpecialPage $specialPage ) {
-               if ( $this->isAllowedCallable === null ) {
-                       return true;
-               } else {
-                       return call_user_func(
-                               $this->isAllowedCallable,
-                               get_class( $specialPage ),
-                               $specialPage->getContext()
-                       );
-               }
-       }
-
        /**
         * Gets the CSS class
         *
@@ -405,6 +387,8 @@ abstract class ChangesListFilter {
                );
 
                foreach ( $conflicts as $conflictInfo ) {
+                       unset( $conflictInfo['filterObject'] );
+                       unset( $conflictInfo['groupObject'] );
                        $output['conflicts'][] = $conflictInfo;
                        array_push(
                                $output['messageKeys'],
@@ -415,4 +399,105 @@ abstract class ChangesListFilter {
 
                return $output;
        }
+
+       /**
+        * Checks whether this filter is selected in the provided options
+        *
+        * @param FormOptions $opts
+        * @return bool
+        */
+       abstract public function isSelected( FormOptions $opts );
+
+       /**
+        * Get groups conflicting with this filter
+        *
+        * @return ChangesListFilterGroup[]
+        */
+       public function getConflictingGroups() {
+               return array_map(
+                       function ( $conflictDesc ) {
+                               return $conflictDesc[ 'groupObject' ];
+                       },
+                       $this->conflictingGroups
+               );
+       }
+
+       /**
+        * Get filters conflicting with this filter
+        *
+        * @return ChangesListFilter[]
+        */
+       public function getConflictingFilters() {
+               return array_map(
+                       function ( $conflictDesc ) {
+                               return $conflictDesc[ 'filterObject' ];
+                       },
+                       $this->conflictingFilters
+               );
+       }
+
+       /**
+        * Check if the conflict with a group is currently "active"
+        *
+        * @param ChangesListFilterGroup $group
+        * @param FormOptions $opts
+        * @return bool
+        */
+       public function activelyInConflictWithGroup( ChangesListFilterGroup $group, FormOptions $opts ) {
+               if ( $group->anySelected( $opts ) && $this->isSelected( $opts ) ) {
+                       /** @var ChangesListFilter $siblingFilter */
+                       foreach ( $this->getSiblings() as $siblingFilter ) {
+                               if ( $siblingFilter->isSelected( $opts ) && !$siblingFilter->hasConflictWithGroup( $group ) ) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       private function hasConflictWithGroup( ChangesListFilterGroup $group ) {
+               return in_array( $group, $this->getConflictingGroups() );
+       }
+
+       /**
+        * Check if the conflict with a filter is currently "active"
+        *
+        * @param ChangesListFilter $filter
+        * @param FormOptions $opts
+        * @return bool
+        */
+       public function activelyInConflictWithFilter( ChangeslistFilter $filter, FormOptions $opts ) {
+               if ( $this->isSelected( $opts ) && $filter->isSelected( $opts ) ) {
+                       /** @var ChangesListFilter $siblingFilter */
+                       foreach ( $this->getSiblings() as $siblingFilter ) {
+                               if (
+                                       $siblingFilter->isSelected( $opts ) &&
+                                       !$siblingFilter->hasConflictWithFilter( $filter )
+                               ) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       private function hasConflictWithFilter( ChangeslistFilter $filter ) {
+               return in_array( $filter, $this->getConflictingFilters() );
+       }
+
+       /**
+        * Get filters in the same group
+        *
+        * @return ChangesListFilter[]
+        */
+       protected function getSiblings() {
+               return array_filter(
+                       $this->getGroup()->getFilters(),
+                       function ( $filter ) {
+                               return $filter !== $this;
+                       }
+               );
+       }
 }